diff --git a/src/main/java/li/cil/oc/Settings.scala b/src/main/java/li/cil/oc/Settings.scala index 4ba13789b..f42630e7b 100644 --- a/src/main/java/li/cil/oc/Settings.scala +++ b/src/main/java/li/cil/oc/Settings.scala @@ -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. diff --git a/src/main/required/com/typesafe/config/Config.java b/src/main/required/com/typesafe/config/Config.java index 42e4df0cb..f04361443 100644 --- a/src/main/required/com/typesafe/config/Config.java +++ b/src/main/required/com/typesafe/config/Config.java @@ -9,46 +9,76 @@ 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 foo.bar.baz. Values are as in JSON + * (booleans, strings, numbers, lists, or objects), represented by + * {@link ConfigValue} instances. Values accessed through the + * Config interface are never null. + * *

- * Contrast with {@link ConfigObject} which is a map from config keys, - * 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." + * *

- * 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 resolving substitutions with {@link Config#resolve()}, and + * merging configs using {@link Config#withFallback(ConfigMergeable)}. + * + *

+ * All operations return a new immutable {@code Config} rather than modifying + * the original instance. + * + *

+ * Examples + * + *

+ * You can find an example app and library on + * GitHub. Also be sure to read the package overview which + * describes the big picture as shown in those examples. + * + *

+ * Paths, keys, and Config vs. ConfigObject + * + *

+ * Config is a view onto a tree of {@link ConfigObject}; the + * corresponding object tree can be found through {@link Config#root()}. + * ConfigObject is a map from config keys, rather than + * paths, to config values. Think of ConfigObject as a JSON object + * and Config as a configuration API. + * + *

+ * 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 spec for * Human-Optimized Config Object Notation. In brief, a path is * 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 * path expressions. - * + * *

* 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} * is a one-level map from paths to values, while a * {@code ConfigObject} is a tree of nested maps from keys to values. - * + * *

* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * between path expressions and individual path elements (keys). - * + * *

* Another difference between {@code Config} and {@code ConfigObject} is that * conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType() * valueType()} of {@link ConfigValueType#NULL NULL} exist in a * {@code ConfigObject}, while a {@code Config} treats null values as if they * were missing. - * + * *

- * {@code Config} is an immutable object and thus safe to use from multiple - * threads. There's never a need for "defensive copies." - * + * Getting configuration values + * *

* The "getters" on a {@code Config} all work in the same way. They never return * 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 * a type and the value has an incompatible type. Reasonable type conversions * are performed for you though. - * + * + *

+ * Iteration + * *

* 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 java.util.Map). Or, you * can use {@link #entrySet()} which recurses the object tree for you and builds * up a Set of all path-value pairs where the value is not null. - * - *

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). - * - *

You can find an example app and library on - * GitHub. Also be sure to read the package - * overview which describes the big picture as shown in those - * examples. - * + * + *

+ * Resolving substitutions + * + *

+ * Substitutions are the ${foo.bar} syntax in config + * files, described in the specification. Resolving substitutions replaces these references with real + * values. + * + *

+ * 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). + * + *

+ * Merging + * + *

+ * The full Config 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 application.conf over the top of + * reference.conf, using withFallback. You can add in + * additional sources of configuration in the same way (usually, custom layers + * should go either just above or just below application.conf, + * keeping reference.conf at the bottom and system properties at + * the top). + * + *

+ * Serialization + * + *

