Updated to latest config release from typesafehub, v0.1.8
This commit is contained in:
parent
18601fbbb8
commit
31e2cb354f
20 changed files with 700 additions and 240 deletions
|
|
@ -4,6 +4,8 @@
|
|||
package com.typesafe.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An immutable map from config paths to config values.
|
||||
|
|
@ -32,6 +34,10 @@ import java.util.List;
|
|||
* {@code ConfigObject} is a tree of nested maps from <em>keys</em> to values.
|
||||
*
|
||||
* <p>
|
||||
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
|
||||
* between path expressions and individual path elements (keys).
|
||||
*
|
||||
* <p>
|
||||
* Another difference between {@code Config} and {@code ConfigObject} is that
|
||||
* conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType()
|
||||
* valueType()} of {@link ConfigValueType#NULL NULL} exist in a
|
||||
|
|
@ -54,10 +60,11 @@ import java.util.List;
|
|||
* are performed for you though.
|
||||
*
|
||||
* <p>
|
||||
* If you want to iterate over the contents of a {@code Config}, you have to get
|
||||
* its {@code ConfigObject} with {@link #root()}, and then iterate over the
|
||||
* {@code ConfigObject}.
|
||||
*
|
||||
* If you want to iterate over the contents of a {@code Config}, you can get its
|
||||
* {@code ConfigObject} with {@link #root()}, and then iterate over the
|
||||
* {@code ConfigObject} (which implements <code>java.util.Map</code>). Or, you
|
||||
* can use {@link #entrySet()} which recurses the object tree for you and builds
|
||||
* up a <code>Set</code> of all path-value pairs where the value is not null.
|
||||
*
|
||||
* <p>
|
||||
* <em>Do not implement {@code Config}</em>; it should only be implemented by
|
||||
|
|
@ -256,6 +263,17 @@ public interface Config extends ConfigMergeable {
|
|||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Returns the set of path-value pairs, excluding any null values, found by
|
||||
* recursing {@link #root() the root object}. Note that this is very
|
||||
* different from <code>root().entrySet()</code> which returns the set of
|
||||
* immediate-child keys in the root object and includes null values.
|
||||
*
|
||||
* @return set of paths with non-null values, built up by recursing the
|
||||
* entire tree of {@link ConfigObject}
|
||||
*/
|
||||
Set<Map.Entry<String, ConfigValue>> entrySet();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param path
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import java.util.Map;
|
|||
import java.util.Properties;
|
||||
|
||||
import com.typesafe.config.impl.ConfigImpl;
|
||||
import com.typesafe.config.impl.ConfigUtil;
|
||||
import com.typesafe.config.impl.ConfigImplUtil;
|
||||
import com.typesafe.config.impl.Parseable;
|
||||
|
||||
/**
|
||||
|
|
@ -179,7 +179,7 @@ public final class ConfigFactory {
|
|||
try {
|
||||
return DefaultConfigHolder.defaultConfig;
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
throw ConfigUtil.extractInitializerError(e);
|
||||
throw ConfigImplUtil.extractInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,34 +8,38 @@ import java.util.Map;
|
|||
/**
|
||||
* Subtype of {@link ConfigValue} representing an object (dictionary, map)
|
||||
* value, as in JSON's <code>{ "a" : 42 }</code> syntax.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so
|
||||
* you can use it like a regular Java map. Or call {@link #unwrapped()} to
|
||||
* unwrap the map to a map with plain Java values rather than
|
||||
* {@code ConfigValue}.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
|
||||
* This makes it threadsafe and you never have to create "defensive copies." The
|
||||
* mutator methods from {@link java.util.Map} all throw
|
||||
* {@link java.lang.UnsupportedOperationException}.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* The {@link ConfigValue#valueType} method on an object returns
|
||||
* {@link ConfigValueType#OBJECT}.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* In most cases you want to use the {@link Config} interface rather than this
|
||||
* one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a
|
||||
* {@code Config}.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* The API for a {@code ConfigObject} is in terms of keys, while the API for a
|
||||
* {@link Config} is in terms of path expressions. Conceptually,
|
||||
* {@code ConfigObject} is a tree of maps from keys to values, while a
|
||||
* {@code ConfigObject} is a one-level map from paths to values.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
|
||||
* between path expressions and individual path elements (keys).
|
||||
*
|
||||
* <p>
|
||||
* A {@code ConfigObject} may contain null values, which will have
|
||||
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
|
||||
|
|
@ -43,7 +47,7 @@ import java.util.Map;
|
|||
* file (or wherever this value tree came from). If {@code get()} returns a
|
||||
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was
|
||||
* set to null explicitly in the config file.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented
|
||||
* by the config library. Arbitrary implementations will not work because the
|
||||
|
|
|
|||
70
akka-actor/src/main/java/com/typesafe/config/ConfigUtil.java
Normal file
70
akka-actor/src/main/java/com/typesafe/config/ConfigUtil.java
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package com.typesafe.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.impl.ConfigImplUtil;
|
||||
|
||||
public final class ConfigUtil {
|
||||
private ConfigUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes and escapes a string, as in the JSON specification.
|
||||
*
|
||||
* @param s
|
||||
* a string
|
||||
* @return the string quoted and escaped
|
||||
*/
|
||||
public static String quoteString(String s) {
|
||||
return ConfigImplUtil.renderJsonString(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of keys to a path expression, by quoting the path
|
||||
* elements as needed and then joining them separated by a period. A path
|
||||
* expression is usable with a {@link Config}, while individual path
|
||||
* elements are usable with a {@link ConfigObject}.
|
||||
*
|
||||
* @param elements
|
||||
* the keys in the path
|
||||
* @return a path expression
|
||||
* @throws ConfigException
|
||||
* if there are no elements
|
||||
*/
|
||||
public static String joinPath(String... elements) {
|
||||
return ConfigImplUtil.joinPath(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of strings to a path expression, by quoting the path
|
||||
* elements as needed and then joining them separated by a period. A path
|
||||
* expression is usable with a {@link Config}, while individual path
|
||||
* elements are usable with a {@link ConfigObject}.
|
||||
*
|
||||
* @param elements
|
||||
* the keys in the path
|
||||
* @return a path expression
|
||||
* @throws ConfigException
|
||||
* if the list is empty
|
||||
*/
|
||||
public static String joinPath(List<String> elements) {
|
||||
return ConfigImplUtil.joinPath(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a path expression into a list of keys, by splitting on period
|
||||
* and unquoting the individual path elements. A path expression is usable
|
||||
* with a {@link Config}, while individual path elements are usable with a
|
||||
* {@link ConfigObject}.
|
||||
*
|
||||
* @param path
|
||||
* a path expression
|
||||
* @return the individual keys in the path
|
||||
* @throws ConfigException
|
||||
* if the path expression is invalid
|
||||
*/
|
||||
public static List<String> splitPath(String path) {
|
||||
return ConfigImplUtil.splitPath(path);
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
|
|||
return canEqual(other)
|
||||
&& (this.valueType() ==
|
||||
((ConfigValue) other).valueType())
|
||||
&& ConfigUtil.equalsHandlingNull(this.unwrapped(),
|
||||
&& ConfigImplUtil.equalsHandlingNull(this.unwrapped(),
|
||||
((ConfigValue) other).unwrapped());
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -178,7 +178,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
|
|||
|
||||
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
|
||||
if (atKey != null) {
|
||||
sb.append(ConfigUtil.renderJsonString(atKey));
|
||||
sb.append(ConfigImplUtil.renderJsonString(atKey));
|
||||
sb.append(" : ");
|
||||
}
|
||||
render(sb, indent, formatted);
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
|||
indent(sb, indent);
|
||||
if (atKey != null) {
|
||||
sb.append("# unmerged value " + i + " for key "
|
||||
+ ConfigUtil.renderJsonString(atKey) + " from ");
|
||||
+ ConfigImplUtil.renderJsonString(atKey) + " from ");
|
||||
} else {
|
||||
sb.append("# unmerged value " + i + " from ");
|
||||
}
|
||||
|
|
@ -200,7 +200,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
|||
}
|
||||
|
||||
if (atKey != null) {
|
||||
sb.append(ConfigUtil.renderJsonString(atKey));
|
||||
sb.append(ConfigImplUtil.renderJsonString(atKey));
|
||||
sb.append(" : ");
|
||||
}
|
||||
v.render(sb, indent, formatted);
|
||||
|
|
|
|||
|
|
@ -40,47 +40,66 @@ public class ConfigImpl {
|
|||
|| name.endsWith(".properties")) {
|
||||
ConfigParseable p = source.nameToParseable(name);
|
||||
|
||||
if (p != null) {
|
||||
obj = p.parse(p.options().setAllowMissing(
|
||||
options.getAllowMissing()));
|
||||
} else {
|
||||
obj = SimpleConfigObject.emptyMissing(SimpleConfigOrigin.newSimple(name));
|
||||
}
|
||||
obj = p.parse(p.options().setAllowMissing(options.getAllowMissing()));
|
||||
} else {
|
||||
ConfigParseable confHandle = source.nameToParseable(name + ".conf");
|
||||
ConfigParseable jsonHandle = source.nameToParseable(name + ".json");
|
||||
ConfigParseable propsHandle = source.nameToParseable(name
|
||||
+ ".properties");
|
||||
|
||||
if (!options.getAllowMissing() && confHandle == null
|
||||
&& jsonHandle == null && propsHandle == null) {
|
||||
throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name),
|
||||
"No config files {.conf,.json,.properties} found");
|
||||
}
|
||||
boolean gotSomething = false;
|
||||
List<String> failMessages = new ArrayList<String>();
|
||||
|
||||
ConfigSyntax syntax = options.getSyntax();
|
||||
|
||||
obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name));
|
||||
if (confHandle != null
|
||||
&& (syntax == null || syntax == ConfigSyntax.CONF)) {
|
||||
obj = confHandle.parse(confHandle.options()
|
||||
.setAllowMissing(true).setSyntax(ConfigSyntax.CONF));
|
||||
if (syntax == null || syntax == ConfigSyntax.CONF) {
|
||||
try {
|
||||
obj = confHandle.parse(confHandle.options().setAllowMissing(false)
|
||||
.setSyntax(ConfigSyntax.CONF));
|
||||
gotSomething = true;
|
||||
} catch (ConfigException.IO e) {
|
||||
failMessages.add(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonHandle != null
|
||||
&& (syntax == null || syntax == ConfigSyntax.JSON)) {
|
||||
ConfigObject parsed = jsonHandle.parse(jsonHandle
|
||||
.options().setAllowMissing(true)
|
||||
.setSyntax(ConfigSyntax.JSON));
|
||||
obj = obj.withFallback(parsed);
|
||||
if (syntax == null || syntax == ConfigSyntax.JSON) {
|
||||
try {
|
||||
ConfigObject parsed = jsonHandle.parse(jsonHandle.options()
|
||||
.setAllowMissing(false).setSyntax(ConfigSyntax.JSON));
|
||||
obj = obj.withFallback(parsed);
|
||||
gotSomething = true;
|
||||
} catch (ConfigException.IO e) {
|
||||
failMessages.add(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (propsHandle != null
|
||||
&& (syntax == null || syntax == ConfigSyntax.PROPERTIES)) {
|
||||
ConfigObject parsed = propsHandle.parse(propsHandle.options()
|
||||
.setAllowMissing(true)
|
||||
.setSyntax(ConfigSyntax.PROPERTIES));
|
||||
obj = obj.withFallback(parsed);
|
||||
if (syntax == null || syntax == ConfigSyntax.PROPERTIES) {
|
||||
try {
|
||||
ConfigObject parsed = propsHandle.parse(propsHandle.options()
|
||||
.setAllowMissing(false).setSyntax(ConfigSyntax.PROPERTIES));
|
||||
obj = obj.withFallback(parsed);
|
||||
gotSomething = true;
|
||||
} catch (ConfigException.IO e) {
|
||||
failMessages.add(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.getAllowMissing() && !gotSomething) {
|
||||
String failMessage;
|
||||
if (failMessages.isEmpty()) {
|
||||
// this should not happen
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"should not be reached: nothing found but no exceptions thrown");
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String msg : failMessages) {
|
||||
sb.append(msg);
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.setLength(sb.length() - 2);
|
||||
failMessage = sb.toString();
|
||||
}
|
||||
throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name), failMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -269,7 +288,14 @@ public class ConfigImpl {
|
|||
NameSource source = new NameSource() {
|
||||
@Override
|
||||
public ConfigParseable nameToParseable(String name) {
|
||||
return context.relativeTo(name);
|
||||
ConfigParseable p = context.relativeTo(name);
|
||||
if (p == null) {
|
||||
// avoid returning null
|
||||
return Parseable.newNotFound(name, "include was not found: '" + name + "'",
|
||||
ConfigParseOptions.defaults());
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -308,7 +334,7 @@ public class ConfigImpl {
|
|||
try {
|
||||
return DefaultIncluderHolder.defaultIncluder;
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
throw ConfigUtil.extractInitializerError(e);
|
||||
throw ConfigImplUtil.extractInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +352,7 @@ public class ConfigImpl {
|
|||
try {
|
||||
return SystemPropertiesHolder.systemProperties;
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
throw ConfigUtil.extractInitializerError(e);
|
||||
throw ConfigImplUtil.extractInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +388,7 @@ public class ConfigImpl {
|
|||
try {
|
||||
return EnvVariablesHolder.envVariables;
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
throw ConfigUtil.extractInitializerError(e);
|
||||
throw ConfigImplUtil.extractInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +410,7 @@ public class ConfigImpl {
|
|||
try {
|
||||
return ReferenceHolder.referenceConfig;
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
throw ConfigUtil.extractInitializerError(e);
|
||||
throw ConfigImplUtil.extractInitializerError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ package com.typesafe.config.impl;
|
|||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
|
||||
|
||||
/** This is public just for the "config" package to use, don't touch it */
|
||||
final public class ConfigUtil {
|
||||
final public class ConfigImplUtil {
|
||||
static boolean equalsHandlingNull(Object a, Object b) {
|
||||
if (a == null && b != null)
|
||||
return false;
|
||||
|
|
@ -23,7 +25,11 @@ final public class ConfigUtil {
|
|||
return a.equals(b);
|
||||
}
|
||||
|
||||
static String renderJsonString(String s) {
|
||||
/**
|
||||
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
|
||||
* may change.
|
||||
*/
|
||||
public static String renderJsonString(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('"');
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
|
|
@ -146,4 +152,34 @@ final public class ConfigUtil {
|
|||
return new File(url.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
|
||||
* may change. You can use the version in ConfigUtil instead.
|
||||
*/
|
||||
public static String joinPath(String... elements) {
|
||||
return (new Path(elements)).render();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
|
||||
* may change. You can use the version in ConfigUtil instead.
|
||||
*/
|
||||
public static String joinPath(List<String> elements) {
|
||||
return joinPath(elements.toArray(new String[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
|
||||
* may change. You can use the version in ConfigUtil instead.
|
||||
*/
|
||||
public static List<String> splitPath(String path) {
|
||||
Path p = Path.newPath(path);
|
||||
List<String> elements = new ArrayList<String>();
|
||||
while (p != null) {
|
||||
elements.add(p.first());
|
||||
p = p.remainder();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,6 @@ final class ConfigString extends AbstractConfigValue {
|
|||
|
||||
@Override
|
||||
protected void render(StringBuilder sb, int indent, boolean formatted) {
|
||||
sb.append(ConfigUtil.renderJsonString(value));
|
||||
sb.append(ConfigImplUtil.renderJsonString(value));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements
|
|||
if (p instanceof SubstitutionExpression) {
|
||||
sb.append(p.toString());
|
||||
} else {
|
||||
sb.append(ConfigUtil.renderJsonString((String) p));
|
||||
sb.append(ConfigImplUtil.renderJsonString((String) p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package com.typesafe.config.impl;
|
|||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FilterReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -261,6 +262,34 @@ public abstract class Parseable implements ConfigParseable {
|
|||
return new File(parent, filename);
|
||||
}
|
||||
|
||||
// this is a parseable that doesn't exist and just throws when you try to
|
||||
// parse it
|
||||
private final static class ParseableNotFound extends Parseable {
|
||||
final private String what;
|
||||
final private String message;
|
||||
|
||||
ParseableNotFound(String what, String message, ConfigParseOptions options) {
|
||||
this.what = what;
|
||||
this.message = message;
|
||||
postConstruct(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Reader reader() throws IOException {
|
||||
throw new FileNotFoundException(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigOrigin createOrigin() {
|
||||
return SimpleConfigOrigin.newSimple(what);
|
||||
}
|
||||
}
|
||||
|
||||
public static Parseable newNotFound(String whatNotFound, String message,
|
||||
ConfigParseOptions options) {
|
||||
return new ParseableNotFound(whatNotFound, message, options);
|
||||
}
|
||||
|
||||
private final static class ParseableReader extends Parseable {
|
||||
final private Reader reader;
|
||||
|
||||
|
|
@ -355,7 +384,7 @@ public abstract class Parseable implements ConfigParseable {
|
|||
// we want file: URLs and files to always behave the same, so switch
|
||||
// to a file if it's a file: URL
|
||||
if (input.getProtocol().equals("file")) {
|
||||
return newFile(ConfigUtil.urlToFile(input), options);
|
||||
return newFile(ConfigImplUtil.urlToFile(input), options);
|
||||
} else {
|
||||
return new ParseableURL(input, options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ final class Parser {
|
|||
final private ConfigSyntax flavor;
|
||||
final private ConfigOrigin baseOrigin;
|
||||
final private LinkedList<Path> pathStack;
|
||||
// this is the number of "equals" we are inside,
|
||||
// used to modify the error message to reflect that
|
||||
// someone may think this is .properties format.
|
||||
int equalsCount;
|
||||
|
||||
ParseContext(ConfigSyntax flavor, ConfigOrigin origin,
|
||||
Iterator<Token> tokens, ConfigIncluder includer,
|
||||
|
|
@ -53,6 +57,7 @@ final class Parser {
|
|||
this.includer = includer;
|
||||
this.includeContext = includeContext;
|
||||
this.pathStack = new LinkedList<Path>();
|
||||
this.equalsCount = 0;
|
||||
}
|
||||
|
||||
private Token nextToken() {
|
||||
|
|
@ -63,12 +68,25 @@ final class Parser {
|
|||
t = buffer.pop();
|
||||
}
|
||||
|
||||
if (Tokens.isProblem(t)) {
|
||||
ConfigOrigin origin = t.origin();
|
||||
String message = Tokens.getProblemMessage(t);
|
||||
Throwable cause = Tokens.getProblemCause(t);
|
||||
boolean suggestQuotes = Tokens.getProblemSuggestQuotes(t);
|
||||
if (suggestQuotes) {
|
||||
message = addQuoteSuggestion(t.toString(), message);
|
||||
} else {
|
||||
message = addKeyName(message);
|
||||
}
|
||||
throw new ConfigException.Parse(origin, message, cause);
|
||||
}
|
||||
|
||||
if (flavor == ConfigSyntax.JSON) {
|
||||
if (Tokens.isUnquotedText(t)) {
|
||||
throw parseError("Token not allowed in valid JSON: '"
|
||||
+ Tokens.getUnquotedText(t) + "'");
|
||||
throw parseError(addKeyName("Token not allowed in valid JSON: '"
|
||||
+ Tokens.getUnquotedText(t) + "'"));
|
||||
} else if (Tokens.isSubstitution(t)) {
|
||||
throw parseError("Substitutions (${} syntax) not allowed in JSON");
|
||||
throw parseError(addKeyName("Substitutions (${} syntax) not allowed in JSON"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +102,7 @@ final class Parser {
|
|||
while (Tokens.isNewline(t)) {
|
||||
// line number tokens have the line that was _ended_ by the
|
||||
// newline, so we have to add one.
|
||||
lineNumber = Tokens.getLineNumber(t) + 1;
|
||||
lineNumber = t.lineNumber() + 1;
|
||||
t = nextToken();
|
||||
}
|
||||
return t;
|
||||
|
|
@ -111,7 +129,7 @@ final class Parser {
|
|||
while (true) {
|
||||
if (Tokens.isNewline(t)) {
|
||||
// newline number is the line just ended, so add one
|
||||
lineNumber = Tokens.getLineNumber(t) + 1;
|
||||
lineNumber = t.lineNumber() + 1;
|
||||
sawSeparatorOrNewline = true;
|
||||
// we want to continue to also eat
|
||||
// a comma if there is one.
|
||||
|
|
@ -172,11 +190,11 @@ final class Parser {
|
|||
} else if (Tokens.isUnquotedText(valueToken)) {
|
||||
String text = Tokens.getUnquotedText(valueToken);
|
||||
if (firstOrigin == null)
|
||||
firstOrigin = Tokens.getUnquotedTextOrigin(valueToken);
|
||||
firstOrigin = valueToken.origin();
|
||||
sb.append(text);
|
||||
} else if (Tokens.isSubstitution(valueToken)) {
|
||||
if (firstOrigin == null)
|
||||
firstOrigin = Tokens.getSubstitutionOrigin(valueToken);
|
||||
firstOrigin = valueToken.origin();
|
||||
|
||||
if (sb.length() > 0) {
|
||||
// save string so far
|
||||
|
|
@ -186,8 +204,7 @@ final class Parser {
|
|||
// now save substitution
|
||||
List<Token> expression = Tokens
|
||||
.getSubstitutionPathExpression(valueToken);
|
||||
Path path = parsePathExpression(expression.iterator(),
|
||||
Tokens.getSubstitutionOrigin(valueToken));
|
||||
Path path = parsePathExpression(expression.iterator(), valueToken.origin());
|
||||
boolean optional = Tokens.getSubstitutionOptional(valueToken);
|
||||
|
||||
minimized.add(new SubstitutionExpression(path, optional));
|
||||
|
|
@ -233,6 +250,65 @@ final class Parser {
|
|||
return new ConfigException.Parse(lineOrigin(), message, cause);
|
||||
}
|
||||
|
||||
|
||||
private String previousFieldName(Path lastPath) {
|
||||
if (lastPath != null) {
|
||||
return lastPath.render();
|
||||
} else if (pathStack.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return pathStack.peek().render();
|
||||
}
|
||||
|
||||
private String previousFieldName() {
|
||||
return previousFieldName(null);
|
||||
}
|
||||
|
||||
private String addKeyName(String message) {
|
||||
String previousFieldName = previousFieldName();
|
||||
if (previousFieldName != null) {
|
||||
return "in value for key '" + previousFieldName + "': " + message;
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
private String addQuoteSuggestion(String badToken, String message) {
|
||||
return addQuoteSuggestion(null, equalsCount > 0, badToken, message);
|
||||
}
|
||||
|
||||
private String addQuoteSuggestion(Path lastPath, boolean insideEquals, String badToken,
|
||||
String message) {
|
||||
String previousFieldName = previousFieldName(lastPath);
|
||||
|
||||
String part;
|
||||
if (badToken.equals(Tokens.END.toString())) {
|
||||
// EOF requires special handling for the error to make sense.
|
||||
if (previousFieldName != null)
|
||||
part = message + " (if you intended '" + previousFieldName
|
||||
+ "' to be part of a value, instead of a key, "
|
||||
+ "try adding double quotes around the whole value";
|
||||
else
|
||||
return message;
|
||||
} else {
|
||||
if (previousFieldName != null) {
|
||||
part = message + " (if you intended " + badToken
|
||||
+ " to be part of the value for '" + previousFieldName + "', "
|
||||
+ "try enclosing the value in double quotes";
|
||||
} else {
|
||||
part = message + " (if you intended " + badToken
|
||||
+ " to be part of a key or string value, "
|
||||
+ "try enclosing the key or value in double quotes";
|
||||
}
|
||||
}
|
||||
|
||||
if (insideEquals)
|
||||
return part
|
||||
+ ", or you may be able to rename the file .properties rather than .conf)";
|
||||
else
|
||||
return part + ")";
|
||||
}
|
||||
|
||||
private AbstractConfigValue parseValue(Token token) {
|
||||
if (Tokens.isValue(token)) {
|
||||
return Tokens.getValue(token);
|
||||
|
|
@ -241,8 +317,8 @@ final class Parser {
|
|||
} else if (token == Tokens.OPEN_SQUARE) {
|
||||
return parseArray();
|
||||
} else {
|
||||
throw parseError("Expecting a value but got wrong token: "
|
||||
+ token);
|
||||
throw parseError(addQuoteSuggestion(token.toString(),
|
||||
"Expecting a value but got wrong token: " + token));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,8 +359,8 @@ final class Parser {
|
|||
String key = (String) Tokens.getValue(token).unwrapped();
|
||||
return Path.newKey(key);
|
||||
} else {
|
||||
throw parseError("Expecting close brace } or a field name, got "
|
||||
+ token);
|
||||
throw parseError(addKeyName("Expecting close brace } or a field name here, got "
|
||||
+ token));
|
||||
}
|
||||
} else {
|
||||
List<Token> expression = new ArrayList<Token>();
|
||||
|
|
@ -293,6 +369,12 @@ final class Parser {
|
|||
expression.add(t);
|
||||
t = nextToken(); // note: don't cross a newline
|
||||
}
|
||||
|
||||
if (expression.isEmpty()) {
|
||||
throw parseError(addKeyName("expecting a close brace or a field name here, got "
|
||||
+ t));
|
||||
}
|
||||
|
||||
putBack(t); // put back the token we ended with
|
||||
return parsePathExpression(expression.iterator(), lineOrigin());
|
||||
}
|
||||
|
|
@ -311,7 +393,7 @@ final class Parser {
|
|||
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char c = s.charAt(i);
|
||||
if (!ConfigUtil.isWhitespace(c))
|
||||
if (!ConfigImplUtil.isWhitespace(c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -362,13 +444,18 @@ final class Parser {
|
|||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||
ConfigOrigin objectOrigin = lineOrigin();
|
||||
boolean afterComma = false;
|
||||
Path lastPath = null;
|
||||
boolean lastInsideEquals = false;
|
||||
|
||||
while (true) {
|
||||
Token t = nextTokenIgnoringNewline();
|
||||
if (t == Tokens.CLOSE_CURLY) {
|
||||
if (flavor == ConfigSyntax.JSON && afterComma) {
|
||||
throw parseError("expecting a field name after comma, got a close brace }");
|
||||
throw parseError(addQuoteSuggestion(t.toString(),
|
||||
"expecting a field name after a comma, got a close brace } instead"));
|
||||
} else if (!hadOpenCurly) {
|
||||
throw parseError("unbalanced close brace '}' with no open brace");
|
||||
throw parseError(addQuoteSuggestion(t.toString(),
|
||||
"unbalanced close brace '}' with no open brace"));
|
||||
}
|
||||
break;
|
||||
} else if (t == Tokens.END && !hadOpenCurly) {
|
||||
|
|
@ -381,6 +468,7 @@ final class Parser {
|
|||
} else {
|
||||
Path path = parseKey(t);
|
||||
Token afterKey = nextTokenIgnoringNewline();
|
||||
boolean insideEquals = false;
|
||||
|
||||
// path must be on-stack while we parse the value
|
||||
pathStack.push(path);
|
||||
|
|
@ -394,8 +482,14 @@ final class Parser {
|
|||
newValue = parseObject(true);
|
||||
} else {
|
||||
if (!isKeyValueSeparatorToken(afterKey)) {
|
||||
throw parseError("Key may not be followed by token: "
|
||||
+ afterKey);
|
||||
throw parseError(addQuoteSuggestion(afterKey.toString(),
|
||||
"Key '" + path.render() + "' may not be followed by token: "
|
||||
+ afterKey));
|
||||
}
|
||||
|
||||
if (afterKey == Tokens.EQUALS) {
|
||||
insideEquals = true;
|
||||
equalsCount += 1;
|
||||
}
|
||||
|
||||
consolidateValueTokens();
|
||||
|
|
@ -403,7 +497,11 @@ final class Parser {
|
|||
newValue = parseValue(valueToken);
|
||||
}
|
||||
|
||||
pathStack.pop();
|
||||
lastPath = pathStack.pop();
|
||||
if (insideEquals) {
|
||||
equalsCount -= 1;
|
||||
}
|
||||
lastInsideEquals = insideEquals;
|
||||
|
||||
String key = path.first();
|
||||
Path remaining = path.remainder();
|
||||
|
|
@ -451,25 +549,25 @@ final class Parser {
|
|||
t = nextTokenIgnoringNewline();
|
||||
if (t == Tokens.CLOSE_CURLY) {
|
||||
if (!hadOpenCurly) {
|
||||
throw parseError("unbalanced close brace '}' with no open brace");
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "unbalanced close brace '}' with no open brace"));
|
||||
}
|
||||
break;
|
||||
} else if (hadOpenCurly) {
|
||||
throw parseError("Expecting close brace } or a comma, got "
|
||||
+ t);
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "Expecting close brace } or a comma, got " + t));
|
||||
} else {
|
||||
if (t == Tokens.END) {
|
||||
putBack(t);
|
||||
break;
|
||||
} else {
|
||||
throw parseError("Expecting end of input or a comma, got "
|
||||
+ t);
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "Expecting end of input or a comma, got " + t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new SimpleConfigObject(objectOrigin,
|
||||
values);
|
||||
return new SimpleConfigObject(objectOrigin, values);
|
||||
}
|
||||
|
||||
private SimpleConfigList parseArray() {
|
||||
|
|
@ -492,8 +590,11 @@ final class Parser {
|
|||
} else if (t == Tokens.OPEN_SQUARE) {
|
||||
values.add(parseArray());
|
||||
} else {
|
||||
throw parseError("List should have ] or a first element after the open [, instead had token: "
|
||||
+ t);
|
||||
throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: "
|
||||
+ t
|
||||
+ " (if you want "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)"));
|
||||
}
|
||||
|
||||
// now remaining elements
|
||||
|
|
@ -506,8 +607,11 @@ final class Parser {
|
|||
if (t == Tokens.CLOSE_SQUARE) {
|
||||
return new SimpleConfigList(arrayOrigin, values);
|
||||
} else {
|
||||
throw parseError("List should have ended with ] or had a comma, instead had token: "
|
||||
+ t);
|
||||
throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: "
|
||||
+ t
|
||||
+ " (if you want "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,8 +630,11 @@ final class Parser {
|
|||
// we allow one trailing comma
|
||||
putBack(t);
|
||||
} else {
|
||||
throw parseError("List should have had new element after a comma, instead had token: "
|
||||
+ t);
|
||||
throw parseError(addKeyName("List should have had new element after a comma, instead had token: "
|
||||
+ t
|
||||
+ " (if you want the comma or "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -659,9 +766,12 @@ final class Parser {
|
|||
} else if (Tokens.isUnquotedText(t)) {
|
||||
text = Tokens.getUnquotedText(t);
|
||||
} else {
|
||||
throw new ConfigException.BadPath(origin, originalText,
|
||||
throw new ConfigException.BadPath(
|
||||
origin,
|
||||
originalText,
|
||||
"Token not allowed in path expression: "
|
||||
+ t);
|
||||
+ t
|
||||
+ " (you can double-quote this token if you really want it here)");
|
||||
}
|
||||
|
||||
addPathText(buf, false, text);
|
||||
|
|
@ -728,7 +838,7 @@ final class Parser {
|
|||
// do something much faster than the full parser if
|
||||
// we just have something like "foo" or "foo.bar"
|
||||
private static Path speculativeFastParsePath(String path) {
|
||||
String s = ConfigUtil.unicodeTrim(path);
|
||||
String s = ConfigImplUtil.unicodeTrim(path);
|
||||
if (s.isEmpty())
|
||||
return null;
|
||||
if (hasUnsafeChars(s))
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ final class Path {
|
|||
if (other instanceof Path) {
|
||||
Path that = (Path) other;
|
||||
return this.first.equals(that.first)
|
||||
&& ConfigUtil.equalsHandlingNull(this.remainder,
|
||||
&& ConfigImplUtil.equalsHandlingNull(this.remainder,
|
||||
that.remainder);
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -167,7 +167,7 @@ final class Path {
|
|||
|
||||
private void appendToStringBuilder(StringBuilder sb) {
|
||||
if (hasFunkyChars(first) || first.isEmpty())
|
||||
sb.append(ConfigUtil.renderJsonString(first));
|
||||
sb.append(ConfigImplUtil.renderJsonString(first));
|
||||
else
|
||||
sb.append(first);
|
||||
if (remainder != null) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
*/
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
|
|
@ -20,12 +23,10 @@ import com.typesafe.config.ConfigValue;
|
|||
import com.typesafe.config.ConfigValueType;
|
||||
|
||||
/**
|
||||
* One thing to keep in mind in the future: if any Collection-like APIs are
|
||||
* added here, including iterators or size() or anything, then we'd have to
|
||||
* grapple with whether ConfigNull values are "in" the Config (probably not) and
|
||||
* we'd probably want to make the collection look flat - not like a tree. So the
|
||||
* key-value pairs would be all the tree's leaf values, in a big flat list with
|
||||
* their full paths.
|
||||
* One thing to keep in mind in the future: as Collection-like APIs are added
|
||||
* here, including iterators or size() or anything, they should be consistent
|
||||
* with a one-level java.util.Map from paths to non-null values. Null values are
|
||||
* not "in" the map.
|
||||
*/
|
||||
final class SimpleConfig implements Config, MergeableValue {
|
||||
|
||||
|
|
@ -73,6 +74,31 @@ final class SimpleConfig implements Config, MergeableValue {
|
|||
return object.isEmpty();
|
||||
}
|
||||
|
||||
private static void findPaths(Set<Map.Entry<String, ConfigValue>> entries, Path parent,
|
||||
AbstractConfigObject obj) {
|
||||
for (Map.Entry<String, ConfigValue> entry : obj.entrySet()) {
|
||||
String elem = entry.getKey();
|
||||
ConfigValue v = entry.getValue();
|
||||
Path path = Path.newKey(elem);
|
||||
if (parent != null)
|
||||
path = path.prepend(parent);
|
||||
if (v instanceof AbstractConfigObject) {
|
||||
findPaths(entries, path, (AbstractConfigObject) v);
|
||||
} else if (v instanceof ConfigNull) {
|
||||
// nothing; nulls are conceptually not in a Config
|
||||
} else {
|
||||
entries.add(new AbstractMap.SimpleImmutableEntry<String, ConfigValue>(path.render(), v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<String, ConfigValue>> entrySet() {
|
||||
Set<Map.Entry<String, ConfigValue>> entries = new HashSet<Map.Entry<String, ConfigValue>>();
|
||||
findPaths(entries, null, object);
|
||||
return entries;
|
||||
}
|
||||
|
||||
static private AbstractConfigValue find(AbstractConfigObject self,
|
||||
String pathExpression, ConfigValueType expected, String originalPath) {
|
||||
Path path = Path.newPath(pathExpression);
|
||||
|
|
@ -440,10 +466,10 @@ final class SimpleConfig implements Config, MergeableValue {
|
|||
*/
|
||||
public static long parseDuration(String input,
|
||||
ConfigOrigin originForException, String pathForException) {
|
||||
String s = ConfigUtil.unicodeTrim(input);
|
||||
String s = ConfigImplUtil.unicodeTrim(input);
|
||||
String originalUnitString = getUnits(s);
|
||||
String unitString = originalUnitString;
|
||||
String numberString = ConfigUtil.unicodeTrim(s.substring(0, s.length()
|
||||
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length()
|
||||
- unitString.length()));
|
||||
TimeUnit units = null;
|
||||
|
||||
|
|
@ -592,9 +618,9 @@ final class SimpleConfig implements Config, MergeableValue {
|
|||
*/
|
||||
public static long parseBytes(String input, ConfigOrigin originForException,
|
||||
String pathForException) {
|
||||
String s = ConfigUtil.unicodeTrim(input);
|
||||
String s = ConfigImplUtil.unicodeTrim(input);
|
||||
String unitString = getUnits(s);
|
||||
String numberString = ConfigUtil.unicodeTrim(s.substring(0,
|
||||
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0,
|
||||
s.length() - unitString.length()));
|
||||
|
||||
// this would be caught later anyway, but the error message
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
|
|||
&& this.lineNumber == otherOrigin.lineNumber
|
||||
&& this.endLineNumber == otherOrigin.endLineNumber
|
||||
&& this.originType == otherOrigin.originType
|
||||
&& ConfigUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull);
|
||||
&& ConfigImplUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -227,7 +227,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
|
|||
}
|
||||
|
||||
String mergedURL;
|
||||
if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) {
|
||||
if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) {
|
||||
mergedURL = a.urlOrNull;
|
||||
} else {
|
||||
mergedURL = null;
|
||||
|
|
@ -252,7 +252,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
|
|||
count += 1;
|
||||
if (a.endLineNumber == b.endLineNumber)
|
||||
count += 1;
|
||||
if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull))
|
||||
if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull))
|
||||
count += 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,20 +3,57 @@
|
|||
*/
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
|
||||
class Token {
|
||||
final private TokenType tokenType;
|
||||
final private String debugString;
|
||||
final private ConfigOrigin origin;
|
||||
|
||||
Token(TokenType tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
Token(TokenType tokenType, ConfigOrigin origin) {
|
||||
this(tokenType, origin, null);
|
||||
}
|
||||
|
||||
public TokenType tokenType() {
|
||||
Token(TokenType tokenType, ConfigOrigin origin, String debugString) {
|
||||
this.tokenType = tokenType;
|
||||
this.origin = origin;
|
||||
this.debugString = debugString;
|
||||
}
|
||||
|
||||
// this is used for singleton tokens like COMMA or OPEN_CURLY
|
||||
static Token newWithoutOrigin(TokenType tokenType, String debugString) {
|
||||
return new Token(tokenType, null, debugString);
|
||||
}
|
||||
|
||||
final TokenType tokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
// this is final because we don't always use the origin() accessor,
|
||||
// and we don't because it throws if origin is null
|
||||
final ConfigOrigin origin() {
|
||||
// code is only supposed to call origin() on token types that are
|
||||
// expected to have an origin.
|
||||
if (origin == null)
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"tried to get origin from token that doesn't have one: " + this);
|
||||
return origin;
|
||||
}
|
||||
|
||||
final int lineNumber() {
|
||||
if (origin != null)
|
||||
return origin.lineNumber();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tokenType.name();
|
||||
if (debugString != null)
|
||||
return debugString;
|
||||
else
|
||||
return tokenType.name();
|
||||
}
|
||||
|
||||
protected boolean canEqual(Object other) {
|
||||
|
|
@ -26,6 +63,7 @@ class Token {
|
|||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof Token) {
|
||||
// origin is deliberately left out
|
||||
return canEqual(other)
|
||||
&& this.tokenType == ((Token) other).tokenType;
|
||||
} else {
|
||||
|
|
@ -35,6 +73,7 @@ class Token {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// origin is deliberately left out
|
||||
return tokenType.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,18 @@
|
|||
package com.typesafe.config.impl;
|
||||
|
||||
enum TokenType {
|
||||
START, END, COMMA, EQUALS, COLON, OPEN_CURLY, CLOSE_CURLY, OPEN_SQUARE, CLOSE_SQUARE, VALUE, NEWLINE, UNQUOTED_TEXT, SUBSTITUTION;
|
||||
START,
|
||||
END,
|
||||
COMMA,
|
||||
EQUALS,
|
||||
COLON,
|
||||
OPEN_CURLY,
|
||||
CLOSE_CURLY,
|
||||
OPEN_SQUARE,
|
||||
CLOSE_SQUARE,
|
||||
VALUE,
|
||||
NEWLINE,
|
||||
UNQUOTED_TEXT,
|
||||
SUBSTITUTION,
|
||||
PROBLEM;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,34 @@ import com.typesafe.config.ConfigOrigin;
|
|||
import com.typesafe.config.ConfigSyntax;
|
||||
|
||||
final class Tokenizer {
|
||||
// this exception should not leave this file
|
||||
private static class ProblemException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
final private Token problem;
|
||||
|
||||
ProblemException(Token problem) {
|
||||
this.problem = problem;
|
||||
}
|
||||
|
||||
Token problem() {
|
||||
return problem;
|
||||
}
|
||||
}
|
||||
|
||||
private static String asString(int codepoint) {
|
||||
if (codepoint == '\n')
|
||||
return "newline";
|
||||
else if (codepoint == '\t')
|
||||
return "tab";
|
||||
else if (codepoint == -1)
|
||||
return "end of file";
|
||||
else if (Character.isISOControl(codepoint))
|
||||
return String.format("control character 0x%x", codepoint);
|
||||
else
|
||||
return String.format("%c", codepoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenizes a Reader. Does not close the reader; you have to arrange to do
|
||||
* that after you're done with the returned iterator.
|
||||
|
|
@ -85,20 +113,22 @@ final class Tokenizer {
|
|||
}
|
||||
}
|
||||
|
||||
final private ConfigOrigin origin;
|
||||
final private SimpleConfigOrigin origin;
|
||||
final private Reader input;
|
||||
final private LinkedList<Integer> buffer;
|
||||
private int lineNumber;
|
||||
private ConfigOrigin lineOrigin;
|
||||
final private Queue<Token> tokens;
|
||||
final private WhitespaceSaver whitespaceSaver;
|
||||
final private boolean allowComments;
|
||||
|
||||
TokenIterator(ConfigOrigin origin, Reader input, boolean allowComments) {
|
||||
this.origin = origin;
|
||||
this.origin = (SimpleConfigOrigin) origin;
|
||||
this.input = input;
|
||||
this.allowComments = allowComments;
|
||||
this.buffer = new LinkedList<Integer>();
|
||||
lineNumber = 1;
|
||||
lineOrigin = this.origin.setLineNumber(lineNumber);
|
||||
tokens = new LinkedList<Token>();
|
||||
tokens.add(Tokens.START);
|
||||
whitespaceSaver = new WhitespaceSaver();
|
||||
|
|
@ -131,11 +161,11 @@ final class Tokenizer {
|
|||
}
|
||||
|
||||
static boolean isWhitespace(int c) {
|
||||
return ConfigUtil.isWhitespace(c);
|
||||
return ConfigImplUtil.isWhitespace(c);
|
||||
}
|
||||
|
||||
static boolean isWhitespaceNotNewline(int c) {
|
||||
return c != '\n' && ConfigUtil.isWhitespace(c);
|
||||
return c != '\n' && ConfigImplUtil.isWhitespace(c);
|
||||
}
|
||||
|
||||
private int slurpComment() {
|
||||
|
|
@ -194,27 +224,44 @@ final class Tokenizer {
|
|||
}
|
||||
}
|
||||
|
||||
private ConfigException parseError(String message) {
|
||||
return parseError(message, null);
|
||||
private ProblemException problem(String message) {
|
||||
return problem("", message, null);
|
||||
}
|
||||
|
||||
private ConfigException parseError(String message, Throwable cause) {
|
||||
return parseError(lineOrigin(), message, cause);
|
||||
private ProblemException problem(String what, String message) {
|
||||
return problem(what, message, null);
|
||||
}
|
||||
|
||||
private static ConfigException parseError(ConfigOrigin origin,
|
||||
private ProblemException problem(String what, String message, boolean suggestQuotes) {
|
||||
return problem(what, message, suggestQuotes, null);
|
||||
}
|
||||
|
||||
private ProblemException problem(String what, String message, Throwable cause) {
|
||||
return problem(lineOrigin, what, message, cause);
|
||||
}
|
||||
|
||||
private ProblemException problem(String what, String message, boolean suggestQuotes,
|
||||
Throwable cause) {
|
||||
return problem(lineOrigin, what, message, suggestQuotes, cause);
|
||||
}
|
||||
|
||||
private static ProblemException problem(ConfigOrigin origin, String what,
|
||||
String message,
|
||||
Throwable cause) {
|
||||
return new ConfigException.Parse(origin, message, cause);
|
||||
return problem(origin, what, message, false, cause);
|
||||
}
|
||||
|
||||
private static ConfigException parseError(ConfigOrigin origin,
|
||||
String message) {
|
||||
return parseError(origin, message, null);
|
||||
private static ProblemException problem(ConfigOrigin origin, String what, String message,
|
||||
boolean suggestQuotes, Throwable cause) {
|
||||
if (what == null || message == null)
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"internal error, creating bad ProblemException");
|
||||
return new ProblemException(Tokens.newProblem(origin, what, message, suggestQuotes,
|
||||
cause));
|
||||
}
|
||||
|
||||
private ConfigOrigin lineOrigin() {
|
||||
return lineOrigin(origin, lineNumber);
|
||||
private static ProblemException problem(ConfigOrigin origin, String message) {
|
||||
return problem(origin, "", message, null);
|
||||
}
|
||||
|
||||
private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin,
|
||||
|
|
@ -234,7 +281,7 @@ final class Tokenizer {
|
|||
// that parses as JSON is treated the JSON way and otherwise
|
||||
// we assume it's a string and let the parser sort it out.
|
||||
private Token pullUnquotedText() {
|
||||
ConfigOrigin origin = lineOrigin();
|
||||
ConfigOrigin origin = lineOrigin;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int c = nextCharSkippingComments();
|
||||
while (true) {
|
||||
|
|
@ -273,7 +320,7 @@ final class Tokenizer {
|
|||
return Tokens.newUnquotedText(origin, s);
|
||||
}
|
||||
|
||||
private Token pullNumber(int firstChar) {
|
||||
private Token pullNumber(int firstChar) throws ProblemException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.appendCodePoint(firstChar);
|
||||
boolean containedDecimalOrE = false;
|
||||
|
|
@ -291,23 +338,20 @@ final class Tokenizer {
|
|||
try {
|
||||
if (containedDecimalOrE) {
|
||||
// force floating point representation
|
||||
return Tokens.newDouble(lineOrigin(),
|
||||
Double.parseDouble(s), s);
|
||||
return Tokens.newDouble(lineOrigin, Double.parseDouble(s), s);
|
||||
} else {
|
||||
// this should throw if the integer is too large for Long
|
||||
return Tokens.newLong(lineOrigin(), Long.parseLong(s), s);
|
||||
return Tokens.newLong(lineOrigin, Long.parseLong(s), s);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw parseError("Invalid number: '" + s
|
||||
+ "' (if this is in a path, try quoting it with double quotes)",
|
||||
e);
|
||||
throw problem(s, "Invalid number: '" + s + "'", true /* suggestQuotes */, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void pullEscapeSequence(StringBuilder sb) {
|
||||
private void pullEscapeSequence(StringBuilder sb) throws ProblemException {
|
||||
int escaped = nextCharRaw();
|
||||
if (escaped == -1)
|
||||
throw parseError("End of input but backslash in string had nothing after it");
|
||||
throw problem("End of input but backslash in string had nothing after it");
|
||||
|
||||
switch (escaped) {
|
||||
case '"':
|
||||
|
|
@ -340,67 +384,57 @@ final class Tokenizer {
|
|||
for (int i = 0; i < 4; ++i) {
|
||||
int c = nextCharSkippingComments();
|
||||
if (c == -1)
|
||||
throw parseError("End of input but expecting 4 hex digits for \\uXXXX escape");
|
||||
throw problem("End of input but expecting 4 hex digits for \\uXXXX escape");
|
||||
a[i] = (char) c;
|
||||
}
|
||||
String digits = new String(a);
|
||||
try {
|
||||
sb.appendCodePoint(Integer.parseInt(digits, 16));
|
||||
} catch (NumberFormatException e) {
|
||||
throw parseError(
|
||||
String.format(
|
||||
"Malformed hex digits after \\u escape in string: '%s'",
|
||||
digits), e);
|
||||
throw problem(digits, String.format(
|
||||
"Malformed hex digits after \\u escape in string: '%s'", digits), e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw parseError(String
|
||||
.format("backslash followed by '%c', this is not a valid escape sequence",
|
||||
escaped));
|
||||
throw problem(
|
||||
asString(escaped),
|
||||
String.format(
|
||||
"backslash followed by '%s', this is not a valid escape sequence (quoted strings use JSON escaping, so use double-backslash \\\\ for literal backslash)",
|
||||
asString(escaped)));
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigException controlCharacterError(int c) {
|
||||
String asString;
|
||||
if (c == '\n')
|
||||
asString = "newline";
|
||||
else if (c == '\t')
|
||||
asString = "tab";
|
||||
else
|
||||
asString = String.format("control character 0x%x", c);
|
||||
return parseError("JSON does not allow unescaped " + asString
|
||||
+ " in quoted strings, use a backslash escape");
|
||||
}
|
||||
|
||||
private Token pullQuotedString() {
|
||||
private Token pullQuotedString() throws ProblemException {
|
||||
// the open quote has already been consumed
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int c = '\0'; // value doesn't get used
|
||||
do {
|
||||
c = nextCharRaw();
|
||||
if (c == -1)
|
||||
throw parseError("End of input but string quote was still open");
|
||||
throw problem("End of input but string quote was still open");
|
||||
|
||||
if (c == '\\') {
|
||||
pullEscapeSequence(sb);
|
||||
} else if (c == '"') {
|
||||
// end the loop, done!
|
||||
} else if (Character.isISOControl(c)) {
|
||||
throw controlCharacterError(c);
|
||||
throw problem(asString(c), "JSON does not allow unescaped " + asString(c)
|
||||
+ " in quoted strings, use a backslash escape");
|
||||
} else {
|
||||
sb.appendCodePoint(c);
|
||||
}
|
||||
} while (c != '"');
|
||||
return Tokens.newString(lineOrigin(), sb.toString());
|
||||
return Tokens.newString(lineOrigin, sb.toString());
|
||||
}
|
||||
|
||||
private Token pullSubstitution() {
|
||||
private Token pullSubstitution() throws ProblemException {
|
||||
// the initial '$' has already been consumed
|
||||
ConfigOrigin origin = lineOrigin();
|
||||
ConfigOrigin origin = lineOrigin;
|
||||
int c = nextCharSkippingComments();
|
||||
if (c != '{') {
|
||||
throw parseError("'$' not followed by {");
|
||||
throw problem(asString(c), "'$' not followed by {, '" + asString(c)
|
||||
+ "' not allowed after '$'", true /* suggestQuotes */);
|
||||
}
|
||||
|
||||
boolean optional = false;
|
||||
|
|
@ -425,7 +459,7 @@ final class Tokenizer {
|
|||
// end the loop, done!
|
||||
break;
|
||||
} else if (t == Tokens.END) {
|
||||
throw parseError(origin,
|
||||
throw problem(origin,
|
||||
"Substitution ${ was not closed with a }");
|
||||
} else {
|
||||
Token whitespace = saver.check(t, origin, lineNumber);
|
||||
|
|
@ -438,14 +472,16 @@ final class Tokenizer {
|
|||
return Tokens.newSubstitution(origin, optional, expression);
|
||||
}
|
||||
|
||||
private Token pullNextToken(WhitespaceSaver saver) {
|
||||
private Token pullNextToken(WhitespaceSaver saver) throws ProblemException {
|
||||
int c = nextCharAfterWhitespace(saver);
|
||||
if (c == -1) {
|
||||
return Tokens.END;
|
||||
} else if (c == '\n') {
|
||||
// newline tokens have the just-ended line number
|
||||
Token line = Tokens.newLine(lineOrigin);
|
||||
lineNumber += 1;
|
||||
return Tokens.newLine(lineNumber - 1);
|
||||
lineOrigin = origin.setLineNumber(lineNumber);
|
||||
return line;
|
||||
} else {
|
||||
Token t = null;
|
||||
switch (c) {
|
||||
|
|
@ -482,9 +518,8 @@ final class Tokenizer {
|
|||
if (firstNumberChars.indexOf(c) >= 0) {
|
||||
t = pullNumber(c);
|
||||
} else if (notInUnquotedText.indexOf(c) >= 0) {
|
||||
throw parseError(String
|
||||
.format("Character '%c' is not the start of any valid token",
|
||||
c));
|
||||
throw problem(asString(c), "Reserved character '" + asString(c)
|
||||
+ "' is not allowed outside quotes", true /* suggestQuotes */);
|
||||
} else {
|
||||
putBack(c);
|
||||
t = pullUnquotedText();
|
||||
|
|
@ -508,7 +543,7 @@ final class Tokenizer {
|
|||
}
|
||||
}
|
||||
|
||||
private void queueNextToken() {
|
||||
private void queueNextToken() throws ProblemException {
|
||||
Token t = pullNextToken(whitespaceSaver);
|
||||
Token whitespace = whitespaceSaver.check(t, origin, lineNumber);
|
||||
if (whitespace != null)
|
||||
|
|
@ -525,7 +560,11 @@ final class Tokenizer {
|
|||
public Token next() {
|
||||
Token t = tokens.remove();
|
||||
if (tokens.isEmpty() && t != Tokens.END) {
|
||||
queueNextToken();
|
||||
try {
|
||||
queueNextToken();
|
||||
} catch (ProblemException e) {
|
||||
tokens.add(e.problem());
|
||||
}
|
||||
if (tokens.isEmpty())
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"bug: tokens queue should not be empty here");
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ import com.typesafe.config.ConfigException;
|
|||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigValueType;
|
||||
|
||||
/* FIXME the way the subclasses of Token are private with static isFoo and accessors is kind of ridiculous. */
|
||||
final class Tokens {
|
||||
static private class Value extends Token {
|
||||
|
||||
final private AbstractConfigValue value;
|
||||
|
||||
Value(AbstractConfigValue value) {
|
||||
super(TokenType.VALUE);
|
||||
super(TokenType.VALUE, value.origin());
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
|
@ -25,10 +26,7 @@ final class Tokens {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = tokenType().name() + "(" + value.valueType().name()
|
||||
+ ")";
|
||||
|
||||
return s + "='" + value().unwrapped() + "'";
|
||||
return "'" + value().unwrapped() + "' (" + value.valueType().name() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -48,20 +46,13 @@ final class Tokens {
|
|||
}
|
||||
|
||||
static private class Line extends Token {
|
||||
final private int lineNumber;
|
||||
|
||||
Line(int lineNumber) {
|
||||
super(TokenType.NEWLINE);
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
int lineNumber() {
|
||||
return lineNumber;
|
||||
Line(ConfigOrigin origin) {
|
||||
super(TokenType.NEWLINE, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NEWLINE@" + lineNumber;
|
||||
return "'\n'@" + lineNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -71,38 +62,31 @@ final class Tokens {
|
|||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return super.equals(other)
|
||||
&& ((Line) other).lineNumber == lineNumber;
|
||||
return super.equals(other) && ((Line) other).lineNumber() == lineNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 41 * (41 + super.hashCode()) + lineNumber;
|
||||
return 41 * (41 + super.hashCode()) + lineNumber();
|
||||
}
|
||||
}
|
||||
|
||||
// This is not a Value, because it requires special processing
|
||||
static private class UnquotedText extends Token {
|
||||
final private ConfigOrigin origin;
|
||||
final private String value;
|
||||
|
||||
UnquotedText(ConfigOrigin origin, String s) {
|
||||
super(TokenType.UNQUOTED_TEXT);
|
||||
this.origin = origin;
|
||||
super(TokenType.UNQUOTED_TEXT, origin);
|
||||
this.value = s;
|
||||
}
|
||||
|
||||
ConfigOrigin origin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tokenType().name() + "(" + value + ")";
|
||||
return "'" + value + "'";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -122,23 +106,78 @@ final class Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
static private class Problem extends Token {
|
||||
final private String what;
|
||||
final private String message;
|
||||
final private boolean suggestQuotes;
|
||||
final private Throwable cause;
|
||||
|
||||
Problem(ConfigOrigin origin, String what, String message, boolean suggestQuotes,
|
||||
Throwable cause) {
|
||||
super(TokenType.PROBLEM, origin);
|
||||
this.what = what;
|
||||
this.message = message;
|
||||
this.suggestQuotes = suggestQuotes;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
boolean suggestQuotes() {
|
||||
return suggestQuotes;
|
||||
}
|
||||
|
||||
Throwable cause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('\'');
|
||||
sb.append(what);
|
||||
sb.append('\'');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canEqual(Object other) {
|
||||
return other instanceof Problem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return super.equals(other) && ((Problem) other).what.equals(what)
|
||||
&& ((Problem) other).message.equals(message)
|
||||
&& ((Problem) other).suggestQuotes == suggestQuotes
|
||||
&& ConfigImplUtil.equalsHandlingNull(((Problem) other).cause, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 41 * (41 + super.hashCode());
|
||||
h = 41 * (h + what.hashCode());
|
||||
h = 41 * (h + message.hashCode());
|
||||
h = 41 * (h + Boolean.valueOf(suggestQuotes).hashCode());
|
||||
if (cause != null)
|
||||
h = 41 * (h + cause.hashCode());
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
// This is not a Value, because it requires special processing
|
||||
static private class Substitution extends Token {
|
||||
final private ConfigOrigin origin;
|
||||
final private boolean optional;
|
||||
final private List<Token> value;
|
||||
|
||||
Substitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
|
||||
super(TokenType.SUBSTITUTION);
|
||||
this.origin = origin;
|
||||
super(TokenType.SUBSTITUTION, origin);
|
||||
this.optional = optional;
|
||||
this.value = expression;
|
||||
}
|
||||
|
||||
ConfigOrigin origin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
boolean optional() {
|
||||
return optional;
|
||||
}
|
||||
|
|
@ -149,7 +188,11 @@ final class Tokens {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tokenType().name() + "(" + value.toString() + ")";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Token t : value) {
|
||||
sb.append(t.toString());
|
||||
}
|
||||
return "'${" + sb.toString() + "}'";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -190,12 +233,32 @@ final class Tokens {
|
|||
return token instanceof Line;
|
||||
}
|
||||
|
||||
static int getLineNumber(Token token) {
|
||||
if (token instanceof Line) {
|
||||
return ((Line) token).lineNumber();
|
||||
static boolean isProblem(Token token) {
|
||||
return token instanceof Problem;
|
||||
}
|
||||
|
||||
static String getProblemMessage(Token token) {
|
||||
if (token instanceof Problem) {
|
||||
return ((Problem) token).message();
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"tried to get line number from non-newline " + token);
|
||||
throw new ConfigException.BugOrBroken("tried to get problem message from " + token);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean getProblemSuggestQuotes(Token token) {
|
||||
if (token instanceof Problem) {
|
||||
return ((Problem) token).suggestQuotes();
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken("tried to get problem suggestQuotes from "
|
||||
+ token);
|
||||
}
|
||||
}
|
||||
|
||||
static Throwable getProblemCause(Token token) {
|
||||
if (token instanceof Problem) {
|
||||
return ((Problem) token).cause();
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken("tried to get problem cause from " + token);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,15 +275,6 @@ final class Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
static ConfigOrigin getUnquotedTextOrigin(Token token) {
|
||||
if (token instanceof UnquotedText) {
|
||||
return ((UnquotedText) token).origin();
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"tried to get unquoted text from " + token);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isSubstitution(Token token) {
|
||||
return token instanceof Substitution;
|
||||
}
|
||||
|
|
@ -234,15 +288,6 @@ final class Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
static ConfigOrigin getSubstitutionOrigin(Token token) {
|
||||
if (token instanceof Substitution) {
|
||||
return ((Substitution) token).origin();
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"tried to get substitution origin from " + token);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean getSubstitutionOptional(Token token) {
|
||||
if (token instanceof Substitution) {
|
||||
return ((Substitution) token).optional();
|
||||
|
|
@ -252,18 +297,23 @@ final class Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
final static Token START = new Token(TokenType.START);
|
||||
final static Token END = new Token(TokenType.END);
|
||||
final static Token COMMA = new Token(TokenType.COMMA);
|
||||
final static Token EQUALS = new Token(TokenType.EQUALS);
|
||||
final static Token COLON = new Token(TokenType.COLON);
|
||||
final static Token OPEN_CURLY = new Token(TokenType.OPEN_CURLY);
|
||||
final static Token CLOSE_CURLY = new Token(TokenType.CLOSE_CURLY);
|
||||
final static Token OPEN_SQUARE = new Token(TokenType.OPEN_SQUARE);
|
||||
final static Token CLOSE_SQUARE = new Token(TokenType.CLOSE_SQUARE);
|
||||
final static Token START = Token.newWithoutOrigin(TokenType.START, "start of file");
|
||||
final static Token END = Token.newWithoutOrigin(TokenType.END, "end of file");
|
||||
final static Token COMMA = Token.newWithoutOrigin(TokenType.COMMA, "','");
|
||||
final static Token EQUALS = Token.newWithoutOrigin(TokenType.EQUALS, "'='");
|
||||
final static Token COLON = Token.newWithoutOrigin(TokenType.COLON, "':'");
|
||||
final static Token OPEN_CURLY = Token.newWithoutOrigin(TokenType.OPEN_CURLY, "'{'");
|
||||
final static Token CLOSE_CURLY = Token.newWithoutOrigin(TokenType.CLOSE_CURLY, "'}'");
|
||||
final static Token OPEN_SQUARE = Token.newWithoutOrigin(TokenType.OPEN_SQUARE, "'['");
|
||||
final static Token CLOSE_SQUARE = Token.newWithoutOrigin(TokenType.CLOSE_SQUARE, "']'");
|
||||
|
||||
static Token newLine(int lineNumberJustEnded) {
|
||||
return new Line(lineNumberJustEnded);
|
||||
static Token newLine(ConfigOrigin origin) {
|
||||
return new Line(origin);
|
||||
}
|
||||
|
||||
static Token newProblem(ConfigOrigin origin, String what, String message,
|
||||
boolean suggestQuotes, Throwable cause) {
|
||||
return new Problem(origin, what, message, suggestQuotes, cause);
|
||||
}
|
||||
|
||||
static Token newUnquotedText(ConfigOrigin origin, String s) {
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ A custom ``application.conf`` might look like this::
|
|||
Config file format
|
||||
------------------
|
||||
|
||||
The configuration file syntax is described in the `HOCON <https://github.com/havocp/config/blob/master/HOCON.md>`_
|
||||
The configuration file syntax is described in the `HOCON <https://github.com/typesafehub/config/blob/master/HOCON.md>`_
|
||||
specification. Note that it supports three formats; conf, json, and properties.
|
||||
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ dev.conf:
|
|||
loglevel = "DEBUG"
|
||||
}
|
||||
|
||||
More advanced include and substitution mechanisms are explained in the `HOCON <https://github.com/havocp/config/blob/master/HOCON.md>`_
|
||||
More advanced include and substitution mechanisms are explained in the `HOCON <https://github.com/typesafehub/config/blob/master/HOCON.md>`_
|
||||
specification.
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue