diff --git a/src/main/java/com/typesafe/config/Config.java b/src/main/java/com/typesafe/config/Config.java index 2eb6f834..04518802 100644 --- a/src/main/java/com/typesafe/config/Config.java +++ b/src/main/java/com/typesafe/config/Config.java @@ -1,9 +1,16 @@ package com.typesafe.config; +import java.io.File; +import java.io.Reader; +import java.net.URL; +import java.util.Collection; +import java.util.Map; +import java.util.Properties; import java.util.concurrent.TimeUnit; import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.ConfigUtil; +import com.typesafe.config.impl.Parseable; /** * This class holds some global static methods for the config package. @@ -11,23 +18,125 @@ import com.typesafe.config.impl.ConfigUtil; public final class Config { /** - * Loads a configuration for the given root path. The root path should be a - * short word that scopes the package being configured; typically it's the + * Loads a configuration for the given root path in a "standard" way. + * Oversimplified, if your root path is foo.bar then this will load files + * from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties, + * foo-bar-reference.conf, foo-bar-reference.json, + * foo-bar-reference.properties. It will override all those files with any + * system properties that begin with "foo.bar.", as well. + * + * The root path should be a path expression, usually just a single short + * word, that scopes the package being configured; typically it's the * package name or something similar. System properties overriding values in * the configuration will have to be prefixed with the root path. The root * path may have periods in it if you like but other punctuation or * whitespace will probably cause you headaches. Example root paths: "akka", * "sbt", "jsoup", "heroku", "mongo", etc. * - * This object will already be resolved (substitutions have already been - * processed). + * The loaded object will already be resolved (substitutions have already + * been processed). As a result, if you add more fallbacks then they won't + * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If + * you want to parse additional files or something then you need to use + * loadWithoutResolving(). * * @param rootPath * the configuration "domain" * @return configuration object for the requested root path */ public static ConfigRoot load(String rootPath) { - return ConfigImpl.loadConfig(rootPath); + return loadWithoutResolving(rootPath).resolve(); + } + + /** + * Like load() but does not resolve the object, so you can go ahead and add + * more fallbacks and stuff and have them seen by substitutions when you do + * call {@link ConfigRoot.resolve()}. + * + * @param rootPath + * @return + */ + public static ConfigRoot loadWithoutResolving(String rootPath) { + ConfigRoot system = systemPropertiesRoot(rootPath); + + ConfigValue mainFiles = parse(rootPath, ConfigParseOptions.defaults()); + ConfigValue referenceFiles = parse(rootPath + ".reference", + ConfigParseOptions.defaults()); + + return system.withFallbacks(mainFiles, referenceFiles); + } + + public static ConfigRoot emptyRoot(String rootPath) { + return ConfigImpl.emptyRoot(rootPath); + } + + public static ConfigRoot systemPropertiesRoot(String rootPath) { + return ConfigImpl.systemPropertiesRoot(rootPath); + } + + public static ConfigObject systemProperties() { + return ConfigImpl.systemPropertiesAsConfig(); + } + + public static ConfigObject systemEnvironment() { + return ConfigImpl.envVariablesAsConfig(); + } + + public static ConfigObject parse(Properties properties, + ConfigParseOptions options) { + return ConfigImpl.parse(properties, + options.withFallbackOriginDescription("properties")); + } + + public static ConfigObject parse(Reader reader, ConfigParseOptions options) { + Parseable p = Parseable.newReader(reader); + return ConfigImpl.parse(p, + options.withFallbackOriginDescription("Reader")); + } + + public static ConfigObject parse(URL url, ConfigParseOptions options) { + Parseable p = Parseable.newURL(url); + return ConfigImpl.parse(p, + options.withFallbackOriginDescription(url.toExternalForm())); + } + + public static ConfigObject parse(File file, ConfigParseOptions options) { + Parseable p = Parseable.newFile(file); + return ConfigImpl.parse(p, + options.withFallbackOriginDescription(file.getPath())); + } + + public static ConfigObject parse(Class<?> klass, String resource, + ConfigParseOptions options) { + Parseable p = Parseable.newResource(klass, resource); + return ConfigImpl.parse(p, + options.withFallbackOriginDescription(resource)); + } + + /** + * Parses classpath resources corresponding to this path expression. + * Essentially if the path is "foo.bar" then the resources are + * "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than + * one of those exists, they are merged. + * + * @param path + * @param options + * @return + */ + public static ConfigObject parse(String path, ConfigParseOptions options) { + // null originDescription is allowed in parseResourcesForPath + return ConfigImpl.parseResourcesForPath(path, options); + } + + public static ConfigValue fromAnyRef(Object object) { + throw new UnsupportedOperationException("not implemented yet"); + } + + public static ConfigObject fromMap(Map<String, ? extends Object> values) { + throw new UnsupportedOperationException("not implemented yet"); + } + + public static ConfigList fromCollection(Collection<? extends Object> values) { + throw new UnsupportedOperationException("not implemented yet"); } private static String getUnits(String s) { diff --git a/src/main/java/com/typesafe/config/ConfigObject.java b/src/main/java/com/typesafe/config/ConfigObject.java index 1371c751..2f3743d5 100644 --- a/src/main/java/com/typesafe/config/ConfigObject.java +++ b/src/main/java/com/typesafe/config/ConfigObject.java @@ -35,6 +35,9 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> { @Override ConfigObject withFallback(ConfigValue other); + @Override + ConfigObject withFallbacks(ConfigValue... others); + boolean getBoolean(String path); Number getNumber(String path); diff --git a/src/main/java/com/typesafe/config/ConfigParseOptions.java b/src/main/java/com/typesafe/config/ConfigParseOptions.java new file mode 100644 index 00000000..818e3f46 --- /dev/null +++ b/src/main/java/com/typesafe/config/ConfigParseOptions.java @@ -0,0 +1,90 @@ +package com.typesafe.config; + + +public final class ConfigParseOptions { + final ConfigSyntax syntax; + final String originDescription; + final boolean allowMissing; + + protected ConfigParseOptions(ConfigSyntax syntax, String originDescription, + boolean allowMissing) { + this.syntax = syntax; + this.originDescription = originDescription; + this.allowMissing = allowMissing; + } + + public static ConfigParseOptions defaults() { + return new ConfigParseOptions(null, null, true); + } + + /** + * Set the file format. If set to null, try to guess from any available + * filename extension; if guessing fails, assume ConfigSyntax.CONF. + * + * @param syntax + * @return + */ + public ConfigParseOptions setSyntax(ConfigSyntax syntax) { + if (this.syntax == syntax) + return this; + else + return new ConfigParseOptions(syntax, this.originDescription, + this.allowMissing); + } + + public ConfigSyntax getSyntax() { + return syntax; + } + + /** + * Set a description for the thing being parsed. In most cases this will be + * set up for you to something like the filename, but if you provide just an + * input stream you might want to improve on it. Set to null to allow the + * library to come up with something automatically. + * + * @param originDescription + * @return + */ + public ConfigParseOptions setOriginDescription(String originDescription) { + if (this.originDescription == originDescription) + return this; + else if (this.originDescription != null && originDescription != null + && this.originDescription.equals(originDescription)) + return this; + else + return new ConfigParseOptions(this.syntax, originDescription, + this.allowMissing); + } + + public String getOriginDescription() { + return originDescription; + } + + /** this is package-private, not public API */ + ConfigParseOptions withFallbackOriginDescription(String originDescription) { + if (this.originDescription == null) + return setOriginDescription(originDescription); + else + return this; + } + + /** + * Set to false to throw an exception if the item being parsed (for example + * a file) is missing. Set to true to just return an empty document in that + * case. + * + * @param allowMissing + * @return + */ + public ConfigParseOptions setAllowMissing(boolean allowMissing) { + if (this.allowMissing == allowMissing) + return this; + else + return new ConfigParseOptions(this.syntax, this.originDescription, + allowMissing); + } + + public boolean getAllowMissing() { + return allowMissing; + } +} diff --git a/src/main/java/com/typesafe/config/ConfigResolveOptions.java b/src/main/java/com/typesafe/config/ConfigResolveOptions.java new file mode 100644 index 00000000..91e3320c --- /dev/null +++ b/src/main/java/com/typesafe/config/ConfigResolveOptions.java @@ -0,0 +1,36 @@ +package com.typesafe.config; + +public final class ConfigResolveOptions { + private final boolean useSystemProperties; + private final boolean useSystemEnvironment; + + private ConfigResolveOptions(boolean useSystemProperties, + boolean useSystemEnvironment) { + this.useSystemProperties = useSystemProperties; + this.useSystemEnvironment = useSystemEnvironment; + } + + public static ConfigResolveOptions defaults() { + return new ConfigResolveOptions(true, true); + } + + public static ConfigResolveOptions noSystem() { + return new ConfigResolveOptions(false, false); + } + + public ConfigResolveOptions setUseSystemProperties(boolean value) { + return new ConfigResolveOptions(value, useSystemEnvironment); + } + + public ConfigResolveOptions setUseSystemEnvironment(boolean value) { + return new ConfigResolveOptions(useSystemProperties, value); + } + + public boolean getUseSystemProperties() { + return useSystemProperties; + } + + public boolean getUseSystemEnvironment() { + return useSystemEnvironment; + } +} diff --git a/src/main/java/com/typesafe/config/ConfigRoot.java b/src/main/java/com/typesafe/config/ConfigRoot.java index 62e54d40..6e3f3695 100644 --- a/src/main/java/com/typesafe/config/ConfigRoot.java +++ b/src/main/java/com/typesafe/config/ConfigRoot.java @@ -10,12 +10,24 @@ public interface ConfigRoot extends ConfigObject { * Returns a replacement root object with all substitutions (the * "${foo.bar}" syntax) resolved. Substitutions are looked up in this root * object. A configuration value tree must be resolved before you can use - * it. - * + * it. This method uses ConfigResolveOptions.defaults(). + * * @return an immutable object with substitutions resolved */ ConfigRoot resolve(); + ConfigRoot resolve(ConfigResolveOptions options); + @Override ConfigRoot withFallback(ConfigValue fallback); + + @Override + ConfigRoot withFallbacks(ConfigValue... fallbacks); + + /** + * Gets the global app name that this root represents. + * + * @return the app's root config path + */ + String rootPath(); } diff --git a/src/main/java/com/typesafe/config/ConfigSyntax.java b/src/main/java/com/typesafe/config/ConfigSyntax.java new file mode 100644 index 00000000..c1c68d07 --- /dev/null +++ b/src/main/java/com/typesafe/config/ConfigSyntax.java @@ -0,0 +1,5 @@ +package com.typesafe.config; + +public enum ConfigSyntax { + JSON, CONF, PROPERTIES; +} diff --git a/src/main/java/com/typesafe/config/ConfigValue.java b/src/main/java/com/typesafe/config/ConfigValue.java index 0e0152fc..f830a8d0 100644 --- a/src/main/java/com/typesafe/config/ConfigValue.java +++ b/src/main/java/com/typesafe/config/ConfigValue.java @@ -1,6 +1,5 @@ package com.typesafe.config; - /** * Interface implemented by any configuration value. From the perspective of * users of this interface, the object is immutable. It is therefore safe to use @@ -42,4 +41,13 @@ public interface ConfigValue { * used) */ ConfigValue withFallback(ConfigValue other); + + /** + * Convenience method just calls withFallback() on each of the values; + * earlier values in the list win over later ones. + * + * @param fallbacks + * @return a version of the object with the requested fallbacks merged in + */ + ConfigValue withFallbacks(ConfigValue... fallbacks); } diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index 4344ca59..2c4d2621 100644 --- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -16,7 +16,7 @@ import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigRoot; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -38,14 +38,20 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements * * @return a config root */ - protected ConfigRoot asRoot() { - return new RootConfigObject(this); + protected ConfigRootImpl asRoot(Path rootPath) { + return new RootConfigObject(this, rootPath); } - protected static ConfigRoot resolve(ConfigRoot root) { + protected static ConfigRootImpl resolve(ConfigRootImpl root) { + return resolve(root, ConfigResolveOptions.defaults()); + } + + protected static ConfigRootImpl resolve(ConfigRootImpl root, + ConfigResolveOptions options) { AbstractConfigValue resolved = SubstitutionResolver.resolve( - (AbstractConfigValue) root, (AbstractConfigObject) root); - return ((AbstractConfigObject) resolved).asRoot(); + (AbstractConfigValue) root, (AbstractConfigObject) root, + options); + return ((AbstractConfigObject) resolved).asRoot(root.rootPathObject()); } /** @@ -58,12 +64,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements protected abstract AbstractConfigValue peek(String key); protected AbstractConfigValue peek(String key, - SubstitutionResolver resolver, - int depth, boolean withFallbacks) { + SubstitutionResolver resolver, int depth, + ConfigResolveOptions options) { AbstractConfigValue v = peek(key); if (v != null && resolver != null) { - v = resolver.resolve(v, depth, withFallbacks); + v = resolver.resolve(v, depth, options); } return v; @@ -75,18 +81,18 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements * resolver != null. */ protected ConfigValue peekPath(Path path, SubstitutionResolver resolver, - int depth, - boolean withFallbacks) { - return peekPath(this, path, resolver, depth, withFallbacks); + int depth, ConfigResolveOptions options) { + return peekPath(this, path, resolver, depth, options); } private static ConfigValue peekPath(AbstractConfigObject self, Path path, - SubstitutionResolver resolver, int depth, boolean withFallbacks) { + SubstitutionResolver resolver, int depth, + ConfigResolveOptions options) { String key = path.first(); Path next = path.remainder(); if (next == null) { - ConfigValue v = self.peek(key, resolver, depth, withFallbacks); + ConfigValue v = self.peek(key, resolver, depth, options); return v; } else { // it's important to ONLY resolve substitutions here, not @@ -98,13 +104,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements ConfigValue v = self.peek(key); if (v instanceof ConfigSubstitution && resolver != null) { - v = resolver.resolve((AbstractConfigValue) v, depth, - withFallbacks); + v = resolver.resolve((AbstractConfigValue) v, depth, options); } if (v instanceof AbstractConfigObject) { return peekPath((AbstractConfigObject) v, next, resolver, - depth, withFallbacks); + depth, options); } else { return null; } @@ -177,6 +182,11 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements originalPath); } + @Override + public AbstractConfigObject withFallbacks(ConfigValue... others) { + return (AbstractConfigObject) super.withFallbacks(others); + } + @Override public AbstractConfigObject withFallback(ConfigValue other) { if (other instanceof Unmergeable) { @@ -291,7 +301,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements @Override AbstractConfigObject resolveSubstitutions(final SubstitutionResolver resolver, final int depth, - final boolean withFallbacks) { + final ConfigResolveOptions options) { if (resolveStatus() == ResolveStatus.RESOLVED) return this; @@ -299,7 +309,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements @Override public AbstractConfigValue modifyChild(AbstractConfigValue v) { - return resolver.resolve(v, depth, withFallbacks); + return resolver.resolve(v, depth, options); } }, ResolveStatus.RESOLVED); diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index 12b6f3b6..12349eb3 100644 --- a/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -1,6 +1,7 @@ package com.typesafe.config.impl; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; abstract class AbstractConfigValue implements ConfigValue { @@ -24,13 +25,13 @@ abstract class AbstractConfigValue implements ConfigValue { * @param depth * the number of substitutions followed in resolving the current * one - * @param withFallbacks + * @param options * whether to look at system props and env vars * @return a new value if there were changes, or this if no changes */ AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, int depth, - boolean withFallbacks) { + ConfigResolveOptions options) { return this; } @@ -63,6 +64,15 @@ abstract class AbstractConfigValue implements ConfigValue { return this; } + @Override + public AbstractConfigValue withFallbacks(ConfigValue... fallbacks) { + AbstractConfigValue merged = this; + for (ConfigValue f : fallbacks) { + merged = merged.withFallback(f); + } + return merged; + } + AbstractConfigValue transformed(ConfigTransformer transformer) { return this; } diff --git a/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java b/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java index 414eb6d3..ecde179a 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java +++ b/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -6,6 +6,7 @@ import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -46,21 +47,20 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements @Override AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, - int depth, boolean withFallbacks) { - return resolveSubstitutions(stack, resolver, depth, withFallbacks); + int depth, ConfigResolveOptions options) { + return resolveSubstitutions(stack, resolver, depth, options); } // static method also used by ConfigDelayedMergeObject static AbstractConfigValue resolveSubstitutions( List<AbstractConfigValue> stack, SubstitutionResolver resolver, - int depth, boolean withFallbacks) { + int depth, ConfigResolveOptions options) { // to resolve substitutions, we need to recursively resolve // the stack of stuff to merge, and then merge the stack. List<AbstractConfigObject> toMerge = new ArrayList<AbstractConfigObject>(); for (AbstractConfigValue v : stack) { - AbstractConfigValue resolved = resolver.resolve(v, depth, - withFallbacks); + AbstractConfigValue resolved = resolver.resolve(v, depth, options); if (resolved instanceof AbstractConfigObject) { toMerge.add((AbstractConfigObject) resolved); diff --git a/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java index f75a8c2a..7220b88f 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -8,7 +8,7 @@ import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; -import com.typesafe.config.ConfigRoot; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; // This is just like ConfigDelayedMerge except we know statically @@ -37,30 +37,56 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements } final private static class Root extends ConfigDelayedMergeObject implements - ConfigRoot { - Root(ConfigDelayedMergeObject original) { + ConfigRootImpl { + final private Path rootPath; + + Root(ConfigDelayedMergeObject original, Path rootPath) { super(original.origin(), original.stack); + this.rootPath = rootPath; } @Override - protected Root asRoot() { - return this; + protected Root asRoot(Path newRootPath) { + if (newRootPath.equals(this.rootPath)) + return this; + else + return new Root(this, newRootPath); } @Override - public ConfigRoot resolve() { + public ConfigRootImpl resolve() { return resolve(this); } + @Override + public ConfigRootImpl resolve(ConfigResolveOptions options) { + return resolve(this, options); + } + @Override public Root withFallback(ConfigValue value) { - return super.withFallback(value).asRoot(); + return super.withFallback(value).asRoot(rootPath); + } + + @Override + public Root withFallbacks(ConfigValue... values) { + return super.withFallbacks(values).asRoot(rootPath); + } + + @Override + public String rootPath() { + return rootPath.render(); + } + + @Override + public Path rootPathObject() { + return rootPath; } } @Override - protected Root asRoot() { - return new Root(this); + protected Root asRoot(Path rootPath) { + return new Root(this, rootPath); } @Override @@ -74,10 +100,10 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements @Override AbstractConfigObject resolveSubstitutions(SubstitutionResolver resolver, - int depth, boolean withFallbacks) { + int depth, ConfigResolveOptions options) { AbstractConfigValue merged = ConfigDelayedMerge.resolveSubstitutions( stack, resolver, depth, - withFallbacks); + options); if (merged instanceof AbstractConfigObject) { return (AbstractConfigObject) merged; } else { @@ -122,6 +148,11 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements } } + @Override + public ConfigDelayedMergeObject withFallbacks(ConfigValue... others) { + return (ConfigDelayedMergeObject) super.withFallbacks(others); + } + @Override public Collection<AbstractConfigValue> unmergedValues() { return stack; diff --git a/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 74b91cbc..b54495bb 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -1,71 +1,185 @@ package com.typesafe.config.impl; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Properties; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigObject; +import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigRoot; +import com.typesafe.config.ConfigSyntax; +import com.typesafe.config.ConfigValue; /** This is public but is only supposed to be used by the "config" package */ public class ConfigImpl { - public static ConfigRoot loadConfig(String rootPath) { - ConfigTransformer transformer = withExtraTransformer(null); - - AbstractConfigObject system = null; - try { - system = systemPropertiesConfig().getObject(rootPath); - } catch (ConfigException e) { - // no system props in the requested root path - } - List<AbstractConfigObject> stack = new ArrayList<AbstractConfigObject>(); - - // higher-priority configs are first - if (system != null) - stack.add(system.transformed(transformer)); - - // this is a conceptual placeholder for a customizable - // object that the app might be able to pass in. - IncludeHandler includer = defaultIncluder(); - - stack.add(includer.include(rootPath).transformed( - transformer)); - - AbstractConfigObject merged = AbstractConfigObject.merge(stack); - - ConfigRoot resolved = merged.asRoot().resolve(); - - return resolved; - } - - static ConfigObject getEnvironmentAsConfig() { - // This should not need to create a new config object - // as long as the transformer is just the default transformer. - return envVariablesConfig().transformed(withExtraTransformer(null)); - } - - static ConfigObject getSystemPropertiesAsConfig() { - // This should not need to create a new config object - // as long as the transformer is just the default transformer. - return systemPropertiesConfig().transformed(withExtraTransformer(null)); - } - - private static ConfigTransformer withExtraTransformer( - ConfigTransformer extraTransformer) { - // idea is to avoid creating a new, unique transformer if there's no - // extraTransformer - if (extraTransformer != null) { - List<ConfigTransformer> transformerStack = new ArrayList<ConfigTransformer>(); - transformerStack.add(defaultConfigTransformer()); - transformerStack.add(extraTransformer); - return new StackTransformer(transformerStack); + private static AbstractConfigObject forceParsedToObject(ConfigValue value) { + if (value instanceof AbstractConfigObject) { + return (AbstractConfigObject) value; } else { - return defaultConfigTransformer(); + throw new ConfigException.WrongType(value.origin(), "", + "object at file root", value.valueType().name()); } } + static AbstractConfigValue parseValue(Parseable parseable, + ConfigParseOptions options) { + ConfigOrigin origin = new SimpleConfigOrigin( + options.getOriginDescription()); + return Parser.parse(parseable, origin, options); + } + + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public static ConfigObject parse(Parseable parseable, + ConfigParseOptions options) { + return forceParsedToObject(parseValue(parseable, options)); + } + + private static String makeResourceBasename(Path path) { + StringBuilder sb = new StringBuilder("/"); + String next = path.first(); + Path remaining = path.remainder(); + while (next != null) { + sb.append(next); + sb.append('-'); + + if (remaining == null) + break; + + next = remaining.first(); + remaining = remaining.remainder(); + } + sb.setLength(sb.length() - 1); // chop extra hyphen + return sb.toString(); + } + + // ConfigParseOptions has a package-private method for this but I don't want + // to make it public + static ConfigOrigin originWithSuffix(ConfigParseOptions options, + String suffix) { + return new SimpleConfigOrigin(options.getOriginDescription() + suffix); + } + + static String syntaxSuffix(ConfigSyntax syntax) { + switch (syntax) { + case PROPERTIES: + return ".properties"; + case CONF: + return ".conf"; + case JSON: + return ".json"; + } + throw new ConfigException.BugOrBroken("not a valid ConfigSyntax: " + + syntax); + } + + static AbstractConfigObject loadForResource(Class<?> loadClass, + String basename, ConfigSyntax syntax, ConfigParseOptions options) { + String suffix = syntaxSuffix(syntax); + String resource = basename + suffix; + + // we want null rather than empty object if missingness is allowed, + // so we can handle it. + if (options.getAllowMissing() + && loadClass.getResource(resource) == null) { + return null; + } else { + return forceParsedToObject(Parser.parse( + Parseable.newResource(loadClass, resource), + originWithSuffix(options, suffix), + options.setSyntax(syntax))); + } + } + + static AbstractConfigObject checkAllowMissing(AbstractConfigObject obj, + ConfigOrigin origin, ConfigParseOptions options) { + if (obj == null) { + if (options.getAllowMissing()) { + return SimpleConfigObject.emptyMissing(origin); + } else { + throw new ConfigException.IO(origin, + "Resource not found on classpath"); + } + } else { + return obj; + } + } + + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public static ConfigObject parseResourcesForPath(String expression, + ConfigParseOptions baseOptions) { + Path path = Parser.parsePath(expression); + String basename = makeResourceBasename(path); + + Class<?> loadClass = ConfigImpl.class; + + ConfigParseOptions options; + if (baseOptions.getOriginDescription() != null) + options = baseOptions; + else + options = baseOptions.setOriginDescription(basename); + + if (options.getSyntax() != null) { + ConfigSyntax syntax = options.getSyntax(); + AbstractConfigObject obj = loadForResource(loadClass, basename, + syntax, options); + return checkAllowMissing(obj, originWithSuffix(options, syntaxSuffix(syntax)), options); + } else { + // we want to try all three then + + ConfigParseOptions allowMissing = options.setAllowMissing(true); + AbstractConfigObject conf = loadForResource(loadClass, basename, + ConfigSyntax.CONF, allowMissing); + AbstractConfigObject json = loadForResource(loadClass, basename, + ConfigSyntax.JSON, allowMissing); + AbstractConfigObject props = loadForResource(loadClass, basename, + ConfigSyntax.PROPERTIES, allowMissing); + + ConfigOrigin baseOrigin = new SimpleConfigOrigin(options + .getOriginDescription()); + + if (!options.getAllowMissing() && conf == null && json == null && props == null) { + throw new ConfigException.IO(baseOrigin, + "No config files {.conf,.json,.properties} found on classpath"); + } + + AbstractConfigObject merged = SimpleConfigObject + .empty(baseOrigin); + + if (conf != null) + merged = merged.withFallback(conf); + if (json != null) + merged = merged.withFallback(json); + if (props != null) + merged = merged.withFallback(props); + + return merged; + } + } + + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public static ConfigRoot emptyRoot(String rootPath) { + return SimpleConfigObject.empty().asRoot(Parser.parsePath(rootPath)); + } + + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public static ConfigRoot systemPropertiesRoot(String rootPath) { + Path path = Parser.parsePath(rootPath); + try { + return systemPropertiesAsConfig().getObject(rootPath).asRoot(path); + } catch (ConfigException.Missing e) { + return SimpleConfigObject.empty().asRoot(path); + } + } + + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public static ConfigObject parse(Properties properties, + ConfigParseOptions options) { + return Loader + .fromProperties(options.getOriginDescription(), properties); + } + private static ConfigTransformer defaultTransformer = null; synchronized static ConfigTransformer defaultConfigTransformer() { @@ -92,7 +206,8 @@ public class ConfigImpl { private static AbstractConfigObject systemProperties = null; - synchronized static AbstractConfigObject systemPropertiesConfig() { + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public synchronized static AbstractConfigObject systemPropertiesAsConfig() { if (systemProperties == null) { systemProperties = loadSystemProperties(); } @@ -110,7 +225,8 @@ public class ConfigImpl { private static AbstractConfigObject envVariables = null; - synchronized static AbstractConfigObject envVariablesConfig() { + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public synchronized static AbstractConfigObject envVariablesAsConfig() { if (envVariables == null) { envVariables = loadEnvVariables(); } diff --git a/src/main/java/com/typesafe/config/impl/ConfigRootImpl.java b/src/main/java/com/typesafe/config/impl/ConfigRootImpl.java new file mode 100644 index 00000000..a55ed055 --- /dev/null +++ b/src/main/java/com/typesafe/config/impl/ConfigRootImpl.java @@ -0,0 +1,11 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.ConfigRoot; +import com.typesafe.config.ConfigValue; + +interface ConfigRootImpl extends ConfigRoot { + Path rootPathObject(); + + @Override + ConfigRootImpl withFallbacks(ConfigValue... fallbacks); +} diff --git a/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java index 20b1a89e..ff1350d6 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java +++ b/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java @@ -7,6 +7,7 @@ import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -85,7 +86,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements private ConfigValue findInObject(AbstractConfigObject root, SubstitutionResolver resolver, /* null if we should not have refs */ Path subst, int depth, - boolean withFallbacks) { + ConfigResolveOptions options) { if (depth > MAX_DEPTH) { throw new ConfigException.BadValue(origin(), subst.render(), "Substitution ${" + subst.render() @@ -93,7 +94,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements } ConfigValue result = root.peekPath(subst, resolver, depth, - withFallbacks); + options); if (result instanceof ConfigSubstitution) { throw new ConfigException.BugOrBroken( @@ -108,31 +109,34 @@ final class ConfigSubstitution extends AbstractConfigValue implements } private ConfigValue resolve(SubstitutionResolver resolver, Path subst, - int depth, boolean withFallbacks) { + int depth, ConfigResolveOptions options) { ConfigValue result = findInObject(resolver.root(), resolver, subst, - depth, withFallbacks); - if (withFallbacks) { - // when looking up system props and env variables, - // we don't want the prefix that was added when - // we were included in another file. - Path unprefixed = subst.subPath(prefixLength); - if (result == null) { - result = findInObject(ConfigImpl.systemPropertiesConfig(), - null, unprefixed, depth, withFallbacks); - } - if (result == null) { - result = findInObject(ConfigImpl.envVariablesConfig(), null, - unprefixed, depth, withFallbacks); - } + depth, options); + + // when looking up system props and env variables, + // we don't want the prefix that was added when + // we were included in another file. + Path unprefixed = subst.subPath(prefixLength); + + if (result == null && options.getUseSystemProperties()) { + result = findInObject(ConfigImpl.systemPropertiesAsConfig(), null, + unprefixed, depth, options); } + + if (result == null && options.getUseSystemEnvironment()) { + result = findInObject(ConfigImpl.envVariablesAsConfig(), null, + unprefixed, depth, options); + } + if (result == null) { result = new ConfigNull(origin()); } + return result; } private ConfigValue resolve(SubstitutionResolver resolver, int depth, - boolean withFallbacks) { + ConfigResolveOptions options) { if (pieces.size() > 1) { // need to concat everything into a string StringBuilder sb = new StringBuilder(); @@ -140,8 +144,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements if (p instanceof String) { sb.append((String) p); } else { - ConfigValue v = resolve(resolver, (Path) p, - depth, withFallbacks); + ConfigValue v = resolve(resolver, (Path) p, depth, options); switch (v.valueType()) { case NULL: // nothing; becomes empty string @@ -162,19 +165,18 @@ final class ConfigSubstitution extends AbstractConfigValue implements if (!(pieces.get(0) instanceof Path)) throw new ConfigException.BugOrBroken( "ConfigSubstitution should never contain a single String piece"); - return resolve(resolver, (Path) pieces.get(0), depth, - withFallbacks); + return resolve(resolver, (Path) pieces.get(0), depth, options); } } @Override AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver, int depth, - boolean withFallbacks) { + ConfigResolveOptions options) { // only ConfigSubstitution adds to depth here, because the depth // is the substitution depth not the recursion depth AbstractConfigValue resolved = (AbstractConfigValue) resolve(resolver, - depth + 1, withFallbacks); + depth + 1, options); return resolved; } diff --git a/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java b/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java index 801723c2..033fadd8 100644 --- a/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java @@ -29,6 +29,14 @@ abstract class DelegatingConfigObject extends AbstractConfigObject { return underlying.resolveStatus(); } + @Override + final protected ConfigRootImpl asRoot(Path rootPath) { + return asRoot(underlying, rootPath); + } + + protected abstract ConfigRootImpl asRoot(AbstractConfigObject underlying, + Path rootPath); + @Override public boolean containsKey(Object key) { return underlying.containsKey(key); @@ -54,6 +62,11 @@ abstract class DelegatingConfigObject extends AbstractConfigObject { return underlying.withFallback(value); } + @Override + public AbstractConfigObject withFallbacks(ConfigValue... values) { + return underlying.withFallbacks(values); + } + @Override public boolean containsValue(Object value) { return underlying.containsValue(value); diff --git a/src/main/java/com/typesafe/config/impl/Loader.java b/src/main/java/com/typesafe/config/impl/Loader.java index 0061f1a6..9f7ea06f 100644 --- a/src/main/java/com/typesafe/config/impl/Loader.java +++ b/src/main/java/com/typesafe/config/impl/Loader.java @@ -14,7 +14,13 @@ import java.util.Properties; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigParseOptions; +/** + * FIXME this file needs to die; the load() part should use the code in + * ConfigImpl instead and the properties stuff should be in its own file. + * + */ final class Loader { static AbstractConfigObject load(String name, IncludeHandler includer) { List<AbstractConfigObject> stack = new ArrayList<AbstractConfigObject>(); @@ -65,7 +71,10 @@ final class Loader { } return fromProperties(url.toExternalForm(), props); } else { - return forceParsedToObject(Parser.parse(url, includer)); + return forceParsedToObject(Parser.parse(Parseable.newURL(url), + new SimpleConfigOrigin(url.toExternalForm()), + ConfigParseOptions.defaults(), + includer)); } } diff --git a/src/main/java/com/typesafe/config/impl/Parseable.java b/src/main/java/com/typesafe/config/impl/Parseable.java new file mode 100644 index 00000000..465c4a86 --- /dev/null +++ b/src/main/java/com/typesafe/config/impl/Parseable.java @@ -0,0 +1,227 @@ +package com.typesafe.config.impl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FilterReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.URL; + +import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigSyntax; + +/** + * This is public but it's only for use by the config package; DO NOT TOUCH. The + * point of this class is to avoid "propagating" each overload on + * "thing which can be parsed" through multiple interfaces. Most interfaces can + * have just one overload that takes a Parseable. + */ +public abstract class Parseable { + protected Parseable() { + } + + // the general idea is that any work should be in here, not in the + // constructor, + // so that exceptions are thrown from the public parse() function and not + // from the creation of the Parseable. Essentially this is a lazy field. + // The parser should close the reader when it's done with it. + // ALSO, IMPORTANT: if the file or URL is not found, this must throw. + // to support the "allow missing" feature. + abstract Reader reader() throws IOException; + + ConfigSyntax guessSyntax() { + return null; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + private static ConfigSyntax syntaxFromExtension(String name) { + if (name.endsWith(".json")) + return ConfigSyntax.JSON; + else if (name.endsWith(".conf")) + return ConfigSyntax.CONF; + else if (name.endsWith(".properties")) + return ConfigSyntax.PROPERTIES; + else + return null; + } + + private static Reader readerFromStream(InputStream input) { + try { + // well, this is messed up. If we aren't going to close + // the passed-in InputStream then we have no way to + // close these readers. So maybe we should not have an + // InputStream version, only a Reader version. + Reader reader = new InputStreamReader(input, "UTF-8"); + return new BufferedReader(reader); + } catch (UnsupportedEncodingException e) { + throw new ConfigException.BugOrBroken( + "Java runtime does not support UTF-8", e); + } + } + + private static Reader doNotClose(Reader input) { + return new FilterReader(input) { + @Override + public void close() { + // NOTHING. + } + }; + } + + private final static class ParseableInputStream extends Parseable { + final private InputStream input; + + ParseableInputStream(InputStream input) { + this.input = input; + } + + @Override + Reader reader() { + return doNotClose(readerFromStream(input)); + } + } + + /** + * note that we will never close this stream; you have to do it when parsing + * is complete. + */ + public static Parseable newInputStream(InputStream input) { + return new ParseableInputStream(input); + } + + private final static class ParseableReader extends Parseable { + final private Reader reader; + + ParseableReader(Reader reader) { + this.reader = reader; + } + + @Override + Reader reader() { + return reader; + } + } + + /** + * note that we will never close this reader; you have to do it when parsing + * is complete. + */ + public static Parseable newReader(Reader reader) { + return new ParseableReader(doNotClose(reader)); + } + + private final static class ParseableString extends Parseable { + final private String input; + + ParseableString(String input) { + this.input = input; + } + + @Override + Reader reader() { + return new StringReader(input); + } + } + + public static Parseable newString(String input) { + return new ParseableString(input); + } + + private final static class ParseableURL extends Parseable { + final private URL input; + + ParseableURL(URL input) { + this.input = input; + } + + @Override + Reader reader() throws IOException { + InputStream stream = input.openStream(); + return readerFromStream(stream); + } + + @Override + ConfigSyntax guessSyntax() { + return syntaxFromExtension(input.getPath()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + input.toExternalForm() + + ")"; + } + } + + public static Parseable newURL(URL input) { + return new ParseableURL(input); + } + + private final static class ParseableFile extends Parseable { + final private File input; + + ParseableFile(File input) { + this.input = input; + } + + @Override + Reader reader() throws IOException { + InputStream stream = new FileInputStream(input); + return readerFromStream(stream); + } + + @Override + ConfigSyntax guessSyntax() { + return syntaxFromExtension(input.getName()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + input.getPath() + ")"; + } + } + + public static Parseable newFile(File input) { + return new ParseableFile(input); + } + + private final static class ParseableResource extends Parseable { + final private Class<?> klass; + final private String resource; + + ParseableResource(Class<?> klass, String resource) { + this.klass = klass; + this.resource = resource; + } + + @Override + Reader reader() throws IOException { + InputStream stream = klass.getResourceAsStream(resource); + return readerFromStream(stream); + } + + @Override + ConfigSyntax guessSyntax() { + return syntaxFromExtension(resource); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + resource + "," + + klass.getName() + + ")"; + } + } + + public static Parseable newResource(Class<?> klass, String resource) { + return new ParseableResource(klass, resource); + } +} diff --git a/src/main/java/com/typesafe/config/impl/Parser.java b/src/main/java/com/typesafe/config/impl/Parser.java index 5fc453ea..16fde62c 100644 --- a/src/main/java/com/typesafe/config/impl/Parser.java +++ b/src/main/java/com/typesafe/config/impl/Parser.java @@ -1,15 +1,8 @@ package com.typesafe.config.impl; -import java.io.BufferedReader; -import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -22,94 +15,52 @@ import java.util.Stack; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigParseOptions; +import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigValueType; final class Parser { - /** - * Parses an input stream, which must be in UTF-8 encoding and should not be - * buffered because we'll use a BufferedReader instead. Does not close the - * stream; you have to arrange to do that yourself. - */ - static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin, - InputStream input, IncludeHandler includer) { - try { - // well, this is messed up. If we aren't going to close - // the passed-in InputStream then we have no way to - // close these readers. So maybe we should not have an - // InputStream version, only a Reader version. - Reader reader = new InputStreamReader(input, "UTF-8"); - reader = new BufferedReader(reader); - return parse(flavor, origin, reader, - includer); - } catch (UnsupportedEncodingException e) { - throw new ConfigException.BugOrBroken( - "Java runtime does not support UTF-8", e); + + static AbstractConfigValue parse(Parseable input, ConfigOrigin origin, + ConfigParseOptions options) { + return parse(input, origin, options, ConfigImpl.defaultIncluder()); + } + + static AbstractConfigValue parse(Parseable input, ConfigOrigin origin, + ConfigParseOptions baseOptions, IncludeHandler includer) { + ConfigSyntax syntax = baseOptions.getSyntax(); + if (syntax == null) { + syntax = input.guessSyntax(); } - } - - static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin, - Reader input, IncludeHandler includer) { - Iterator<Token> tokens = Tokenizer.tokenize(origin, input, flavor); - return parse(flavor, origin, tokens, includer); - } - - static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin, - String input, IncludeHandler includer) { - return parse(flavor, origin, new StringReader(input), includer); - } - - private static SyntaxFlavor flavorFromExtension(String name, - ConfigOrigin origin) { - if (name.endsWith(".json")) - return SyntaxFlavor.JSON; - else if (name.endsWith(".conf")) - return SyntaxFlavor.CONF; - else - throw new ConfigException.IO(origin, "Unknown filename extension"); - } - - static AbstractConfigValue parse(File f, IncludeHandler includer) { - return parse(null, f, includer); - } - - static AbstractConfigValue parse(SyntaxFlavor flavor, File f, - IncludeHandler includer) { - ConfigOrigin origin = new SimpleConfigOrigin(f.getPath()); - try { - return parse(flavor, origin, f.toURI().toURL(), includer); - } catch (MalformedURLException e) { - throw new ConfigException.IO(origin, - "failed to create url from file path", e); + if (syntax == null) { + syntax = ConfigSyntax.CONF; } - } + ConfigParseOptions options = baseOptions.setSyntax(syntax); - static AbstractConfigValue parse(URL url, IncludeHandler includer) { - return parse(null, url, includer); - } - - static AbstractConfigValue parse(SyntaxFlavor flavor, URL url, - IncludeHandler includer) { - ConfigOrigin origin = new SimpleConfigOrigin(url.toExternalForm()); - return parse(flavor, origin, url, includer); - } - - static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin, - URL url, IncludeHandler includer) { - AbstractConfigValue result = null; try { - InputStream stream = url.openStream(); + Reader reader = input.reader(); try { - result = parse( - flavor != null ? flavor : flavorFromExtension( - url.getPath(), origin), origin, stream, - includer); + Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, + syntax); + return parse(tokens, origin, options, includer); } finally { - stream.close(); + reader.close(); } } catch (IOException e) { - throw new ConfigException.IO(origin, "failed to read url", e); + if (options.getAllowMissing()) { + return SimpleConfigObject.emptyMissing(origin); + } else { + throw new ConfigException.IO(origin, e.getMessage(), e); + } } - return result; + } + + private static AbstractConfigValue parse(Iterator<Token> tokens, + ConfigOrigin origin, ConfigParseOptions options, + IncludeHandler includer) { + ParseContext context = new ParseContext(options.getSyntax(), origin, + tokens, includer); + return context.parse(); } static private final class ParseContext { @@ -117,11 +68,11 @@ final class Parser { final private Stack<Token> buffer; final private Iterator<Token> tokens; final private IncludeHandler includer; - final private SyntaxFlavor flavor; + final private ConfigSyntax flavor; final private ConfigOrigin baseOrigin; final private LinkedList<Path> pathStack; - ParseContext(SyntaxFlavor flavor, ConfigOrigin origin, + ParseContext(ConfigSyntax flavor, ConfigOrigin origin, Iterator<Token> tokens, IncludeHandler includer) { lineNumber = 0; buffer = new Stack<Token>(); @@ -140,7 +91,7 @@ final class Parser { t = buffer.pop(); } - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { if (Tokens.isUnquotedText(t)) { throw parseError("Token not allowed in valid JSON: '" + Tokens.getUnquotedText(t) + "'"); @@ -172,7 +123,7 @@ final class Parser { // either a newline or a comma. The iterator // is left just after the comma or the newline. private boolean checkElementSeparator() { - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { Token t = nextTokenIgnoringNewline(); if (t == Tokens.COMMA) { return true; @@ -206,7 +157,7 @@ final class Parser { // value. private void consolidateValueTokens() { // this trick is not done in JSON - if (flavor == SyntaxFlavor.JSON) + if (flavor == ConfigSyntax.JSON) return; List<Token> values = null; // create only if we have value tokens @@ -351,7 +302,7 @@ final class Parser { } private Path parseKey(Token token) { - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { if (Tokens.isValueWithType(token, ConfigValueType.STRING)) { String key = (String) Tokens.getValue(token).unwrapped(); return Path.newKey(key); @@ -422,7 +373,7 @@ final class Parser { } private boolean isKeyValueSeparatorToken(Token t) { - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { return t == Tokens.COLON; } else { return t == Tokens.COLON || t == Tokens.EQUALS; @@ -437,7 +388,7 @@ final class Parser { while (true) { Token t = nextTokenIgnoringNewline(); if (t == Tokens.CLOSE_CURLY) { - if (flavor == SyntaxFlavor.JSON && afterComma) { + if (flavor == ConfigSyntax.JSON && afterComma) { throw parseError("expecting a field name after comma, got a close brace }"); } else if (!hadOpenCurly) { throw parseError("unbalanced close brace '}' with no open brace"); @@ -446,7 +397,7 @@ final class Parser { } else if (t == Tokens.END && !hadOpenCurly) { putBack(t); break; - } else if (flavor != SyntaxFlavor.JSON && isIncludeKeyword(t)) { + } else if (flavor != ConfigSyntax.JSON && isIncludeKeyword(t)) { parseInclude(values); afterComma = false; @@ -459,7 +410,7 @@ final class Parser { Token valueToken; AbstractConfigValue newValue; - if (flavor == SyntaxFlavor.CONF + if (flavor == ConfigSyntax.CONF && afterKey == Tokens.OPEN_CURLY) { // can omit the ':' or '=' before an object value valueToken = afterKey; @@ -488,7 +439,7 @@ final class Parser { // if the value is an object (or substitution that // could become an object). - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { throw parseError("JSON does not allow duplicate fields: '" + key + "' was already seen at " @@ -499,7 +450,7 @@ final class Parser { } values.put(key, newValue); } else { - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { throw new ConfigException.BugOrBroken( "somehow got multi-element path in JSON mode"); } @@ -593,7 +544,7 @@ final class Parser { values.add(parseObject(true)); } else if (t == Tokens.OPEN_SQUARE) { values.add(parseArray()); - } else if (flavor != SyntaxFlavor.JSON + } else if (flavor != ConfigSyntax.JSON && t == Tokens.CLOSE_SQUARE) { // we allow one trailing comma putBack(t); @@ -620,7 +571,7 @@ final class Parser { } else if (t == Tokens.OPEN_SQUARE) { result = parseArray(); } else { - if (flavor == SyntaxFlavor.JSON) { + if (flavor == ConfigSyntax.JSON) { if (t == Tokens.END) { throw parseError("Empty document"); } else { @@ -646,13 +597,6 @@ final class Parser { } } - private static AbstractConfigValue parse(SyntaxFlavor flavor, - ConfigOrigin origin, Iterator<Token> tokens, IncludeHandler includer) { - ParseContext context = new ParseContext(flavor, origin, tokens, - includer); - return context.parse(); - } - static class Element { StringBuilder sb; // an element can be empty if it has a quoted empty string "" in it @@ -762,7 +706,7 @@ final class Parser { try { Iterator<Token> tokens = Tokenizer.tokenize(apiOrigin, reader, - SyntaxFlavor.CONF); + ConfigSyntax.CONF); tokens.next(); // drop START return parsePathExpression(tokens, apiOrigin); } finally { diff --git a/src/main/java/com/typesafe/config/impl/RootConfigObject.java b/src/main/java/com/typesafe/config/impl/RootConfigObject.java index f6a7ff6b..a1d8156a 100644 --- a/src/main/java/com/typesafe/config/impl/RootConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/RootConfigObject.java @@ -1,35 +1,61 @@ package com.typesafe.config.impl; -import com.typesafe.config.ConfigRoot; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; final class RootConfigObject extends DelegatingConfigObject implements - ConfigRoot { + ConfigRootImpl { - RootConfigObject(AbstractConfigObject underlying) { + final private Path rootPath; + + RootConfigObject(AbstractConfigObject underlying, Path rootPath) { super(underlying.transformer, underlying); + this.rootPath = rootPath; } @Override - protected ConfigRoot asRoot() { - return this; + protected ConfigRootImpl asRoot(AbstractConfigObject underlying, + Path newRootPath) { + if (newRootPath.equals(this.rootPath)) + return this; + else + return new RootConfigObject(underlying, newRootPath); } @Override public RootConfigObject newCopy(AbstractConfigObject underlying, ConfigTransformer newTransformer, ResolveStatus newStatus) { return new RootConfigObject(underlying.newCopy(newTransformer, - newStatus)); + newStatus), rootPath); } @Override - public ConfigRoot resolve() { - return ((AbstractConfigObject) SubstitutionResolver.resolve(this, this)) - .asRoot(); + public ConfigRootImpl resolve() { + return resolve(this); + } + + @Override + public ConfigRootImpl resolve(ConfigResolveOptions options) { + return resolve(this, options); } @Override public RootConfigObject withFallback(ConfigValue value) { - return new RootConfigObject(super.withFallback(value)); + return new RootConfigObject(super.withFallback(value), rootPath); + } + + @Override + public RootConfigObject withFallbacks(ConfigValue... values) { + return new RootConfigObject(super.withFallbacks(values), rootPath); + } + + @Override + public Path rootPathObject() { + return rootPath; + } + + @Override + public String rootPath() { + return rootPath.render(); } } diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfigList.java b/src/main/java/com/typesafe/config/impl/SimpleConfigList.java index f02c2402..aa341eb5 100644 --- a/src/main/java/com/typesafe/config/impl/SimpleConfigList.java +++ b/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -9,6 +9,7 @@ import java.util.ListIterator; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -84,14 +85,14 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { @Override SimpleConfigList resolveSubstitutions(final SubstitutionResolver resolver, - final int depth, final boolean withFallbacks) { + final int depth, final ConfigResolveOptions options) { if (resolved) return this; return modify(new Modifier() { @Override public AbstractConfigValue modifyChild(AbstractConfigValue v) { - return resolver.resolve(v, depth, withFallbacks); + return resolver.resolve(v, depth, options); } }, ResolveStatus.RESOLVED); diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 1c2a0ba2..83ca5d91 100644 --- a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -117,4 +117,15 @@ final class SimpleConfigObject extends AbstractConfigObject { return new SimpleConfigObject(new SimpleConfigOrigin("empty config"), Collections.<String, AbstractConfigValue> emptyMap()); } + + final static SimpleConfigObject empty(ConfigOrigin origin) { + return new SimpleConfigObject(origin, + Collections.<String, AbstractConfigValue> emptyMap()); + } + + final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { + return new SimpleConfigObject(new SimpleConfigOrigin( + baseOrigin.description() + " (not found)"), + Collections.<String, AbstractConfigValue> emptyMap()); + } } diff --git a/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java b/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java index fd321143..1ec58ce6 100644 --- a/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java +++ b/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java @@ -4,6 +4,7 @@ import java.util.IdentityHashMap; import java.util.Map; import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigResolveOptions; /** * This exists because we have to memoize resolved substitutions as we go @@ -21,13 +22,12 @@ final class SubstitutionResolver { } AbstractConfigValue resolve(AbstractConfigValue original, int depth, - boolean withFallbacks) { + ConfigResolveOptions options) { if (memos.containsKey(original)) { return memos.get(original); } else { AbstractConfigValue resolved = original.resolveSubstitutions(this, - depth, - withFallbacks); + depth, options); if (resolved.resolveStatus() != ResolveStatus.RESOLVED) throw new ConfigException.BugOrBroken( "resolveSubstitutions() did not give us a resolved object"); @@ -40,19 +40,9 @@ final class SubstitutionResolver { return this.root; } - private static AbstractConfigValue resolve(AbstractConfigValue value, - AbstractConfigObject root, boolean withFallbacks) { - SubstitutionResolver resolver = new SubstitutionResolver(root); - return resolver.resolve(value, 0, withFallbacks); - } - static AbstractConfigValue resolve(AbstractConfigValue value, - AbstractConfigObject root) { - return resolve(value, root, true /* withFallbacks */); - } - - static AbstractConfigValue resolveWithoutFallbacks( - AbstractConfigValue value, AbstractConfigObject root) { - return resolve(value, root, false /* withFallbacks */); + AbstractConfigObject root, ConfigResolveOptions options) { + SubstitutionResolver resolver = new SubstitutionResolver(root); + return resolver.resolve(value, 0, options); } } diff --git a/src/main/java/com/typesafe/config/impl/SyntaxFlavor.java b/src/main/java/com/typesafe/config/impl/SyntaxFlavor.java deleted file mode 100644 index 868b413a..00000000 --- a/src/main/java/com/typesafe/config/impl/SyntaxFlavor.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.typesafe.config.impl; - -enum SyntaxFlavor { - JSON, CONF -} diff --git a/src/main/java/com/typesafe/config/impl/Tokenizer.java b/src/main/java/com/typesafe/config/impl/Tokenizer.java index b0b28c7e..8b5c8442 100644 --- a/src/main/java/com/typesafe/config/impl/Tokenizer.java +++ b/src/main/java/com/typesafe/config/impl/Tokenizer.java @@ -10,14 +10,15 @@ import java.util.Queue; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigSyntax; final class Tokenizer { /** * Tokenizes a Reader. Does not close the reader; you have to arrange to do * that after you're done with the returned iterator. */ - static Iterator<Token> tokenize(ConfigOrigin origin, Reader input, SyntaxFlavor flavor) { - return new TokenIterator(origin, input, flavor != SyntaxFlavor.JSON); + static Iterator<Token> tokenize(ConfigOrigin origin, Reader input, ConfigSyntax flavor) { + return new TokenIterator(origin, input, flavor != ConfigSyntax.JSON); } private static class TokenIterator implements Iterator<Token> { diff --git a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index 53283842..4f4af876 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -11,7 +11,10 @@ import scala.collection.JavaConverters._ class ConfParserTest extends TestUtils { def parseWithoutResolving(s: String) = { - Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s, includer()) + val options = ConfigParseOptions.defaults(). + setOriginDescription("test conf string"). + setSyntax(ConfigSyntax.CONF); + ConfigImpl.parseValue(Parseable.newString(s), options); } def parse(s: String) = { @@ -21,7 +24,7 @@ class ConfParserTest extends TestUtils { // interpolating arrays into strings tree match { case obj: AbstractConfigObject => - SubstitutionResolver.resolveWithoutFallbacks(tree, obj) + SubstitutionResolver.resolve(tree, obj, ConfigResolveOptions.noSystem()) case _ => tree } diff --git a/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala index 5255204a..f84ddfd0 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala @@ -4,21 +4,26 @@ import org.junit.Assert._ import org.junit._ import com.typesafe.config.ConfigValue import com.typesafe.config.ConfigException +import com.typesafe.config.ConfigResolveOptions class ConfigSubstitutionTest extends TestUtils { private def resolveWithoutFallbacks(v: AbstractConfigObject) = { - SubstitutionResolver.resolveWithoutFallbacks(v, v).asInstanceOf[AbstractConfigObject] + val options = ConfigResolveOptions.noSystem() + SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject] } private def resolveWithoutFallbacks(s: ConfigSubstitution, root: AbstractConfigObject) = { - SubstitutionResolver.resolveWithoutFallbacks(s, root) + val options = ConfigResolveOptions.noSystem() + SubstitutionResolver.resolve(s, root, options) } private def resolve(v: AbstractConfigObject) = { - SubstitutionResolver.resolve(v, v).asInstanceOf[AbstractConfigObject] + val options = ConfigResolveOptions.defaults() + SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject] } private def resolve(s: ConfigSubstitution, root: AbstractConfigObject) = { - SubstitutionResolver.resolve(s, root) + val options = ConfigResolveOptions.defaults() + SubstitutionResolver.resolve(s, root, options) } private val simpleObject = { diff --git a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index 801165a5..4d917c8e 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -8,6 +8,7 @@ import com.typesafe.config.ConfigObject import com.typesafe.config.ConfigException import java.util.concurrent.TimeUnit import scala.collection.JavaConverters._ +import com.typesafe.config.ConfigResolveOptions class ConfigTest extends TestUtils { @@ -212,13 +213,17 @@ class ConfigTest extends TestUtils { } } + private def resolveNoSystem(v: AbstractConfigValue, root: AbstractConfigObject) = { + SubstitutionResolver.resolve(v, root, ConfigResolveOptions.noSystem()) + } + @Test def mergeSubstitutedValues() { val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : ${a} }""") val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""") val merged = merge(obj1, obj2) - val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match { + val resolved = resolveNoSystem(merged, merged) match { case x: ConfigObject => x } @@ -234,7 +239,7 @@ class ConfigTest extends TestUtils { val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""") val merged = merge(obj1, obj2) - val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match { + val resolved = resolveNoSystem(merged, merged) match { case x: ConfigObject => x } @@ -243,7 +248,7 @@ class ConfigTest extends TestUtils { assertEquals(42, resolved.getInt("c.z")) val merged2 = merge(obj2, obj1) - val resolved2 = SubstitutionResolver.resolveWithoutFallbacks(merged2, merged2) match { + val resolved2 = resolveNoSystem(merged2, merged2) match { case x: ConfigObject => x } @@ -268,13 +273,13 @@ class ConfigTest extends TestUtils { // that's been overridden, and thus not end up with a cycle as long // as we override the problematic link in the cycle. val e = intercept[ConfigException.BadValue] { - val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), cycleObject) + val v = resolveNoSystem(subst("foo"), cycleObject) } assertTrue(e.getMessage().contains("cycle")) val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : 57 } } } """) val merged = merge(fixUpCycle, cycleObject) - val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), merged) + val v = resolveNoSystem(subst("foo"), merged) assertEquals(intValue(57), v); } @@ -284,14 +289,14 @@ class ConfigTest extends TestUtils { // we have to evaluate the substitution to see if it's an object to merge, // so we don't avoid the cycle. val e = intercept[ConfigException.BadValue] { - val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), cycleObject) + val v = resolveNoSystem(subst("foo"), cycleObject) } assertTrue(e.getMessage().contains("cycle")) val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : { "q" : "u" } } } } """) val merged = merge(fixUpCycle, cycleObject) val e2 = intercept[ConfigException.BadValue] { - val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), merged) + val v = resolveNoSystem(subst("foo"), merged) } assertTrue(e2.getMessage().contains("cycle")) } @@ -303,7 +308,7 @@ class ConfigTest extends TestUtils { val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => - val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match { + val resolved = resolveNoSystem(merged, merged) match { case x: ConfigObject => x } @@ -322,7 +327,7 @@ class ConfigTest extends TestUtils { val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => - val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match { + val resolved = resolveNoSystem(merged, merged) match { case x: ConfigObject => x } @@ -340,7 +345,7 @@ class ConfigTest extends TestUtils { val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => - val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match { + val resolved = resolveNoSystem(merged, merged) match { case x: ConfigObject => x } @@ -360,7 +365,7 @@ class ConfigTest extends TestUtils { val obj4 = parseObject("""{ "c" : { "z" : 4, "q" : 8 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3, obj4)) { merged => - val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match { + val resolved = resolveNoSystem(merged, merged) match { case x: ConfigObject => x } diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index 82781f36..0099288e 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -75,7 +75,7 @@ class ConfigValueTest extends TestUtils { checkNotEqualObjects(a, c) checkNotEqualObjects(b, c) - val root = a.asRoot() + val root = a.asRoot(path("foo")) checkEqualObjects(a, root) checkNotEqualObjects(root, b) checkNotEqualObjects(root, c) diff --git a/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala b/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala index 94623f3e..5d881e8c 100644 --- a/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala +++ b/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala @@ -34,18 +34,20 @@ class EquivalentsTest extends TestUtils { // for purposes of these tests, substitutions are only // against the same file's root, and without looking at // system prop or env variable fallbacks. - SubstitutionResolver.resolveWithoutFallbacks(v, v) + SubstitutionResolver.resolve(v, v, ConfigResolveOptions.noSystem()) case v => v } } - private def parse(flavor: SyntaxFlavor, f: File) = { - postParse(Parser.parse(flavor, f, includer())) + private def parse(flavor: ConfigSyntax, f: File) = { + val options = ConfigParseOptions.defaults().setSyntax(flavor) + postParse(Config.parse(f, options)) } private def parse(f: File) = { - postParse(Parser.parse(f, includer())) + val options = ConfigParseOptions.defaults() + postParse(Config.parse(f, options)) } // would like each "equivNN" directory to be a suite and each file in the dir @@ -75,7 +77,7 @@ class EquivalentsTest extends TestUtils { // check that all .json files can be parsed as .conf, // i.e. .conf must be a superset of JSON if (testFile.getName().endsWith(".json")) { - val parsedAsConf = parse(SyntaxFlavor.CONF, testFile) + val parsedAsConf = parse(ConfigSyntax.CONF, testFile) describeFailure(testFile.getPath() + " parsed as .conf") { assertEquals(original, parsedAsConf) } diff --git a/src/test/scala/com/typesafe/config/impl/JsonTest.scala b/src/test/scala/com/typesafe/config/impl/JsonTest.scala index c85d22ee..184a2c7f 100644 --- a/src/test/scala/com/typesafe/config/impl/JsonTest.scala +++ b/src/test/scala/com/typesafe/config/impl/JsonTest.scala @@ -12,11 +12,17 @@ import java.util.Collections class JsonTest extends TestUtils { def parse(s: String): ConfigValue = { - Parser.parse(SyntaxFlavor.JSON, new SimpleConfigOrigin("test json string"), s, includer()) + val options = ConfigParseOptions.defaults(). + setOriginDescription("test json string"). + setSyntax(ConfigSyntax.JSON); + ConfigImpl.parseValue(Parseable.newString(s), options); } def parseAsConf(s: String): ConfigValue = { - Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s, includer()) + val options = ConfigParseOptions.defaults(). + setOriginDescription("test conf string"). + setSyntax(ConfigSyntax.CONF); + ConfigImpl.parseValue(Parseable.newString(s), options); } private[this] def toLift(value: ConfigValue): lift.JValue = { diff --git a/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/src/test/scala/com/typesafe/config/impl/TestUtils.scala index ad5d8ce4..aeb25d97 100644 --- a/src/test/scala/com/typesafe/config/impl/TestUtils.scala +++ b/src/test/scala/com/typesafe/config/impl/TestUtils.scala @@ -5,6 +5,9 @@ import org.junit._ import com.typesafe.config.ConfigOrigin import java.io.Reader import java.io.StringReader +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.Config +import com.typesafe.config.ConfigSyntax abstract trait TestUtils { protected def intercept[E <: Throwable: Manifest](block: => Unit): E = { @@ -319,7 +322,10 @@ abstract trait TestUtils { protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d, null) protected def parseObject(s: String) = { - Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test string"), s, includer()).asInstanceOf[AbstractConfigObject] + val options = ConfigParseOptions.defaults(). + setOriginDescription("test string"). + setSyntax(ConfigSyntax.CONF); + Config.parse(new StringReader(s), options).asInstanceOf[AbstractConfigObject] } protected def subst(ref: String) = { @@ -354,7 +360,7 @@ abstract trait TestUtils { def tokenKeySubstitution(s: String) = tokenSubstitution(tokenString(s)) def tokenize(origin: ConfigOrigin, input: Reader): java.util.Iterator[Token] = { - Tokenizer.tokenize(origin, input, SyntaxFlavor.CONF) + Tokenizer.tokenize(origin, input, ConfigSyntax.CONF) } def tokenize(input: Reader): java.util.Iterator[Token] = {