+ * Convert a Config to a JSON or HOCON string by calling + * {@link ConfigObject#render()} on the root object, + * myConfig.root().render(). 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 Config 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. + * + *

+ * As an alternative to {@link ConfigObject#render()}, the + * toString() method produces a debug-output-oriented + * representation (which is not valid JSON). + * + *

+ * Java serialization is supported as well for Config and all + * subtypes of ConfigValue. + * + *

+ * This is an interface but don't implement it yourself + * *

* Do not implement {@code Config}; it should only be implemented by * the config library. Arbitrary implementations will not work because the @@ -114,19 +190,20 @@ public interface Config extends ConfigMergeable { * Config as the root object, that is, a substitution * ${foo.bar} will be replaced with the result of * getValue("foo.bar"). - * + * *

* This method uses {@link ConfigResolveOptions#defaults()}, there is * another variant {@link Config#resolve(ConfigResolveOptions)} which lets * you specify non-default options. - * + * *

* A given {@link Config} must be resolved before using it to retrieve * 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. + * *

* resolve() should be invoked on root config objects, rather * 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 * config.getConfig("foo").resolve() on the below config file, * it would not work: - * + * *

      *   common-value = 10
      *   foo {
      *      whatever = ${common-value}
      *   }
      * 
- * + * *

- * 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 * Config on the loaded stack of config files. - * - *

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. - * + * + *

+ * 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 * if any substitutions refer to nonexistent paths @@ -168,10 +245,67 @@ public interface Config extends ConfigMergeable { * * @param options * resolve options - * @return the resolved Config + * @return the resolved Config (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 + * allowUnresolved 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. + *

+ * 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 Config (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. * - * @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 @@ -548,8 +682,8 @@ 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 diff --git a/src/main/required/com/typesafe/config/ConfigFactory.java b/src/main/required/com/typesafe/config/ConfigFactory.java index f818a2c44..ce8ab19e6 100644 --- a/src/main/required/com/typesafe/config/ConfigFactory.java +++ b/src/main/required/com/typesafe/config/ConfigFactory.java @@ -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); diff --git a/src/main/required/com/typesafe/config/ConfigMergeable.java b/src/main/required/com/typesafe/config/ConfigMergeable.java index fc8b6f9ff..7af36f7bf 100644 --- a/src/main/required/com/typesafe/config/ConfigMergeable.java +++ b/src/main/required/com/typesafe/config/ConfigMergeable.java @@ -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. * - *

The semantics of merging are described in the + * This associative operation may be used to combine configurations from + * multiple sources (such as multiple configuration files). + * + *

+ * The semantics of merging are described in the spec - * for HOCON. 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. Merging typically occurs when either the same object is + * created twice in the same file, or two config files are both loaded. For + * example: + * *

      *  foo = { a: 42 }
      *  foo = { b: 43 }
      * 
+ * * Here, the two objects are merged as if you had written: + * *
      *  foo = { a: 42, b: 43 }
      * 
- * + * *

- * 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 * object.withFallback(nonObject).withFallback(otherObject), * then otherObject 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: + * *

      * foo = { a: 42 }
      * foo = 10
      * 
+ * * Here, the number 10 "wins" and the value of foo 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) */ diff --git a/src/main/required/com/typesafe/config/ConfigObject.java b/src/main/required/com/typesafe/config/ConfigObject.java index 54f6bd813..76414edc9 100644 --- a/src/main/required/com/typesafe/config/ConfigObject.java +++ b/src/main/required/com/typesafe/config/ConfigObject.java @@ -6,53 +6,58 @@ package com.typesafe.config; import java.util.Map; /** - * Subtype of {@link ConfigValue} representing an object (dictionary, map) - * value, as in JSON's { "a" : 42 } syntax. - * + * Subtype of {@link ConfigValue} representing an object (AKA dictionary or map) + * value, as in JSON's curly brace { "a" : 42 } syntax. + * + *

+ * An object may also be viewed as a {@link Config} by calling + * {@link ConfigObject#toConfig()}. + * *

* {@code ConfigObject} implements {@code java.util.Map} so * you can use it like a regular Java map. Or call {@link #unwrapped()} to * unwrap the map to a map with plain Java values rather than * {@code ConfigValue}. - * + * *

* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable. * This makes it threadsafe and you never have to create "defensive copies." The * mutator methods from {@link java.util.Map} all throw * {@link java.lang.UnsupportedOperationException}. - * + * *

* The {@link ConfigValue#valueType} method on an object returns * {@link ConfigValueType#OBJECT}. - * + * *

* In most cases you want to use the {@link Config} interface rather than this * one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a * {@code Config}. - * + * *

* The API for a {@code ConfigObject} is in terms of keys, while the API for a * {@link Config} is in terms of path expressions. Conceptually, * {@code ConfigObject} is a tree of maps from keys to values, while a * {@code Config} is a one-level map from paths to values. - * + * *

* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * between path expressions and individual path elements (keys). - * + * *

* 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. + * *

- * Do not implement {@code ConfigObject}; 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 + * Do not implement interface {@code ConfigObject}; 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 { @@ -82,11 +87,11 @@ public interface ConfigObject extends ConfigValue, Map { * 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 - * + * * @return the value at the key or null if none */ @Override @@ -115,7 +120,7 @@ public interface ConfigObject extends ConfigValue, Map { * 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 diff --git a/src/main/required/com/typesafe/config/ConfigResolveOptions.java b/src/main/required/com/typesafe/config/ConfigResolveOptions.java index d82a6be71..01c7ff88c 100644 --- a/src/main/required/com/typesafe/config/ConfigResolveOptions.java +++ b/src/main/required/com/typesafe/config/ConfigResolveOptions.java @@ -9,6 +9,9 @@ package com.typesafe.config; * href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON * spec. *

+ * Typically this class would be used with the method + * {@link Config#resolve(ConfigResolveOptions)}. + *

* This object is immutable, so the "setters" return a new object. *

* 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; + } } diff --git a/src/main/required/com/typesafe/config/ConfigSyntax.java b/src/main/required/com/typesafe/config/ConfigSyntax.java index 54529fad0..ed560296c 100644 --- a/src/main/required/com/typesafe/config/ConfigSyntax.java +++ b/src/main/required/com/typesafe/config/ConfigSyntax.java @@ -4,29 +4,33 @@ package com.typesafe.config; /** - * The syntax of a character stream, JSON, JSON, HOCON * aka ".conf", or Java properties. - * + * >Java properties). + * */ public enum ConfigSyntax { /** * Pedantically strict JSON format; no * comments, no unexpected commas, no duplicate keys in the same object. + * Associated with the .json file extension and + * application/json Content-Type. */ JSON, /** * The JSON-superset HOCON format. + * >HOCON format. Associated with the .conf file extension + * and application/hocon Content-Type. */ CONF, /** * Standard Java properties format. + * >Java properties format. Associated with the .properties + * file extension and text/x-java-properties Content-Type. */ PROPERTIES; } diff --git a/src/main/required/com/typesafe/config/ConfigValue.java b/src/main/required/com/typesafe/config/ConfigValue.java index 514336c74..35c40b8ba 100644 --- a/src/main/required/com/typesafe/config/ConfigValue.java +++ b/src/main/required/com/typesafe/config/ConfigValue.java @@ -6,16 +6,16 @@ package com.typesafe.config; /** * An immutable value, following the JSON type * schema. - * + * *

* Because this object is immutable, it is safe to use from multiple threads and * there's no need for "defensive copies." - * + * *

- * Do not implement {@code ConfigValue}; 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 + * Do not implement interface {@code ConfigValue}; 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 { @@ -47,36 +47,36 @@ public interface ConfigValue extends ConfigMergeable { * Renders the config value as a HOCON string. This method is primarily * intended for debugging, so it tries to add helpful comments and * whitespace. - * + * *

* 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. + * *

* This method is equivalent to * {@code render(ConfigRenderOptions.defaults())}. - * + * * @return the rendered value */ String render(); /** * Renders the config value to a string, using the provided options. - * + * *

* 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. + * *

* If the config value has been resolved and the options disable all * HOCON-specific features (such as comments), the rendering will be valid * JSON. If you enable HOCON-only features such as comments, the rendering * will not be valid JSON. - * + * * @param options * the rendering options * @return the rendered value @@ -87,9 +87,9 @@ 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. * @return a {@code Config} instance containing this value at the given @@ -98,9 +98,9 @@ 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. * @return a {@code Config} instance containing this value at the given key. diff --git a/src/main/required/com/typesafe/config/ConfigValueFactory.java b/src/main/required/com/typesafe/config/ConfigValueFactory.java index 02cb095e9..e92bb7722 100644 --- a/src/main/required/com/typesafe/config/ConfigValueFactory.java +++ b/src/main/required/com/typesafe/config/ConfigValueFactory.java @@ -17,40 +17,45 @@ 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 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 + * {@link ConfigObject} and an Iterable will become a + * {@link ConfigList}. If the Iterable is not an ordered + * collection, results could be strange, since ConfigList is + * ordered. + * *

- * 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 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". + * *

* 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 * from, or something short describing the value such as "default settings". * The originDescription is prefixed to error messages so users can tell * where problematic values are coming from. - * + * *

* Supplying the result of ConfigValue.unwrapped() to this function is * guaranteed to work and should give you back a ConfigValue that matches * the one you unwrapped. The re-wrapped ConfigValue will lose some * information that was present in the original such as its origin, but it * will have matching values. - * + * *

* 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 * should never handle the exception. Just fix your program (or report a bug * against this library). - * + * * @param object * object to convert to ConfigValue * @param originDescription @@ -62,23 +67,24 @@ 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}. + * *

- * 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 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. + * *

* See also {@link ConfigFactory#parseMap(Map,String)} which interprets the * keys in the map as path expressions. - * + * * @param values * @param originDescription * @return a new {@link ConfigObject} value @@ -89,10 +95,10 @@ 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 * @return a new {@link ConfigList} value diff --git a/src/main/required/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/required/com/typesafe/config/impl/AbstractConfigObject.java index d4dfc6ad8..394931dee 100644 --- a/src/main/required/com/typesafe/config/impl/AbstractConfigObject.java +++ b/src/main/required/com/typesafe/config/impl/AbstractConfigObject.java @@ -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." diff --git a/src/main/required/com/typesafe/config/impl/AbstractConfigValue.java b/src/main/required/com/typesafe/config/impl/AbstractConfigValue.java index 0b2cb7665..9b6b1fd91 100644 --- a/src/main/required/com/typesafe/config/impl/AbstractConfigValue.java +++ b/src/main/required/com/typesafe/config/impl/AbstractConfigValue.java @@ -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(); } diff --git a/src/main/required/com/typesafe/config/impl/ConfigConcatenation.java b/src/main/required/com/typesafe/config/impl/ConfigConcatenation.java index a38b0003b..958d82e5e 100644 --- a/src/main/required/com/typesafe/config/impl/ConfigConcatenation.java +++ b/src/main/required/com/typesafe/config/impl/ConfigConcatenation.java @@ -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); } } diff --git a/src/main/required/com/typesafe/config/impl/ConfigDelayedMerge.java b/src/main/required/com/typesafe/config/impl/ConfigDelayedMerge.java index 21f5371b5..28153331f 100644 --- a/src/main/required/com/typesafe/config/impl/ConfigDelayedMerge.java +++ b/src/main/required/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -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 stack, StringBuilder sb, int indent, String atKey, + static void render(List 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'); diff --git a/src/main/required/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/src/main/required/com/typesafe/config/impl/ConfigDelayedMergeObject.java index 7befaea9a..7e6ced525 100644 --- a/src/main/required/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/src/main/required/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -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() { diff --git a/src/main/required/com/typesafe/config/impl/ConfigNull.java b/src/main/required/com/typesafe/config/impl/ConfigNull.java index 577618108..38e3651d6 100644 --- a/src/main/required/com/typesafe/config/impl/ConfigNull.java +++ b/src/main/required/com/typesafe/config/impl/ConfigNull.java @@ -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"); } diff --git a/src/main/required/com/typesafe/config/impl/ConfigReference.java b/src/main/required/com/typesafe/config/impl/ConfigReference.java index 880c0ef12..8c024daf7 100644 --- a/src/main/required/com/typesafe/config/impl/ConfigReference.java +++ b/src/main/required/com/typesafe/config/impl/ConfigReference.java @@ -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()); } diff --git a/src/main/required/com/typesafe/config/impl/ConfigString.java b/src/main/required/com/typesafe/config/impl/ConfigString.java index 97fb8746c..ee61d76bb 100644 --- a/src/main/required/com/typesafe/config/impl/ConfigString.java +++ b/src/main/required/com/typesafe/config/impl/ConfigString.java @@ -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); diff --git a/src/main/required/com/typesafe/config/impl/Parser.java b/src/main/required/com/typesafe/config/impl/Parser.java index 94c6189ad..83da109a8 100644 --- a/src/main/required/com/typesafe/config/impl/Parser.java +++ b/src/main/required/com/typesafe/config/impl/Parser.java @@ -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); } diff --git a/src/main/required/com/typesafe/config/impl/Path.java b/src/main/required/com/typesafe/config/impl/Path.java index dec485828..c3b8f39e2 100644 --- a/src/main/required/com/typesafe/config/impl/Path.java +++ b/src/main/required/com/typesafe/config/impl/Path.java @@ -35,10 +35,14 @@ final class Path { // append all the paths in the list together into one path Path(List pathsToConcat) { - if (pathsToConcat.isEmpty()) + this(pathsToConcat.iterator()); + } + + // append all the paths in the iterator together into one path + Path(Iterator i) { + if (!i.hasNext()) throw new ConfigException.BugOrBroken("empty path"); - Iterator i = pathsToConcat.iterator(); Path firstPath = i.next(); this.first = firstPath.first; diff --git a/src/main/required/com/typesafe/config/impl/ResolveContext.java b/src/main/required/com/typesafe/config/impl/ResolveContext.java index d7fe99bdc..b73c064c0 100644 --- a/src/main/required/com/typesafe/config/impl/ResolveContext.java +++ b/src/main/required/com/typesafe/config/impl/ResolveContext.java @@ -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"); diff --git a/src/main/required/com/typesafe/config/impl/SimpleConfig.java b/src/main/required/com/typesafe/config/impl/SimpleConfig.java index d3be5cf43..277c2d047 100644 --- a/src/main/required/com/typesafe/config/impl/SimpleConfig.java +++ b/src/main/required/com/typesafe/config/impl/SimpleConfig.java @@ -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; diff --git a/src/main/required/com/typesafe/config/impl/SimpleConfigList.java b/src/main/required/com/typesafe/config/impl/SimpleConfigList.java index dcb9fa61f..5ec13f295 100644 --- a/src/main/required/com/typesafe/config/impl/SimpleConfigList.java +++ b/src/main/required/com/typesafe/config/impl/SimpleConfigList.java @@ -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'); diff --git a/src/main/required/com/typesafe/config/impl/SimpleConfigObject.java b/src/main/required/com/typesafe/config/impl/SimpleConfigObject.java index 1977bd1ba..cc3810183 100644 --- a/src/main/required/com/typesafe/config/impl/SimpleConfigObject.java +++ b/src/main/required/com/typesafe/config/impl/SimpleConfigObject.java @@ -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 diff --git a/src/main/required/com/typesafe/config/package.html b/src/main/required/com/typesafe/config/package.html index e554397b2..7d7fe7f29 100644 --- a/src/main/required/com/typesafe/config/package.html +++ b/src/main/required/com/typesafe/config/package.html @@ -15,7 +15,9 @@ for more information.

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 HOCON files; you may also +build your own configuration in code or from your own file formats.

@@ -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. +
Example application code: Java and Scala. +
Showing a couple of more special-purpose features, a more complex example: Java and Scala.

@@ -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 Config parameter and one which uses {@link com.typesafe.config.ConfigFactory#load()}. +
Example library code: Java and Scala.

-You can find an example app and library on GitHub. +Check out the full examples directory on GitHub. +

+ +

+What else to read: +