diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java
index fefec7640a..a08060f52d 100644
--- a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java
+++ b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java
@@ -7,7 +7,6 @@ import org.junit.Test;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.Config;
-import com.typesafe.config.ConfigParseOptions;
import static org.junit.Assert.*;
@@ -26,14 +25,14 @@ public class JavaExtension {
}
static class TestExtension implements Extension {
- public final ActorSystemImpl system;
- public TestExtension(ActorSystemImpl i) {
- system = i;
- }
+ public final ActorSystemImpl system;
+
+ public TestExtension(ActorSystemImpl i) {
+ system = i;
+ }
}
- private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]",
- ConfigParseOptions.defaults());
+ private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]");
private ActorSystem system = ActorSystem.create("JavaExtension", c);
diff --git a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala
index 0e5602a899..fe5c7642bf 100644
--- a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala
@@ -10,7 +10,6 @@ import org.scalatest.WordSpec
import akka.event.Logging
import akka.util.Duration
import com.typesafe.config.ConfigFactory
-import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import java.util.Properties
diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala
index 53fa4ea5bf..d23bc8ce57 100644
--- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala
@@ -9,7 +9,6 @@ import akka.dispatch._
import akka.testkit.AkkaSpec
import scala.collection.JavaConverters._
import com.typesafe.config.ConfigFactory
-import com.typesafe.config.ConfigParseOptions
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class DispatchersSpec extends AkkaSpec {
@@ -41,7 +40,7 @@ class DispatchersSpec extends AkkaSpec {
throughput = 17
}
}
- """, ConfigParseOptions.defaults)
+ """)
lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = {
validTypes.map(t ⇒ (t, from(ConfigFactory.parseMap(Map(tipe -> t).asJava).withFallback(defaultDispatcherConfig)))).toMap
diff --git a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala
index b594ea0f19..80d1ca30e5 100644
--- a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala
@@ -6,13 +6,12 @@ package akka.config
import akka.testkit.AkkaSpec
import com.typesafe.config.ConfigFactory
-import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import akka.util.duration._
import akka.util.Duration
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
-class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-actor-reference.conf", ConfigParseOptions.defaults)) {
+class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) {
"The default configuration file (i.e. akka-actor-reference.conf)" must {
"contain all configuration properties for akka-actor that are used in code with their correct defaults" in {
diff --git a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala
index 14158e7454..031fb1ccb3 100644
--- a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala
@@ -7,7 +7,6 @@ import akka.testkit.AkkaSpec
import akka.util.duration._
import akka.actor.{ Actor, ActorRef, ActorSystemImpl }
import com.typesafe.config.ConfigFactory
-import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import akka.actor.ActorSystem
@@ -19,7 +18,7 @@ object EventStreamSpec {
loglevel = INFO
event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"]
}
- """.format(Logging.StandardOutLoggerName), ConfigParseOptions.defaults)
+ """.format(Logging.StandardOutLoggerName))
case class M(i: Int)
diff --git a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala
index 764afefe3c..49d3b23b42 100644
--- a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala
+++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala
@@ -222,7 +222,7 @@ class Report(
sb.append("Akka version: ").append(system.settings.ConfigVersion)
sb.append("\n")
sb.append("Akka config:")
- for ((key, value) ← system.settings.config.toObject) {
+ for ((key, value) ← system.settings.config.root) {
sb.append("\n ").append(key).append("=").append(value)
}
diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala
index 0e2f43a3d8..73afd0dc0a 100644
--- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala
@@ -11,7 +11,6 @@ import akka.actor.{ ActorSystem, ActorSystemImpl }
import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream }
import akka.actor.DeadLetterActorRef
import com.typesafe.config.ConfigFactory
-import com.typesafe.config.ConfigParseOptions
object SerializeSpec {
@@ -32,7 +31,7 @@ object SerializeSpec {
}
}
}
- """, ConfigParseOptions.defaults)
+ """)
@BeanInfo
case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") }
diff --git a/akka-actor/src/main/java/com/typesafe/config/Config.java b/akka-actor/src/main/java/com/typesafe/config/Config.java
index 3a820033a8..0d83504fe3 100644
--- a/akka-actor/src/main/java/com/typesafe/config/Config.java
+++ b/akka-actor/src/main/java/com/typesafe/config/Config.java
@@ -6,51 +6,82 @@ package com.typesafe.config;
import java.util.List;
/**
- * This class represents an immutable map from config paths to config values. It
- * also contains some static methods for creating configs.
+ * An immutable map from config paths to config values.
*
+ *
+ * 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.
+ *
+ *
* 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 spec for "HOCON", which can be
- * found at https://github.com/havocp/config/blob/master/HOCON.md; 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.
+ * 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 Config is in terms of path expressions, while the API for a
- * ConfigObject is in terms of keys. Conceptually, Config is a one-level map
- * from paths to values, while a ConfigObject is a tree of maps from keys to
- * values.
+ *
+ * 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.
*
- * Another difference between Config and ConfigObject is that conceptually,
- * ConfigValue with valueType() of ConfigValueType.NULL exist in a ConfigObject,
- * while a Config treats null values as if they were missing.
+ *
+ * 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.
*
- * Config is an immutable object and thus safe to use from multiple threads.
+ *
+ * {@code Config} is an immutable object and thus safe to use from multiple
+ * threads. There's never a need for "defensive copies."
*
- * The "getters" on a Config all work in the same way. They never return null,
- * nor do they return a ConfigValue with valueType() of ConfigValueType.NULL.
- * Instead, they throw ConfigException.Missing if the value is completely absent
- * or set to null. If the value is set to null, a subtype of
- * ConfigException.Missing called ConfigException.Null will be thrown.
- * 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.
+ *
+ * The "getters" on a {@code Config} all work in the same way. They never return
+ * null, nor do they return a {@code ConfigValue} with
+ * {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL
+ * NULL}. Instead, they throw {@link ConfigException.Missing} if the value is
+ * completely absent or set to null. If the value is set to null, a subtype of
+ * {@code ConfigException.Missing} called {@link ConfigException.Null} will be
+ * 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.
*
- * If you want to iterate over the contents of a Config, you have to get its
- * ConfigObject with toObject, and then iterate over the ConfigObject.
+ *
+ * If you want to iterate over the contents of a {@code Config}, you have to get
+ * its {@code ConfigObject} with {@link #root()}, and then iterate over the
+ * {@code ConfigObject}.
*
+ *
+ *
+ * Do not implement {@code Config}; 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 Config extends ConfigMergeable {
/**
- * Gets the config as a tree of ConfigObject. This is a constant-time
- * operation (it is not proportional to the number of values in the Config).
+ * Gets the {@code Config} as a tree of {@link ConfigObject}. This is a
+ * constant-time operation (it is not proportional to the number of values
+ * in the {@code Config}).
*
- * @return
+ * @return the root object in the configuration
*/
- ConfigObject toObject();
+ ConfigObject root();
+ /**
+ * Gets the origin of the {@code Config}, which may be a file, or a file
+ * with a line number, or just a descriptive phrase.
+ *
+ * @return the origin of the {@code Config} for use in error messages
+ */
ConfigOrigin origin();
@Override
@@ -60,15 +91,157 @@ public interface Config extends ConfigMergeable {
ConfigObject toValue();
/**
- * Checks whether a value is present and non-null at the given path. This
- * differs in two ways from ConfigObject.containsKey(): it looks for a path
- * expression, not a key; and it returns false for null values, while
- * containsKey() returns true indicating that the object contains a null
- * value for the key.
+ * Returns a replacement config with all substitutions (the
+ * ${foo.bar} syntax, see the
+ * spec) resolved. Substitutions are looked up using this
+ * Config as the root object, that is, a substitution
+ * ${foo.bar} will be replaced with the result of
+ * getValue("foo.bar").
*
- * If a path exists according to hasPath(), then getValue() will never throw
- * an exception. However, the typed getters, such as getInt(), will still
- * throw if the value is not convertible to the requested type.
+ *
+ * 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.
+ *
+ *
+ * resolve() should be invoked on root config objects, rather
+ * than on a subtree (a subtree is the result of something like
+ * config.getConfig("foo")). The problem with
+ * resolve() on a subtree is that substitutions are relative to
+ * the root of the config and the subtree will have no way to get values
+ * from the root. For example, if you did
+ * config.getConfig("foo").resolve() on the below config file,
+ * it would not work:
+ *
+ *
+ *
+ * @return an immutable object with substitutions resolved
+ * @throws ConfigException.UnresolvedSubstitution
+ * if any substitutions refer to nonexistent paths
+ * @throws ConfigException
+ * some other config exception if there are other problems
+ */
+ Config resolve();
+
+ /**
+ * Like {@link Config#resolve()} but allows you to specify non-default
+ * options.
+ *
+ * @param options
+ * resolve options
+ * @return the resolved Config
+ */
+ Config resolve(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
+ * comprehensive list of problems; in general, anything this method can find
+ * would be detected later when trying to use the config, but it's often
+ * more user-friendly to fail right away when loading the config.
+ *
+ *
+ * Using this method is always optional, since you can "fail late" instead.
+ *
+ *
+ * You must restrict validation to paths you "own" (those whose meaning are
+ * defined by your code module). If you validate globally, you may trigger
+ * errors about paths that happen to be in the config but have nothing to do
+ * with your module. It's best to allow the modules owning those paths to
+ * validate them. Also, if every module validates only its own stuff, there
+ * isn't as much redundant work being done.
+ *
+ *
+ * If no paths are specified in checkValid()'s parameter list,
+ * validation is for the entire config.
+ *
+ *
+ * If you specify paths that are not in the reference config, those paths
+ * are ignored. (There's nothing to validate.)
+ *
+ *
+ * Here's what validation involves:
+ *
+ *
+ *
All paths found in the reference config must be present in this
+ * config or an exception will be thrown.
+ *
+ * Some changes in type from the reference config to this config will cause
+ * an exception to be thrown. Not all potential type problems are detected,
+ * in particular it's assumed that strings are compatible with everything
+ * except objects and lists. This is because string types are often "really"
+ * some other type (system properties always start out as strings, or a
+ * string like "5ms" could be used with {@link #getMilliseconds}). Also,
+ * it's allowed to set any type to null or override null with any type.
+ *
+ * Any unresolved substitutions in this config will cause a validation
+ * failure; both the reference config and this config should be resolved
+ * before validation. If the reference config is unresolved, it's a bug in
+ * the caller of this method.
+ *
+ *
+ *
+ * If you want to allow a certain setting to have a flexible type (or
+ * otherwise want validation to be looser for some settings), you could
+ * either remove the problematic setting from the reference config provided
+ * to this method, or you could intercept the validation exception and
+ * screen out certain problems. Of course, this will only work if all other
+ * callers of this method are careful to restrict validation to their own
+ * paths, as they should be.
+ *
+ *
+ * If validation fails, the thrown exception contains a list of all problems
+ * found. See {@link ConfigException.ValidationFailed#problems}. The
+ * exception's getMessage() will have all the problems
+ * concatenated into one huge string, as well.
+ *
+ *
+ * Again, checkValid() can't guess every domain-specific way a
+ * setting can be invalid, so some problems may arise later when attempting
+ * to use the config. checkValid() is limited to reporting
+ * generic, but common, problems such as missing settings and blatant type
+ * incompatibilities.
+ *
+ * @param reference
+ * a reference configuration
+ * @param restrictToPaths
+ * only validate values underneath these paths that your code
+ * module owns and understands
+ * @throws ConfigException.ValidationFailed
+ * if there are any validation issues
+ * @throws ConfigException.NotResolved
+ * if this config is not resolved
+ * @throws ConfigException.BugOrBroken
+ * if the reference config is unresolved or caller otherwise
+ * misuses the API
+ */
+ void checkValid(Config reference, String... restrictToPaths);
+
+ /**
+ * Checks whether a value is present and non-null at the given path. This
+ * differs in two ways from {@code Map.containsKey()} as implemented by
+ * {@link ConfigObject}: it looks for a path expression, not a key; and it
+ * returns false for null values, while {@code containsKey()} returns true
+ * indicating that the object contains a null value for the key.
+ *
+ *
+ * If a path exists according to {@link #hasPath(String)}, then
+ * {@link #getValue(String)} will never throw an exception. However, the
+ * typed getters, such as {@link #getInt(String)}, will still throw if the
+ * value is not convertible to the requested type.
*
* @param path
* the path expression
@@ -78,12 +251,19 @@ public interface Config extends ConfigMergeable {
*/
boolean hasPath(String path);
+ /**
+ * Returns true if the {@code Config}'s root object contains no key-value
+ * pairs.
+ *
+ * @return true if the configuration is empty
+ */
boolean isEmpty();
/**
*
* @param path
- * @return
+ * path expression
+ * @return the boolean value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -93,7 +273,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
- * @return
+ * path expression
+ * @return the numeric value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -103,17 +284,20 @@ public interface Config extends ConfigMergeable {
/**
* @param path
- * @return
+ * path expression
+ * @return the 32-bit integer value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
- * if value is not convertible to an int
+ * if value is not convertible to an int (for example it is out
+ * of range, or it's a boolean value)
*/
int getInt(String path);
/**
* @param path
- * @return
+ * path expression
+ * @return the 64-bit long value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -123,7 +307,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
- * @return
+ * path expression
+ * @return the floating-point value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -133,7 +318,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
- * @return
+ * path expression
+ * @return the string value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -143,7 +329,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
- * @return
+ * path expression
+ * @return the {@link ConfigObject} value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -153,7 +340,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
- * @return
+ * path expression
+ * @return the nested {@code Config} value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -162,9 +350,13 @@ public interface Config extends ConfigMergeable {
Config getConfig(String path);
/**
- * Gets the value at the path as an unwrapped Java boxed value (Boolean,
- * Integer, Long, etc.)
+ * Gets the value at the path as an unwrapped Java boxed value (
+ * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and
+ * so on - see {@link ConfigValue#unwrapped()}).
*
+ * @param path
+ * path expression
+ * @return the unwrapped value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
*/
@@ -172,35 +364,47 @@ public interface Config extends ConfigMergeable {
/**
* Gets the value at the given path, unless the value is a null value or
- * missing, in which case it throws just like the other getters. Use get()
- * from the Map interface if you want an unprocessed value.
+ * missing, in which case it throws just like the other getters. Use
+ * {@code get()} from the {@link java.util.Map Map} interface if you want an
+ * unprocessed value.
*
* @param path
- * @return
+ * path expression
+ * @return the value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
*/
ConfigValue getValue(String path);
/**
- * Get value as a size in bytes (parses special strings like "128M"). The
- * size units are interpreted as for memory, not as for disk space, so they
- * are in powers of two.
+ * Gets a value as a size in bytes (parses special strings like "128M"). If
+ * the value is already a number, then it's left alone; if it's a string,
+ * it's parsed understanding unit suffixes such as "128K", as documented in
+ * the the
+ * spec.
*
+ * @param path
+ * path expression
+ * @return the value at the requested path, in bytes
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to Long or String
* @throws ConfigException.BadValue
- * if value cannot be parsed as a memory size
+ * if value cannot be parsed as a size in bytes
*/
- Long getMemorySizeInBytes(String path);
+ Long getBytes(String path);
/**
* Get value as a duration in milliseconds. If the value is already a
* number, then it's left alone; if it's a string, it's parsed understanding
- * units suffixes like "10m" or "5ns"
+ * units suffixes like "10m" or "5ns" as documented in the the
+ * spec.
*
+ * @param path
+ * path expression
+ * @return the duration value at the requested path, in milliseconds
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -213,8 +417,12 @@ public interface Config extends ConfigMergeable {
/**
* Get value as a duration in nanoseconds. If the value is already a number
* it's taken as milliseconds and converted to nanoseconds. If it's a
- * string, it's parsed understanding unit suffixes.
+ * string, it's parsed understanding unit suffixes, as for
+ * {@link #getMilliseconds(String)}.
*
+ * @param path
+ * path expression
+ * @return the duration value at the requested path, in nanoseconds
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@@ -225,13 +433,13 @@ public interface Config extends ConfigMergeable {
Long getNanoseconds(String path);
/**
- * Gets a list value (with any element type) as a ConfigList, which
- * implements java.util.List. Throws if the path is unset or
- * null.
+ * Gets a list value (with any element type) as a {@link ConfigList}, which
+ * implements {@code java.util.List}. Throws if the path is
+ * unset or null.
*
* @param path
* the path to the list value.
- * @return the ConfigList at the path
+ * @return the {@link ConfigList} at the path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigException.java b/akka-actor/src/main/java/com/typesafe/config/ConfigException.java
index 7763231108..3973116064 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigException.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigException.java
@@ -3,15 +3,19 @@
*/
package com.typesafe.config;
+
/**
* All exceptions thrown by the library are subclasses of ConfigException.
*/
public class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L;
+ final private ConfigOrigin origin;
+
protected ConfigException(ConfigOrigin origin, String message,
Throwable cause) {
super(origin.description() + ": " + message, cause);
+ this.origin = origin;
}
protected ConfigException(ConfigOrigin origin, String message) {
@@ -20,12 +24,26 @@ public class ConfigException extends RuntimeException {
protected ConfigException(String message, Throwable cause) {
super(message, cause);
+ this.origin = null;
}
protected ConfigException(String message) {
this(message, null);
}
+ /**
+ * Returns an "origin" (such as a filename and line number) for the
+ * exception, or null if none is available. If there's no sensible origin
+ * for a given exception, or the kind of exception doesn't meaningfully
+ * relate to a particular origin file, this returns null. Never assume this
+ * will return non-null, it can always return null.
+ *
+ * @return origin of the problem, or null if unknown/inapplicable
+ */
+ public ConfigOrigin origin() {
+ return origin;
+ }
+
/**
* Exception indicating that the type of a value does not match the type you
* requested.
@@ -163,10 +181,11 @@ public class ConfigException extends RuntimeException {
}
/**
- * Exception indicating that there's a bug in something or the runtime
- * environment is broken. This exception should never be handled; instead,
- * something should be fixed to keep the exception from occurring.
- *
+ * Exception indicating that there's a bug in something (possibly the
+ * library itself) or the runtime environment is broken. This exception
+ * should never be handled; instead, something should be fixed to keep the
+ * exception from occurring. This exception can be thrown by any method in
+ * the library.
*/
public static class BugOrBroken extends ConfigException {
private static final long serialVersionUID = 1L;
@@ -212,12 +231,29 @@ public class ConfigException extends RuntimeException {
}
}
+ /**
+ * Exception indicating that a substitution did not resolve to anything.
+ * Thrown by {@link Config#resolve}.
+ */
+ public static class UnresolvedSubstitution extends Parse {
+ private static final long serialVersionUID = 1L;
+
+ public UnresolvedSubstitution(ConfigOrigin origin, String expression, Throwable cause) {
+ super(origin, "Could not resolve substitution to a value: " + expression, cause);
+ }
+
+ public UnresolvedSubstitution(ConfigOrigin origin, String expression) {
+ this(origin, expression, null);
+ }
+ }
+
/**
* Exception indicating that you tried to use a function that requires
- * substitutions to be resolved, but substitutions have not been resolved.
- * This is always a bug in either application code or the library; it's
- * wrong to write a handler for this exception because you should be able to
- * fix the code to avoid it.
+ * substitutions to be resolved, but substitutions have not been resolved
+ * (that is, {@link Config#resolve} was not called). This is always a bug in
+ * either application code or the library; it's wrong to write a handler for
+ * this exception because you should be able to fix the code to avoid it by
+ * adding calls to {@link Config#resolve}.
*/
public static class NotResolved extends BugOrBroken {
private static final long serialVersionUID = 1L;
@@ -230,4 +266,59 @@ public class ConfigException extends RuntimeException {
this(message, null);
}
}
+
+ public static class ValidationProblem {
+
+ final private String path;
+ final private ConfigOrigin origin;
+ final private String problem;
+
+ public ValidationProblem(String path, ConfigOrigin origin, String problem) {
+ this.path = path;
+ this.origin = origin;
+ this.problem = problem;
+ }
+
+ public String path() {
+ return path;
+ }
+
+ public ConfigOrigin origin() {
+ return origin;
+ }
+
+ public String problem() {
+ return problem;
+ }
+ }
+
+ public static class ValidationFailed extends ConfigException {
+ private static final long serialVersionUID = 1L;
+
+ final private Iterable problems;
+
+ public ValidationFailed(Iterable problems) {
+ super(makeMessage(problems), null);
+ this.problems = problems;
+ }
+
+ public Iterable problems() {
+ return problems;
+ }
+
+ private static String makeMessage(Iterable problems) {
+ StringBuilder sb = new StringBuilder();
+ for (ValidationProblem p : problems) {
+ sb.append(p.origin().description());
+ sb.append(": ");
+ sb.append(p.path());
+ sb.append(": ");
+ sb.append(p.problem());
+ sb.append(", ");
+ }
+ sb.setLength(sb.length() - 2); // chop comma and space
+
+ return sb.toString();
+ }
+ }
}
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java b/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java
index b55c9abdf8..36ad5c54b4 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java
@@ -13,203 +13,430 @@ import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable;
/**
- * This class contains static methods for creating Config objects.
+ * Contains static methods for creating {@link Config} instances.
*
+ *
+ * See also {@link ConfigValueFactory} which contains static methods for
+ * converting Java values into a {@link ConfigObject}. You can then convert a
+ * {@code ConfigObject} into a {@code Config} with {@link ConfigObject#toConfig}.
+ *
+ *
* The static methods with "load" in the name do some sort of higher-level
* operation potentially parsing multiple resources and resolving substitutions,
- * while the ones with "parse" in the name just create a ConfigValue from a
- * resource and nothing else.
+ * while the ones with "parse" in the name just create a {@link ConfigValue}
+ * from a resource and nothing else.
*/
public final class ConfigFactory {
+ private ConfigFactory() {
+ }
+
/**
- * Loads a configuration for the given root path in a "standard" way.
- * Oversimplified, if your root path is foo.bar then this will load files
- * from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties,
- * foo-bar-reference.conf, foo-bar-reference.json,
- * foo-bar-reference.properties. It will override all those files with any
- * system properties that begin with "foo.bar.", as well.
- *
- * The root path should be a path expression, usually just a single short
- * word, that scopes the package being configured; typically it's the
- * package name or something similar. System properties overriding values in
- * the configuration will have to be prefixed with the root path. The root
- * path may have periods in it if you like but other punctuation or
- * whitespace will probably cause you headaches. Example root paths: "akka",
- * "sbt", "jsoup", "heroku", "mongo", etc.
+ * Loads an application's configuration from the given classpath resource or
+ * classpath resource basename, sandwiches it between default reference
+ * config and default overrides, and then resolves it. The classpath
+ * resource is "raw" (it should have no "/" prefix, and is not made relative
+ * to any package, so it's like {@link ClassLoader#getResource} not
+ * {@link Class#getResource}).
*
+ *
* The loaded object will already be resolved (substitutions have already
* been processed). As a result, if you add more fallbacks then they won't
* be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
* you want to parse additional files or something then you need to use
- * loadWithoutResolving().
+ * {@link #load(Config)}.
*
- * @param rootPath
- * the configuration "domain"
- * @return configuration object for the requested root path
+ * @param resourceBasename
+ * name (optionally without extension) of a resource on classpath
+ * @return configuration for an application
*/
- public static ConfigRoot load(String rootPath) {
- return loadWithoutResolving(rootPath).resolve();
- }
-
- public static ConfigRoot load(String rootPath,
- ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
- return loadWithoutResolving(rootPath, parseOptions).resolve(
- resolveOptions);
+ public static Config load(String resourceBasename) {
+ return load(resourceBasename, ConfigParseOptions.defaults(),
+ ConfigResolveOptions.defaults());
}
/**
- * Like load() but does not resolve the object, so you can go ahead and add
- * more fallbacks and stuff and have them seen by substitutions when you do
- * call {@link ConfigRoot.resolve()}.
+ * Like {@link #load(String)} but allows you to specify parse and resolve
+ * options.
*
- * @param rootPath
- * @return
+ *
+ * To be aware of: using
+ * {@link ConfigResolveOptions#setUseSystemProperties(boolean)
+ * setUseSystemProperties(false)} with this method has no meaningful effect,
+ * because the system properties are merged into the config as overrides
+ * anyway. setUseSystemProperties affects whether to fall back
+ * to system properties when they are not found in the config, but with
+ * load(), they will be in the config.
+ *
+ * @param resourceBasename
+ * the classpath resource name with optional extension
+ * @param parseOptions
+ * options to use when parsing the resource
+ * @param resolveOptions
+ * options to use when resolving the stack
+ * @return configuration for an application
*/
- public static ConfigRoot loadWithoutResolving(String rootPath) {
- return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
+ public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
+ ConfigResolveOptions resolveOptions) {
+ Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ + resourceBasename, parseOptions);
+ return load(appConfig, resolveOptions);
}
- public static ConfigRoot loadWithoutResolving(String rootPath,
- ConfigParseOptions options) {
- ConfigRoot system = systemPropertiesRoot(rootPath);
-
- Config mainFiles = parseResourcesForPath(rootPath, options);
- Config referenceFiles = parseResourcesForPath(rootPath + ".reference",
- options);
-
- return system.withFallback(mainFiles).withFallback(referenceFiles);
+ /**
+ * Assembles a standard configuration using a custom Config
+ * object rather than loading "application.conf". The Config
+ * object will be sandwiched between the default reference config and
+ * default overrides and then resolved.
+ *
+ * @param config
+ * the application's portion of the configuration
+ * @return resolved configuration with overrides and fallbacks added
+ */
+ public static Config load(Config config) {
+ return load(config, ConfigResolveOptions.defaults());
}
- public static ConfigRoot emptyRoot(String rootPath) {
- return emptyRoot(rootPath, null);
+ /**
+ * Like {@link #load(Config)} but allows you to specify
+ * {@link ConfigResolveOptions}.
+ *
+ *
+ * To be aware of: using
+ * {@link ConfigResolveOptions#setUseSystemProperties(boolean)
+ * setUseSystemProperties(false)} with this method has no meaningful effect,
+ * because the system properties are merged into the config as overrides
+ * anyway. setUseSystemProperties affects whether to fall back
+ * to system properties when they are not found in the config, but with
+ * load(), they will be in the config.
+ *
+ * @param config
+ * the application's portion of the configuration
+ * @param resolveOptions
+ * options for resolving the assembled config stack
+ * @return resolved configuration with overrides and fallbacks added
+ */
+ public static Config load(Config config, ConfigResolveOptions resolveOptions) {
+ return defaultOverrides().withFallback(config).withFallback(defaultReference())
+ .resolve(resolveOptions);
}
+ private static class DefaultConfigHolder {
+ static final Config defaultConfig = load("application");
+ }
+
+ /**
+ * Loads a default configuration, equivalent to {@link #load(String)
+ * load("application")}. This configuration should be used by libraries and
+ * frameworks unless an application provides a different one.
+ *
+ * This method may return a cached singleton.
+ *
+ * @return configuration for an application
+ */
+ public static Config load() {
+ return DefaultConfigHolder.defaultConfig;
+ }
+
+ /**
+ * Obtains the default reference configuration, which is currently created
+ * by merging all resources "reference.conf" found on the classpath and
+ * overriding the result with system properties. The returned reference
+ * configuration will already have substitutions resolved.
+ *
+ *
+ * Libraries and frameworks should ship with a "reference.conf" in their
+ * jar.
+ *
+ *
+ * The {@link #load()} methods merge this configuration for you
+ * automatically.
+ *
+ *
+ * Future versions may look for reference configuration in more places. It
+ * is not guaranteed that this method only looks at
+ * "reference.conf".
+ *
+ * @return the default reference config
+ */
+ public static Config defaultReference() {
+ return ConfigImpl.defaultReference();
+ }
+
+ /**
+ * Obtains the default override configuration, which currently consists of
+ * system properties. The returned override configuration will already have
+ * substitutions resolved.
+ *
+ *
+ * The {@link #load()} methods merge this configuration for you
+ * automatically.
+ *
+ *
+ * Future versions may get overrides in more places. It is not guaranteed
+ * that this method only uses system properties.
+ *
+ * @return the default override configuration
+ */
+ public static Config defaultOverrides() {
+ return systemProperties();
+ }
+
+ /**
+ * Gets an empty configuration. See also {@link #empty(String)} to create an
+ * empty configuration with a description, which may improve user-visible
+ * error messages.
+ *
+ * @return an empty configuration
+ */
public static Config empty() {
return empty(null);
}
- public static ConfigRoot emptyRoot(String rootPath, String originDescription) {
- return ConfigImpl.emptyRoot(rootPath, originDescription);
- }
-
+ /**
+ * Gets an empty configuration with a description to be used to create a
+ * {@link ConfigOrigin} for this Config. The description should
+ * be very short and say what the configuration is, like "default settings"
+ * or "foo settings" or something. (Presumably you will merge some actual
+ * settings into this empty config using {@link Config#withFallback}, making
+ * the description more useful.)
+ *
+ * @param originDescription
+ * description of the config
+ * @return an empty configuration
+ */
public static Config empty(String originDescription) {
return ConfigImpl.emptyConfig(originDescription);
}
- public static ConfigRoot systemPropertiesRoot(String rootPath) {
- return ConfigImpl.systemPropertiesRoot(rootPath);
- }
-
+ /**
+ * Gets a Config containing the system properties from
+ * {@link java.lang.System#getProperties()}, parsed and converted as with
+ * {@link #parseProperties}. This method can return a global immutable
+ * singleton, so it's preferred over parsing system properties yourself.
+ *
+ *
+ * {@link #load} will include the system properties as overrides already, as
+ * will {@link #defaultReference} and {@link #defaultOverrides}.
+ *
+ *
+ * Because this returns a singleton, it will not notice changes to system
+ * properties made after the first time this method is called.
+ *
+ * @return system properties parsed into a Config
+ */
public static Config systemProperties() {
return ConfigImpl.systemPropertiesAsConfig();
}
+ /**
+ * Gets a Config containing the system's environment variables.
+ * This method can return a global immutable singleton.
+ *
+ *
+ * Environment variables are used as fallbacks when resolving substitutions
+ * whether or not this object is included in the config being resolved, so
+ * you probably don't need to use this method for most purposes. It can be a
+ * nicer API for accessing environment variables than raw
+ * {@link java.lang.System#getenv(String)} though, since you can use methods
+ * such as {@link Config#getInt}.
+ *
+ * @return system environment variables parsed into a Config
+ */
public static Config systemEnvironment() {
return ConfigImpl.envVariablesAsConfig();
}
/**
- * Converts a Java Properties object to a ConfigObject using the rules
- * documented in https://github.com/havocp/config/blob/master/HOCON.md The
- * keys in the Properties object are split on the period character '.' and
- * treated as paths. The values will all end up as string values. If you
- * have both "a=foo" and "a.b=bar" in your properties file, so "a" is both
- * the object containing "b" and the string "foo", then the string value is
- * dropped.
+ * Converts a Java {@link java.util.Properties} object to a
+ * {@link ConfigObject} using the rules documented in the HOCON
+ * spec. The keys in the Properties object are split on the
+ * period character '.' and treated as paths. The values will all end up as
+ * string values. If you have both "a=foo" and "a.b=bar" in your properties
+ * file, so "a" is both the object containing "b" and the string "foo", then
+ * the string value is dropped.
*
- * If you want to get System.getProperties() as a ConfigObject, it's better
- * to use the systemProperties() or systemPropertiesRoot() methods. Those
- * methods are able to use a cached global singleton ConfigObject for the
- * system properties.
+ *
+ * If you want to have System.getProperties() as a
+ * ConfigObject, it's better to use the {@link #systemProperties()} method
+ * which returns a cached global singleton.
*
* @param properties
* a Java Properties object
* @param options
- * @return
+ * @return the parsed configuration
*/
public static Config parseProperties(Properties properties,
ConfigParseOptions options) {
return Parseable.newProperties(properties, options).parse().toConfig();
}
+ public static Config parseProperties(Properties properties) {
+ return parseProperties(properties, ConfigParseOptions.defaults());
+ }
+
public static Config parseReader(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse().toConfig();
}
+ public static Config parseReader(Reader reader) {
+ return parseReader(reader, ConfigParseOptions.defaults());
+ }
+
public static Config parseURL(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse().toConfig();
}
+ public static Config parseURL(URL url) {
+ return parseURL(url, ConfigParseOptions.defaults());
+ }
+
public static Config parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig();
}
+ public static Config parseFile(File file) {
+ return parseFile(file, ConfigParseOptions.defaults());
+ }
+
/**
- * Parses a file. If the fileBasename already ends in a known extension,
- * just parses it according to that extension. If the fileBasename does not
- * end in an extension, then parse all known extensions and merge whatever
- * is found. If options force a specific syntax, only parse files with an
- * extension matching that syntax. If options.getAllowMissing() is true,
- * then no files have to exist; if false, then at least one file has to
- * exist.
+ * Parses a file with a flexible extension. If the fileBasename
+ * already ends in a known extension, this method parses it according to
+ * that extension (the file's syntax must match its extension). If the
+ * fileBasename does not end in an extension, it parses files
+ * with all known extensions and merges whatever is found.
+ *
+ *
+ * In the current implementation, the extension ".conf" forces
+ * {@link ConfigSyntax#CONF}, ".json" forces {@link ConfigSyntax#JSON}, and
+ * ".properties" forces {@link ConfigSyntax#PROPERTIES}. When merging files,
+ * ".conf" falls back to ".json" falls back to ".properties".
+ *
+ *
+ * Future versions of the implementation may add additional syntaxes or
+ * additional extensions. However, the ordering (fallback priority) of the
+ * three current extensions will remain the same.
+ *
+ *
+ * If options forces a specific syntax, this method only parses
+ * files with an extension matching that syntax.
+ *
+ *
+ * If {@link ConfigParseOptions#getAllowMissing options.getAllowMissing()}
+ * is true, then no files have to exist; if false, then at least one file
+ * has to exist.
*
* @param fileBasename
+ * a filename with or without extension
* @param options
- * @return
+ * parse options
+ * @return the parsed configuration
*/
public static Config parseFileAnySyntax(File fileBasename,
ConfigParseOptions options) {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
}
- public static Config parseResource(Class> klass, String resource,
- ConfigParseOptions options) {
- return Parseable.newResource(klass, resource, options).parse()
- .toConfig();
+ public static Config parseFileAnySyntax(File fileBasename) {
+ return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
}
/**
- * Same behavior as parseFileAnySyntax() but for classpath resources
- * instead.
+ * Parses all resources on the classpath with the given name and merges them
+ * into a single Config.
+ *
+ *
+ * If the resource name does not begin with a "/", it will have the supplied
+ * class's package added to it, in the same way as
+ * {@link java.lang.Class#getResource}.
+ *
+ *
+ * Duplicate resources with the same name are merged such that ones returned
+ * earlier from {@link ClassLoader#getResources} fall back to (have higher
+ * priority than) the ones returned later. This implies that resources
+ * earlier in the classpath override those later in the classpath when they
+ * configure the same setting. However, in practice real applications may
+ * not be consistent about classpath ordering, so be careful. It may be best
+ * to avoid assuming too much.
*
* @param klass
- * @param resourceBasename
+ * klass.getClassLoader() will be used to load
+ * resources, and non-absolute resource names will have this
+ * class's package added
+ * @param resource
+ * resource to look up, relative to klass's package
+ * or absolute starting with a "/"
* @param options
- * @return
+ * parse options
+ * @return the parsed configuration
*/
- public static Config parseResourceAnySyntax(Class> klass, String resourceBasename,
+ public static Config parseResources(Class> klass, String resource,
ConfigParseOptions options) {
- return ConfigImpl.parseResourceAnySyntax(klass, resourceBasename,
+ return Parseable.newResources(klass, resource, options).parse()
+ .toConfig();
+ }
+
+ public static Config parseResources(Class> klass, String resource) {
+ return parseResources(klass, resource, ConfigParseOptions.defaults());
+ }
+
+ /**
+ * Parses classpath resources with a flexible extension. In general, this
+ * method has the same behavior as
+ * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath
+ * resources instead, as in {@link #parseResources}.
+ *
+ *
+ * There is a thorny problem with this method, which is that
+ * {@link java.lang.ClassLoader#getResources} must be called separately for
+ * each possible extension. The implementation ends up with separate lists
+ * of resources called "basename.conf" and "basename.json" for example. As a
+ * result, the ideal ordering between two files with different extensions is
+ * unknown; there is no way to figure out how to merge the two lists in
+ * classpath order. To keep it simple, the lists are simply concatenated,
+ * with the same syntax priorities as
+ * {@link #parseFileAnySyntax(File,ConfigParseOptions) parseFileAnySyntax()}
+ * - all ".conf" resources are ahead of all ".json" resources which are
+ * ahead of all ".properties" resources.
+ *
+ * @param klass
+ * class which determines the ClassLoader and the
+ * package for relative resource names
+ * @param resourceBasename
+ * a resource name as in {@link java.lang.Class#getResource},
+ * with or without extension
+ * @param options
+ * parse options
+ * @return the parsed configuration
+ */
+ public static Config parseResourcesAnySyntax(Class> klass, String resourceBasename,
+ ConfigParseOptions options) {
+ return ConfigImpl.parseResourcesAnySyntax(klass, resourceBasename,
options).toConfig();
}
+ public static Config parseResourcesAnySyntax(Class> klass, String resourceBasename) {
+ return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
+ }
+
public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig();
}
- /**
- * Parses classpath resources corresponding to this path expression.
- * Essentially if the path is "foo.bar" then the resources are
- * "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than
- * one of those exists, they are merged.
- *
- * @param path
- * @param options
- * @return
- */
- public static Config parseResourcesForPath(String rootPath,
- ConfigParseOptions options) {
- // null originDescription is allowed in parseResourcesForPath
- return ConfigImpl.parseResourcesForPath(rootPath, options).toConfig();
+ public static Config parseString(String s) {
+ return parseString(s, ConfigParseOptions.defaults());
}
/**
- * Similar to ConfigValueFactory.fromMap(), but the keys in the map are path
- * expressions, rather than keys; and correspondingly it returns a Config
- * instead of a ConfigObject. This is more convenient if you are writing
- * literal maps in code, and less convenient if you are getting your maps
- * from some data source such as a parser.
+ * Creates a {@code Config} based on a {@link java.util.Map} from paths to
+ * plain Java values. Similar to
+ * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the
+ * map are path expressions, rather than keys; and correspondingly it
+ * returns a {@code Config} instead of a {@code ConfigObject}. This is more
+ * convenient if you are writing literal maps in code, and less convenient
+ * if you are getting your maps from some data source such as a parser.
*
+ *
* An exception will be thrown (and it is a bug in the caller of the method)
* if a path is both an object and a value, for example if you had both
* "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent
@@ -221,7 +448,7 @@ public final class ConfigFactory {
* description of what this map represents, like a filename, or
* "default settings" (origin description is used in error
* messages)
- * @return
+ * @return the map converted to a {@code Config}
*/
public static Config parseMap(Map values,
String originDescription) {
@@ -229,11 +456,11 @@ public final class ConfigFactory {
}
/**
- * See the other overload of parseMap() for details, this one just uses a
- * default origin description.
+ * See the other overload of {@link #parseMap(Map, String)} for details,
+ * this one just uses a default origin description.
*
* @param values
- * @return
+ * @return the map converted to a {@code Config}
*/
public static Config parseMap(Map values) {
return parseMap(values, null);
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java b/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java
index a770250171..2be0abff34 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java
@@ -5,8 +5,9 @@ package com.typesafe.config;
/**
- * A ConfigIncludeContext is passed to a ConfigIncluder. This interface is not
- * intended for apps to implement.
+ * Context provided to a {@link ConfigIncluder}; this interface is only useful
+ * inside a {@code ConfigIncluder} implementation, and is not intended for apps
+ * to implement.
*/
public interface ConfigIncludeContext {
/**
@@ -15,7 +16,7 @@ public interface ConfigIncludeContext {
* null if it can't meaningfully create a relative name. The returned
* parseable may not exist; this function is not required to do any IO, just
* compute what the name would be.
- *
+ *
* The passed-in filename has to be a complete name (with extension), not
* just a basename. (Include statements in config files are allowed to give
* just a basename.)
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java b/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java
index 364b8c5e30..1ac6f3383d 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java
@@ -4,8 +4,9 @@
package com.typesafe.config;
/**
- * Interface you have to implement to customize "include" statements in config
- * files.
+ * Implement this interface and provide an instance to
+ * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to
+ * customize handling of {@code include} statements in config files.
*/
public interface ConfigIncluder {
/**
@@ -29,7 +30,7 @@ public interface ConfigIncluder {
* Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null.
- *
+ *
* @param context
* some info about the include context
* @param what
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigList.java b/akka-actor/src/main/java/com/typesafe/config/ConfigList.java
index 0688c29abe..2024efe744 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigList.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigList.java
@@ -6,9 +6,30 @@ package com.typesafe.config;
import java.util.List;
/**
- * A list (aka array) value corresponding to ConfigValueType.LIST or JSON's
- * "[1,2,3]" value. Implements java.util.List so you can use it
- * like a regular Java list.
+ * Subtype of {@link ConfigValue} representing a list value, as in JSON's
+ * {@code [1,2,3]} syntax.
+ *
+ *
+ * {@code ConfigList} implements {@code java.util.List} so you can
+ * use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the
+ * list elements into plain Java values.
+ *
+ *
+ * Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This
+ * makes it threadsafe and you never have to create "defensive copies." The
+ * mutator methods from {@link java.util.List} all throw
+ * {@link java.lang.UnsupportedOperationException}.
+ *
+ *
+ * The {@link ConfigValue#valueType} method on a list returns
+ * {@link ConfigValueType#LIST}.
+ *
+ *
+ * Do not implement {@code ConfigList}; 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 ConfigList extends List, ConfigValue {
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java b/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java
index deec42bec1..356f89021b 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java
@@ -4,33 +4,48 @@
package com.typesafe.config;
/**
- * This is a marker for types that can be merged as a fallback into a Config or
- * a ConfigValue. Both Config and ConfigValue are mergeable.
+ * Marker for types whose instances can be merged, that is {@link Config} and
+ * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can
+ * be combined into a single new instance using the
+ * {@link ConfigMergeable#withFallback withFallback()} method.
+ *
+ *
+ * Do not implement this interface; 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 ConfigMergeable {
/**
- * Converts the mergeable to a ConfigValue to be merged.
+ * Converts this instance to a {@link ConfigValue}. If called on a
+ * {@code ConfigValue} it returns {@code this}, if called on a
+ * {@link Config} it's equivalent to {@link Config#root()}.
*
- * @return
+ * @return this instance as a {@code ConfigValue}
*/
ConfigValue toValue();
/**
* Returns a new value computed by merging this value with another, with
- * keys in this value "winning" over the other one. Only ConfigObject and
- * 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.
- *
- * The semantics of merging are described in
- * https://github.com/havocp/config/blob/master/HOCON.md
- *
- * Note that objects do not merge "across" non-objects; if you do
+ * 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.
+ *
+ *
+ * The semantics of merging are described in the spec for
+ * HOCON.
+ *
+ *
+ * Note 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. Both non-objects, and any object
* which has fallen back to a non-object, block subsequent fallbacks.
- *
+ *
* @param other
* an object whose keys should be used if the keys are not
* present in this one
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java b/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java
index c559f9e4ee..8613840223 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java
@@ -6,54 +6,67 @@ package com.typesafe.config;
import java.util.Map;
/**
- * A ConfigObject is a read-only configuration object, which may have nested
- * child objects. Implementations of ConfigObject should be immutable (at least
- * from the perspective of anyone using this interface) and thus thread-safe.
+ * Subtype of {@link ConfigValue} representing an object (dictionary, map)
+ * value, as in JSON's { "a" : 42 } syntax.
*
- * In most cases you want to use the Config interface rather than this one. Call
- * toConfig() to convert a ConfigObject to a config.
+ *
+ * {@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}.
*
- * The API for a ConfigObject is in terms of keys, while the API for a Config is
- * in terms of path expressions. Conceptually, ConfigObject is a tree of maps
- * from keys to values, while a ConfigObject is a one-level map from paths to
- * values.
+ *
+ * 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}.
*
- * 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. A path is used to traverse nested ConfigObject by looking up each key
- * in the path. Path expressions are described in the spec for "HOCON", which
- * can be found at https://github.com/havocp/config/blob/master/HOCON.md; 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 {@link ConfigValue#valueType} method on an object returns
+ * {@link ConfigValueType#OBJECT}.
*
- * ConfigObject implements java.util.Map and all methods
- * work with keys, not path expressions.
+ *
+ * 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}.
*
- * While ConfigObject implements the standard Java Map interface, the mutator
- * methods all throw UnsupportedOperationException. This Map is immutable.
+ *
+ * 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 ConfigObject} is a one-level map from paths to values.
*
- * The Map may contain null values, which will have ConfigValue.valueType() ==
- * ConfigValueType.NULL. If get() returns Java's null then the key was not
- * present in the parsed file (or wherever this value tree came from). If get()
- * returns a ConfigValue with type ConfigValueType.NULL then the key was set to
- * null explicitly.
+ *
+ * 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.
+ *
+ *
+ * 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
+ * implementations will break.
*/
public interface ConfigObject extends ConfigValue, Map {
/**
- * Converts this object to a Config instance, enabling you to use path
- * expressions to find values in the object. This is a constant-time
+ * Converts this object to a {@link Config} instance, enabling you to use
+ * path expressions to find values in the object. This is a constant-time
* operation (it is not proportional to the size of the object).
*
- * @return
+ * @return a {@link Config} with this object as its root
*/
Config toConfig();
/**
* Recursively unwraps the object, returning a map from String to whatever
* plain Java values are unwrapped from the object's values.
+ *
+ * @return a {@link java.util.Map} containing plain Java objects
*/
@Override
Map unwrapped();
@@ -62,10 +75,15 @@ public interface ConfigObject extends ConfigValue, Map {
ConfigObject withFallback(ConfigMergeable other);
/**
- * Gets a ConfigValue at the given key, or returns null if there is no
- * value. The returned ConfigValue may have ConfigValueType.NULL or any
- * other type, and the passed-in key must be a key in this object, rather
- * than a path expression.
+ * 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.
+ *
+ * @param key
+ * key to look up
+ *
+ * @return the value at the key or null if none
*/
@Override
ConfigValue get(Object key);
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java b/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java
index 73240736e5..013d91eb9e 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java
@@ -3,10 +3,67 @@
*/
package com.typesafe.config;
+import java.net.URL;
+
+
/**
- * ConfigOrigin is used to track the origin (such as filename and line number)
- * of a ConfigValue or other object. The origin is used in error messages.
+ * Represents the origin (such as filename and line number) of a
+ * {@link ConfigValue} for use in error messages. Obtain the origin of a value
+ * with {@link ConfigValue#origin}. Exceptions may have an origin, see
+ * {@link ConfigException#origin}, but be careful because
+ * ConfigException.origin() may return null.
+ *
+ *
+ * It's best to use this interface only for debugging; its accuracy is
+ * "best effort" rather than guaranteed, and a potentially-noticeable amount of
+ * memory could probably be saved if origins were not kept around, so in the
+ * future there might be some option to discard origins.
+ *
+ *
+ * Do not implement this interface; 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 ConfigOrigin {
+ /**
+ * Returns a string describing the origin of a value or exception. This will
+ * never return null.
+ *
+ * @return string describing the origin
+ */
public String description();
+
+ /**
+ * Returns a filename describing the origin. This will return null if the
+ * origin was not a file.
+ *
+ * @return filename of the origin or null
+ */
+ public String filename();
+
+ /**
+ * Returns a URL describing the origin. This will return null if the origin
+ * has no meaningful URL.
+ *
+ * @return url of the origin or null
+ */
+ public URL url();
+
+ /**
+ * Returns a classpath resource name describing the origin. This will return
+ * null if the origin was not a classpath resource.
+ *
+ * @return resource name of the origin or null
+ */
+ public String resource();
+
+ /**
+ * Returns a line number where the value or exception originated. This will
+ * return -1 if there's no meaningful line number.
+ *
+ * @return line number or -1 if none is available
+ */
+ public int lineNumber();
}
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java b/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java
index ac0c8f3974..f3765a5479 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java
@@ -4,6 +4,22 @@
package com.typesafe.config;
+/**
+ * A set of options related to parsing.
+ *
+ *
+ * This object is immutable, so the "setters" return a new object.
+ *
+ *
+ * Here is an example of creating a custom {@code ConfigParseOptions}:
+ *
+ *
+ *
+ */
public final class ConfigParseOptions {
final ConfigSyntax syntax;
final String originDescription;
@@ -24,10 +40,11 @@ public final class ConfigParseOptions {
/**
* Set the file format. If set to null, try to guess from any available
- * filename extension; if guessing fails, assume ConfigSyntax.CONF.
- *
+ * filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}.
+ *
* @param syntax
- * @return
+ * a syntax or {@code null} for best guess
+ * @return options with the syntax set
*/
public ConfigParseOptions setSyntax(ConfigSyntax syntax) {
if (this.syntax == syntax)
@@ -45,10 +62,11 @@ public final class ConfigParseOptions {
* Set a description for the thing being parsed. In most cases this will be
* set up for you to something like the filename, but if you provide just an
* input stream you might want to improve on it. Set to null to allow the
- * library to come up with something automatically.
+ * library to come up with something automatically. This description is the
+ * basis for the {@link ConfigOrigin} of the parsed values.
*
* @param originDescription
- * @return
+ * @return options with the origin description set
*/
public ConfigParseOptions setOriginDescription(String originDescription) {
if (this.originDescription == originDescription)
@@ -79,7 +97,7 @@ public final class ConfigParseOptions {
* case.
*
* @param allowMissing
- * @return
+ * @return options with the "allow missing" flag set
*/
public ConfigParseOptions setAllowMissing(boolean allowMissing) {
if (this.allowMissing == allowMissing)
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java b/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java
index 4c19b5451a..1cc39614ca 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java
@@ -3,21 +3,40 @@
*/
package com.typesafe.config;
-import java.net.URL;
-/** An opaque handle to something that can be parsed. */
+/**
+ * An opaque handle to something that can be parsed, obtained from
+ * {@link ConfigIncludeContext}.
+ *
+ *
+ * Do not implement this interface; 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 ConfigParseable {
/**
- * Parse whatever it is.
- *
+ * Parse whatever it is. The options should come from
+ * {@link ConfigParseable#options options()} but you could tweak them if you
+ * like.
+ *
* @param options
- * parse options, should be based on the ones from options()
+ * parse options, should be based on the ones from
+ * {@link ConfigParseable#options options()}
*/
ConfigObject parse(ConfigParseOptions options);
- /** Possibly return a URL representing the resource; this may return null. */
- URL url();
+ /**
+ * Returns a {@link ConfigOrigin} describing the origin of the parseable
+ * item.
+ */
+ ConfigOrigin origin();
- /** Get the initial options, which can be modified then passed to parse(). */
+ /**
+ * Get the initial options, which can be modified then passed to parse().
+ * These options will have the right description, includer, and other
+ * parameters already set up.
+ */
ConfigParseOptions options();
}
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java b/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java
index 6f572a84e8..8ed11c4bba 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java
@@ -3,6 +3,28 @@
*/
package com.typesafe.config;
+/**
+ * A set of options related to resolving substitutions. Substitutions use the
+ * ${foo.bar} syntax and are documented in the HOCON spec.
+ *
+ *
+ * This object is immutable, so the "setters" return a new object.
+ *
+ *
+ * Here is an example of creating a custom {@code ConfigResolveOptions}:
+ *
+ *
+ * In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt
+ * {@link ConfigResolveOptions#noSystem} which avoids looking at any system
+ * properties or environment variables.
+ */
public final class ConfigResolveOptions {
private final boolean useSystemProperties;
private final boolean useSystemEnvironment;
@@ -13,26 +35,68 @@ public final class ConfigResolveOptions {
this.useSystemEnvironment = useSystemEnvironment;
}
+ /**
+ * Returns the default resolve options.
+ *
+ * @return the default resolve options
+ */
public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, true);
}
+ /**
+ * Returns resolve options that disable any reference to "system" data
+ * (system properties or environment variables).
+ *
+ * @return the resolve options with system properties and env variables
+ * disabled
+ */
public static ConfigResolveOptions noSystem() {
- return new ConfigResolveOptions(false, false);
+ return defaults().setUseSystemEnvironment(false).setUseSystemProperties(false);
}
+ /**
+ * Returns options with use of Java system properties set to the given
+ * value.
+ *
+ * @param value
+ * true to resolve substitutions falling back to Java system
+ * properties.
+ * @return options with requested setting for use of system properties
+ */
public ConfigResolveOptions setUseSystemProperties(boolean value) {
return new ConfigResolveOptions(value, useSystemEnvironment);
}
+ /**
+ * Returns options with use of environment variables set to the given value.
+ *
+ * @param value
+ * true to resolve substitutions falling back to environment
+ * variables.
+ * @return options with requested setting for use of environment variables
+ */
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(useSystemProperties, value);
}
+ /**
+ * Returns whether the options enable use of system properties. This method
+ * is mostly used by the config lib internally, not by applications.
+ *
+ * @return true if system properties should be used
+ */
public boolean getUseSystemProperties() {
return useSystemProperties;
}
+ /**
+ * Returns whether the options enable use of system environment variables.
+ * This method is mostly used by the config lib internally, not by
+ * applications.
+ *
+ * @return true if environment variables should be used
+ */
public boolean getUseSystemEnvironment() {
return useSystemEnvironment;
}
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java b/akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java
deleted file mode 100644
index d8f25e89d6..0000000000
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright (C) 2011 Typesafe Inc.
- */
-package com.typesafe.config;
-
-/**
- * A root object. The only special thing about a root object is that you can
- * resolve substitutions against it. So it can have a resolve() method that
- * doesn't require you to pass in an object to resolve against.
- */
-public interface ConfigRoot extends Config {
- /**
- * Returns a replacement root object with all substitutions (the
- * "${foo.bar}" syntax) resolved. Substitutions are looked up in this root
- * object. A configuration value tree must be resolved before you can use
- * it. This method uses ConfigResolveOptions.defaults().
- *
- * @return an immutable object with substitutions resolved
- */
- ConfigRoot resolve();
-
- ConfigRoot resolve(ConfigResolveOptions options);
-
- @Override
- ConfigRoot withFallback(ConfigMergeable fallback);
-
- /**
- * Gets the global app name that this root represents.
- *
- * @return the app's root config path
- */
- String rootPath();
-}
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java b/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java
index 4f43fe7365..58e7fc020b 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java
@@ -3,6 +3,30 @@
*/
package com.typesafe.config;
+/**
+ * The syntax of a character stream, JSON, HOCON aka
+ * ".conf", or Java properties.
+ *
+ */
public enum ConfigSyntax {
- JSON, CONF, PROPERTIES;
+ /**
+ * Pedantically strict JSON format; no
+ * comments, no unexpected commas, no duplicate keys in the same object.
+ */
+ JSON,
+ /**
+ * The JSON-superset HOCON
+ * format.
+ */
+ CONF,
+ /**
+ * Standard Java properties format.
+ */
+ PROPERTIES;
}
diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java b/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java
index 8b50f7b205..b636c6f4cd 100644
--- a/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java
+++ b/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java
@@ -4,32 +4,57 @@
package com.typesafe.config;
/**
- * Interface implemented by any configuration value. From the perspective of
- * users of this interface, the object is immutable. It is therefore safe to use
- * from multiple threads.
+ * 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
+ * implementations will break.
*/
public interface ConfigValue extends ConfigMergeable {
/**
- * The origin of the value, for debugging and error messages.
+ * The origin of the value (file, line number, etc.), for debugging and
+ * error messages.
*
* @return where the value came from
*/
ConfigOrigin origin();
/**
- * The type of the value; matches the JSON type schema.
+ * The {@link ConfigValueType} of the value; matches the JSON type schema.
*
* @return value's type
*/
ConfigValueType valueType();
/**
- * Returns the config value as a plain Java boxed value, should be a String,
- * Number, etc. matching the valueType() of the ConfigValue. If the value is
- * a ConfigObject or ConfigList, it is recursively unwrapped.
+ * Returns the value as a plain Java boxed value, that is, a {@code String},
+ * {@code Number}, {@code Boolean}, {@code Map},
+ * {@code List