updated typesafe config

This commit is contained in:
Florian Nücke 2014-02-03 18:23:26 +01:00
parent 47d9b91201
commit 3567b28e47
24 changed files with 449 additions and 209 deletions

View File

@ -196,8 +196,6 @@ object Settings {
val renderSettings = ConfigRenderOptions.defaults.setJson(false).setOriginComments(false) val renderSettings = ConfigRenderOptions.defaults.setJson(false).setOriginComments(false)
val out = new PrintWriter(file) val out = new PrintWriter(file)
out.write(config.root.render(renderSettings).lines. out.write(config.root.render(renderSettings).lines.
// Strip extra spaces in front.
map(_.stripPrefix(" ")).
// Indent two spaces instead of four. // Indent two spaces instead of four.
map(line => """^(\s*)""".r.replaceAllIn(line, m => m.group(1).replace(" ", " "))). map(line => """^(\s*)""".r.replaceAllIn(line, m => m.group(1).replace(" ", " "))).
// Finalize the string. // Finalize the string.

View File

@ -9,46 +9,76 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* An immutable map from config paths to config values. * An immutable map from config paths to config values. Paths are dot-separated
* * expressions such as <code>foo.bar.baz</code>. Values are as in JSON
* (booleans, strings, numbers, lists, or objects), represented by
* {@link ConfigValue} instances. Values accessed through the
* <code>Config</code> interface are never null.
*
* <p> * <p>
* Contrast with {@link ConfigObject} which is a map from config <em>keys</em>, * {@code Config} is an immutable object and thus safe to use from multiple
* rather than paths, to config values. A {@code Config} contains a tree of * threads. There's never a need for "defensive copies."
* {@code ConfigObject}, and {@link Config#root()} returns the tree's root *
* object.
*
* <p> * <p>
* Throughout the API, there is a distinction between "keys" and "paths". A key * Fundamental operations on a {@code Config} include getting configuration
* is a key in a JSON object; it's just a string that's the key in a map. A * values, <em>resolving</em> substitutions with {@link Config#resolve()}, and
* "path" is a parseable expression with a syntax and it refers to a series of * merging configs using {@link Config#withFallback(ConfigMergeable)}.
* keys. Path expressions are described in the <a *
* <p>
* All operations return a new immutable {@code Config} rather than modifying
* the original instance.
*
* <p>
* <strong>Examples</strong>
*
* <p>
* You can find an example app and library <a
* href="https://github.com/typesafehub/config/tree/master/examples">on
* GitHub</a>. Also be sure to read the <a
* href="package-summary.html#package_description">package overview</a> which
* describes the big picture as shown in those examples.
*
* <p>
* <strong>Paths, keys, and Config vs. ConfigObject</strong>
*
* <p>
* <code>Config</code> is a view onto a tree of {@link ConfigObject}; the
* corresponding object tree can be found through {@link Config#root()}.
* <code>ConfigObject</code> is a map from config <em>keys</em>, rather than
* paths, to config values. Think of <code>ConfigObject</code> as a JSON object
* and <code>Config</code> as a configuration API.
*
* <p>
* The API tries to consistently use the terms "key" and "path." A key is a key
* in a JSON object; it's just a string that's the key in a map. A "path" is a
* parseable expression with a syntax and it refers to a series of keys. Path
* expressions are described in the <a
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">spec for * href="https://github.com/typesafehub/config/blob/master/HOCON.md">spec for
* Human-Optimized Config Object Notation</a>. In brief, a path is * Human-Optimized Config Object Notation</a>. In brief, a path is
* period-separated so "a.b.c" looks for key c in object b in object a in the * period-separated so "a.b.c" looks for key c in object b in object a in the
* root object. Sometimes double quotes are needed around special characters in * root object. Sometimes double quotes are needed around special characters in
* path expressions. * path expressions.
* *
* <p> * <p>
* The API for a {@code Config} is in terms of path expressions, while the API * The API for a {@code Config} is in terms of path expressions, while the API
* for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config} * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config}
* is a one-level map from <em>paths</em> to values, while a * is a one-level map from <em>paths</em> to values, while a
* {@code ConfigObject} is a tree of nested maps from <em>keys</em> to values. * {@code ConfigObject} is a tree of nested maps from <em>keys</em> to values.
* *
* <p> * <p>
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
* between path expressions and individual path elements (keys). * between path expressions and individual path elements (keys).
* *
* <p> * <p>
* Another difference between {@code Config} and {@code ConfigObject} is that * Another difference between {@code Config} and {@code ConfigObject} is that
* conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType() * conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType()
* valueType()} of {@link ConfigValueType#NULL NULL} exist in a * valueType()} of {@link ConfigValueType#NULL NULL} exist in a
* {@code ConfigObject}, while a {@code Config} treats null values as if they * {@code ConfigObject}, while a {@code Config} treats null values as if they
* were missing. * were missing.
* *
* <p> * <p>
* {@code Config} is an immutable object and thus safe to use from multiple * <strong>Getting configuration values</strong>
* threads. There's never a need for "defensive copies." *
*
* <p> * <p>
* The "getters" on a {@code Config} all work in the same way. They never return * The "getters" on a {@code Config} all work in the same way. They never return
* null, nor do they return a {@code ConfigValue} with * null, nor do they return a {@code ConfigValue} with
@ -59,25 +89,71 @@ import java.util.concurrent.TimeUnit;
* thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for * thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for
* a type and the value has an incompatible type. Reasonable type conversions * a type and the value has an incompatible type. Reasonable type conversions
* are performed for you though. * are performed for you though.
* *
* <p>
* <strong>Iteration</strong>
*
* <p> * <p>
* If you want to iterate over the contents of a {@code Config}, you can get its * If you want to iterate over the contents of a {@code Config}, you can get its
* {@code ConfigObject} with {@link #root()}, and then iterate over the * {@code ConfigObject} with {@link #root()}, and then iterate over the
* {@code ConfigObject} (which implements <code>java.util.Map</code>). Or, you * {@code ConfigObject} (which implements <code>java.util.Map</code>). Or, you
* can use {@link #entrySet()} which recurses the object tree for you and builds * can use {@link #entrySet()} which recurses the object tree for you and builds
* up a <code>Set</code> of all path-value pairs where the value is not null. * up a <code>Set</code> of all path-value pairs where the value is not null.
* *
* <p>Before using a {@code Config} it's necessary to call {@link Config#resolve()} * <p>
* to handle substitutions (though {@link ConfigFactory#load()} and similar methods * <strong>Resolving substitutions</strong>
* will do the resolve for you already). *
* * <p>
* <p> You can find an example app and library <a * <em>Substitutions</em> are the <code>${foo.bar}</code> syntax in config
* href="https://github.com/typesafehub/config/tree/master/examples">on * files, described in the <a href=
* GitHub</a>. Also be sure to read the <a * "https://github.com/typesafehub/config/blob/master/HOCON.md#substitutions"
* href="package-summary.html#package_description">package * >specification</a>. Resolving substitutions replaces these references with real
* overview</a> which describes the big picture as shown in those * values.
* examples. *
* * <p>
* Before using a {@code Config} it's necessary to call {@link Config#resolve()}
* to handle substitutions (though {@link ConfigFactory#load()} and similar
* methods will do the resolve for you already).
*
* <p>
* <strong>Merging</strong>
*
* <p>
* The full <code>Config</code> for your application can be constructed using
* the associative operation {@link Config#withFallback(ConfigMergeable)}. If
* you use {@link ConfigFactory#load()} (recommended), it merges system
* properties over the top of <code>application.conf</code> over the top of
* <code>reference.conf</code>, using <code>withFallback</code>. You can add in
* additional sources of configuration in the same way (usually, custom layers
* should go either just above or just below <code>application.conf</code>,
* keeping <code>reference.conf</code> at the bottom and system properties at
* the top).
*
* <p>
* <strong>Serialization</strong>
*
* <p>
* Convert a <code>Config</code> to a JSON or HOCON string by calling
* {@link ConfigObject#render()} on the root object,
* <code>myConfig.root().render()</code>. There's also a variant
* {@link ConfigObject#render(ConfigRenderOptions)} which allows you to control
* the format of the rendered string. (See {@link ConfigRenderOptions}.) Note
* that <code>Config</code> does not remember the formatting of the original
* file, so if you load, modify, and re-save a config file, it will be
* substantially reformatted.
*
* <p>
* As an alternative to {@link ConfigObject#render()}, the
* <code>toString()</code> method produces a debug-output-oriented
* representation (which is not valid JSON).
*
* <p>
* Java serialization is supported as well for <code>Config</code> and all
* subtypes of <code>ConfigValue</code>.
*
* <p>
* <strong>This is an interface but don't implement it yourself</strong>
*
* <p> * <p>
* <em>Do not implement {@code Config}</em>; it should only be implemented by * <em>Do not implement {@code Config}</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the * the config library. Arbitrary implementations will not work because the
@ -114,19 +190,20 @@ public interface Config extends ConfigMergeable {
* <code>Config</code> as the root object, that is, a substitution * <code>Config</code> as the root object, that is, a substitution
* <code>${foo.bar}</code> will be replaced with the result of * <code>${foo.bar}</code> will be replaced with the result of
* <code>getValue("foo.bar")</code>. * <code>getValue("foo.bar")</code>.
* *
* <p> * <p>
* This method uses {@link ConfigResolveOptions#defaults()}, there is * This method uses {@link ConfigResolveOptions#defaults()}, there is
* another variant {@link Config#resolve(ConfigResolveOptions)} which lets * another variant {@link Config#resolve(ConfigResolveOptions)} which lets
* you specify non-default options. * you specify non-default options.
* *
* <p> * <p>
* A given {@link Config} must be resolved before using it to retrieve * A given {@link Config} must be resolved before using it to retrieve
* config values, but ideally should be resolved one time for your entire * config values, but ideally should be resolved one time for your entire
* stack of fallbacks (see {@link Config#withFallback}). Otherwise, some * stack of fallbacks (see {@link Config#withFallback}). Otherwise, some
* substitutions that could have resolved with all fallbacks available may * substitutions that could have resolved with all fallbacks available may
* not resolve, which will be a user-visible oddity. * not resolve, which will be potentially confusing for your application's
* * users.
*
* <p> * <p>
* <code>resolve()</code> should be invoked on root config objects, rather * <code>resolve()</code> should be invoked on root config objects, rather
* than on a subtree (a subtree is the result of something like * than on a subtree (a subtree is the result of something like
@ -136,24 +213,24 @@ public interface Config extends ConfigMergeable {
* from the root. For example, if you did * from the root. For example, if you did
* <code>config.getConfig("foo").resolve()</code> on the below config file, * <code>config.getConfig("foo").resolve()</code> on the below config file,
* it would not work: * it would not work:
* *
* <pre> * <pre>
* common-value = 10 * common-value = 10
* foo { * foo {
* whatever = ${common-value} * whatever = ${common-value}
* } * }
* </pre> * </pre>
* *
* <p> * <p>
* Many methods on {@link ConfigFactory} such as {@link * Many methods on {@link ConfigFactory} such as
* ConfigFactory#load()} automatically resolve the loaded * {@link ConfigFactory#load()} automatically resolve the loaded
* <code>Config</code> on the loaded stack of config files. * <code>Config</code> on the loaded stack of config files.
* *
* <p> Resolving an already-resolved config is a harmless * <p>
* no-op, but again, it is best to resolve an entire stack of * Resolving an already-resolved config is a harmless no-op, but again, it
* fallbacks (such as all your config files combined) rather * is best to resolve an entire stack of fallbacks (such as all your config
* than resolving each one individually. * files combined) rather than resolving each one individually.
* *
* @return an immutable object with substitutions resolved * @return an immutable object with substitutions resolved
* @throws ConfigException.UnresolvedSubstitution * @throws ConfigException.UnresolvedSubstitution
* if any substitutions refer to nonexistent paths * if any substitutions refer to nonexistent paths
@ -168,10 +245,67 @@ public interface Config extends ConfigMergeable {
* *
* @param options * @param options
* resolve options * resolve options
* @return the resolved <code>Config</code> * @return the resolved <code>Config</code> (may be only partially resolved if options are set to allow unresolved)
*/ */
Config resolve(ConfigResolveOptions options); Config resolve(ConfigResolveOptions options);
/**
* Checks whether the config is completely resolved. After a successful call
* to {@link Config#resolve()} it will be completely resolved, but after
* calling {@link Config#resolve(ConfigResolveOptions)} with
* <code>allowUnresolved</code> set in the options, it may or may not be
* completely resolved. A newly-loaded config may or may not be completely
* resolved depending on whether there were substitutions present in the
* file.
*
* @return true if there are no unresolved substitutions remaining in this
* configuration.
* @since 1.2.0
*/
boolean isResolved();
/**
* Like {@link Config#resolve()} except that substitution values are looked
* up in the given source, rather than in this instance. This is a
* special-purpose method which doesn't make sense to use in most cases;
* it's only needed if you're constructing some sort of app-specific custom
* approach to configuration. The more usual approach if you have a source
* of substitution values would be to merge that source into your config
* stack using {@link Config#withFallback} and then resolve.
* <p>
* Note that this method does NOT look in this instance for substitution
* values. If you want to do that, you could either merge this instance into
* your value source using {@link Config#withFallback}, or you could resolve
* multiple times with multiple sources (using
* {@link ConfigResolveOptions#setAllowUnresolved(boolean)} so the partial
* resolves don't fail).
*
* @param source
* configuration to pull values from
* @return an immutable object with substitutions resolved
* @throws ConfigException.UnresolvedSubstitution
* if any substitutions refer to paths which are not in the
* source
* @throws ConfigException
* some other config exception if there are other problems
* @since 1.2.0
*/
Config resolveWith(Config source);
/**
* Like {@link Config#resolveWith(Config)} but allows you to specify
* non-default options.
*
* @param source
* source configuration to pull values from
* @param options
* resolve options
* @return the resolved <code>Config</code> (may be only partially resolved
* if options are set to allow unresolved)
* @since 1.2.0
*/
Config resolveWith(Config source, ConfigResolveOptions options);
/** /**
* Validates this config against a reference config, throwing an exception * Validates this config against a reference config, throwing an exception
* if it is invalid. The purpose of this method is to "fail early" with a * if it is invalid. The purpose of this method is to "fail early" with a
@ -484,7 +618,7 @@ public interface Config extends ConfigMergeable {
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the * href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
* spec</a>. * spec</a>.
* *
* @since 1.1 * @since 1.2.0
* *
* @param path * @param path
* path expression * path expression
@ -498,7 +632,7 @@ public interface Config extends ConfigMergeable {
* @throws ConfigException.BadValue * @throws ConfigException.BadValue
* if value cannot be parsed as a number of the given TimeUnit * if value cannot be parsed as a number of the given TimeUnit
*/ */
Long getDuration(String path, TimeUnit unit); long getDuration(String path, TimeUnit unit);
/** /**
* Gets a list value (with any element type) as a {@link ConfigList}, which * Gets a list value (with any element type) as a {@link ConfigList}, which
@ -548,8 +682,8 @@ public interface Config extends ConfigMergeable {
/** /**
* Gets a list, converting each value in the list to a duration, using the * Gets a list, converting each value in the list to a duration, using the
* same rules as {@link #getDuration(String, TimeUnit)}. * same rules as {@link #getDuration(String, TimeUnit)}.
* *
* @since 1.1 * @since 1.2.0
* @param path * @param path
* a path expression * a path expression
* @param unit * @param unit

View File

@ -219,21 +219,22 @@ public final class ConfigFactory {
+ "', config.url='" + url + "', config.resource='" + resource + "', config.url='" + url + "', config.resource='" + resource
+ "'; don't know which one to use!"); + "'; don't know which one to use!");
} else { } else {
// the override file/url/resource MUST be present or it's an error
ConfigParseOptions overrideOptions = parseOptions.setAllowMissing(false);
if (resource != null) { if (resource != null) {
if (resource.startsWith("/")) if (resource.startsWith("/"))
resource = resource.substring(1); resource = resource.substring(1);
// this deliberately does not parseResourcesAnySyntax; if // this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement. // people want that they can use an include statement.
return load(loader, parseResources(loader, resource, parseOptions), resolveOptions); Config parsedResources = parseResources(loader, resource, overrideOptions);
return load(loader, parsedResources, resolveOptions);
} else if (file != null) { } else if (file != null) {
return load( Config parsedFile = parseFile(new File(file), overrideOptions);
loader, return load(loader, parsedFile, resolveOptions);
parseFile(new File(file), parseOptions), resolveOptions);
} else { } else {
try { try {
return load( Config parsedURL = parseURL(new URL(url), overrideOptions);
loader, return load(loader, parsedURL, resolveOptions);
parseURL(new URL(url), parseOptions), resolveOptions);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new ConfigException.Generic("Bad URL in config.url system property: '" throw new ConfigException.Generic("Bad URL in config.url system property: '"
+ url + "': " + e.getMessage(), e); + url + "': " + e.getMessage(), e);

View File

@ -19,43 +19,52 @@ package com.typesafe.config;
public interface ConfigMergeable { public interface ConfigMergeable {
/** /**
* Returns a new value computed by merging this value with another, with * Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only * keys in this value "winning" over the other one.
* {@link ConfigObject} and {@link Config} instances do anything in this
* method (they need to merge the fallback keys into themselves). All other
* values just return the original value, since they automatically override
* any fallback.
* *
* <p> The semantics of merging are described in the <a * <p>
* This associative operation may be used to combine configurations from
* multiple sources (such as multiple configuration files).
*
* <p>
* The semantics of merging are described in the <a
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">spec * href="https://github.com/typesafehub/config/blob/master/HOCON.md">spec
* for HOCON</a>. Merging typically occurs when either the * for HOCON</a>. Merging typically occurs when either the same object is
* same object is created twice in the same file, or two * created twice in the same file, or two config files are both loaded. For
* config files are both loaded. For example: * example:
*
* <pre> * <pre>
* foo = { a: 42 } * foo = { a: 42 }
* foo = { b: 43 } * foo = { b: 43 }
* </pre> * </pre>
*
* Here, the two objects are merged as if you had written: * Here, the two objects are merged as if you had written:
*
* <pre> * <pre>
* foo = { a: 42, b: 43 } * foo = { a: 42, b: 43 }
* </pre> * </pre>
* *
* <p> * <p>
* Note that objects do not merge "across" non-objects; if you write * Only {@link ConfigObject} and {@link Config} instances do anything in
* this method (they need to merge the fallback keys into themselves). All
* other values just return the original value, since they automatically
* override any fallback. This means that objects do not merge "across"
* non-objects; if you write
* <code>object.withFallback(nonObject).withFallback(otherObject)</code>, * <code>object.withFallback(nonObject).withFallback(otherObject)</code>,
* then <code>otherObject</code> will simply be ignored. This is an * then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works, because non-objects such as * intentional part of how merging works, because non-objects such as
* strings and integers replace (rather than merging with) any * strings and integers replace (rather than merging with) any prior value:
* prior value: *
* <pre> * <pre>
* foo = { a: 42 } * foo = { a: 42 }
* foo = 10 * foo = 10
* </pre> * </pre>
*
* Here, the number 10 "wins" and the value of <code>foo</code> would be * Here, the number 10 "wins" and the value of <code>foo</code> would be
* simply 10. Again, for details see the spec. * simply 10. Again, for details see the spec.
* *
* @param other * @param other
* an object whose keys should be used if the keys are not * an object whose keys should be used as fallbacks, if the keys
* present in this one * are not present in this one
* @return a new object (or the original one, if the fallback doesn't get * @return a new object (or the original one, if the fallback doesn't get
* used) * used)
*/ */

View File

@ -6,53 +6,58 @@ package com.typesafe.config;
import java.util.Map; import java.util.Map;
/** /**
* Subtype of {@link ConfigValue} representing an object (dictionary, map) * Subtype of {@link ConfigValue} representing an object (AKA dictionary or map)
* value, as in JSON's <code>{ "a" : 42 }</code> syntax. * value, as in JSON's curly brace <code>{ "a" : 42 }</code> syntax.
* *
* <p>
* An object may also be viewed as a {@link Config} by calling
* {@link ConfigObject#toConfig()}.
*
* <p> * <p>
* {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so * {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so
* you can use it like a regular Java map. Or call {@link #unwrapped()} to * you can use it like a regular Java map. Or call {@link #unwrapped()} to
* unwrap the map to a map with plain Java values rather than * unwrap the map to a map with plain Java values rather than
* {@code ConfigValue}. * {@code ConfigValue}.
* *
* <p> * <p>
* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable. * Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* This makes it threadsafe and you never have to create "defensive copies." The * This makes it threadsafe and you never have to create "defensive copies." The
* mutator methods from {@link java.util.Map} all throw * mutator methods from {@link java.util.Map} all throw
* {@link java.lang.UnsupportedOperationException}. * {@link java.lang.UnsupportedOperationException}.
* *
* <p> * <p>
* The {@link ConfigValue#valueType} method on an object returns * The {@link ConfigValue#valueType} method on an object returns
* {@link ConfigValueType#OBJECT}. * {@link ConfigValueType#OBJECT}.
* *
* <p> * <p>
* In most cases you want to use the {@link Config} interface rather than this * In most cases you want to use the {@link Config} interface rather than this
* one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a * one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a
* {@code Config}. * {@code Config}.
* *
* <p> * <p>
* The API for a {@code ConfigObject} is in terms of keys, while the API for a * The API for a {@code ConfigObject} is in terms of keys, while the API for a
* {@link Config} is in terms of path expressions. Conceptually, * {@link Config} is in terms of path expressions. Conceptually,
* {@code ConfigObject} is a tree of maps from keys to values, while a * {@code ConfigObject} is a tree of maps from keys to values, while a
* {@code Config} is a one-level map from paths to values. * {@code Config} is a one-level map from paths to values.
* *
* <p> * <p>
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
* between path expressions and individual path elements (keys). * between path expressions and individual path elements (keys).
* *
* <p> * <p>
* A {@code ConfigObject} may contain null values, which will have * A {@code ConfigObject} may contain null values, which will have
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
* {@code get()} returns Java's null then the key was not present in the parsed * {@link ConfigObject#get(Object)} returns Java's null then the key was not
* file (or wherever this value tree came from). If {@code get()} returns a * present in the parsed file (or wherever this value tree came from). If
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was * {@code get("key")} returns a {@link ConfigValue} with type
* set to null explicitly in the config file. * {@code ConfigValueType#NULL} then the key was set to null explicitly in the
* * config file.
*
* <p> * <p>
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented * <em>Do not implement interface {@code ConfigObject}</em>; it should only be
* by the config library. Arbitrary implementations will not work because the * implemented by the config library. Arbitrary implementations will not work
* library internals assume a specific concrete implementation. Also, this * because the library internals assume a specific concrete implementation.
* interface is likely to grow new methods over time, so third-party * Also, this interface is likely to grow new methods over time, so third-party
* implementations will break. * implementations will break.
*/ */
public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> { public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
@ -82,11 +87,11 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
* Gets a {@link ConfigValue} at the given key, or returns null if there is * Gets a {@link ConfigValue} at the given key, or returns null if there is
* no value. The returned {@link ConfigValue} may have * no value. The returned {@link ConfigValue} may have
* {@link ConfigValueType#NULL} or any other type, and the passed-in key * {@link ConfigValueType#NULL} or any other type, and the passed-in key
* must be a key in this object, rather than a path expression. * must be a key in this object (rather than a path expression).
* *
* @param key * @param key
* key to look up * key to look up
* *
* @return the value at the key or null if none * @return the value at the key or null if none
*/ */
@Override @Override
@ -115,7 +120,7 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
* Returns a {@code ConfigObject} based on this one, but with the given key * Returns a {@code ConfigObject} based on this one, but with the given key
* set to the given value. Does not modify this instance (since it's * set to the given value. Does not modify this instance (since it's
* immutable). If the key already has a value, that value is replaced. To * immutable). If the key already has a value, that value is replaced. To
* remove a value, use withoutKey(). * remove a value, use {@link ConfigObject#withoutKey(String)}.
* *
* @param key * @param key
* key to add * key to add

View File

@ -9,6 +9,9 @@ package com.typesafe.config;
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON</a> * href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON</a>
* spec. * spec.
* <p> * <p>
* Typically this class would be used with the method
* {@link Config#resolve(ConfigResolveOptions)}.
* <p>
* This object is immutable, so the "setters" return a new object. * This object is immutable, so the "setters" return a new object.
* <p> * <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}: * Here is an example of creating a custom {@code ConfigResolveOptions}:
@ -25,18 +28,21 @@ package com.typesafe.config;
*/ */
public final class ConfigResolveOptions { public final class ConfigResolveOptions {
private final boolean useSystemEnvironment; private final boolean useSystemEnvironment;
private final boolean allowUnresolved;
private ConfigResolveOptions(boolean useSystemEnvironment) { private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) {
this.useSystemEnvironment = useSystemEnvironment; this.useSystemEnvironment = useSystemEnvironment;
this.allowUnresolved = allowUnresolved;
} }
/** /**
* Returns the default resolve options. * Returns the default resolve options. By default the system environment
* * will be used and unresolved substitutions are not allowed.
*
* @return the default resolve options * @return the default resolve options
*/ */
public static ConfigResolveOptions defaults() { public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true); return new ConfigResolveOptions(true, false);
} }
/** /**
@ -57,9 +63,8 @@ public final class ConfigResolveOptions {
* variables. * variables.
* @return options with requested setting for use of environment variables * @return options with requested setting for use of environment variables
*/ */
@SuppressWarnings("static-method")
public ConfigResolveOptions setUseSystemEnvironment(boolean value) { public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(value); return new ConfigResolveOptions(value, allowUnresolved);
} }
/** /**
@ -72,4 +77,31 @@ public final class ConfigResolveOptions {
public boolean getUseSystemEnvironment() { public boolean getUseSystemEnvironment() {
return useSystemEnvironment; return useSystemEnvironment;
} }
/**
* Returns options with "allow unresolved" set to the given value. By
* default, unresolved substitutions are an error. If unresolved
* substitutions are allowed, then a future attempt to use the unresolved
* value may fail, but {@link Config#resolve(ConfigResolveOptions)} itself
* will now throw.
*
* @param value
* true to silently ignore unresolved substitutions.
* @return options with requested setting for whether to allow substitutions
* @since 1.2.0
*/
public ConfigResolveOptions setAllowUnresolved(boolean value) {
return new ConfigResolveOptions(useSystemEnvironment, value);
}
/**
* Returns whether the options allow unresolved substitutions. This method
* is mostly used by the config lib internally, not by applications.
*
* @return true if unresolved substitutions are allowed
* @since 1.2.0
*/
public boolean getAllowUnresolved() {
return allowUnresolved;
}
} }

View File

@ -4,29 +4,33 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* The syntax of a character stream, <a href="http://json.org">JSON</a>, <a * The syntax of a character stream (<a href="http://json.org">JSON</a>, <a
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON</a> * href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON</a>
* aka ".conf", or <a href= * aka ".conf", or <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29" * "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a>. * >Java properties</a>).
* *
*/ */
public enum ConfigSyntax { public enum ConfigSyntax {
/** /**
* Pedantically strict <a href="http://json.org">JSON</a> format; no * Pedantically strict <a href="http://json.org">JSON</a> format; no
* comments, no unexpected commas, no duplicate keys in the same object. * comments, no unexpected commas, no duplicate keys in the same object.
* Associated with the <code>.json</code> file extension and
* <code>application/json</code> Content-Type.
*/ */
JSON, JSON,
/** /**
* The JSON-superset <a * The JSON-superset <a
* href="https://github.com/typesafehub/config/blob/master/HOCON.md" * href="https://github.com/typesafehub/config/blob/master/HOCON.md"
* >HOCON</a> format. * >HOCON</a> format. Associated with the <code>.conf</code> file extension
* and <code>application/hocon</code> Content-Type.
*/ */
CONF, CONF,
/** /**
* Standard <a href= * Standard <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29" * "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a> format. * >Java properties</a> format. Associated with the <code>.properties</code>
* file extension and <code>text/x-java-properties</code> Content-Type.
*/ */
PROPERTIES; PROPERTIES;
} }

View File

@ -6,16 +6,16 @@ package com.typesafe.config;
/** /**
* An immutable value, following the <a href="http://json.org">JSON</a> type * An immutable value, following the <a href="http://json.org">JSON</a> type
* schema. * schema.
* *
* <p> * <p>
* Because this object is immutable, it is safe to use from multiple threads and * Because this object is immutable, it is safe to use from multiple threads and
* there's no need for "defensive copies." * there's no need for "defensive copies."
* *
* <p> * <p>
* <em>Do not implement {@code ConfigValue}</em>; it should only be implemented * <em>Do not implement interface {@code ConfigValue}</em>; it should only be
* by the config library. Arbitrary implementations will not work because the * implemented by the config library. Arbitrary implementations will not work
* library internals assume a specific concrete implementation. Also, this * because the library internals assume a specific concrete implementation.
* interface is likely to grow new methods over time, so third-party * Also, this interface is likely to grow new methods over time, so third-party
* implementations will break. * implementations will break.
*/ */
public interface ConfigValue extends ConfigMergeable { public interface ConfigValue extends ConfigMergeable {
@ -47,36 +47,36 @@ public interface ConfigValue extends ConfigMergeable {
* Renders the config value as a HOCON string. This method is primarily * Renders the config value as a HOCON string. This method is primarily
* intended for debugging, so it tries to add helpful comments and * intended for debugging, so it tries to add helpful comments and
* whitespace. * whitespace.
* *
* <p> * <p>
* If the config value has not been resolved (see {@link Config#resolve}), * If the config value has not been resolved (see {@link Config#resolve}),
* it's possible that it can't be rendered as valid HOCON. In that case the * it's possible that it can't be rendered as valid HOCON. In that case the
* rendering should still be useful for debugging but you might not be able * rendering should still be useful for debugging but you might not be able
* to parse it. * to parse it. If the value has been resolved, it will always be parseable.
* *
* <p> * <p>
* This method is equivalent to * This method is equivalent to
* {@code render(ConfigRenderOptions.defaults())}. * {@code render(ConfigRenderOptions.defaults())}.
* *
* @return the rendered value * @return the rendered value
*/ */
String render(); String render();
/** /**
* Renders the config value to a string, using the provided options. * Renders the config value to a string, using the provided options.
* *
* <p> * <p>
* If the config value has not been resolved (see {@link Config#resolve}), * If the config value has not been resolved (see {@link Config#resolve}),
* it's possible that it can't be rendered as valid HOCON. In that case the * it's possible that it can't be rendered as valid HOCON. In that case the
* rendering should still be useful for debugging but you might not be able * rendering should still be useful for debugging but you might not be able
* to parse it. * to parse it. If the value has been resolved, it will always be parseable.
* *
* <p> * <p>
* If the config value has been resolved and the options disable all * If the config value has been resolved and the options disable all
* HOCON-specific features (such as comments), the rendering will be valid * HOCON-specific features (such as comments), the rendering will be valid
* JSON. If you enable HOCON-only features such as comments, the rendering * JSON. If you enable HOCON-only features such as comments, the rendering
* will not be valid JSON. * will not be valid JSON.
* *
* @param options * @param options
* the rendering options * the rendering options
* @return the rendered value * @return the rendered value
@ -87,9 +87,9 @@ public interface ConfigValue extends ConfigMergeable {
ConfigValue withFallback(ConfigMergeable other); ConfigValue withFallback(ConfigMergeable other);
/** /**
* Places the value inside a {@code Config} at the given path. See also * Places the value inside a {@link Config} at the given path. See also
* atKey(). * {@link ConfigValue#atKey(String)}.
* *
* @param path * @param path
* path to store this value at. * path to store this value at.
* @return a {@code Config} instance containing this value at the given * @return a {@code Config} instance containing this value at the given
@ -98,9 +98,9 @@ public interface ConfigValue extends ConfigMergeable {
Config atPath(String path); Config atPath(String path);
/** /**
* Places the value inside a {@code Config} at the given key. See also * Places the value inside a {@link Config} at the given key. See also
* atPath(). * {@link ConfigValue#atPath(String)}.
* *
* @param key * @param key
* key to store this value at. * key to store this value at.
* @return a {@code Config} instance containing this value at the given key. * @return a {@code Config} instance containing this value at the given key.

View File

@ -17,40 +17,45 @@ public final class ConfigValueFactory {
} }
/** /**
* Creates a ConfigValue from a plain Java boxed value, which may be a * Creates a {@link ConfigValue} from a plain Java boxed value, which may be
* Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from * a <code>Boolean</code>, <code>Number</code>, <code>String</code>,
* String to more values that can be supplied to fromAnyRef(). An Iterable * <code>Map</code>, <code>Iterable</code>, or <code>null</code>. A
* must iterate over more values that can be supplied to fromAnyRef(). A Map * <code>Map</code> must be a <code>Map</code> from String to more values
* will become a ConfigObject and an Iterable will become a ConfigList. If * that can be supplied to <code>fromAnyRef()</code>. An
* the Iterable is not an ordered collection, results could be strange, * <code>Iterable</code> must iterate over more values that can be supplied
* since ConfigList is ordered. * to <code>fromAnyRef()</code>. A <code>Map</code> will become a
* * {@link ConfigObject} and an <code>Iterable</code> will become a
* {@link ConfigList}. If the <code>Iterable</code> is not an ordered
* collection, results could be strange, since <code>ConfigList</code> is
* ordered.
*
* <p> * <p>
* In a Map passed to fromAnyRef(), the map's keys are plain keys, not path * In a <code>Map</code> passed to <code>fromAnyRef()</code>, the map's keys
* expressions. So if your Map has a key "foo.bar" then you will get one * are plain keys, not path expressions. So if your <code>Map</code> has a
* object with a key called "foo.bar", rather than an object with a key * key "foo.bar" then you will get one object with a key called "foo.bar",
* "foo" containing another object with a key "bar". * rather than an object with a key "foo" containing another object with a
* * key "bar".
*
* <p> * <p>
* The originDescription will be used to set the origin() field on the * The originDescription will be used to set the origin() field on the
* ConfigValue. It should normally be the name of the file the values came * ConfigValue. It should normally be the name of the file the values came
* from, or something short describing the value such as "default settings". * from, or something short describing the value such as "default settings".
* The originDescription is prefixed to error messages so users can tell * The originDescription is prefixed to error messages so users can tell
* where problematic values are coming from. * where problematic values are coming from.
* *
* <p> * <p>
* Supplying the result of ConfigValue.unwrapped() to this function is * Supplying the result of ConfigValue.unwrapped() to this function is
* guaranteed to work and should give you back a ConfigValue that matches * guaranteed to work and should give you back a ConfigValue that matches
* the one you unwrapped. The re-wrapped ConfigValue will lose some * the one you unwrapped. The re-wrapped ConfigValue will lose some
* information that was present in the original such as its origin, but it * information that was present in the original such as its origin, but it
* will have matching values. * will have matching values.
* *
* <p> * <p>
* This function throws if you supply a value that cannot be converted to a * This function throws if you supply a value that cannot be converted to a
* ConfigValue, but supplying such a value is a bug in your program, so you * ConfigValue, but supplying such a value is a bug in your program, so you
* should never handle the exception. Just fix your program (or report a bug * should never handle the exception. Just fix your program (or report a bug
* against this library). * against this library).
* *
* @param object * @param object
* object to convert to ConfigValue * object to convert to ConfigValue
* @param originDescription * @param originDescription
@ -62,23 +67,24 @@ public final class ConfigValueFactory {
} }
/** /**
* See the fromAnyRef() documentation for details. This is a typesafe * See the {@link #fromAnyRef(Object,String)} documentation for details.
* wrapper that only works on {@link java.util.Map} and returns * This is a typesafe wrapper that only works on {@link java.util.Map} and
* {@link ConfigObject} rather than {@link ConfigValue}. * returns {@link ConfigObject} rather than {@link ConfigValue}.
* *
* <p> * <p>
* If your Map has a key "foo.bar" then you will get one object with a key * If your <code>Map</code> has a key "foo.bar" then you will get one object
* called "foo.bar", rather than an object with a key "foo" containing * with a key called "foo.bar", rather than an object with a key "foo"
* another object with a key "bar". The keys in the map are keys; not path * containing another object with a key "bar". The keys in the map are keys;
* expressions. That is, the Map corresponds exactly to a single * not path expressions. That is, the <code>Map</code> corresponds exactly
* {@code ConfigObject}. The keys will not be parsed or modified, and the * to a single {@code ConfigObject}. The keys will not be parsed or
* values are wrapped in ConfigValue. To get nested {@code ConfigObject}, * modified, and the values are wrapped in ConfigValue. To get nested
* some of the values in the map would have to be more maps. * {@code ConfigObject}, some of the values in the map would have to be more
* * maps.
*
* <p> * <p>
* See also {@link ConfigFactory#parseMap(Map,String)} which interprets the * See also {@link ConfigFactory#parseMap(Map,String)} which interprets the
* keys in the map as path expressions. * keys in the map as path expressions.
* *
* @param values * @param values
* @param originDescription * @param originDescription
* @return a new {@link ConfigObject} value * @return a new {@link ConfigObject} value
@ -89,10 +95,10 @@ public final class ConfigValueFactory {
} }
/** /**
* See the fromAnyRef() documentation for details. This is a typesafe * See the {@link #fromAnyRef(Object,String)} documentation for details.
* wrapper that only works on {@link java.lang.Iterable} and returns * This is a typesafe wrapper that only works on {@link java.lang.Iterable}
* {@link ConfigList} rather than {@link ConfigValue}. * and returns {@link ConfigList} rather than {@link ConfigValue}.
* *
* @param values * @param values
* @param originDescription * @param originDescription
* @return a new {@link ConfigList} value * @return a new {@link ConfigList} value

View File

@ -218,7 +218,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi
public abstract AbstractConfigValue get(Object key); public abstract AbstractConfigValue get(Object key);
@Override @Override
protected abstract void render(StringBuilder sb, int indent, ConfigRenderOptions options); protected abstract void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options);
private static UnsupportedOperationException weAreImmutable(String method) { private static UnsupportedOperationException weAreImmutable(String method) {
return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map." return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map."

View File

@ -279,7 +279,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
@Override @Override
public final String toString() { public final String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
render(sb, 0, null /* atKey */, ConfigRenderOptions.concise()); render(sb, 0, true /* atRoot */, null /* atKey */, ConfigRenderOptions.concise());
return getClass().getSimpleName() + "(" + sb.toString() + ")"; return getClass().getSimpleName() + "(" + sb.toString() + ")";
} }
@ -293,7 +293,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
} }
} }
protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) {
if (atKey != null) { if (atKey != null) {
String renderedKey; String renderedKey;
if (options.getJson()) if (options.getJson())
@ -318,10 +318,10 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
} }
} }
} }
render(sb, indent, options); render(sb, indent, atRoot, options);
} }
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
Object u = unwrapped(); Object u = unwrapped();
sb.append(u.toString()); sb.append(u.toString());
} }
@ -334,7 +334,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
@Override @Override
public final String render(ConfigRenderOptions options) { public final String render(ConfigRenderOptions options) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
render(sb, 0, null, options); render(sb, 0, true, null, options);
return sb.toString(); return sb.toString();
} }

View File

@ -232,9 +232,9 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
for (AbstractConfigValue p : pieces) { for (AbstractConfigValue p : pieces) {
p.render(sb, indent, options); p.render(sb, indent, atRoot, options);
} }
} }

View File

@ -216,12 +216,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
} }
@Override @Override
protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) {
render(stack, sb, indent, atKey, options); render(stack, sb, indent, atRoot, atKey, options);
} }
// static method also used by ConfigDelayedMergeObject. // static method also used by ConfigDelayedMergeObject.
static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, String atKey, static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, boolean atRoot, String atKey,
ConfigRenderOptions options) { ConfigRenderOptions options) {
boolean commentMerge = options.getComments(); boolean commentMerge = options.getComments();
if (commentMerge) { if (commentMerge) {
@ -268,7 +268,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
else else
sb.append(":"); sb.append(":");
} }
v.render(sb, indent, options); v.render(sb, indent, atRoot, options);
sb.append(","); sb.append(",");
if (options.getFormatted()) if (options.getFormatted())
sb.append('\n'); sb.append('\n');

View File

@ -179,13 +179,13 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
} }
@Override @Override
protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) {
ConfigDelayedMerge.render(stack, sb, indent, atKey, options); ConfigDelayedMerge.render(stack, sb, indent, atRoot, atKey, options);
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
render(sb, indent, null, options); render(sb, indent, atRoot, null, options);
} }
private static ConfigException notResolved() { private static ConfigException notResolved() {

View File

@ -42,7 +42,7 @@ final class ConfigNull extends AbstractConfigValue implements Serializable {
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
sb.append("null"); sb.append("null");
} }

View File

@ -81,9 +81,13 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
} }
if (v == null && !expr.optional()) { if (v == null && !expr.optional()) {
throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString()); if (context.options().getAllowUnresolved())
return this;
else
throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString());
} else {
return v;
} }
return v;
} finally { } finally {
context.source().unreplace(this); context.source().unreplace(this);
} }
@ -127,7 +131,7 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
sb.append(expr.toString()); sb.append(expr.toString());
} }

