mirror of
https://github.com/lightbend/config.git
synced 2025-03-23 07:40:25 +08:00
Overhaul the public API.
This patch is a little half-baked (it needs tests, and there are a few bugs I know about). Fixes will be along.
This commit is contained in:
parent
f1bedacdfc
commit
54cd4ec3dd
@ -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) {
|
||||
|
@ -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);
|
||||
|
90
src/main/java/com/typesafe/config/ConfigParseOptions.java
Normal file
90
src/main/java/com/typesafe/config/ConfigParseOptions.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
36
src/main/java/com/typesafe/config/ConfigResolveOptions.java
Normal file
36
src/main/java/com/typesafe/config/ConfigResolveOptions.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
5
src/main/java/com/typesafe/config/ConfigSyntax.java
Normal file
5
src/main/java/com/typesafe/config/ConfigSyntax.java
Normal file
@ -0,0 +1,5 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
public enum ConfigSyntax {
|
||||
JSON, CONF, PROPERTIES;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
11
src/main/java/com/typesafe/config/impl/ConfigRootImpl.java
Normal file
11
src/main/java/com/typesafe/config/impl/ConfigRootImpl.java
Normal file
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
227
src/main/java/com/typesafe/config/impl/Parseable.java
Normal file
227
src/main/java/com/typesafe/config/impl/Parseable.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
enum SyntaxFlavor {
|
||||
JSON, CONF
|
||||
}
|
@ -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> {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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] = {
|
||||
|
Loading…
Reference in New Issue
Block a user