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 out = new PrintWriter(file)
out.write(config.root.render(renderSettings).lines.
// Strip extra spaces in front.
map(_.stripPrefix(" ")).
// Indent two spaces instead of four.
map(line => """^(\s*)""".r.replaceAllIn(line, m => m.group(1).replace(" ", " "))).
// Finalize the string.

View File

@ -9,19 +9,50 @@ import java.util.Set;
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>
* Contrast with {@link ConfigObject} which is a map from config <em>keys</em>,
* rather than paths, to config values. A {@code Config} contains a tree of
* {@code ConfigObject}, and {@link Config#root()} returns the tree's root
* object.
* {@code Config} is an immutable object and thus safe to use from multiple
* threads. There's never a need for "defensive copies."
*
* <p>
* Throughout the API, there is a distinction between "keys" and "paths". 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
* Fundamental operations on a {@code Config} include getting configuration
* values, <em>resolving</em> substitutions with {@link Config#resolve()}, and
* merging configs using {@link Config#withFallback(ConfigMergeable)}.
*
* <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
* 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
@ -46,8 +77,7 @@ import java.util.concurrent.TimeUnit;
* were missing.
*
* <p>
* {@code Config} is an immutable object and thus safe to use from multiple
* threads. There's never a need for "defensive copies."
* <strong>Getting configuration values</strong>
*
* <p>
* The "getters" on a {@code Config} all work in the same way. They never return
@ -61,22 +91,68 @@ import java.util.concurrent.TimeUnit;
* are performed for you though.
*
* <p>
* <strong>Iteration</strong>
*
* <p>
* If you want to iterate over the contents of a {@code Config}, you can get its
* {@code ConfigObject} with {@link #root()}, and then iterate over the
* {@code ConfigObject} (which implements <code>java.util.Map</code>). Or, you
* can use {@link #entrySet()} which recurses the object tree for you and builds
* up a <code>Set</code> of all path-value pairs where the value is not null.
*
* <p>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>Resolving substitutions</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>
* <em>Substitutions</em> are the <code>${foo.bar}</code> syntax in config
* files, described in the <a href=
* "https://github.com/typesafehub/config/blob/master/HOCON.md#substitutions"
* >specification</a>. Resolving substitutions replaces these references with real
* values.
*
* <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>
* <em>Do not implement {@code Config}</em>; it should only be implemented by
@ -125,7 +201,8 @@ public interface Config extends ConfigMergeable {
* config values, but ideally should be resolved one time for your entire
* stack of fallbacks (see {@link Config#withFallback}). Otherwise, some
* 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>
* <code>resolve()</code> should be invoked on root config objects, rather
@ -145,14 +222,14 @@ public interface Config extends ConfigMergeable {
* </pre>
*
* <p>
* Many methods on {@link ConfigFactory} such as {@link
* ConfigFactory#load()} automatically resolve the loaded
* Many methods on {@link ConfigFactory} such as
* {@link ConfigFactory#load()} automatically resolve the loaded
* <code>Config</code> on the loaded stack of config files.
*
* <p> Resolving an already-resolved config is a harmless
* no-op, but again, it is best to resolve an entire stack of
* fallbacks (such as all your config files combined) rather
* than resolving each one individually.
* <p>
* Resolving an already-resolved config is a harmless no-op, but again, it
* is best to resolve an entire stack of fallbacks (such as all your config
* files combined) rather than resolving each one individually.
*
* @return an immutable object with substitutions resolved
* @throws ConfigException.UnresolvedSubstitution
@ -168,10 +245,67 @@ public interface Config extends ConfigMergeable {
*
* @param 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);
/**
* 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
* 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
* spec</a>.
*
* @since 1.1
* @since 1.2.0
*
* @param path
* path expression
@ -498,7 +632,7 @@ public interface Config extends ConfigMergeable {
* @throws ConfigException.BadValue
* 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
@ -549,7 +683,7 @@ public interface Config extends ConfigMergeable {
* Gets a list, converting each value in the list to a duration, using the
* same rules as {@link #getDuration(String, TimeUnit)}.
*
* @since 1.1
* @since 1.2.0
* @param path
* a path expression
* @param unit

View File

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

View File

@ -19,43 +19,52 @@ package com.typesafe.config;
public interface ConfigMergeable {
/**
* Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. 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.
* keys in this value "winning" over the other one.
*
* <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
* for HOCON</a>. Merging typically occurs when either the
* same object is created twice in the same file, or two
* config files are both loaded. For example:
* for HOCON</a>. Merging typically occurs when either the same object is
* created twice in the same file, or two config files are both loaded. For
* example:
*
* <pre>
* foo = { a: 42 }
* foo = { b: 43 }
* </pre>
*
* Here, the two objects are merged as if you had written:
*
* <pre>
* foo = { a: 42, b: 43 }
* </pre>
*
* <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>,
* then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works, because non-objects such as
* strings and integers replace (rather than merging with) any
* prior value:
* strings and integers replace (rather than merging with) any prior value:
*
* <pre>
* foo = { a: 42 }
* foo = 10
* </pre>
*
* Here, the number 10 "wins" and the value of <code>foo</code> would be
* simply 10. Again, for details see the spec.
*
* @param other
* an object whose keys should be used if the keys are not
* present in this one
* an object whose keys should be used as fallbacks, if the keys
* are not present in this one
* @return a new object (or the original one, if the fallback doesn't get
* used)
*/

View File

@ -6,8 +6,12 @@ package com.typesafe.config;
import java.util.Map;
/**
* Subtype of {@link ConfigValue} representing an object (dictionary, map)
* value, as in JSON's <code>{ "a" : 42 }</code> syntax.
* Subtype of {@link ConfigValue} representing an object (AKA dictionary or map)
* 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>
* {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so
@ -43,16 +47,17 @@ import java.util.Map;
* <p>
* A {@code ConfigObject} may contain null values, which will have
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
* {@code get()} returns Java's null then the key was not present in the parsed
* file (or wherever this value tree came from). If {@code get()} returns a
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was
* set to null explicitly in the config file.
* {@link ConfigObject#get(Object)} returns Java's null then the key was not
* present in the parsed file (or wherever this value tree came from). If
* {@code get("key")} returns a {@link ConfigValue} with type
* {@code ConfigValueType#NULL} then the key was set to null explicitly in the
* config file.
*
* <p>
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* <em>Do not implement interface {@code ConfigObject}</em>; it should only be
* implemented by the config library. Arbitrary implementations will not work
* because the library internals assume a specific concrete implementation.
* Also, this interface is likely to grow new methods over time, so third-party
* implementations will break.
*/
public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
@ -82,7 +87,7 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
* Gets a {@link ConfigValue} at the given key, or returns null if there is
* no value. The returned {@link ConfigValue} may have
* {@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
* key to look up
@ -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
* 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
* remove a value, use withoutKey().
* remove a value, use {@link ConfigObject#withoutKey(String)}.
*
* @param key
* 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>
* spec.
* <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.
* <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}:
@ -25,18 +28,21 @@ package com.typesafe.config;
*/
public final class ConfigResolveOptions {
private final boolean useSystemEnvironment;
private final boolean allowUnresolved;
private ConfigResolveOptions(boolean useSystemEnvironment) {
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) {
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
*/
public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true);
return new ConfigResolveOptions(true, false);
}
/**
@ -57,9 +63,8 @@ public final class ConfigResolveOptions {
* variables.
* @return options with requested setting for use of environment variables
*/
@SuppressWarnings("static-method")
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() {
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;
/**
* 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>
* aka ".conf", or <a href=
* "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 {
/**
* Pedantically strict <a href="http://json.org">JSON</a> format; no
* 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,
/**
* The JSON-superset <a
* 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,
/**
* Standard <a href=
* "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;
}

View File

@ -12,10 +12,10 @@ package com.typesafe.config;
* there's no need for "defensive copies."
*
* <p>
* <em>Do not implement {@code ConfigValue}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* <em>Do not implement interface {@code ConfigValue}</em>; it should only be
* implemented by the config library. Arbitrary implementations will not work
* because the library internals assume a specific concrete implementation.
* Also, this interface is likely to grow new methods over time, so third-party
* implementations will break.
*/
public interface ConfigValue extends ConfigMergeable {
@ -52,7 +52,7 @@ public interface ConfigValue extends ConfigMergeable {
* 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
* 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>
* This method is equivalent to
@ -69,7 +69,7 @@ public interface ConfigValue extends ConfigMergeable {
* 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
* 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>
* If the config value has been resolved and the options disable all
@ -87,8 +87,8 @@ public interface ConfigValue extends ConfigMergeable {
ConfigValue withFallback(ConfigMergeable other);
/**
* Places the value inside a {@code Config} at the given path. See also
* atKey().
* Places the value inside a {@link Config} at the given path. See also
* {@link ConfigValue#atKey(String)}.
*
* @param path
* path to store this value at.
@ -98,8 +98,8 @@ public interface ConfigValue extends ConfigMergeable {
Config atPath(String path);
/**
* Places the value inside a {@code Config} at the given key. See also
* atPath().
* Places the value inside a {@link Config} at the given key. See also
* {@link ConfigValue#atPath(String)}.
*
* @param key
* key to store this value at.

View File

@ -17,19 +17,24 @@ public final class ConfigValueFactory {
}
/**
* Creates a ConfigValue from a plain Java boxed value, which may be a
* Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from
* String to more values that can be supplied to fromAnyRef(). An Iterable
* must iterate over more values that can be supplied to fromAnyRef(). A Map
* will become a ConfigObject and an Iterable will become a ConfigList. If
* the Iterable is not an ordered collection, results could be strange,
* since ConfigList is ordered.
* Creates a {@link ConfigValue} from a plain Java boxed value, which may be
* a <code>Boolean</code>, <code>Number</code>, <code>String</code>,
* <code>Map</code>, <code>Iterable</code>, or <code>null</code>. A
* <code>Map</code> must be a <code>Map</code> from String to more values
* that can be supplied to <code>fromAnyRef()</code>. An
* <code>Iterable</code> must iterate over more values that can be supplied
* 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>
* In a Map passed to fromAnyRef(), the map's keys are plain keys, not path
* expressions. So if your Map has a key "foo.bar" then you will get one
* object with a key called "foo.bar", rather than an object with a key
* "foo" containing another object with a key "bar".
* In a <code>Map</code> passed to <code>fromAnyRef()</code>, the map's keys
* are plain keys, not path expressions. So if your <code>Map</code> has a
* key "foo.bar" then you will get one object with a key called "foo.bar",
* rather than an object with a key "foo" containing another object with a
* key "bar".
*
* <p>
* The originDescription will be used to set the origin() field on the
@ -62,18 +67,19 @@ public final class ConfigValueFactory {
}
/**
* See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on {@link java.util.Map} and returns
* {@link ConfigObject} rather than {@link ConfigValue}.
* See the {@link #fromAnyRef(Object,String)} documentation for details.
* This is a typesafe wrapper that only works on {@link java.util.Map} and
* returns {@link ConfigObject} rather than {@link ConfigValue}.
*
* <p>
* If your Map has a key "foo.bar" then you will get one object with a key
* called "foo.bar", rather than an object with a key "foo" containing
* another object with a key "bar". The keys in the map are keys; not path
* expressions. That is, the Map corresponds exactly to a single
* {@code ConfigObject}. The keys will not be parsed or modified, and the
* values are wrapped in ConfigValue. To get nested {@code ConfigObject},
* some of the values in the map would have to be more maps.
* If your <code>Map</code> has a key "foo.bar" then you will get one object
* with a key called "foo.bar", rather than an object with a key "foo"
* containing another object with a key "bar". The keys in the map are keys;
* not path expressions. That is, the <code>Map</code> corresponds exactly
* to a single {@code ConfigObject}. The keys will not be parsed or
* modified, and the values are wrapped in ConfigValue. To get nested
* {@code ConfigObject}, some of the values in the map would have to be more
* maps.
*
* <p>
* See also {@link ConfigFactory#parseMap(Map,String)} which interprets the
@ -89,9 +95,9 @@ public final class ConfigValueFactory {
}
/**
* See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on {@link java.lang.Iterable} and returns
* {@link ConfigList} rather than {@link ConfigValue}.
* See the {@link #fromAnyRef(Object,String)} documentation for details.
* This is a typesafe wrapper that only works on {@link java.lang.Iterable}
* and returns {@link ConfigList} rather than {@link ConfigValue}.
*
* @param values
* @param originDescription

View File

@ -218,7 +218,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi
public abstract AbstractConfigValue get(Object key);
@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) {
return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map."

View File

@ -279,7 +279,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
@Override
public final String toString() {
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() + ")";
}
@ -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) {
String renderedKey;
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();
sb.append(u.toString());
}
@ -334,7 +334,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
@Override
public final String render(ConfigRenderOptions options) {
StringBuilder sb = new StringBuilder();
render(sb, 0, null, options);
render(sb, 0, true, null, options);
return sb.toString();
}

View File

@ -232,9 +232,9 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
}
@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) {
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
protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) {
render(stack, sb, indent, atKey, options);
protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) {
render(stack, sb, indent, atRoot, atKey, options);
}
// 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) {
boolean commentMerge = options.getComments();
if (commentMerge) {
@ -268,7 +268,7 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
else
sb.append(":");
}
v.render(sb, indent, options);
v.render(sb, indent, atRoot, options);
sb.append(",");
if (options.getFormatted())
sb.append('\n');

View File

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

View File

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

View File

@ -81,9 +81,13 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
}
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 {
context.source().unreplace(this);
}
@ -127,7 +131,7 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
}
@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());
}

View File

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

View File

@ -683,7 +683,9 @@ final class Parser {
}
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);
}

View File

@ -35,10 +35,14 @@ final class Path {
// append all the paths in the list together into one path
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");
Iterator<Path> i = pathsToConcat.iterator();
Path firstPath = i.next();
this.first = firstPath.first;

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,9 @@ for more information.
<p>
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>
@ -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()}
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.
<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>
@ -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()}
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()}.
<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>
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>
</body>