View File

@ -37,7 +37,7 @@ final class ConfigString extends AbstractConfigValue implements Serializable {
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
String rendered; String rendered;
if (options.getJson()) if (options.getJson())
rendered = ConfigImplUtil.renderJsonString(value); rendered = ConfigImplUtil.renderJsonString(value);

View File

@ -683,7 +683,9 @@ final class Parser {
} }
if (!pathStack.isEmpty()) { if (!pathStack.isEmpty()) {
Path prefix = new Path(pathStack); // The stack is in reverse order (most recent first on the
// iterator), so build the path from the reversed iterator.
Path prefix = new Path(pathStack.descendingIterator());
obj = obj.relativized(prefix); obj = obj.relativized(prefix);
} }

View File

@ -35,10 +35,14 @@ final class Path {
// append all the paths in the list together into one path // append all the paths in the list together into one path
Path(List<Path> pathsToConcat) { Path(List<Path> pathsToConcat) {
if (pathsToConcat.isEmpty()) this(pathsToConcat.iterator());
}
// append all the paths in the iterator together into one path
Path(Iterator<Path> i) {
if (!i.hasNext())
throw new ConfigException.BugOrBroken("empty path"); throw new ConfigException.BugOrBroken("empty path");
Iterator<Path> i = pathsToConcat.iterator();
Path firstPath = i.next(); Path firstPath = i.next();
this.first = firstPath.first; this.first = firstPath.first;

View File

@ -121,14 +121,16 @@ final class ResolveContext {
memos.put(fullKey, resolved); memos.put(fullKey, resolved);
} else { } else {
// if we have an unresolved object then either we did a // if we have an unresolved object then either we did a
// partial resolve restricted to a certain child, or it's // partial resolve restricted to a certain child, or we are
// a bug. // allowing incomplete resolution, or it's a bug.
if (isRestrictedToChild()) { if (isRestrictedToChild()) {
if (restrictedKey == null) { if (restrictedKey == null) {
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"restrictedKey should not be null here"); "restrictedKey should not be null here");
} }
memos.put(restrictedKey, resolved); memos.put(restrictedKey, resolved);
} else if (options().getAllowUnresolved()) {
memos.put(fullKey, resolved);
} else { } else {
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object"); "resolveSubstitutions() did not give us a resolved object");

View File

@ -57,7 +57,17 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
@Override @Override
public SimpleConfig resolve(ConfigResolveOptions options) { public SimpleConfig resolve(ConfigResolveOptions options) {
AbstractConfigValue resolved = ResolveContext.resolve(object, object, options); return resolveWith(this, options);
}
@Override
public SimpleConfig resolveWith(Config source) {
return resolveWith(source, ConfigResolveOptions.defaults());
}
@Override
public SimpleConfig resolveWith(Config source, ConfigResolveOptions options) {
AbstractConfigValue resolved = ResolveContext.resolve(object, ((SimpleConfig) source).object, options);
if (resolved == object) if (resolved == object)
return this; return this;
@ -65,7 +75,6 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
return new SimpleConfig((AbstractConfigObject) resolved); return new SimpleConfig((AbstractConfigObject) resolved);
} }
@Override @Override
public boolean hasPath(String pathExpression) { public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression); Path path = Path.newPath(pathExpression);
@ -247,9 +256,9 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
} }
@Override @Override
public Long getDuration(String path, TimeUnit unit) { public long getDuration(String path, TimeUnit unit) {
ConfigValue v = find(path, ConfigValueType.STRING); ConfigValue v = find(path, ConfigValueType.STRING);
Long result = unit.convert( long result = unit.convert(
parseDuration((String) v.unwrapped(), v.origin(), path), parseDuration((String) v.unwrapped(), v.origin(), path),
TimeUnit.NANOSECONDS); TimeUnit.NANOSECONDS);
return result; return result;
@ -815,6 +824,11 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
} }
} }
@Override
public boolean isResolved() {
return root().resolveStatus() == ResolveStatus.RESOLVED;
}
@Override @Override
public void checkValid(Config reference, String... restrictToPaths) { public void checkValid(Config reference, String... restrictToPaths) {
SimpleConfig ref = (SimpleConfig) reference; SimpleConfig ref = (SimpleConfig) reference;

View File

@ -167,7 +167,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
if (value.isEmpty()) { if (value.isEmpty()) {
sb.append("[]"); sb.append("[]");
} else { } else {
@ -191,7 +191,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
} }
indent(sb, indent + 1, options); indent(sb, indent + 1, options);
v.render(sb, indent + 1, options); v.render(sb, indent + 1, atRoot, options);
sb.append(","); sb.append(",");
if (options.getFormatted()) if (options.getFormatted())
sb.append('\n'); sb.append('\n');

View File

@ -364,17 +364,22 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
} }
@Override @Override
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
if (isEmpty()) { if (isEmpty()) {
sb.append("{}"); sb.append("{}");
} else { } else {
boolean outerBraces = indent > 0 || options.getJson(); boolean outerBraces = options.getJson() || !atRoot;
if (outerBraces) int innerIndent;
if (outerBraces) {
innerIndent = indent + 1;
sb.append("{"); sb.append("{");
if (options.getFormatted()) if (options.getFormatted())
sb.append('\n'); sb.append('\n');
} else {
innerIndent = indent;
}
int separatorCount = 0; int separatorCount = 0;
for (String k : keySet()) { for (String k : keySet()) {
@ -382,14 +387,14 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
v = value.get(k); v = value.get(k);
if (options.getOriginComments()) { if (options.getOriginComments()) {
indent(sb, indent + 1, options); indent(sb, innerIndent, options);
sb.append("# "); sb.append("# ");
sb.append(v.origin().description()); sb.append(v.origin().description());
sb.append("\n"); sb.append("\n");
} }
if (options.getComments()) { if (options.getComments()) {
for (String comment : v.origin().comments()) { for (String comment : v.origin().comments()) {
indent(sb, indent + 1, options); indent(sb, innerIndent, options);
sb.append("#"); sb.append("#");
if (!comment.startsWith(" ")) if (!comment.startsWith(" "))
sb.append(' '); sb.append(' ');
@ -397,8 +402,8 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
sb.append("\n"); sb.append("\n");
} }
} }
indent(sb, indent + 1, options); indent(sb, innerIndent, options);
v.render(sb, indent + 1, k, options); v.render(sb, innerIndent, false /* atRoot */, k, options);
if (options.getFormatted()) { if (options.getFormatted()) {
if (options.getJson()) { if (options.getJson()) {
@ -415,14 +420,18 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
} }
// chop last commas/newlines // chop last commas/newlines
sb.setLength(sb.length() - separatorCount); sb.setLength(sb.length() - separatorCount);
if (options.getFormatted()) {
sb.append("\n"); // put a newline back
indent(sb, indent, options);
}
if (outerBraces) if (outerBraces) {
if (options.getFormatted()) {
sb.append('\n'); // put a newline back
if (outerBraces)
indent(sb, indent, options);
}
sb.append("}"); sb.append("}");
}
} }
if (atRoot && options.getFormatted())
sb.append('\n');
} }
@Override @Override

View File

@ -15,7 +15,9 @@ for more information.
<p> <p>
Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use
it with methods in the {@link com.typesafe.config.Config} interface. it with methods in the {@link com.typesafe.config.Config} interface. Configuration may be in the form of JSON files,
Java properties, or <a href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON files</a>; you may also
build your own configuration in code or from your own file formats.
</p> </p>
<p> <p>
@ -24,6 +26,8 @@ its configuration in "application.conf" on the classpath.
If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()} If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()}
there's no need to pass a configuration to your libraries there's no need to pass a configuration to your libraries
and frameworks, as long as they all default to this same default, which they should. and frameworks, as long as they all default to this same default, which they should.
<br/><strong>Example application code:</strong> <a href="https://github.com/typesafehub/config/tree/master/examples/java/simple-app/src/main">Java</a> and <a href="https://github.com/typesafehub/config/tree/master/examples/scala/simple-app/src/main">Scala</a>.
<br/>Showing a couple of more special-purpose features, <strong>a more complex example:</strong> <a href="https://github.com/typesafehub/config/tree/master/examples/java/complex-app/src/main">Java</a> and <a href="https://github.com/typesafehub/config/tree/master/examples/scala/complex-app/src/main">Scala</a>.
</p> </p>
<p> <p>
@ -32,10 +36,22 @@ A library or framework should ship a file "reference.conf" in its jar, and allow
call {@link com.typesafe.config.ConfigFactory#load()} call {@link com.typesafe.config.ConfigFactory#load()}
to get the default one. Typically a library might offer two constructors, one with a <code>Config</code> parameter to get the default one. Typically a library might offer two constructors, one with a <code>Config</code> parameter
and one which uses {@link com.typesafe.config.ConfigFactory#load()}. and one which uses {@link com.typesafe.config.ConfigFactory#load()}.
<br/><strong>Example library code:</strong> <a href="https://github.com/typesafehub/config/tree/master/examples/java/simple-lib/src/main">Java</a> and <a href="https://github.com/typesafehub/config/tree/master/examples/scala/simple-lib/src/main">Scala</a>.
</p> </p>
<p> <p>
You can find an example app and library <a href="https://github.com/typesafehub/config/tree/master/examples">on GitHub</a>. Check out the full <a href="https://github.com/typesafehub/config/tree/master/examples">examples directory on GitHub</a>.
</p>
<p>
What else to read:
<ul>
<li>The overview documentation for interface {@link com.typesafe.config.Config}.</li>
<li>The <a href="https://github.com/typesafehub/config/blob/master/README.md">README</a> for the library.</li>
<li>If you want to use <code>.conf</code> files in addition to <code>.json</code> and <code>.properties</code>,
see the <a href="https://github.com/typesafehub/config/blob/master/README.md">README</a> for some short examples
and the full <a href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON spec</a> for the long version.</li>
</ul>
</p> </p>
</body> </body>