Updated to latest config lib and changed how reference config files are loaded.

* Config lib 4f3a91f
* All reference files named reference.conf, all will be loaded
* Usage of ConfigFactor.load as default way
* Extensions use same config as ActorSystem.settings.config
This commit is contained in:
Patrik Nordwall 2011-11-29 11:50:22 +01:00
parent 8ab25a23ac
commit b56201ab7f
72 changed files with 2361 additions and 977 deletions

View file

@ -7,7 +7,6 @@ import org.junit.Test;
import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigFactory;
import com.typesafe.config.Config; import com.typesafe.config.Config;
import com.typesafe.config.ConfigParseOptions;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -26,14 +25,14 @@ public class JavaExtension {
} }
static class TestExtension implements Extension { static class TestExtension implements Extension {
public final ActorSystemImpl system; public final ActorSystemImpl system;
public TestExtension(ActorSystemImpl i) {
system = i; public TestExtension(ActorSystemImpl i) {
} system = i;
}
} }
private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]", private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]");
ConfigParseOptions.defaults());
private ActorSystem system = ActorSystem.create("JavaExtension", c); private ActorSystem system = ActorSystem.create("JavaExtension", c);

View file

@ -10,7 +10,6 @@ import org.scalatest.WordSpec
import akka.event.Logging import akka.event.Logging
import akka.util.Duration import akka.util.Duration
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import java.util.Properties import java.util.Properties

View file

@ -9,7 +9,6 @@ import akka.dispatch._
import akka.testkit.AkkaSpec import akka.testkit.AkkaSpec
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class DispatchersSpec extends AkkaSpec { class DispatchersSpec extends AkkaSpec {
@ -41,7 +40,7 @@ class DispatchersSpec extends AkkaSpec {
throughput = 17 throughput = 17
} }
} }
""", ConfigParseOptions.defaults) """)
lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = { lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = {
validTypes.map(t (t, from(ConfigFactory.parseMap(Map(tipe -> t).asJava).withFallback(defaultDispatcherConfig)))).toMap validTypes.map(t (t, from(ConfigFactory.parseMap(Map(tipe -> t).asJava).withFallback(defaultDispatcherConfig)))).toMap

View file

@ -6,13 +6,12 @@ package akka.config
import akka.testkit.AkkaSpec import akka.testkit.AkkaSpec
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import akka.util.duration._ import akka.util.duration._
import akka.util.Duration import akka.util.Duration
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) @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 { "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 { "contain all configuration properties for akka-actor that are used in code with their correct defaults" in {

View file

@ -7,7 +7,6 @@ import akka.testkit.AkkaSpec
import akka.util.duration._ import akka.util.duration._
import akka.actor.{ Actor, ActorRef, ActorSystemImpl } import akka.actor.{ Actor, ActorRef, ActorSystemImpl }
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import akka.actor.ActorSystem import akka.actor.ActorSystem
@ -19,7 +18,7 @@ object EventStreamSpec {
loglevel = INFO loglevel = INFO
event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"] event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"]
} }
""".format(Logging.StandardOutLoggerName), ConfigParseOptions.defaults) """.format(Logging.StandardOutLoggerName))
case class M(i: Int) case class M(i: Int)

View file

@ -222,7 +222,7 @@ class Report(
sb.append("Akka version: ").append(system.settings.ConfigVersion) sb.append("Akka version: ").append(system.settings.ConfigVersion)
sb.append("\n") sb.append("\n")
sb.append("Akka config:") sb.append("Akka config:")
for ((key, value) system.settings.config.toObject) { for ((key, value) system.settings.config.toValue) {
sb.append("\n ").append(key).append("=").append(value) sb.append("\n ").append(key).append("=").append(value)
} }

View file

@ -11,7 +11,6 @@ import akka.actor.{ ActorSystem, ActorSystemImpl }
import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream } import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream }
import akka.actor.DeadLetterActorRef import akka.actor.DeadLetterActorRef
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
object SerializeSpec { object SerializeSpec {
@ -32,7 +31,7 @@ object SerializeSpec {
} }
} }
} }
""", ConfigParseOptions.defaults) """)
@BeanInfo @BeanInfo
case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") } case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") }

View file

@ -6,51 +6,82 @@ package com.typesafe.config;
import java.util.List; import java.util.List;
/** /**
* This class represents an immutable map from config paths to config values. It * An immutable map from config paths to config values.
* also contains some static methods for creating configs.
* *
* <p>
* Contrast with {@link ConfigObject} which is a map from config <em>keys</em>,
* rather than paths, to config values. A {@code Config} contains a tree of
* {@code ConfigObject}, and {@link Config#root()} returns the tree's root
* object.
*
* <p>
* Throughout the API, there is a distinction between "keys" and "paths". A key * 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 * 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 * "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 * keys. Path expressions are described in the <a
* found at https://github.com/havocp/config/blob/master/HOCON.md; in brief, a * href="https://github.com/havocp/config/blob/master/HOCON.md">spec for
* path is period-separated so "a.b.c" looks for key c in object b in object a * Human-Optimized Config Object Notation</a>. In brief, a path is
* in the root object. Sometimes double quotes are needed around special * period-separated so "a.b.c" looks for key c in object b in object a in the
* characters in path expressions. * 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 * <p>
* ConfigObject is in terms of keys. Conceptually, Config is a one-level map * The API for a {@code Config} is in terms of path expressions, while the API
* from paths to values, while a ConfigObject is a tree of maps from keys to * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config}
* values. * is a one-level map from <em>paths</em> to values, while a
* {@code ConfigObject} is a tree of nested maps from <em>keys</em> to values.
* *
* Another difference between Config and ConfigObject is that conceptually, * <p>
* ConfigValue with valueType() of ConfigValueType.NULL exist in a ConfigObject, * Another difference between {@code Config} and {@code ConfigObject} is that
* while a Config treats null values as if they were missing. * 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. * <p>
* {@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, * <p>
* nor do they return a ConfigValue with valueType() of ConfigValueType.NULL. * The "getters" on a {@code Config} all work in the same way. They never return
* Instead, they throw ConfigException.Missing if the value is completely absent * null, nor do they return a {@code ConfigValue} with
* or set to null. If the value is set to null, a subtype of * {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL
* ConfigException.Missing called ConfigException.Null will be thrown. * NULL}. Instead, they throw {@link ConfigException.Missing} if the value is
* ConfigException.WrongType will be thrown anytime you ask for a type and the * completely absent or set to null. If the value is set to null, a subtype of
* value has an incompatible type. Reasonable type conversions are performed for * {@code ConfigException.Missing} called {@link ConfigException.Null} will be
* you though. * 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 * <p>
* 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}.
* *
*
* <p>
* <em>Do not implement {@code Config}</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface Config extends ConfigMergeable { public interface Config extends ConfigMergeable {
/** /**
* Gets the config as a tree of ConfigObject. This is a constant-time * Gets the {@code Config} as a tree of {@link ConfigObject}. This is a
* operation (it is not proportional to the number of values in the Config). * 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(); ConfigOrigin origin();
@Override @Override
@ -60,15 +91,157 @@ public interface Config extends ConfigMergeable {
ConfigObject toValue(); ConfigObject toValue();
/** /**
* Checks whether a value is present and non-null at the given path. This * Returns a replacement config with all substitutions (the
* differs in two ways from ConfigObject.containsKey(): it looks for a path * <code>${foo.bar}</code> syntax, see <a
* expression, not a key; and it returns false for null values, while * href="https://github.com/havocp/config/blob/master/HOCON.md">the
* containsKey() returns true indicating that the object contains a null * spec</a>) resolved. Substitutions are looked up using this
* value for the key. * <code>Config</code> as the root object, that is, a substitution
* <code>${foo.bar}</code> will be replaced with the result of
* <code>getValue("foo.bar")</code>.
* *
* If a path exists according to hasPath(), then getValue() will never throw * <p>
* an exception. However, the typed getters, such as getInt(), will still * This method uses {@link ConfigResolveOptions#defaults()}, there is
* throw if the value is not convertible to the requested type. * another variant {@link Config#resolve(ConfigResolveOptions)} which lets
* you specify non-default options.
*
* <p>
* 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.
*
* <p>
* <code>resolve()</code> should be invoked on root config objects, rather
* than on a subtree (a subtree is the result of something like
* <code>config.getConfig("foo")</code>). The problem with
* <code>resolve()</code> 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
* <code>config.getConfig("foo").resolve()</code> on the below config file,
* it would not work:
*
* <pre>
* common-value = 10
* foo {
* whatever = ${common-value}
* }
* </pre>
*
* @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 <code>Config</code>
*/
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.
*
* <p>
* Using this method is always optional, since you can "fail late" instead.
*
* <p>
* 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.
*
* <p>
* If no paths are specified in <code>checkValid()</code>'s parameter list,
* validation is for the entire config.
*
* <p>
* If you specify paths that are not in the reference config, those paths
* are ignored. (There's nothing to validate.)
*
* <p>
* Here's what validation involves:
*
* <ul>
* <li>All paths found in the reference config must be present in this
* config or an exception will be thrown.
* <li>
* 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.
* <li>
* 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.
* </ul>
*
* <p>
* 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.
*
* <p>
* If validation fails, the thrown exception contains a list of all problems
* found. See {@link ConfigException.ValidationFailed#problems}. The
* exception's <code>getMessage()</code> will have all the problems
* concatenated into one huge string, as well.
*
* <p>
* Again, <code>checkValid()</code> can't guess every domain-specific way a
* setting can be invalid, so some problems may arise later when attempting
* to use the config. <code>checkValid()</code> 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.
*
* <p>
* 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 * @param path
* the path expression * the path expression
@ -78,12 +251,19 @@ public interface Config extends ConfigMergeable {
*/ */
boolean hasPath(String path); 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(); boolean isEmpty();
/** /**
* *
* @param path * @param path
* @return * path expression
* @return the boolean value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -93,7 +273,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the numeric value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -103,17 +284,20 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the 32-bit integer value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @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); int getInt(String path);
/** /**
* @param path * @param path
* @return * path expression
* @return the 64-bit long value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -123,7 +307,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the floating-point value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -133,7 +318,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the string value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -143,7 +329,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the {@link ConfigObject} value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -153,7 +340,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the nested {@code Config} value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -162,9 +350,13 @@ public interface Config extends ConfigMergeable {
Config getConfig(String path); Config getConfig(String path);
/** /**
* Gets the value at the path as an unwrapped Java boxed value (Boolean, * Gets the value at the path as an unwrapped Java boxed value (
* Integer, Long, etc.) * {@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 * @throws ConfigException.Missing
* if value is absent or null * 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 * 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() * missing, in which case it throws just like the other getters. Use
* from the Map interface if you want an unprocessed value. * {@code get()} from the {@link java.util.Map Map} interface if you want an
* unprocessed value.
* *
* @param path * @param path
* @return * path expression
* @return the value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
*/ */
ConfigValue getValue(String path); ConfigValue getValue(String path);
/** /**
* Get value as a size in bytes (parses special strings like "128M"). The * Gets a value as a size in bytes (parses special strings like "128M"). If
* size units are interpreted as for memory, not as for disk space, so they * the value is already a number, then it's left alone; if it's a string,
* are in powers of two. * it's parsed understanding unit suffixes such as "128K", as documented in
* the <a href="https://github.com/havocp/config/blob/master/HOCON.md">the
* spec</a>.
* *
* @param path
* path expression
* @return the value at the requested path, in bytes
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
* if value is not convertible to Long or String * if value is not convertible to Long or String
* @throws ConfigException.BadValue * @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 * 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 * 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 <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">the
* spec</a>.
* *
* @param path
* path expression
* @return the duration value at the requested path, in milliseconds
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @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 * 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 * 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 * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -225,13 +433,13 @@ public interface Config extends ConfigMergeable {
Long getNanoseconds(String path); Long getNanoseconds(String path);
/** /**
* Gets a list value (with any element type) as a ConfigList, which * Gets a list value (with any element type) as a {@link ConfigList}, which
* implements java.util.List<ConfigValue>. Throws if the path is unset or * implements {@code java.util.List<ConfigValue>}. Throws if the path is
* null. * unset or null.
* *
* @param path * @param path
* the path to the list value. * the path to the list value.
* @return the ConfigList at the path * @return the {@link ConfigList} at the path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType

View file

@ -3,15 +3,19 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
/** /**
* All exceptions thrown by the library are subclasses of ConfigException. * All exceptions thrown by the library are subclasses of ConfigException.
*/ */
public class ConfigException extends RuntimeException { public class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
final private ConfigOrigin origin;
protected ConfigException(ConfigOrigin origin, String message, protected ConfigException(ConfigOrigin origin, String message,
Throwable cause) { Throwable cause) {
super(origin.description() + ": " + message, cause); super(origin.description() + ": " + message, cause);
this.origin = origin;
} }
protected ConfigException(ConfigOrigin origin, String message) { protected ConfigException(ConfigOrigin origin, String message) {
@ -20,12 +24,26 @@ public class ConfigException extends RuntimeException {
protected ConfigException(String message, Throwable cause) { protected ConfigException(String message, Throwable cause) {
super(message, cause); super(message, cause);
this.origin = null;
} }
protected ConfigException(String message) { protected ConfigException(String message) {
this(message, null); 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 * Exception indicating that the type of a value does not match the type you
* requested. * requested.
@ -163,10 +181,11 @@ public class ConfigException extends RuntimeException {
} }
/** /**
* Exception indicating that there's a bug in something or the runtime * Exception indicating that there's a bug in something (possibly the
* environment is broken. This exception should never be handled; instead, * library itself) or the runtime environment is broken. This exception
* something should be fixed to keep the exception from occurring. * 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 { public static class BugOrBroken extends ConfigException {
private static final long serialVersionUID = 1L; 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 * Exception indicating that you tried to use a function that requires
* substitutions to be resolved, but substitutions have not been resolved. * substitutions to be resolved, but substitutions have not been resolved
* This is always a bug in either application code or the library; it's * (that is, {@link Config#resolve} was not called). This is always a bug in
* wrong to write a handler for this exception because you should be able to * either application code or the library; it's wrong to write a handler for
* fix the code to avoid it. * 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 { public static class NotResolved extends BugOrBroken {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -230,4 +266,59 @@ public class ConfigException extends RuntimeException {
this(message, null); 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<ValidationProblem> problems;
public ValidationFailed(Iterable<ValidationProblem> problems) {
super(makeMessage(problems), null);
this.problems = problems;
}
public Iterable<ValidationProblem> problems() {
return problems;
}
private static String makeMessage(Iterable<ValidationProblem> 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();
}
}
} }

View file

@ -13,203 +13,430 @@ import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable; import com.typesafe.config.impl.Parseable;
/** /**
* This class contains static methods for creating Config objects. * Contains static methods for creating {@link Config} instances.
* *
* <p>
* 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}.
*
* <p>
* The static methods with "load" in the name do some sort of higher-level * The static methods with "load" in the name do some sort of higher-level
* operation potentially parsing multiple resources and resolving substitutions, * operation potentially parsing multiple resources and resolving substitutions,
* while the ones with "parse" in the name just create a ConfigValue from a * while the ones with "parse" in the name just create a {@link ConfigValue}
* resource and nothing else. * from a resource and nothing else.
*/ */
public final class ConfigFactory { public final class ConfigFactory {
private ConfigFactory() {
}
/** /**
* Loads a configuration for the given root path in a "standard" way. * Loads an application's configuration from the given classpath resource or
* Oversimplified, if your root path is foo.bar then this will load files * classpath resource basename, sandwiches it between default reference
* from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties, * config and default overrides, and then resolves it. The classpath
* foo-bar-reference.conf, foo-bar-reference.json, * resource is "raw" (it should have no "/" prefix, and is not made relative
* foo-bar-reference.properties. It will override all those files with any * to any package, so it's like {@link ClassLoader#getResource} not
* system properties that begin with "foo.bar.", as well. * {@link Class#getResource}).
*
* 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.
* *
* <p>
* The loaded object will already be resolved (substitutions have already * The loaded object will already be resolved (substitutions have already
* been processed). As a result, if you add more fallbacks then they won't * 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 * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
* you want to parse additional files or something then you need to use * you want to parse additional files or something then you need to use
* loadWithoutResolving(). * {@link #load(Config)}.
* *
* @param rootPath * @param resourceBasename
* the configuration "domain" * name (optionally without extension) of a resource on classpath
* @return configuration object for the requested root path * @return configuration for an application
*/ */
public static ConfigRoot load(String rootPath) { public static Config load(String resourceBasename) {
return loadWithoutResolving(rootPath).resolve(); return load(resourceBasename, ConfigParseOptions.defaults(),
} ConfigResolveOptions.defaults());
public static ConfigRoot load(String rootPath,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return loadWithoutResolving(rootPath, parseOptions).resolve(
resolveOptions);
} }
/** /**
* Like load() but does not resolve the object, so you can go ahead and add * Like {@link #load(String)} but allows you to specify parse and resolve
* more fallbacks and stuff and have them seen by substitutions when you do * options.
* call {@link ConfigRoot.resolve()}.
* *
* @param rootPath * <p>
* @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. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, 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) { public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults()); ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ resourceBasename, parseOptions);
return load(appConfig, resolveOptions);
} }
public static ConfigRoot loadWithoutResolving(String rootPath, /**
ConfigParseOptions options) { * Assembles a standard configuration using a custom <code>Config</code>
ConfigRoot system = systemPropertiesRoot(rootPath); * object rather than loading "application.conf". The <code>Config</code>
* object will be sandwiched between the default reference config and
Config mainFiles = parseResourcesForPath(rootPath, options); * default overrides and then resolved.
Config referenceFiles = parseResourcesForPath(rootPath + ".reference", *
options); * @param config
* the application's portion of the configuration
return system.withFallback(mainFiles).withFallback(referenceFiles); * @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}.
*
* <p>
* 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. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, 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.
* <p>
* 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.
*
* <p>
* Libraries and frameworks should ship with a "reference.conf" in their
* jar.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
* <p>
* Future versions may look for reference configuration in more places. It
* is not guaranteed that this method <em>only</em> 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.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
* <p>
* Future versions may get overrides in more places. It is not guaranteed
* that this method <em>only</em> 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() { public static Config empty() {
return empty(null); 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 <code>Config</code>. 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) { public static Config empty(String originDescription) {
return ConfigImpl.emptyConfig(originDescription); return ConfigImpl.emptyConfig(originDescription);
} }
public static ConfigRoot systemPropertiesRoot(String rootPath) { /**
return ConfigImpl.systemPropertiesRoot(rootPath); * Gets a <code>Config</code> 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.
*
* <p>
* {@link #load} will include the system properties as overrides already, as
* will {@link #defaultReference} and {@link #defaultOverrides}.
*
* <p>
* 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 <code>Config</code>
*/
public static Config systemProperties() { public static Config systemProperties() {
return ConfigImpl.systemPropertiesAsConfig(); return ConfigImpl.systemPropertiesAsConfig();
} }
/**
* Gets a <code>Config</code> containing the system's environment variables.
* This method can return a global immutable singleton.
*
* <p>
* 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 <code>Config</code>
*/
public static Config systemEnvironment() { public static Config systemEnvironment() {
return ConfigImpl.envVariablesAsConfig(); return ConfigImpl.envVariablesAsConfig();
} }
/** /**
* Converts a Java Properties object to a ConfigObject using the rules * Converts a Java {@link java.util.Properties} object to a
* documented in https://github.com/havocp/config/blob/master/HOCON.md The * {@link ConfigObject} using the rules documented in the <a
* keys in the Properties object are split on the period character '.' and * href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON
* treated as paths. The values will all end up as string values. If you * spec</a>. The keys in the <code>Properties</code> object are split on the
* have both "a=foo" and "a.b=bar" in your properties file, so "a" is both * period character '.' and treated as paths. The values will all end up as
* the object containing "b" and the string "foo", then the string value is * string values. If you have both "a=foo" and "a.b=bar" in your properties
* dropped. * 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 * <p>
* to use the systemProperties() or systemPropertiesRoot() methods. Those * If you want to have <code>System.getProperties()</code> as a
* methods are able to use a cached global singleton ConfigObject for the * ConfigObject, it's better to use the {@link #systemProperties()} method
* system properties. * which returns a cached global singleton.
* *
* @param properties * @param properties
* a Java Properties object * a Java Properties object
* @param options * @param options
* @return * @return the parsed configuration
*/ */
public static Config parseProperties(Properties properties, public static Config parseProperties(Properties properties,
ConfigParseOptions options) { ConfigParseOptions options) {
return Parseable.newProperties(properties, options).parse().toConfig(); 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) { public static Config parseReader(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse().toConfig(); 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) { public static Config parseURL(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse().toConfig(); 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) { public static Config parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig(); 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, * Parses a file with a flexible extension. If the <code>fileBasename</code>
* just parses it according to that extension. If the fileBasename does not * already ends in a known extension, this method parses it according to
* end in an extension, then parse all known extensions and merge whatever * that extension (the file's syntax must match its extension). If the
* is found. If options force a specific syntax, only parse files with an * <code>fileBasename</code> does not end in an extension, it parses files
* extension matching that syntax. If options.getAllowMissing() is true, * with all known extensions and merges whatever is found.
* then no files have to exist; if false, then at least one file has to *
* exist. * <p>
* 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".
*
* <p>
* 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.
*
* <p>
* If <code>options</code> forces a specific syntax, this method only parses
* files with an extension matching that syntax.
*
* <p>
* 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 * @param fileBasename
* a filename with or without extension
* @param options * @param options
* @return * parse options
* @return the parsed configuration
*/ */
public static Config parseFileAnySyntax(File fileBasename, public static Config parseFileAnySyntax(File fileBasename,
ConfigParseOptions options) { ConfigParseOptions options) {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
} }
public static Config parseResource(Class<?> klass, String resource, public static Config parseFileAnySyntax(File fileBasename) {
ConfigParseOptions options) { return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
return Parseable.newResource(klass, resource, options).parse()
.toConfig();
} }
/** /**
* Same behavior as parseFileAnySyntax() but for classpath resources * Parses all resources on the classpath with the given name and merges them
* instead. * into a single <code>Config</code>.
*
* <p>
* 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}.
*
* <p>
* 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 klass
* @param resourceBasename * <code>klass.getClassLoader()</code> 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 <code>klass</code>'s package
* or absolute starting with a "/"
* @param options * @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) { 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}.
*
* <p>
* 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 <code>ClassLoader</code> 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(); options).toConfig();
} }
public static Config parseResourcesAnySyntax(Class<?> klass, String resourceBasename) {
return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
}
public static Config parseString(String s, ConfigParseOptions options) { public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig(); return Parseable.newString(s, options).parse().toConfig();
} }
/** public static Config parseString(String s) {
* Parses classpath resources corresponding to this path expression. return parseString(s, ConfigParseOptions.defaults());
* 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();
} }
/** /**
* Similar to ConfigValueFactory.fromMap(), but the keys in the map are path * Creates a {@code Config} based on a {@link java.util.Map} from paths to
* expressions, rather than keys; and correspondingly it returns a Config * plain Java values. Similar to
* instead of a ConfigObject. This is more convenient if you are writing * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the
* literal maps in code, and less convenient if you are getting your maps * map are path expressions, rather than keys; and correspondingly it
* from some data source such as a parser. * 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.
* *
* <p>
* An exception will be thrown (and it is a bug in the caller of the method) * 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 * 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 * "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 * description of what this map represents, like a filename, or
* "default settings" (origin description is used in error * "default settings" (origin description is used in error
* messages) * messages)
* @return * @return the map converted to a {@code Config}
*/ */
public static Config parseMap(Map<String, ? extends Object> values, public static Config parseMap(Map<String, ? extends Object> values,
String originDescription) { String originDescription) {
@ -229,11 +456,11 @@ public final class ConfigFactory {
} }
/** /**
* See the other overload of parseMap() for details, this one just uses a * See the other overload of {@link #parseMap(Map, String)} for details,
* default origin description. * this one just uses a default origin description.
* *
* @param values * @param values
* @return * @return the map converted to a {@code Config}
*/ */
public static Config parseMap(Map<String, ? extends Object> values) { public static Config parseMap(Map<String, ? extends Object> values) {
return parseMap(values, null); return parseMap(values, null);

View file

@ -5,8 +5,9 @@ package com.typesafe.config;
/** /**
* A ConfigIncludeContext is passed to a ConfigIncluder. This interface is not * Context provided to a {@link ConfigIncluder}; this interface is only useful
* intended for apps to implement. * inside a {@code ConfigIncluder} implementation, and is not intended for apps
* to implement.
*/ */
public interface ConfigIncludeContext { public interface ConfigIncludeContext {
/** /**
@ -15,7 +16,7 @@ public interface ConfigIncludeContext {
* null if it can't meaningfully create a relative name. The returned * 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 * parseable may not exist; this function is not required to do any IO, just
* compute what the name would be. * compute what the name would be.
* *
* The passed-in filename has to be a complete name (with extension), not * 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. (Include statements in config files are allowed to give
* just a basename.) * just a basename.)

View file

@ -4,8 +4,9 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* Interface you have to implement to customize "include" statements in config * Implement this interface and provide an instance to
* files. * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to
* customize handling of {@code include} statements in config files.
*/ */
public interface ConfigIncluder { public interface ConfigIncluder {
/** /**
@ -29,7 +30,7 @@ public interface ConfigIncluder {
* Parses another item to be included. The returned object typically would * Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to * not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null. * abort parsing, or return an empty object, but may not return null.
* *
* @param context * @param context
* some info about the include context * some info about the include context
* @param what * @param what

View file

@ -6,9 +6,30 @@ package com.typesafe.config;
import java.util.List; import java.util.List;
/** /**
* A list (aka array) value corresponding to ConfigValueType.LIST or JSON's * Subtype of {@link ConfigValue} representing a list value, as in JSON's
* "[1,2,3]" value. Implements java.util.List<ConfigValue> so you can use it * {@code [1,2,3]} syntax.
* like a regular Java list. *
* <p>
* {@code ConfigList} implements {@code java.util.List<ConfigValue>} so you can
* use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the
* list elements into plain Java values.
*
* <p>
* 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}.
*
* <p>
* The {@link ConfigValue#valueType} method on a list returns
* {@link ConfigValueType#LIST}.
*
* <p>
* <em>Do not implement {@code ConfigList}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
* *
*/ */
public interface ConfigList extends List<ConfigValue>, ConfigValue { public interface ConfigList extends List<ConfigValue>, ConfigValue {

View file

@ -4,33 +4,48 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* This is a marker for types that can be merged as a fallback into a Config or * Marker for types whose instances can be merged, that is {@link Config} and
* a ConfigValue. Both Config and ConfigValue are mergeable. * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can
* be combined into a single new instance using the
* {@link ConfigMergeable#withFallback withFallback()} method.
*
* <p>
* <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigMergeable { 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(); ConfigValue toValue();
/** /**
* Returns a new value computed by merging this value with another, with * Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only ConfigObject and * keys in this value "winning" over the other one. Only
* Config instances do anything in this method (they need to merge the * {@link ConfigObject} and {@link Config} instances do anything in this
* fallback keys into themselves). All other values just return the original * method (they need to merge the fallback keys into themselves). All other
* value, since they automatically override any fallback. * 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 * <p>
* * The semantics of merging are described in the <a
* Note that objects do not merge "across" non-objects; if you do * href="https://github.com/havocp/config/blob/master/HOCON.md">spec for
* HOCON</a>.
*
* <p>
* Note that objects do not merge "across" non-objects; if you write
* <code>object.withFallback(nonObject).withFallback(otherObject)</code>, * <code>object.withFallback(nonObject).withFallback(otherObject)</code>,
* then <code>otherObject</code> will simply be ignored. This is an * then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works. Both non-objects, and any object * intentional part of how merging works. Both non-objects, and any object
* which has fallen back to a non-object, block subsequent fallbacks. * which has fallen back to a non-object, block subsequent fallbacks.
* *
* @param other * @param other
* an object whose keys should be used if the keys are not * an object whose keys should be used if the keys are not
* present in this one * present in this one

View file

@ -6,54 +6,67 @@ package com.typesafe.config;
import java.util.Map; import java.util.Map;
/** /**
* A ConfigObject is a read-only configuration object, which may have nested * Subtype of {@link ConfigValue} representing an object (dictionary, map)
* child objects. Implementations of ConfigObject should be immutable (at least * value, as in JSON's <code>{ "a" : 42 }</code> syntax.
* from the perspective of anyone using this interface) and thus thread-safe.
* *
* In most cases you want to use the Config interface rather than this one. Call * <p>
* toConfig() to convert a ConfigObject to a config. * {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} 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 * <p>
* in terms of path expressions. Conceptually, ConfigObject is a tree of maps * Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* from keys to values, while a ConfigObject is a one-level map from paths to * This makes it threadsafe and you never have to create "defensive copies." The
* values. * 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 * <p>
* is a key in a JSON object; it's just a string that's the key in a map. A * The {@link ConfigValue#valueType} method on an object returns
* "path" is a parseable expression with a syntax and it refers to a series of * {@link ConfigValueType#OBJECT}.
* 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.
* *
* ConfigObject implements java.util.Map<String,ConfigValue> and all methods * <p>
* 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 * <p>
* 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() == * <p>
* ConfigValueType.NULL. If get() returns Java's null then the key was not * A {@code ConfigObject} may contain null values, which will have
* present in the parsed file (or wherever this value tree came from). If get() * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
* returns a ConfigValue with type ConfigValueType.NULL then the key was set to * {@code get()} returns Java's null then the key was not present in the parsed
* null explicitly. * 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.
*
* <p>
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> { public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
/** /**
* Converts this object to a Config instance, enabling you to use path * Converts this object to a {@link Config} instance, enabling you to use
* expressions to find values in the object. This is a constant-time * path expressions to find values in the object. This is a constant-time
* operation (it is not proportional to the size of the object). * operation (it is not proportional to the size of the object).
* *
* @return * @return a {@link Config} with this object as its root
*/ */
Config toConfig(); Config toConfig();
/** /**
* Recursively unwraps the object, returning a map from String to whatever * Recursively unwraps the object, returning a map from String to whatever
* plain Java values are unwrapped from the object's values. * plain Java values are unwrapped from the object's values.
*
* @return a {@link java.util.Map} containing plain Java objects
*/ */
@Override @Override
Map<String, Object> unwrapped(); Map<String, Object> unwrapped();
@ -62,10 +75,15 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
ConfigObject withFallback(ConfigMergeable other); ConfigObject withFallback(ConfigMergeable other);
/** /**
* Gets a ConfigValue at the given key, or returns null if there is no * Gets a {@link ConfigValue} at the given key, or returns null if there is
* value. The returned ConfigValue may have ConfigValueType.NULL or any * no value. The returned {@link ConfigValue} may have
* other type, and the passed-in key must be a key in this object, rather * {@link ConfigValueType#NULL} or any other type, and the passed-in key
* 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 @Override
ConfigValue get(Object key); ConfigValue get(Object key);

View file

@ -3,10 +3,67 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
import java.net.URL;
/** /**
* ConfigOrigin is used to track the origin (such as filename and line number) * Represents the origin (such as filename and line number) of a
* of a ConfigValue or other object. The origin is used in error messages. * {@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
* <code>ConfigException.origin()</code> may return null.
*
* <p>
* 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.
*
* <p>
* <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigOrigin { 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(); 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();
} }

View file

@ -4,6 +4,22 @@
package com.typesafe.config; package com.typesafe.config;
/**
* A set of options related to parsing.
*
* <p>
* This object is immutable, so the "setters" return a new object.
*
* <p>
* Here is an example of creating a custom {@code ConfigParseOptions}:
*
* <pre>
* ConfigParseOptions options = ConfigParseOptions.defaults()
* .setSyntax(ConfigSyntax.JSON)
* .setAllowMissing(false)
* </pre>
*
*/
public final class ConfigParseOptions { public final class ConfigParseOptions {
final ConfigSyntax syntax; final ConfigSyntax syntax;
final String originDescription; 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 * 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 * @param syntax
* @return * a syntax or {@code null} for best guess
* @return options with the syntax set
*/ */
public ConfigParseOptions setSyntax(ConfigSyntax syntax) { public ConfigParseOptions setSyntax(ConfigSyntax syntax) {
if (this.syntax == 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 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 * 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 * 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 * @param originDescription
* @return * @return options with the origin description set
*/ */
public ConfigParseOptions setOriginDescription(String originDescription) { public ConfigParseOptions setOriginDescription(String originDescription) {
if (this.originDescription == originDescription) if (this.originDescription == originDescription)
@ -79,7 +97,7 @@ public final class ConfigParseOptions {
* case. * case.
* *
* @param allowMissing * @param allowMissing
* @return * @return options with the "allow missing" flag set
*/ */
public ConfigParseOptions setAllowMissing(boolean allowMissing) { public ConfigParseOptions setAllowMissing(boolean allowMissing) {
if (this.allowMissing == allowMissing) if (this.allowMissing == allowMissing)

View file

@ -3,21 +3,40 @@
*/ */
package com.typesafe.config; 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}.
*
* <p>
* <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/
public interface ConfigParseable { 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 * @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); 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(); ConfigParseOptions options();
} }

View file

@ -3,6 +3,28 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
/**
* A set of options related to resolving substitutions. Substitutions use the
* <code>${foo.bar}</code> syntax and are documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> spec.
*
* <p>
* This object is immutable, so the "setters" return a new object.
*
* <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}:
*
* <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .setUseSystemProperties(false)
* .setUseSystemEnvironment(false)
* </pre>
*
* <p>
* 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 { public final class ConfigResolveOptions {
private final boolean useSystemProperties; private final boolean useSystemProperties;
private final boolean useSystemEnvironment; private final boolean useSystemEnvironment;
@ -13,26 +35,68 @@ public final class ConfigResolveOptions {
this.useSystemEnvironment = useSystemEnvironment; this.useSystemEnvironment = useSystemEnvironment;
} }
/**
* Returns the default resolve options.
*
* @return the default resolve options
*/
public static ConfigResolveOptions defaults() { public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, true); 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() { 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) { public ConfigResolveOptions setUseSystemProperties(boolean value) {
return new ConfigResolveOptions(value, useSystemEnvironment); 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) { public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(useSystemProperties, 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() { public boolean getUseSystemProperties() {
return useSystemProperties; 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() { public boolean getUseSystemEnvironment() {
return useSystemEnvironment; return useSystemEnvironment;
} }

View file

@ -1,33 +0,0 @@
/**
* Copyright (C) 2011 Typesafe Inc. <http://typesafe.com>
*/
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();
}

View file

@ -3,6 +3,30 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
/**
* The syntax of a character stream, <a href="http://json.org">JSON</a>, <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> aka
* ".conf", or <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a>.
*
*/
public enum ConfigSyntax { public enum ConfigSyntax {
JSON, CONF, PROPERTIES; /**
* Pedantically strict <a href="http://json.org">JSON</a> format; no
* comments, no unexpected commas, no duplicate keys in the same object.
*/
JSON,
/**
* The JSON-superset <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a>
* format.
*/
CONF,
/**
* Standard <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a> format.
*/
PROPERTIES;
} }

View file

@ -4,32 +4,57 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* Interface implemented by any configuration value. From the perspective of * An immutable value, following the <a href="http://json.org">JSON</a> type
* users of this interface, the object is immutable. It is therefore safe to use * schema.
* from multiple threads. *
* <p>
* Because this object is immutable, it is safe to use from multiple threads and
* there's no need for "defensive copies."
*
* <p>
* <em>Do not implement {@code ConfigValue}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigValue extends ConfigMergeable { 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 * @return where the value came from
*/ */
ConfigOrigin origin(); 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 * @return value's type
*/ */
ConfigValueType valueType(); ConfigValueType valueType();
/** /**
* Returns the config value as a plain Java boxed value, should be a String, * Returns the value as a plain Java boxed value, that is, a {@code String},
* Number, etc. matching the valueType() of the ConfigValue. If the value is * {@code Number}, {@code Boolean}, {@code Map<String,Object>},
* a ConfigObject or ConfigList, it is recursively unwrapped. * {@code List<Object>}, or {@code null}, matching the {@link #valueType()}
* of this {@code ConfigValue}. If the value is a {@link ConfigObject} or
* {@link ConfigList}, it is recursively unwrapped.
*/ */
Object unwrapped(); Object unwrapped();
/**
* 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.
*
* @return the rendered value
*/
String render();
@Override @Override
ConfigValue withFallback(ConfigMergeable other); ConfigValue withFallback(ConfigMergeable other);
} }

View file

@ -3,7 +3,6 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.ConfigImpl;
@ -14,6 +13,9 @@ import com.typesafe.config.impl.ConfigImpl;
* data structures. * data structures.
*/ */
public final class ConfigValueFactory { public final class ConfigValueFactory {
private ConfigValueFactory() {
}
/** /**
* Creates a ConfigValue from a plain Java boxed value, which may be a * 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 * Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from
@ -23,23 +25,27 @@ public final class ConfigValueFactory {
* the Iterable is not an ordered collection, results could be strange, * the Iterable is not an ordered collection, results could be strange,
* since ConfigList is ordered. * since ConfigList is ordered.
* *
* <p>
* In a Map passed to fromAnyRef(), the map's keys are plain keys, not path * 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 * 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 * object with a key called "foo.bar", rather than an object with a key
* "foo" containing another object with a key "bar". * "foo" containing another object with a key "bar".
* *
* <p>
* The originDescription will be used to set the origin() field on the * The originDescription will be used to set the origin() field on the
* ConfigValue. It should normally be the name of the file the values came * ConfigValue. It should normally be the name of the file the values came
* from, or something short describing the value such as "default settings". * from, or something short describing the value such as "default settings".
* The originDescription is prefixed to error messages so users can tell * The originDescription is prefixed to error messages so users can tell
* where problematic values are coming from. * where problematic values are coming from.
* *
* <p>
* Supplying the result of ConfigValue.unwrapped() to this function is * Supplying the result of ConfigValue.unwrapped() to this function is
* guaranteed to work and should give you back a ConfigValue that matches * guaranteed to work and should give you back a ConfigValue that matches
* the one you unwrapped. The re-wrapped ConfigValue will lose some * the one you unwrapped. The re-wrapped ConfigValue will lose some
* information that was present in the original such as its origin, but it * information that was present in the original such as its origin, but it
* will have matching values. * will have matching values.
* *
* <p>
* This function throws if you supply a value that cannot be converted to a * This function throws if you supply a value that cannot be converted to a
* ConfigValue, but supplying such a value is a bug in your program, so you * ConfigValue, but supplying such a value is a bug in your program, so you
* should never handle the exception. Just fix your program (or report a bug * should never handle the exception. Just fix your program (or report a bug
@ -57,23 +63,25 @@ public final class ConfigValueFactory {
/** /**
* See the fromAnyRef() documentation for details. This is a typesafe * See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Map and returns ConfigObject rather than * wrapper that only works on {@link java.util.Map} and returns
* ConfigValue. * {@link ConfigObject} rather than {@link ConfigValue}.
* *
* <p>
* If your Map has a key "foo.bar" then you will get one object with a key * 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 * 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 * 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 * expressions. That is, the Map corresponds exactly to a single
* ConfigObject. The keys will not be parsed or modified, and the values are * {@code ConfigObject}. The keys will not be parsed or modified, and the
* wrapped in ConfigValue. To get nested ConfigObject, some of the values in * values are wrapped in ConfigValue. To get nested {@code ConfigObject},
* the map would have to be more maps. * some of the values in the map would have to be more maps.
* *
* There is a separate fromPathMap() that interprets the keys in the map as * <p>
* path expressions. * See also {@link ConfigFactory#parseMap(Map,String)} which interprets the
* keys in the map as path expressions.
* *
* @param values * @param values
* @param originDescription * @param originDescription
* @return * @return a new {@link ConfigObject} value
*/ */
public static ConfigObject fromMap(Map<String, ? extends Object> values, public static ConfigObject fromMap(Map<String, ? extends Object> values,
String originDescription) { String originDescription) {
@ -82,12 +90,12 @@ public final class ConfigValueFactory {
/** /**
* See the fromAnyRef() documentation for details. This is a typesafe * See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Iterable and returns ConfigList rather than * wrapper that only works on {@link java.util.Iterable} and returns
* ConfigValue. * {@link ConfigList} rather than {@link ConfigValue}.
* *
* @param values * @param values
* @param originDescription * @param originDescription
* @return * @return a new {@link ConfigList} value
*/ */
public static ConfigList fromIterable(Iterable<? extends Object> values, public static ConfigList fromIterable(Iterable<? extends Object> values,
String originDescription) { String originDescription) {
@ -95,35 +103,39 @@ public final class ConfigValueFactory {
} }
/** /**
* See the other overload of fromAnyRef() for details, this one just uses a * See the other overload {@link #fromAnyRef(Object,String)} for details,
* default origin description. * this one just uses a default origin description.
* *
* @param object * @param object
* @return * @return a new {@link ConfigValue}
*/ */
public static ConfigValue fromAnyRef(Object object) { public static ConfigValue fromAnyRef(Object object) {
return fromAnyRef(object, null); return fromAnyRef(object, null);
} }
/** /**
* See the other overload of fromMap() for details, this one just uses a * See the other overload {@link #fromMap(Map,String)} for details, this one
* default origin description. * just uses a default origin description.
*
* <p>
* See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in
* the map as path expressions.
* *
* @param values * @param values
* @return * @return a new {@link ConfigObject}
*/ */
public static ConfigObject fromMap(Map<String, ? extends Object> values) { public static ConfigObject fromMap(Map<String, ? extends Object> values) {
return fromMap(values, null); return fromMap(values, null);
} }
/** /**
* See the other overload of fromIterable() for details, this one just uses * See the other overload of {@link #fromIterable(Iterable, String)} for
* a default origin description. * details, this one just uses a default origin description.
* *
* @param values * @param values
* @return * @return a new {@link ConfigList}
*/ */
public static ConfigList fromIterable(Collection<? extends Object> values) { public static ConfigList fromIterable(Iterable<? extends Object> values) {
return fromIterable(values, null); return fromIterable(values, null);
} }
} }

View file

@ -4,7 +4,8 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* The type of a configuration value. Value types follow the JSON type schema. * The type of a configuration value (following the <a
* href="http://json.org">JSON</a> type schema).
*/ */
public enum ConfigValueType { public enum ConfigValueType {
OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING

View file

@ -61,19 +61,23 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
* (just returns null if path not found). Does however resolve the path, if * (just returns null if path not found). Does however resolve the path, if
* resolver != null. * resolver != null.
*/ */
protected ConfigValue peekPath(Path path, SubstitutionResolver resolver, protected AbstractConfigValue peekPath(Path path, SubstitutionResolver resolver,
int depth, ConfigResolveOptions options) { int depth, ConfigResolveOptions options) {
return peekPath(this, path, resolver, depth, options); return peekPath(this, path, resolver, depth, options);
} }
private static ConfigValue peekPath(AbstractConfigObject self, Path path, AbstractConfigValue peekPath(Path path) {
return peekPath(this, path, null, 0, null);
}
private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path,
SubstitutionResolver resolver, int depth, SubstitutionResolver resolver, int depth,
ConfigResolveOptions options) { ConfigResolveOptions options) {
String key = path.first(); String key = path.first();
Path next = path.remainder(); Path next = path.remainder();
if (next == null) { if (next == null) {
ConfigValue v = self.peek(key, resolver, depth, options); AbstractConfigValue v = self.peek(key, resolver, depth, options);
return v; return v;
} else { } else {
// it's important to ONLY resolve substitutions here, not // it's important to ONLY resolve substitutions here, not
@ -164,18 +168,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (stack.isEmpty()) if (stack.isEmpty())
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"can't merge origins on empty list"); "can't merge origins on empty list");
final String prefix = "merge of "; List<ConfigOrigin> origins = new ArrayList<ConfigOrigin>();
StringBuilder sb = new StringBuilder();
ConfigOrigin firstOrigin = null; ConfigOrigin firstOrigin = null;
int numMerged = 0; int numMerged = 0;
for (AbstractConfigValue v : stack) { for (AbstractConfigValue v : stack) {
if (firstOrigin == null) if (firstOrigin == null)
firstOrigin = v.origin(); firstOrigin = v.origin();
String desc = v.origin().description();
if (desc.startsWith(prefix))
desc = desc.substring(prefix.length());
if (v instanceof AbstractConfigObject if (v instanceof AbstractConfigObject
&& ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED
&& ((ConfigObject) v).isEmpty()) { && ((ConfigObject) v).isEmpty()) {
@ -183,22 +182,17 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
// config in the description, since they are // config in the description, since they are
// likely to be "implementation details" // likely to be "implementation details"
} else { } else {
sb.append(desc); origins.add(v.origin());
sb.append(",");
numMerged += 1; numMerged += 1;
} }
} }
if (numMerged > 0) {
sb.setLength(sb.length() - 1); // chop comma if (numMerged == 0) {
if (numMerged > 1) { // the configs were all empty, so just use the first one
return new SimpleConfigOrigin(prefix + sb.toString()); origins.add(firstOrigin);
} else {
return new SimpleConfigOrigin(sb.toString());
}
} else {
// the configs were all empty.
return firstOrigin;
} }
return SimpleConfigOrigin.mergeOrigins(origins);
} }
static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) { static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) {
@ -210,6 +204,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
Map<String, AbstractConfigValue> changes = null; Map<String, AbstractConfigValue> changes = null;
for (String k : keySet()) { for (String k : keySet()) {
AbstractConfigValue v = peek(k); AbstractConfigValue v = peek(k);
// "modified" may be null, which means remove the child;
// to do that we put null in the "changes" map.
AbstractConfigValue modified = modifier.modifyChild(v); AbstractConfigValue modified = modifier.modifyChild(v);
if (modified != v) { if (modified != v) {
if (changes == null) if (changes == null)
@ -223,7 +219,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>(); Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>();
for (String k : keySet()) { for (String k : keySet()) {
if (changes.containsKey(k)) { if (changes.containsKey(k)) {
modified.put(k, changes.get(k)); AbstractConfigValue newValue = changes.get(k);
if (newValue != null) {
modified.put(k, newValue);
} else {
// remove this child; don't put it in the new map.
}
} else { } else {
modified.put(k, peek(k)); modified.put(k, peek(k));
} }
@ -271,20 +272,36 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, boolean formatted) {
StringBuilder sb = new StringBuilder(); if (isEmpty()) {
sb.append(valueType().name()); sb.append("{}");
sb.append("("); } else {
for (String k : keySet()) { sb.append("{");
sb.append(k); if (formatted)
sb.append("->"); sb.append('\n');
sb.append(peek(k).toString()); for (String k : keySet()) {
sb.append(","); AbstractConfigValue v = peek(k);
if (formatted) {
indent(sb, indent + 1);
sb.append("# ");
sb.append(v.origin().description());
sb.append("\n");
indent(sb, indent + 1);
}
v.render(sb, indent + 1, k, formatted);
sb.append(",");
if (formatted)
sb.append('\n');
}
// chop comma or newline
sb.setLength(sb.length() - 1);
if (formatted) {
sb.setLength(sb.length() - 1); // also chop comma
sb.append("\n"); // put a newline back
indent(sb, indent);
}
sb.append("}");
} }
if (!keySet().isEmpty())
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
private static boolean mapEquals(Map<String, ConfigValue> a, private static boolean mapEquals(Map<String, ConfigValue> a,

View file

@ -162,8 +162,39 @@ abstract class AbstractConfigValue implements ConfigValue {
} }
@Override @Override
public String toString() { public final String toString() {
return valueType().name() + "(" + unwrapped() + ")"; StringBuilder sb = new StringBuilder();
render(sb, 0, null /* atKey */, false /* formatted */);
return getClass().getSimpleName() + "(" + sb.toString() + ")";
}
protected static void indent(StringBuilder sb, int indent) {
int remaining = indent;
while (remaining > 0) {
sb.append(" ");
--remaining;
}
}
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
if (atKey != null) {
sb.append(ConfigUtil.renderJsonString(atKey));
sb.append(" : ");
}
render(sb, indent, formatted);
}
protected void render(StringBuilder sb, int indent, boolean formatted) {
Object u = unwrapped();
sb.append(u.toString());
}
@Override
public final String render() {
StringBuilder sb = new StringBuilder();
render(sb, 0, null, true /* formatted */);
return sb.toString();
} }
// toString() is a debugging-oriented string but this is defined // toString() is a debugging-oriented string but this is defined

View file

@ -5,6 +5,7 @@ package com.typesafe.config.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
@ -76,10 +77,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
AbstractConfigValue merged = null; AbstractConfigValue merged = null;
for (AbstractConfigValue v : stack) { for (AbstractConfigValue v : stack) {
AbstractConfigValue resolved = resolver.resolve(v, depth, options); AbstractConfigValue resolved = resolver.resolve(v, depth, options);
if (merged == null) if (resolved != null) {
merged = resolved; if (merged == null)
else merged = resolved;
merged = merged.withFallback(resolved); else
merged = merged.withFallback(resolved);
}
} }
return merged; return merged;
@ -160,16 +163,58 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
StringBuilder sb = new StringBuilder(); render(stack, sb, indent, atKey, formatted);
sb.append("DELAYED_MERGE"); }
sb.append("(");
for (Object s : stack) { // static method also used by ConfigDelayedMergeObject.
sb.append(s.toString()); static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, String atKey,
sb.append(","); boolean formatted) {
if (formatted) {
sb.append("# unresolved merge of " + stack.size() + " values follows (\n");
if (atKey == null) {
indent(sb, indent);
sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n");
sb.append("# the HOCON format has no way to list multiple root objects in a single file\n");
}
}
List<AbstractConfigValue> reversed = new ArrayList<AbstractConfigValue>();
reversed.addAll(stack);
Collections.reverse(reversed);
int i = 0;
for (AbstractConfigValue v : reversed) {
if (formatted) {
indent(sb, indent);
if (atKey != null) {
sb.append("# unmerged value " + i + " for key "
+ ConfigUtil.renderJsonString(atKey) + " from ");
} else {
sb.append("# unmerged value " + i + " from ");
}
i += 1;
sb.append(v.origin().description());
sb.append("\n");
indent(sb, indent);
}
if (atKey != null) {
sb.append(ConfigUtil.renderJsonString(atKey));
sb.append(" : ");
}
v.render(sb, indent, formatted);
sb.append(",");
if (formatted)
sb.append('\n');
}
// chop comma or newline
sb.setLength(sb.length() - 1);
if (formatted) {
sb.setLength(sb.length() - 1); // also chop comma
sb.append("\n"); // put a newline back
indent(sb, indent);
sb.append("# ) end of unresolved merge\n");
} }
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
} }

View file

@ -139,17 +139,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
StringBuilder sb = new StringBuilder(); ConfigDelayedMerge.render(stack, sb, indent, atKey, formatted);
sb.append("DELAYED_MERGE_OBJECT");
sb.append("(");
for (Object s : stack) {
sb.append(s.toString());
sb.append(",");
}
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
private static ConfigException notResolved() { private static ConfigException notResolved() {

View file

@ -19,7 +19,6 @@ import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable; import com.typesafe.config.ConfigParseable;
import com.typesafe.config.ConfigRoot;
import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigSyntax;
import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValue;
@ -45,8 +44,7 @@ public class ConfigImpl {
obj = p.parse(p.options().setAllowMissing( obj = p.parse(p.options().setAllowMissing(
options.getAllowMissing())); options.getAllowMissing()));
} else { } else {
obj = SimpleConfigObject.emptyMissing(new SimpleConfigOrigin( obj = SimpleConfigObject.emptyMissing(SimpleConfigOrigin.newSimple(name));
name));
} }
} else { } else {
ConfigParseable confHandle = source.nameToParseable(name + ".conf"); ConfigParseable confHandle = source.nameToParseable(name + ".conf");
@ -56,13 +54,13 @@ public class ConfigImpl {
if (!options.getAllowMissing() && confHandle == null if (!options.getAllowMissing() && confHandle == null
&& jsonHandle == null && propsHandle == null) { && jsonHandle == null && propsHandle == null) {
throw new ConfigException.IO(new SimpleConfigOrigin(name), throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name),
"No config files {.conf,.json,.properties} found"); "No config files {.conf,.json,.properties} found");
} }
ConfigSyntax syntax = options.getSyntax(); ConfigSyntax syntax = options.getSyntax();
obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name)); obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name));
if (confHandle != null if (confHandle != null
&& (syntax == null || syntax == ConfigSyntax.CONF)) { && (syntax == null || syntax == ConfigSyntax.CONF)) {
obj = confHandle.parse(confHandle.options() obj = confHandle.parse(confHandle.options()
@ -89,39 +87,13 @@ public class ConfigImpl {
return obj; return obj;
} }
private static String makeResourceBasename(Path path) {
StringBuilder sb = new StringBuilder("/");
String next = path.first();
Path remaining = path.remainder();
while (next != null) {
sb.append(next);
sb.append('-');
if (remaining == null)
break;
next = remaining.first();
remaining = remaining.remainder();
}
sb.setLength(sb.length() - 1); // chop extra hyphen
return sb.toString();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesForPath(String expression, public static ConfigObject parseResourcesAnySyntax(final Class<?> klass,
final ConfigParseOptions baseOptions) {
Path path = Parser.parsePath(expression);
String basename = makeResourceBasename(path);
return parseResourceAnySyntax(ConfigImpl.class, basename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourceAnySyntax(final Class<?> klass,
String resourceBasename, final ConfigParseOptions baseOptions) { String resourceBasename, final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() { NameSource source = new NameSource() {
@Override @Override
public ConfigParseable nameToParseable(String name) { public ConfigParseable nameToParseable(String name) {
return Parseable.newResource(klass, name, baseOptions); return Parseable.newResources(klass, name, baseOptions);
} }
}; };
return fromBasename(source, resourceBasename, baseOptions); return fromBasename(source, resourceBasename, baseOptions);
@ -139,16 +111,9 @@ public class ConfigImpl {
return fromBasename(source, basename.getPath(), baseOptions); return fromBasename(source, basename.getPath(), baseOptions);
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigRoot emptyRoot(String rootPath, String originDescription) {
String desc = originDescription != null ? originDescription : rootPath;
return emptyObject(desc).toConfig().asRoot(
Path.newPath(rootPath));
}
static AbstractConfigObject emptyObject(String originDescription) { static AbstractConfigObject emptyObject(String originDescription) {
ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin( ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin
originDescription) : null; .newSimple(originDescription) : null;
return emptyObject(origin); return emptyObject(origin);
} }
@ -162,8 +127,8 @@ public class ConfigImpl {
} }
// default origin for values created with fromAnyRef and no origin specified // default origin for values created with fromAnyRef and no origin specified
final private static ConfigOrigin defaultValueOrigin = new SimpleConfigOrigin( final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin
"hardcoded value"); .newSimple("hardcoded value");
final private static ConfigBoolean defaultTrueValue = new ConfigBoolean( final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
defaultValueOrigin, true); defaultValueOrigin, true);
final private static ConfigBoolean defaultFalseValue = new ConfigBoolean( final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
@ -196,7 +161,7 @@ public class ConfigImpl {
if (originDescription == null) if (originDescription == null)
return defaultValueOrigin; return defaultValueOrigin;
else else
return new SimpleConfigOrigin(originDescription); return SimpleConfigOrigin.newSimple(originDescription);
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -290,17 +255,6 @@ public class ConfigImpl {
} }
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigRoot systemPropertiesRoot(String rootPath) {
Path path = Parser.parsePath(rootPath);
try {
return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath)
.asRoot(path);
} catch (ConfigException.Missing e) {
return emptyObject("system properties").toConfig().asRoot(path);
}
}
private static class SimpleIncluder implements ConfigIncluder { private static class SimpleIncluder implements ConfigIncluder {
private ConfigIncluder fallback; private ConfigIncluder fallback;
@ -346,29 +300,26 @@ public class ConfigImpl {
} }
} }
private static ConfigIncluder defaultIncluder = null; private static class DefaultIncluderHolder {
static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
synchronized static ConfigIncluder defaultIncluder() {
if (defaultIncluder == null) {
defaultIncluder = new SimpleIncluder(null);
}
return defaultIncluder;
} }
private static AbstractConfigObject systemProperties = null; static ConfigIncluder defaultIncluder() {
return DefaultIncluderHolder.defaultIncluder;
synchronized static AbstractConfigObject systemPropertiesAsConfigObject() {
if (systemProperties == null) {
systemProperties = loadSystemProperties();
}
return systemProperties;
} }
private static AbstractConfigObject loadSystemProperties() { private static AbstractConfigObject loadSystemProperties() {
return (AbstractConfigObject) Parseable.newProperties( return (AbstractConfigObject) Parseable.newProperties(System.getProperties(),
System.getProperties(), ConfigParseOptions.defaults().setOriginDescription("system properties")).parse();
ConfigParseOptions.defaults().setOriginDescription( }
"system properties")).parse();
private static class SystemPropertiesHolder {
// this isn't final due to the reloadSystemPropertiesConfig() hack below
static AbstractConfigObject systemProperties = loadSystemProperties();
}
static AbstractConfigObject systemPropertiesAsConfigObject() {
return SystemPropertiesHolder.systemProperties;
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -376,18 +327,10 @@ public class ConfigImpl {
return systemPropertiesAsConfigObject().toConfig(); return systemPropertiesAsConfigObject().toConfig();
} }
// this is a hack to let us set system props in the test suite // this is a hack to let us set system props in the test suite.
synchronized static void dropSystemPropertiesConfig() { // obviously not thread-safe.
systemProperties = null; static void reloadSystemPropertiesConfig() {
} SystemPropertiesHolder.systemProperties = loadSystemProperties();
private static AbstractConfigObject envVariables = null;
synchronized static AbstractConfigObject envVariablesAsConfigObject() {
if (envVariables == null) {
envVariables = loadEnvVariables();
}
return envVariables;
} }
private static AbstractConfigObject loadEnvVariables() { private static AbstractConfigObject loadEnvVariables() {
@ -395,15 +338,37 @@ public class ConfigImpl {
Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>(); Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>();
for (Map.Entry<String, String> entry : env.entrySet()) { for (Map.Entry<String, String> entry : env.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
m.put(key, new ConfigString( m.put(key,
new SimpleConfigOrigin("env var " + key), entry.getValue())); new ConfigString(SimpleConfigOrigin.newSimple("env var " + key), entry
.getValue()));
} }
return new SimpleConfigObject(new SimpleConfigOrigin("env variables"), return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"),
m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */);
} }
private static class EnvVariablesHolder {
static final AbstractConfigObject envVariables = loadEnvVariables();
}
static AbstractConfigObject envVariablesAsConfigObject() {
return EnvVariablesHolder.envVariables;
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config envVariablesAsConfig() { public static Config envVariablesAsConfig() {
return envVariablesAsConfigObject().toConfig(); return envVariablesAsConfigObject().toConfig();
} }
private static class ReferenceHolder {
private static final Config unresolvedResources = Parseable
.newResources(ConfigImpl.class, "/reference.conf", ConfigParseOptions.defaults())
.parse().toConfig();
static final Config referenceConfig = systemPropertiesAsConfig().withFallback(
unresolvedResources).resolve();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference() {
return ReferenceHolder.referenceConfig;
}
} }

View file

@ -34,4 +34,9 @@ final class ConfigNull extends AbstractConfigValue {
String transformToString() { String transformToString() {
return "null"; return "null";
} }
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append("null");
}
} }

View file

@ -29,4 +29,9 @@ final class ConfigString extends AbstractConfigValue {
String transformToString() { String transformToString() {
return value; return value;
} }
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append(ConfigUtil.renderJsonString(value));
}
} }

View file

@ -22,8 +22,8 @@ import com.typesafe.config.ConfigValueType;
final class ConfigSubstitution extends AbstractConfigValue implements final class ConfigSubstitution extends AbstractConfigValue implements
Unmergeable { Unmergeable {
// this is a list of String and Path where the Path // this is a list of String and SubstitutionExpression where the
// have to be resolved to values, then if there's more // SubstitutionExpression has to be resolved to values, then if there's more
// than one piece everything is stringified and concatenated // than one piece everything is stringified and concatenated
final private List<Object> pieces; final private List<Object> pieces;
// the length of any prefixes added with relativized() // the length of any prefixes added with relativized()
@ -40,19 +40,24 @@ final class ConfigSubstitution extends AbstractConfigValue implements
this.pieces = pieces; this.pieces = pieces;
this.prefixLength = prefixLength; this.prefixLength = prefixLength;
this.ignoresFallbacks = ignoresFallbacks; this.ignoresFallbacks = ignoresFallbacks;
for (Object p : pieces) {
if (p instanceof Path)
throw new RuntimeException("broken here");
}
} }
@Override @Override
public ConfigValueType valueType() { public ConfigValueType valueType() {
throw new ConfigException.NotResolved( throw new ConfigException.NotResolved(
"tried to get value type on an unresolved substitution: " "need to call resolve() on root config; tried to get value type on an unresolved substitution: "
+ this); + this);
} }
@Override @Override
public Object unwrapped() { public Object unwrapped() {
throw new ConfigException.NotResolved( throw new ConfigException.NotResolved(
"tried to unwrap an unresolved substitution: " + this); "need to call resolve() on root config; tried to unwrap an unresolved substitution: "
+ this);
} }
@Override @Override
@ -124,15 +129,15 @@ final class ConfigSubstitution extends AbstractConfigValue implements
return result; return result;
} }
private ConfigValue resolve(SubstitutionResolver resolver, Path subst, private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst,
int depth, ConfigResolveOptions options) { int depth, ConfigResolveOptions options) {
ConfigValue result = findInObject(resolver.root(), resolver, subst, ConfigValue result = findInObject(resolver.root(), resolver, subst.path(),
depth, options); depth, options);
// when looking up system props and env variables, // when looking up system props and env variables,
// we don't want the prefix that was added when // we don't want the prefix that was added when
// we were included in another file. // we were included in another file.
Path unprefixed = subst.subPath(prefixLength); Path unprefixed = subst.path().subPath(prefixLength);
if (result == null && options.getUseSystemProperties()) { if (result == null && options.getUseSystemProperties()) {
result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null, result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null,
@ -144,10 +149,6 @@ final class ConfigSubstitution extends AbstractConfigValue implements
unprefixed, depth, options); unprefixed, depth, options);
} }
if (result == null) {
result = new ConfigNull(origin());
}
return result; return result;
} }
@ -160,28 +161,40 @@ final class ConfigSubstitution extends AbstractConfigValue implements
if (p instanceof String) { if (p instanceof String) {
sb.append((String) p); sb.append((String) p);
} else { } else {
ConfigValue v = resolve(resolver, (Path) p, depth, options); SubstitutionExpression exp = (SubstitutionExpression) p;
switch (v.valueType()) { ConfigValue v = resolve(resolver, exp, depth, options);
case NULL:
// nothing; becomes empty string if (v == null) {
break; if (exp.optional()) {
case LIST: // append nothing to StringBuilder
case OBJECT: } else {
// cannot substitute lists and objects into strings throw new ConfigException.UnresolvedSubstitution(origin(),
throw new ConfigException.WrongType(v.origin(), exp.toString());
((Path) p).render(), }
"not a list or object", v.valueType().name()); } else {
default: switch (v.valueType()) {
sb.append(((AbstractConfigValue) v).transformToString()); case LIST:
case OBJECT:
// cannot substitute lists and objects into strings
throw new ConfigException.WrongType(v.origin(), exp.path().render(),
"not a list or object", v.valueType().name());
default:
sb.append(((AbstractConfigValue) v).transformToString());
}
} }
} }
} }
return new ConfigString(origin(), sb.toString()); return new ConfigString(origin(), sb.toString());
} else { } else {
if (!(pieces.get(0) instanceof Path)) if (!(pieces.get(0) instanceof SubstitutionExpression))
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"ConfigSubstitution should never contain a single String piece"); "ConfigSubstitution should never contain a single String piece");
return resolve(resolver, (Path) pieces.get(0), depth, options); SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0);
ConfigValue v = resolve(resolver, exp, depth, options);
if (v == null && !exp.optional()) {
throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString());
}
return v;
} }
} }
@ -210,8 +223,10 @@ final class ConfigSubstitution extends AbstractConfigValue implements
ConfigSubstitution relativized(Path prefix) { ConfigSubstitution relativized(Path prefix) {
List<Object> newPieces = new ArrayList<Object>(); List<Object> newPieces = new ArrayList<Object>();
for (Object p : pieces) { for (Object p : pieces) {
if (p instanceof Path) { if (p instanceof SubstitutionExpression) {
newPieces.add(((Path) p).prepend(prefix)); SubstitutionExpression exp = (SubstitutionExpression) p;
newPieces.add(exp.changePath(exp.path().prepend(prefix)));
} else { } else {
newPieces.add(p); newPieces.add(p);
} }
@ -243,16 +258,13 @@ final class ConfigSubstitution extends AbstractConfigValue implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, boolean formatted) {
StringBuilder sb = new StringBuilder();
sb.append("SUBST");
sb.append("(");
for (Object p : pieces) { for (Object p : pieces) {
sb.append(p.toString()); if (p instanceof SubstitutionExpression) {
sb.append(","); sb.append(p.toString());
} else {
sb.append(ConfigUtil.renderJsonString((String) p));
}
} }
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
} }

View file

@ -0,0 +1,8 @@
package com.typesafe.config.impl;
enum OriginType {
GENERIC,
FILE,
URL,
RESOURCE
}

View file

@ -17,6 +17,7 @@ import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.Properties; import java.util.Properties;
@ -39,6 +40,7 @@ import com.typesafe.config.ConfigValue;
public abstract class Parseable implements ConfigParseable { public abstract class Parseable implements ConfigParseable {
private ConfigIncludeContext includeContext; private ConfigIncludeContext includeContext;
private ConfigParseOptions initialOptions; private ConfigParseOptions initialOptions;
private ConfigOrigin initialOrigin;
protected Parseable() { protected Parseable() {
@ -54,9 +56,6 @@ public abstract class Parseable implements ConfigParseable {
} }
ConfigParseOptions modified = baseOptions.setSyntax(syntax); ConfigParseOptions modified = baseOptions.setSyntax(syntax);
if (modified.getOriginDescription() == null)
modified = modified.setOriginDescription(originDescription());
modified = modified.appendIncluder(ConfigImpl.defaultIncluder()); modified = modified.appendIncluder(ConfigImpl.defaultIncluder());
return modified; return modified;
@ -71,6 +70,11 @@ public abstract class Parseable implements ConfigParseable {
return Parseable.this.relativeTo(filename); return Parseable.this.relativeTo(filename);
} }
}; };
if (initialOptions.getOriginDescription() != null)
initialOrigin = SimpleConfigOrigin.newSimple(initialOptions.getOriginDescription());
else
initialOrigin = createOrigin();
} }
// the general idea is that any work should be in here, not in the // the general idea is that any work should be in here, not in the
@ -108,33 +112,26 @@ public abstract class Parseable implements ConfigParseable {
return forceParsedToObject(parseValue(baseOptions)); return forceParsedToObject(parseValue(baseOptions));
} }
AbstractConfigValue parseValue(ConfigParseOptions baseOptions) { final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) {
// note that we are NOT using our "options" and "origin" fields, // note that we are NOT using our "initialOptions",
// but using the ones from the passed-in options. The idea is that // but using the ones from the passed-in options. The idea is that
// callers can get our original options and then parse with different // callers can get our original options and then parse with different
// ones if they want. // ones if they want.
ConfigParseOptions options = fixupOptions(baseOptions); ConfigParseOptions options = fixupOptions(baseOptions);
ConfigOrigin origin = new SimpleConfigOrigin(
options.getOriginDescription()); // passed-in options can override origin
ConfigOrigin origin;
if (options.getOriginDescription() != null)
origin = SimpleConfigOrigin.newSimple(options.getOriginDescription());
else
origin = initialOrigin;
return parseValue(origin, options); return parseValue(origin, options);
} }
protected AbstractConfigValue parseValue(ConfigOrigin origin, final private AbstractConfigValue parseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) { ConfigParseOptions finalOptions) {
try { try {
Reader reader = reader(); return rawParseValue(origin, finalOptions);
try {
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
return PropertiesParser.parse(reader, origin);
} else {
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader,
finalOptions.getSyntax());
return Parser.parse(tokens, origin, finalOptions,
includeContext());
}
} finally {
reader.close();
}
} catch (IOException e) { } catch (IOException e) {
if (finalOptions.getAllowMissing()) { if (finalOptions.getAllowMissing()) {
return SimpleConfigObject.emptyMissing(origin); return SimpleConfigObject.emptyMissing(origin);
@ -144,6 +141,28 @@ public abstract class Parseable implements ConfigParseable {
} }
} }
// this is parseValue without post-processing the IOException or handling
// options.getAllowMissing()
protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions)
throws IOException {
Reader reader = reader();
try {
return rawParseValue(reader, origin, finalOptions);
} finally {
reader.close();
}
}
protected AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin,
ConfigParseOptions finalOptions) throws IOException {
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
return PropertiesParser.parse(reader, origin);
} else {
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax());
return Parser.parse(tokens, origin, finalOptions, includeContext());
}
}
public ConfigObject parse() { public ConfigObject parse() {
return forceParsedToObject(parseValue(options())); return forceParsedToObject(parseValue(options()));
} }
@ -152,13 +171,13 @@ public abstract class Parseable implements ConfigParseable {
return parseValue(options()); return parseValue(options());
} }
abstract String originDescription();
@Override @Override
public URL url() { public final ConfigOrigin origin() {
return null; return initialOrigin;
} }
protected abstract ConfigOrigin createOrigin();
@Override @Override
public ConfigParseOptions options() { public ConfigParseOptions options() {
return initialOptions; return initialOptions;
@ -228,34 +247,6 @@ public abstract class Parseable implements ConfigParseable {
} }
} }
private final static class ParseableInputStream extends Parseable {
final private InputStream input;
ParseableInputStream(InputStream input, ConfigParseOptions options) {
this.input = input;
postConstruct(options);
}
@Override
protected Reader reader() {
return doNotClose(readerFromStream(input));
}
@Override
String originDescription() {
return "InputStream";
}
}
/**
* note that we will never close this stream; you have to do it when parsing
* is complete.
*/
public static Parseable newInputStream(InputStream input,
ConfigParseOptions options) {
return new ParseableInputStream(input, options);
}
private final static class ParseableReader extends Parseable { private final static class ParseableReader extends Parseable {
final private Reader reader; final private Reader reader;
@ -270,8 +261,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return "Reader"; return SimpleConfigOrigin.newSimple("Reader");
} }
} }
@ -297,8 +288,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return "String"; return SimpleConfigOrigin.newSimple("String");
} }
} }
@ -335,13 +326,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return input.toExternalForm(); return SimpleConfigOrigin.newURL(input);
}
@Override
public URL url() {
return input;
} }
@Override @Override
@ -387,17 +373,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return input.getPath(); return SimpleConfigOrigin.newFile(input.getPath());
}
@Override
public URL url() {
try {
return input.toURI().toURL();
} catch (MalformedURLException e) {
return null;
}
} }
@Override @Override
@ -410,25 +387,61 @@ public abstract class Parseable implements ConfigParseable {
return new ParseableFile(input, options); return new ParseableFile(input, options);
} }
private final static class ParseableResource extends Parseable { private final static class ParseableResources extends Parseable {
final private Class<?> klass; final private ClassLoader loader;
final private String resource; final private String resource;
ParseableResource(Class<?> klass, String resource, ParseableResources(ClassLoader loader, String resource,
ConfigParseOptions options) { ConfigParseOptions options) {
this.klass = klass; this.loader = loader;
this.resource = resource; this.resource = resource;
postConstruct(options); postConstruct(options);
} }
@Override @Override
protected Reader reader() throws IOException { protected Reader reader() throws IOException {
InputStream stream = klass.getResourceAsStream(resource); throw new ConfigException.BugOrBroken(
if (stream == null) { "reader() should not be called on resources");
throw new IOException("resource not found on classpath: " }
+ resource);
@Override
protected AbstractConfigObject rawParseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) throws IOException {
Enumeration<URL> e = loader.getResources(resource);
if (!e.hasMoreElements()) {
throw new IOException("resource not found on classpath: " + resource);
} }
return readerFromStream(stream); AbstractConfigObject merged = SimpleConfigObject.empty(origin);
while (e.hasMoreElements()) {
URL url = e.nextElement();
ConfigOrigin elementOrigin = ((SimpleConfigOrigin) origin).addURL(url);
AbstractConfigValue v;
// it's tempting to use ParseableURL here but it would be wrong
// because the wrong relativeTo() would be used for includes.
InputStream stream = url.openStream();
try {
Reader reader = readerFromStream(stream);
stream = null; // reader now owns it
try {
// parse in "raw" mode which will throw any IOException
// from here.
v = rawParseValue(reader, elementOrigin, finalOptions);
} finally {
reader.close();
}
} finally {
// stream is null if the reader owns it
if (stream != null)
stream.close();
}
merged = merged.withFallback(v);
}
return merged;
} }
@Override @Override
@ -436,48 +449,76 @@ public abstract class Parseable implements ConfigParseable {
return syntaxFromExtension(resource); return syntaxFromExtension(resource);
} }
@Override static String parent(String resource) {
ConfigParseable relativeTo(String filename) { int i = resource.lastIndexOf('/');
// not using File.isAbsolute because resource paths always use '/' if (i < 0) {
// (?)
if (filename.startsWith("/"))
return null; return null;
} else {
return resource.substring(0, i);
}
}
@Override
ConfigParseable relativeTo(String sibling) {
// here we want to build a new resource name and let // here we want to build a new resource name and let
// the class loader have it, rather than getting the // the class loader have it, rather than getting the
// url with getResource() and relativizing to that url. // url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to // This is needed in case the class loader is going to
// search a classpath. // search a classpath.
File parent = new File(resource).getParentFile(); String parent = parent(resource);
if (parent == null) if (parent == null)
return newResource(klass, "/" + filename, options() return newResources(loader, sibling, options()
.setOriginDescription(null)); .setOriginDescription(null));
else else
return newResource(klass, new File(parent, filename).getPath(), return newResources(loader, parent + "/" + sibling,
options().setOriginDescription(null)); options().setOriginDescription(null));
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return resource + " on classpath"; return SimpleConfigOrigin.newResource(resource);
}
@Override
public URL url() {
return klass.getResource(resource);
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(" + resource + "," return getClass().getSimpleName() + "(" + resource + ","
+ klass.getName() + loader.getClass().getSimpleName() + ")";
+ ")";
} }
} }
public static Parseable newResource(Class<?> klass, String resource, public static Parseable newResources(Class<?> klass, String resource,
ConfigParseOptions options) { ConfigParseOptions options) {
return new ParseableResource(klass, resource, options); return newResources(klass.getClassLoader(), convertResourceName(klass, resource), options);
}
// this function is supposed to emulate the difference
// between Class.getResource and ClassLoader.getResource
// (unfortunately there doesn't seem to be public API for it).
// We're using it because the Class API is more limited,
// for example it lacks getResources(). So we want to be able to
// use ClassLoader directly.
private static String convertResourceName(Class<?> klass, String resource) {
if (resource.startsWith("/")) {
// "absolute" resource, chop the slash
return resource.substring(1);
} else {
String className = klass.getName();
int i = className.lastIndexOf('.');
if (i < 0) {
// no package
return resource;
} else {
// need to be relative to the package
String packageName = className.substring(0, i);
String packagePath = packageName.replace('.', '/');
return packagePath + "/" + resource;
}
}
}
public static Parseable newResources(ClassLoader loader, String resource,
ConfigParseOptions options) {
return new ParseableResources(loader, resource, options);
} }
private final static class ParseableProperties extends Parseable { private final static class ParseableProperties extends Parseable {
@ -495,7 +536,7 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
protected AbstractConfigObject parseValue(ConfigOrigin origin, protected AbstractConfigObject rawParseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) { ConfigParseOptions finalOptions) {
return PropertiesParser.fromProperties(origin, props); return PropertiesParser.fromProperties(origin, props);
} }
@ -506,13 +547,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return "properties"; return SimpleConfigOrigin.newSimple("properties");
}
@Override
public URL url() {
return null;
} }
@Override @Override

View file

@ -110,7 +110,8 @@ final class Parser {
Token t = nextToken(); Token t = nextToken();
while (true) { while (true) {
if (Tokens.isNewline(t)) { if (Tokens.isNewline(t)) {
lineNumber = Tokens.getLineNumber(t); // newline number is the line just ended, so add one
lineNumber = Tokens.getLineNumber(t) + 1;
sawSeparatorOrNewline = true; sawSeparatorOrNewline = true;
// we want to continue to also eat // we want to continue to also eat
// a comma if there is one. // a comma if there is one.
@ -155,7 +156,7 @@ final class Parser {
return; return;
} }
// this will be a list of String and Path // this will be a list of String and SubstitutionExpression
List<Object> minimized = new ArrayList<Object>(); List<Object> minimized = new ArrayList<Object>();
// we have multiple value tokens or one unquoted text token; // we have multiple value tokens or one unquoted text token;
@ -187,7 +188,9 @@ final class Parser {
.getSubstitutionPathExpression(valueToken); .getSubstitutionPathExpression(valueToken);
Path path = parsePathExpression(expression.iterator(), Path path = parsePathExpression(expression.iterator(),
Tokens.getSubstitutionOrigin(valueToken)); Tokens.getSubstitutionOrigin(valueToken));
minimized.add(path); boolean optional = Tokens.getSubstitutionOptional(valueToken);
minimized.add(new SubstitutionExpression(path, optional));
} else { } else {
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"should not be trying to consolidate token: " "should not be trying to consolidate token: "
@ -219,8 +222,7 @@ final class Parser {
} }
private ConfigOrigin lineOrigin() { private ConfigOrigin lineOrigin() {
return new SimpleConfigOrigin(baseOrigin.description() + ": line " return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
+ lineNumber);
} }
private ConfigException parseError(String message) { private ConfigException parseError(String message) {
@ -681,7 +683,7 @@ final class Parser {
return pb.result(); return pb.result();
} }
static ConfigOrigin apiOrigin = new SimpleConfigOrigin("path parameter"); static ConfigOrigin apiOrigin = SimpleConfigOrigin.newSimple("path parameter");
static Path parsePath(String path) { static Path parsePath(String path) {
Path speculated = speculativeFastParsePath(path); Path speculated = speculativeFastParsePath(path);

View file

@ -152,7 +152,7 @@ final class Path {
} }
private void appendToStringBuilder(StringBuilder sb) { private void appendToStringBuilder(StringBuilder sb) {
if (hasFunkyChars(first)) if (hasFunkyChars(first) || first.isEmpty())
sb.append(ConfigUtil.renderJsonString(first)); sb.append(ConfigUtil.renderJsonString(first));
else else
sb.append(first); sb.append(first);

View file

@ -1,61 +0,0 @@
/**
* Copyright (C) 2011 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config.impl;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigRoot;
final class RootConfig extends SimpleConfig implements ConfigRoot {
final private Path rootPath;
RootConfig(AbstractConfigObject underlying, Path rootPath) {
super(underlying);
this.rootPath = rootPath;
}
@Override
protected RootConfig asRoot(AbstractConfigObject underlying,
Path newRootPath) {
if (newRootPath.equals(this.rootPath))
return this;
else
return new RootConfig(underlying, newRootPath);
}
@Override
public RootConfig resolve() {
return resolve(ConfigResolveOptions.defaults());
}
@Override
public RootConfig resolve(ConfigResolveOptions options) {
// if the object is already resolved then we should end up returning
// "this" here, since asRoot() should return this if the path
// is unchanged.
AbstractConfigObject resolved = resolvedObject(options);
return newRootIfObjectChanged(this, resolved);
}
@Override
public RootConfig withFallback(ConfigMergeable value) {
// this can return "this" if the withFallback does nothing
return newRootIfObjectChanged(this, super.withFallback(value).toObject());
}
Path rootPathObject() {
return rootPath;
}
@Override
public String rootPath() {
return rootPath.render();
}
@Override
public String toString() {
return "Root" + super.toString();
}
}

View file

@ -4,7 +4,9 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config; import com.typesafe.config.Config;
@ -34,7 +36,7 @@ class SimpleConfig implements Config {
} }
@Override @Override
public AbstractConfigObject toObject() { public AbstractConfigObject root() {
return object; return object;
} }
@ -43,35 +45,22 @@ class SimpleConfig implements Config {
return object.origin(); return object.origin();
} }
/** @Override
* Returns a version of this config that implements the ConfigRoot public SimpleConfig resolve() {
* interface. return resolve(ConfigResolveOptions.defaults());
*
* @return a config root
*/
RootConfig asRoot(Path rootPath) {
return asRoot(object, rootPath);
} }
// RootConfig overrides this to avoid a new object on unchanged path. @Override
protected RootConfig asRoot(AbstractConfigObject underlying, public SimpleConfig resolve(ConfigResolveOptions options) {
Path newRootPath) {
return new RootConfig(underlying, newRootPath);
}
static protected RootConfig newRootIfObjectChanged(RootConfig self, AbstractConfigObject underlying) {
if (underlying == self.object)
return self;
else
return new RootConfig(underlying, self.rootPathObject());
}
protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(object, AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
object, options); object, options);
return (AbstractConfigObject) resolved; if (resolved == object)
return this;
else
return new SimpleConfig((AbstractConfigObject) resolved);
} }
@Override @Override
public boolean hasPath(String pathExpression) { public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression); Path path = Path.newPath(pathExpression);
@ -196,13 +185,13 @@ class SimpleConfig implements Config {
} }
@Override @Override
public Long getMemorySizeInBytes(String path) { public Long getBytes(String path) {
Long size = null; Long size = null;
try { try {
size = getLong(path); size = getLong(path);
} catch (ConfigException.WrongType e) { } catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path); ConfigValue v = find(path, ConfigValueType.STRING, path);
size = parseMemorySizeInBytes((String) v.unwrapped(), size = parseBytes((String) v.unwrapped(),
v.origin(), path); v.origin(), path);
} }
return size; return size;
@ -346,7 +335,7 @@ class SimpleConfig implements Config {
l.add(((Number) v.unwrapped()).longValue()); l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) { } else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped(); String s = (String) v.unwrapped();
Long n = parseMemorySizeInBytes(s, v.origin(), path); Long n = parseBytes(s, v.origin(), path);
l.add(n); l.add(n);
} else { } else {
throw new ConfigException.WrongType(v.origin(), path, throw new ConfigException.WrongType(v.origin(), path,
@ -509,23 +498,87 @@ class SimpleConfig implements Config {
} }
private static enum MemoryUnit { private static enum MemoryUnit {
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES( BYTES("", 1024, 0),
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
int bytes; KILOBYTES("kilo", 1000, 1),
MEGABYTES("mega", 1000, 2),
GIGABYTES("giga", 1000, 3),
TERABYTES("tera", 1000, 4),
PETABYTES("peta", 1000, 5),
EXABYTES("exa", 1000, 6),
ZETTABYTES("zetta", 1000, 7),
YOTTABYTES("yotta", 1000, 8),
MemoryUnit(int bytes) { KIBIBYTES("kibi", 1024, 1),
MEBIBYTES("mebi", 1024, 2),
GIBIBYTES("gibi", 1024, 3),
TEBIBYTES("tebi", 1024, 4),
PEBIBYTES("pebi", 1024, 5),
EXBIBYTES("exbi", 1024, 6),
ZEBIBYTES("zebi", 1024, 7),
YOBIBYTES("yobi", 1024, 8);
final String prefix;
final int powerOf;
final int power;
final long bytes;
MemoryUnit(String prefix, int powerOf, int power) {
this.prefix = prefix;
this.powerOf = powerOf;
this.power = power;
int i = power;
long bytes = 1;
while (i > 0) {
bytes *= powerOf;
--i;
}
this.bytes = bytes; this.bytes = bytes;
} }
private static Map<String, MemoryUnit> makeUnitsMap() {
Map<String, MemoryUnit> map = new HashMap<String, MemoryUnit>();
for (MemoryUnit unit : MemoryUnit.values()) {
map.put(unit.prefix + "byte", unit);
map.put(unit.prefix + "bytes", unit);
if (unit.prefix.length() == 0) {
map.put("b", unit);
map.put("B", unit);
map.put("", unit); // no unit specified means bytes
} else {
String first = unit.prefix.substring(0, 1);
String firstUpper = first.toUpperCase();
if (unit.powerOf == 1024) {
map.put(first, unit); // 512m
map.put(firstUpper, unit); // 512M
map.put(firstUpper + "i", unit); // 512Mi
map.put(firstUpper + "iB", unit); // 512MiB
} else if (unit.powerOf == 1000) {
if (unit.power == 1) {
map.put(first + "B", unit); // 512kB
} else {
map.put(firstUpper + "B", unit); // 512MB
}
} else {
throw new RuntimeException("broken MemoryUnit enum");
}
}
}
return map;
}
private static Map<String, MemoryUnit> unitsMap = makeUnitsMap();
static MemoryUnit parseUnit(String unit) {
return unitsMap.get(unit);
}
} }
/** /**
* Parses a memory-size string. If no units are specified in the string, it * Parses a size-in-bytes string. If no units are specified in the string,
* is assumed to be in bytes. The returned value is in bytes. The purpose of * it is assumed to be in bytes. The returned value is in bytes. The purpose
* this function is to implement the memory-size-related methods in the * of this function is to implement the size-in-bytes-related methods in the
* ConfigObject interface. The units parsed are interpreted as powers of * Config interface.
* two, that is, the convention for memory rather than the convention for
* disk space.
* *
* @param input * @param input
* the string to parse * the string to parse
@ -537,19 +590,12 @@ class SimpleConfig implements Config {
* @throws ConfigException * @throws ConfigException
* if string is invalid * if string is invalid
*/ */
public static long parseMemorySizeInBytes(String input, public static long parseBytes(String input, ConfigOrigin originForException,
ConfigOrigin originForException, String pathForException) { String pathForException) {
String s = ConfigUtil.unicodeTrim(input); String s = ConfigUtil.unicodeTrim(input);
String unitStringMaybePlural = getUnits(s); String unitString = getUnits(s);
String unitString; String numberString = ConfigUtil.unicodeTrim(s.substring(0,
if (unitStringMaybePlural.endsWith("s")) s.length() - unitString.length()));
unitString = unitStringMaybePlural.substring(0,
unitStringMaybePlural.length() - 1);
else
unitString = unitStringMaybePlural;
String unitStringLower = unitString.toLowerCase();
String numberString = ConfigUtil.unicodeTrim(s.substring(0, s.length()
- unitStringMaybePlural.length()));
// this would be caught later anyway, but the error message // this would be caught later anyway, but the error message
// is more helpful if we check it here. // is more helpful if we check it here.
@ -558,40 +604,197 @@ class SimpleConfig implements Config {
pathForException, "No number in size-in-bytes value '" pathForException, "No number in size-in-bytes value '"
+ input + "'"); + input + "'");
MemoryUnit units = null; MemoryUnit units = MemoryUnit.parseUnit(unitString);
// the short abbreviations are case-insensitive but you can't write the if (units == null) {
// long form words in all caps. throw new ConfigException.BadValue(originForException, pathForException,
if (unitString.equals("") || unitStringLower.equals("b") "Could not parse size-in-bytes unit '" + unitString
|| unitString.equals("byte")) { + "' (try k, K, kB, KiB, kilobytes, kibibytes)");
units = MemoryUnit.BYTES;
} else if (unitStringLower.equals("k") || unitString.equals("kilobyte")) {
units = MemoryUnit.KILOBYTES;
} else if (unitStringLower.equals("m") || unitString.equals("megabyte")) {
units = MemoryUnit.MEGABYTES;
} else if (unitStringLower.equals("g") || unitString.equals("gigabyte")) {
units = MemoryUnit.GIGABYTES;
} else if (unitStringLower.equals("t") || unitString.equals("terabyte")) {
units = MemoryUnit.TERABYTES;
} else {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse size unit '"
+ unitStringMaybePlural + "' (try b, k, m, g, t)");
} }
try { try {
// if the string is purely digits, parse as an integer to avoid // if the string is purely digits, parse as an integer to avoid
// possible precision loss; // possible precision loss; otherwise as a double.
// otherwise as a double.
if (numberString.matches("[0-9]+")) { if (numberString.matches("[0-9]+")) {
return Long.parseLong(numberString) * units.bytes; return Long.parseLong(numberString) * units.bytes;
} else { } else {
return (long) (Double.parseDouble(numberString) * units.bytes); return (long) (Double.parseDouble(numberString) * units.bytes);
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException, throw new ConfigException.BadValue(originForException, pathForException,
pathForException, "Could not parse memory size number '" "Could not parse size-in-bytes number '" + numberString + "'");
+ numberString + "'"); }
}
private AbstractConfigValue peekPath(Path path) {
return root().peekPath(path);
}
private static void addProblem(List<ConfigException.ValidationProblem> accumulator, Path path,
ConfigOrigin origin, String problem) {
accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem));
}
private static String getDesc(ConfigValue refValue) {
if (refValue instanceof AbstractConfigObject) {
AbstractConfigObject obj = (AbstractConfigObject) refValue;
if (obj.isEmpty())
return "object";
else
return "object with keys " + obj.keySet();
} else if (refValue instanceof SimpleConfigList) {
return "list";
} else {
return refValue.valueType().name().toLowerCase();
}
}
private static void addMissing(List<ConfigException.ValidationProblem> accumulator,
ConfigValue refValue, Path path, ConfigOrigin origin) {
addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: "
+ getDesc(refValue));
}
private static void addWrongType(List<ConfigException.ValidationProblem> accumulator,
ConfigValue refValue, AbstractConfigValue actual, Path path) {
addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render()
+ "', expecting: " + getDesc(refValue) + " but got: "
+ getDesc(actual));
}
private static boolean couldBeNull(AbstractConfigValue v) {
return DefaultTransformer.transform(v, ConfigValueType.NULL)
.valueType() == ConfigValueType.NULL;
}
private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) {
if (couldBeNull((AbstractConfigValue) reference) || couldBeNull(value)) {
// we allow any setting to be null
return true;
} else if (reference instanceof AbstractConfigObject) {
if (value instanceof AbstractConfigObject) {
return true;
} else {
return false;
}
} else if (reference instanceof SimpleConfigList) {
if (value instanceof SimpleConfigList) {
return true;
} else {
return false;
}
} else if (reference instanceof ConfigString) {
// assume a string could be gotten as any non-collection type;
// allows things like getMilliseconds including domain-specific
// interpretations of strings
return true;
} else if (value instanceof ConfigString) {
// assume a string could be gotten as any non-collection type
return true;
} else {
if (reference.valueType() == value.valueType()) {
return true;
} else {
return false;
}
}
}
// path is null if we're at the root
private static void checkValidObject(Path path, AbstractConfigObject reference,
AbstractConfigObject value,
List<ConfigException.ValidationProblem> accumulator) {
for (Map.Entry<String, ConfigValue> entry : reference.entrySet()) {
String key = entry.getKey();
Path childPath;
if (path != null)
childPath = Path.newKey(key).prepend(path);
else
childPath = Path.newKey(key);
AbstractConfigValue v = value.get(key);
if (v == null) {
addMissing(accumulator, entry.getValue(), childPath, value.origin());
} else {
checkValid(childPath, entry.getValue(), v, accumulator);
}
}
}
private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value,
List<ConfigException.ValidationProblem> accumulator) {
// Unmergeable is supposed to be impossible to encounter in here
// because we check for resolve status up front.
if (haveCompatibleTypes(reference, value)) {
if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) {
checkValidObject(path, (AbstractConfigObject) reference,
(AbstractConfigObject) value, accumulator);
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) {
SimpleConfigList listRef = (SimpleConfigList) reference;
SimpleConfigList listValue = (SimpleConfigList) value;
if (listRef.isEmpty() || listValue.isEmpty()) {
// can't verify type, leave alone
} else {
AbstractConfigValue refElement = listRef.get(0);
for (ConfigValue elem : listValue) {
AbstractConfigValue e = (AbstractConfigValue) elem;
if (!haveCompatibleTypes(refElement, e)) {
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
+ "' contains wrong value type, expecting list of "
+ getDesc(refElement) + " but got element of type "
+ getDesc(e));
// don't add a problem for every last array element
break;
}
}
}
}
} else {
addWrongType(accumulator, reference, value, path);
}
}
@Override
public void checkValid(Config reference, String... restrictToPaths) {
SimpleConfig ref = (SimpleConfig) reference;
// unresolved reference config is a bug in the caller of checkValid
if (ref.root().resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"do not call checkValid() with an unresolved reference config, call Config.resolve()");
// unresolved config under validation is probably a bug in something,
// but our whole goal here is to check for bugs in this config, so
// BugOrBroken is not the appropriate exception.
if (root().resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.NotResolved(
"config has unresolved substitutions; must call Config.resolve()");
// Now we know that both reference and this config are resolved
List<ConfigException.ValidationProblem> problems = new ArrayList<ConfigException.ValidationProblem>();
if (restrictToPaths.length == 0) {
checkValidObject(null, ref.root(), root(), problems);
} else {
for (String p : restrictToPaths) {
Path path = Path.newPath(p);
AbstractConfigValue refValue = ref.peekPath(path);
if (refValue != null) {
AbstractConfigValue child = peekPath(path);
if (child != null) {
checkValid(path, refValue, child, problems);
} else {
addMissing(problems, refValue, path, origin());
}
}
}
}
if (!problems.isEmpty()) {
throw new ConfigException.ValidationFailed(problems);
} }
} }
} }

View file

@ -9,7 +9,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigResolveOptions;
@ -68,8 +67,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
// once the new list is created, all elements // once the new list is created, all elements
// have to go in it. // have to go in it. if modifyChild returned
if (changed != null) { // null, we drop that element.
if (changed != null && modified != null) {
changed.add(modified); changed.add(modified);
} }
@ -77,9 +77,6 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
if (changed != null) { if (changed != null) {
if (changed.size() != value.size())
throw new ConfigException.BugOrBroken(
"substituted list's size doesn't match");
return new SimpleConfigList(origin(), changed, newResolveStatus); return new SimpleConfigList(origin(), changed, newResolveStatus);
} else { } else {
return this; return this;
@ -135,18 +132,34 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, boolean formatted) {
StringBuilder sb = new StringBuilder(); if (value.isEmpty()) {
sb.append(valueType().name()); sb.append("[]");
sb.append("("); } else {
for (ConfigValue e : value) { sb.append("[");
sb.append(e.toString()); if (formatted)
sb.append(","); sb.append('\n');
for (AbstractConfigValue v : value) {
if (formatted) {
indent(sb, indent + 1);
sb.append("# ");
sb.append(v.origin().description());
sb.append("\n");
indent(sb, indent + 1);
}
v.render(sb, indent + 1, formatted);
sb.append(",");
if (formatted)
sb.append('\n');
}
sb.setLength(sb.length() - 1); // chop or newline
if (formatted) {
sb.setLength(sb.length() - 1); // also chop comma
sb.append('\n');
indent(sb, indent);
}
sb.append("]");
} }
if (!value.isEmpty())
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
@Override @Override
@ -160,7 +173,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
@Override @Override
public ConfigValue get(int index) { public AbstractConfigValue get(int index) {
return value.get(index); return value.get(index);
} }

View file

@ -112,8 +112,8 @@ final class SimpleConfigObject extends AbstractConfigObject {
} }
final private static String EMPTY_NAME = "empty config"; final private static String EMPTY_NAME = "empty config";
final private static SimpleConfigObject emptyInstance = empty(new SimpleConfigOrigin( final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin
EMPTY_NAME)); .newSimple(EMPTY_NAME));
final static SimpleConfigObject empty() { final static SimpleConfigObject empty() {
return emptyInstance; return emptyInstance;
@ -128,7 +128,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
} }
final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) {
return new SimpleConfigObject(new SimpleConfigOrigin( return new SimpleConfigObject(SimpleConfigOrigin.newSimple(
baseOrigin.description() + " (not found)"), baseOrigin.description() + " (not found)"),
Collections.<String, AbstractConfigValue> emptyMap()); Collections.<String, AbstractConfigValue> emptyMap());
} }

View file

@ -3,26 +3,101 @@
*/ */
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
// it would be cleaner to have a class hierarchy for various origin types,
// but was hoping this would be enough simpler to be a little messy. eh.
final class SimpleConfigOrigin implements ConfigOrigin { final class SimpleConfigOrigin implements ConfigOrigin {
final private String description; final private String description;
final private int lineNumber;
final private int endLineNumber;
final private OriginType originType;
final private String urlOrNull;
SimpleConfigOrigin(String description) { protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber,
OriginType originType,
String urlOrNull) {
this.description = description; this.description = description;
this.lineNumber = lineNumber;
this.endLineNumber = endLineNumber;
this.originType = originType;
this.urlOrNull = urlOrNull;
}
static SimpleConfigOrigin newSimple(String description) {
return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null);
}
static SimpleConfigOrigin newFile(String filename) {
String url;
try {
url = (new File(filename)).toURI().toURL().toExternalForm();
} catch (MalformedURLException e) {
url = null;
}
return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url);
}
static SimpleConfigOrigin newURL(URL url) {
String u = url.toExternalForm();
return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u);
}
static SimpleConfigOrigin newResource(String resource, URL url) {
return new SimpleConfigOrigin(resource, -1, -1, OriginType.RESOURCE,
url != null ? url.toExternalForm() : null);
}
static SimpleConfigOrigin newResource(String resource) {
return newResource(resource, null);
}
SimpleConfigOrigin setLineNumber(int lineNumber) {
if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) {
return this;
} else {
return new SimpleConfigOrigin(this.description, lineNumber, lineNumber,
this.originType, this.urlOrNull);
}
}
SimpleConfigOrigin addURL(URL url) {
return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType,
url != null ? url.toExternalForm() : null);
} }
@Override @Override
public String description() { public String description() {
return description; // not putting the URL in here for files and resources, because people
// parsing "file: line" syntax would hit the ":" in the URL.
if (lineNumber < 0) {
return description;
} else if (endLineNumber == lineNumber) {
return description + ": " + lineNumber;
} else {
return description + ": " + lineNumber + "-" + endLineNumber;
}
} }
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (other instanceof SimpleConfigOrigin) { if (other instanceof SimpleConfigOrigin) {
return this.description SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other;
.equals(((SimpleConfigOrigin) other).description);
return this.description.equals(otherOrigin.description)
&& this.lineNumber == otherOrigin.lineNumber
&& this.endLineNumber == otherOrigin.endLineNumber
&& this.originType == otherOrigin.originType
&& ConfigUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull);
} else { } else {
return false; return false;
} }
@ -30,11 +105,201 @@ final class SimpleConfigOrigin implements ConfigOrigin {
@Override @Override
public int hashCode() { public int hashCode() {
return description.hashCode(); int h = 41 * (41 + description.hashCode());
h = 41 * (h + lineNumber);
h = 41 * (h + endLineNumber);
h = 41 * (h + originType.hashCode());
if (urlOrNull != null)
h = 41 * (h + urlOrNull.hashCode());
return h;
} }
@Override @Override
public String toString() { public String toString() {
return "ConfigOrigin(" + description + ")"; // the url is only really useful on top of description for resources
if (originType == OriginType.RESOURCE && urlOrNull != null) {
return "ConfigOrigin(" + description + "," + urlOrNull + ")";
} else {
return "ConfigOrigin(" + description + ")";
}
}
@Override
public String filename() {
if (originType == OriginType.FILE) {
return description;
} else if (urlOrNull != null) {
URL url;
try {
url = new URL(urlOrNull);
} catch (MalformedURLException e) {
return null;
}
if (url.getProtocol().equals("file")) {
return url.getFile();
} else {
return null;
}
} else {
return null;
}
}
@Override
public URL url() {
if (urlOrNull == null) {
return null;
} else {
try {
return new URL(urlOrNull);
} catch (MalformedURLException e) {
return null;
}
}
}
@Override
public String resource() {
if (originType == OriginType.RESOURCE) {
return description;
} else {
return null;
}
}
@Override
public int lineNumber() {
return lineNumber;
}
static final String MERGE_OF_PREFIX = "merge of ";
private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) {
String mergedDesc;
int mergedStartLine;
int mergedEndLine;
OriginType mergedType;
if (a.originType == b.originType) {
mergedType = a.originType;
} else {
mergedType = OriginType.GENERIC;
}
// first use the "description" field which has no line numbers
// cluttering it.
String aDesc = a.description;
String bDesc = b.description;
if (aDesc.startsWith(MERGE_OF_PREFIX))
aDesc = aDesc.substring(MERGE_OF_PREFIX.length());
if (bDesc.startsWith(MERGE_OF_PREFIX))
bDesc = bDesc.substring(MERGE_OF_PREFIX.length());
if (aDesc.equals(bDesc)) {
mergedDesc = aDesc;
if (a.lineNumber < 0)
mergedStartLine = b.lineNumber;
else if (b.lineNumber < 0)
mergedStartLine = a.lineNumber;
else
mergedStartLine = Math.min(a.lineNumber, b.lineNumber);
mergedEndLine = Math.max(a.endLineNumber, b.endLineNumber);
} else {
// this whole merge song-and-dance was intended to avoid this case
// whenever possible, but we've lost. Now we have to lose some
// structured information and cram into a string.
// description() method includes line numbers, so use it instead
// of description field.
String aFull = a.description();
String bFull = b.description();
if (aFull.startsWith(MERGE_OF_PREFIX))
aFull = aFull.substring(MERGE_OF_PREFIX.length());
if (bFull.startsWith(MERGE_OF_PREFIX))
bFull = bFull.substring(MERGE_OF_PREFIX.length());
mergedDesc = MERGE_OF_PREFIX + aFull + "," + bFull;
mergedStartLine = -1;
mergedEndLine = -1;
}
String mergedURL;
if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) {
mergedURL = a.urlOrNull;
} else {
mergedURL = null;
}
return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType,
mergedURL);
}
private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) {
int count = 0;
if (a.originType == b.originType)
count += 1;
if (a.description.equals(b.description)) {
count += 1;
// only count these if the description field (which is the file
// or resource name) also matches.
if (a.lineNumber == b.lineNumber)
count += 1;
if (a.endLineNumber == b.endLineNumber)
count += 1;
if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull))
count += 1;
}
return count;
}
// this picks the best pair to merge, because the pair has the most in
// common. we want to merge two lines in the same file rather than something
// else with one of the lines; because two lines in the same file can be
// better consolidated.
private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b,
SimpleConfigOrigin c) {
if (similarity(a, b) >= similarity(b, c)) {
return mergeTwo(mergeTwo(a, b), c);
} else {
return mergeTwo(a, mergeTwo(b, c));
}
}
static ConfigOrigin mergeOrigins(Collection<? extends ConfigOrigin> stack) {
if (stack.isEmpty()) {
throw new ConfigException.BugOrBroken("can't merge empty list of origins");
} else if (stack.size() == 1) {
return stack.iterator().next();
} else if (stack.size() == 2) {
Iterator<? extends ConfigOrigin> i = stack.iterator();
return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next());
} else {
List<SimpleConfigOrigin> remaining = new ArrayList<SimpleConfigOrigin>();
for (ConfigOrigin o : stack) {
remaining.add((SimpleConfigOrigin) o);
}
while (remaining.size() > 2) {
SimpleConfigOrigin c = remaining.get(remaining.size() - 1);
remaining.remove(remaining.size() - 1);
SimpleConfigOrigin b = remaining.get(remaining.size() - 1);
remaining.remove(remaining.size() - 1);
SimpleConfigOrigin a = remaining.get(remaining.size() - 1);
remaining.remove(remaining.size() - 1);
SimpleConfigOrigin merged = mergeThree(a, b, c);
remaining.add(merged);
}
// should be down to either 1 or 2
return mergeOrigins(remaining);
}
} }
} }

View file

@ -0,0 +1,46 @@
package com.typesafe.config.impl;
final class SubstitutionExpression {
final private Path path;
final private boolean optional;
SubstitutionExpression(Path path, boolean optional) {
this.path = path;
this.optional = optional;
}
Path path() {
return path;
}
boolean optional() {
return optional;
}
SubstitutionExpression changePath(Path newPath) {
return new SubstitutionExpression(newPath, optional);
}
@Override
public String toString() {
return "${" + (optional ? "?" : "") + path.render() + "}";
}
@Override
public boolean equals(Object other) {
if (other instanceof SubstitutionExpression) {
SubstitutionExpression otherExp = (SubstitutionExpression) other;
return otherExp.path.equals(this.path) && otherExp.optional == this.optional;
} else {
return false;
}
}
@Override
public int hashCode() {
int h = 41 * (41 + path.hashCode());
h = 41 * (h + (optional ? 1 : 0));
return h;
}
}

View file

@ -16,6 +16,8 @@ import com.typesafe.config.ConfigResolveOptions;
*/ */
final class SubstitutionResolver { final class SubstitutionResolver {
final private AbstractConfigObject root; final private AbstractConfigObject root;
// note that we can resolve things to undefined (represented as Java null,
// rather than ConfigNull) so this map can have null values.
final private Map<AbstractConfigValue, AbstractConfigValue> memos; final private Map<AbstractConfigValue, AbstractConfigValue> memos;
SubstitutionResolver(AbstractConfigObject root) { SubstitutionResolver(AbstractConfigObject root) {
@ -31,9 +33,11 @@ final class SubstitutionResolver {
} else { } else {
AbstractConfigValue resolved = original.resolveSubstitutions(this, AbstractConfigValue resolved = original.resolveSubstitutions(this,
depth, options); depth, options);
if (resolved.resolveStatus() != ResolveStatus.RESOLVED) if (resolved != null) {
throw new ConfigException.BugOrBroken( if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
"resolveSubstitutions() did not give us a resolved object"); throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
}
memos.put(original, resolved); memos.put(original, resolved);
return resolved; return resolved;
} }

View file

@ -219,8 +219,7 @@ final class Tokenizer {
private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin, private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin,
int lineNumber) { int lineNumber) {
return new SimpleConfigOrigin(baseOrigin.description() + ": line " return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
+ lineNumber);
} }
// chars JSON allows a number to start with // chars JSON allows a number to start with
@ -228,7 +227,7 @@ final class Tokenizer {
// chars JSON allows to be part of a number // chars JSON allows to be part of a number
static final String numberChars = "0123456789eE+-."; static final String numberChars = "0123456789eE+-.";
// chars that stop an unquoted string // chars that stop an unquoted string
static final String notInUnquotedText = "$\"{}[]:=,\\+#"; static final String notInUnquotedText = "$\"{}[]:=,+#`^?!@*&\\";
// The rules here are intended to maximize convenience while // The rules here are intended to maximize convenience while
// avoiding confusion with real valid JSON. Basically anything // avoiding confusion with real valid JSON. Basically anything
@ -404,6 +403,14 @@ final class Tokenizer {
throw parseError("'$' not followed by {"); throw parseError("'$' not followed by {");
} }
boolean optional = false;
c = nextCharSkippingComments();
if (c == '?') {
optional = true;
} else {
putBack(c);
}
WhitespaceSaver saver = new WhitespaceSaver(); WhitespaceSaver saver = new WhitespaceSaver();
List<Token> expression = new ArrayList<Token>(); List<Token> expression = new ArrayList<Token>();
@ -428,7 +435,7 @@ final class Tokenizer {
} }
} while (true); } while (true);
return Tokens.newSubstitution(origin, expression); return Tokens.newSubstitution(origin, optional, expression);
} }
private Token pullNextToken(WhitespaceSaver saver) { private Token pullNextToken(WhitespaceSaver saver) {

View file

@ -125,11 +125,13 @@ final class Tokens {
// This is not a Value, because it requires special processing // This is not a Value, because it requires special processing
static private class Substitution extends Token { static private class Substitution extends Token {
final private ConfigOrigin origin; final private ConfigOrigin origin;
final private boolean optional;
final private List<Token> value; final private List<Token> value;
Substitution(ConfigOrigin origin, List<Token> expression) { Substitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
super(TokenType.SUBSTITUTION); super(TokenType.SUBSTITUTION);
this.origin = origin; this.origin = origin;
this.optional = optional;
this.value = expression; this.value = expression;
} }
@ -137,6 +139,10 @@ final class Tokens {
return origin; return origin;
} }
boolean optional() {
return optional;
}
List<Token> value() { List<Token> value() {
return value; return value;
} }
@ -237,6 +243,15 @@ final class Tokens {
} }
} }
static boolean getSubstitutionOptional(Token token) {
if (token instanceof Substitution) {
return ((Substitution) token).optional();
} else {
throw new ConfigException.BugOrBroken("tried to get substitution optionality from "
+ token);
}
}
final static Token START = new Token(TokenType.START); final static Token START = new Token(TokenType.START);
final static Token END = new Token(TokenType.END); final static Token END = new Token(TokenType.END);
final static Token COMMA = new Token(TokenType.COMMA); final static Token COMMA = new Token(TokenType.COMMA);
@ -255,8 +270,8 @@ final class Tokens {
return new UnquotedText(origin, s); return new UnquotedText(origin, s);
} }
static Token newSubstitution(ConfigOrigin origin, List<Token> expression) { static Token newSubstitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
return new Substitution(origin, expression); return new Substitution(origin, optional, expression);
} }
static Token newValue(AbstractConfigValue value) { static Token newValue(AbstractConfigValue value) {

View file

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<!--
Copyright (C) 2011 Typesafe Inc. <http://typesafe.com>
-->
</head>
<body bgcolor="white">
<p>
An API for loading and using configuration files, see <a href="https://github.com/havocp/config/">the project site</a>
for more information.
</p>
<p>
Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use
it with methods in the {@link com.typesafe.config.Config} interface.
</p>
<p>
An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place
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.
</p>
<p>
A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a
{@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided,
call {@link com.typesafe.config.ConfigFactory#load()}
to get the default one. Typically a library might offer two constructors, one with a <code>Config</code> parameter
and one which uses {@link com.typesafe.config.ConfigFactory#load()}.
</p>
<p>
You can find an example app and library <a href="https://github.com/havocp/config/tree/master/examples">on GitHub</a>.
</p>
</body>
</html>

View file

@ -1,32 +0,0 @@
############################################
# Akka Serialization Reference Config File #
############################################
# This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf.
akka {
actor {
# Entries for pluggable serializers and their bindings. If a binding for a specific class is not found,
# then the default serializer (Java serialization) is used.
#
serializers {
# java = "akka.serialization.JavaSerializer"
# proto = "akka.testing.ProtobufSerializer"
# sjson = "akka.testing.SJSONSerializer"
default = "akka.serialization.JavaSerializer"
}
# serialization-bindings {
# java = ["akka.serialization.SerializeSpec$Address",
# "akka.serialization.MyJavaSerializableActor",
# "akka.serialization.MyStatelessActorWithMessagesInMailbox",
# "akka.serialization.MyActorWithProtobufMessagesInMailbox"]
# sjson = ["akka.serialization.SerializeSpec$Person"]
# proto = ["com.google.protobuf.Message",
# "akka.actor.ProtobufProtocol$MyMessage"]
# }
}
}

View file

@ -16,7 +16,7 @@ akka {
loglevel = "INFO" # Options: ERROR, WARNING, INFO, DEBUG loglevel = "INFO" # Options: ERROR, WARNING, INFO, DEBUG
# this level is used by the configured loggers (see "event-handlers") as soon # this level is used by the configured loggers (see "event-handlers") as soon
# as they have been started; before that, see "stdout-loglevel" # as they have been started; before that, see "stdout-loglevel"
stdout-loglevel = "INFO" # Loglevel for the very basic logger activated during AkkaApplication startup stdout-loglevel = "WARNING" # Loglevel for the very basic logger activated during AkkaApplication startup
# FIXME: Is there any sensible reason why we have 2 different log levels? # FIXME: Is there any sensible reason why we have 2 different log levels?
logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started. logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started.
@ -125,6 +125,26 @@ akka {
fsm = off # enable DEBUG logging of all LoggingFSMs for events, transitions and timers fsm = off # enable DEBUG logging of all LoggingFSMs for events, transitions and timers
event-stream = off # enable DEBUG logging of subscription changes on the eventStream event-stream = off # enable DEBUG logging of subscription changes on the eventStream
} }
# Entries for pluggable serializers and their bindings. If a binding for a specific class is not found,
# then the default serializer (Java serialization) is used.
#
serializers {
# java = "akka.serialization.JavaSerializer"
# proto = "akka.testing.ProtobufSerializer"
# sjson = "akka.testing.SJSONSerializer"
default = "akka.serialization.JavaSerializer"
}
# serialization-bindings {
# java = ["akka.serialization.SerializeSpec$Address",
# "akka.serialization.MyJavaSerializableActor",
# "akka.serialization.MyStatelessActorWithMessagesInMailbox",
# "akka.serialization.MyActorWithProtobufMessagesInMailbox"]
# sjson = ["akka.serialization.SerializeSpec$Person"]
# proto = ["com.google.protobuf.Message",
# "akka.actor.ProtobufProtocol$MyMessage"]
# }
} }
@ -142,6 +162,5 @@ akka {
tickDuration = 100ms tickDuration = 100ms
ticksPerWheel = 512 ticksPerWheel = 512
} }
} }

View file

@ -13,9 +13,10 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.NANOSECONDS import java.util.concurrent.TimeUnit.NANOSECONDS
import java.io.File import java.io.File
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigResolveOptions
import com.typesafe.config.ConfigException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import akka.util.{ Helpers, Duration, ReflectiveAccess } import akka.util.{ Helpers, Duration, ReflectiveAccess }
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -43,17 +44,32 @@ object ActorSystem {
def create(name: String, config: Config): ActorSystem = apply(name, config) def create(name: String, config: Config): ActorSystem = apply(name, config)
def apply(name: String, config: Config): ActorSystem = new ActorSystemImpl(name, config).start() def apply(name: String, config: Config): ActorSystem = new ActorSystemImpl(name, config).start()
/**
* Uses the standard default Config from ConfigFactory.load(), since none is provided.
*/
def create(name: String): ActorSystem = apply(name) def create(name: String): ActorSystem = apply(name)
def apply(name: String): ActorSystem = apply(name, DefaultConfigurationLoader.defaultConfig) /**
* Uses the standard default Config from ConfigFactory.load(), since none is provided.
*/
def apply(name: String): ActorSystem = apply(name, ConfigFactory.load())
def create(): ActorSystem = apply() def create(): ActorSystem = apply()
def apply(): ActorSystem = apply("default") def apply(): ActorSystem = apply("default")
class Settings(cfg: Config) { class Settings(cfg: Config) {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-actor-reference.conf", // Verify that the Config is sane and has our reference config.
ConfigParseOptions.defaults.setAllowMissing(false)) val config: Config =
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-actor").withFallback(cfg).withFallback(referenceConfig).resolve() try {
cfg.checkValid(ConfigFactory.defaultReference, "akka")
cfg
} catch {
case e: ConfigException
// try again with added defaultReference
val cfg2 = cfg.withFallback(ConfigFactory.defaultReference)
cfg2.checkValid(ConfigFactory.defaultReference, "akka")
cfg2
}
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import config._ import config._
@ -102,9 +118,13 @@ object ActorSystem {
} }
object DefaultConfigurationLoader { // TODO move to migration kit
object OldConfigurationLoader {
val defaultConfig: Config = fromProperties orElse fromClasspath orElse fromHome getOrElse emptyConfig val defaultConfig: Config = {
val cfg = fromProperties orElse fromClasspath orElse fromHome getOrElse emptyConfig
cfg.withFallback(ConfigFactory.defaultReference).resolve(ConfigResolveOptions.defaults)
}
// file extensions (.conf, .json, .properties), are handled by parseFileAnySyntax // file extensions (.conf, .json, .properties), are handled by parseFileAnySyntax
val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka") val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka")
@ -130,7 +150,7 @@ object ActorSystem {
private def fromClasspath = try { private def fromClasspath = try {
Option(ConfigFactory.systemProperties.withFallback( Option(ConfigFactory.systemProperties.withFallback(
ConfigFactory.parseResourceAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions))) ConfigFactory.parseResourcesAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions)))
} catch { case _ None } } catch { case _ None }
private def fromHome = try { private def fromHome = try {
@ -273,7 +293,7 @@ abstract class ActorSystem extends ActorRefFactory {
def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean
} }
class ActorSystemImpl(val name: String, val applicationConfig: Config) extends ActorSystem { class ActorSystemImpl(val name: String, applicationConfig: Config) extends ActorSystem {
import ActorSystem._ import ActorSystem._

View file

@ -88,7 +88,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream,
} }
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
settings.config.getConfig("akka.actor.deployment").toObject.keySet.asScala settings.config.getConfig("akka.actor.deployment").toValue.keySet.asScala
.filterNot("default" ==) .filterNot("default" ==)
.map(path pathSubstring(path)) .map(path pathSubstring(path))
.toSet.toList // toSet to force uniqueness .toSet.toList // toSet to force uniqueness

View file

@ -7,8 +7,7 @@ package akka.serialization
import akka.AkkaException import akka.AkkaException
import akka.util.ReflectiveAccess import akka.util.ReflectiveAccess
import scala.util.DynamicVariable import scala.util.DynamicVariable
import com.typesafe.config.{ ConfigRoot, ConfigParseOptions, ConfigFactory, Config } import com.typesafe.config.Config
import com.typesafe.config.Config._
import akka.config.ConfigurationException import akka.config.ConfigurationException
import akka.actor.{ Extension, ActorSystem, ActorSystemImpl } import akka.actor.{ Extension, ActorSystem, ActorSystemImpl }
@ -19,11 +18,7 @@ object Serialization {
// TODO ensure that these are always set (i.e. withValue()) when doing deserialization // TODO ensure that these are always set (i.e. withValue()) when doing deserialization
val currentSystem = new DynamicVariable[ActorSystemImpl](null) val currentSystem = new DynamicVariable[ActorSystemImpl](null)
class Settings(cfg: Config) { class Settings(val config: Config) {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-serialization-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-serialization").withFallback(cfg).withFallback(referenceConfig).resolve()
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import config._ import config._
@ -37,7 +32,7 @@ object Serialization {
hasPath(configPath) match { hasPath(configPath) match {
case false Map() case false Map()
case true case true
val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toObject.unwrapped.asScala.toMap.map { val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toValue.unwrapped.asScala.toMap.map {
case (k: String, v: java.util.Collection[_]) (k -> v.asScala.toSeq.asInstanceOf[Seq[String]]) case (k: String, v: java.util.Collection[_]) (k -> v.asScala.toSeq.asInstanceOf[Seq[String]])
case invalid throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid)) case invalid throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid))
} }
@ -47,7 +42,7 @@ object Serialization {
} }
private def toStringMap(mapConfig: Config): Map[String, String] = private def toStringMap(mapConfig: Config): Map[String, String] =
mapConfig.toObject.unwrapped.asScala.toMap.map { case (k, v) (k, v.toString) } mapConfig.toValue.unwrapped.asScala.toMap.map { case (k, v) (k, v.toString) }
} }
} }
@ -58,7 +53,7 @@ object Serialization {
class Serialization(val system: ActorSystemImpl) extends Extension { class Serialization(val system: ActorSystemImpl) extends Extension {
import Serialization._ import Serialization._
val settings = new Settings(system.applicationConfig) val settings = new Settings(system.settings.config)
//TODO document me //TODO document me
def serialize(o: AnyRef): Either[Exception, Array[Byte]] = def serialize(o: AnyRef): Either[Exception, Array[Byte]] =

View file

@ -6,7 +6,6 @@ import org.scalatest.matchers.MustMatchers
//#imports //#imports
import akka.actor.ActorSystem import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
//#imports //#imports
@ -21,7 +20,7 @@ class ConfigDocSpec extends WordSpec {
nr-of-instances = 3 nr-of-instances = 3
} }
} }
""", ConfigParseOptions.defaults) """)
val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf)) val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf))
//#custom-config //#custom-config

View file

@ -16,6 +16,8 @@ configuration files that you see below. You can specify your own configuration f
property in the reference config. You only have to define the properties that differ from the default property in the reference config. You only have to define the properties that differ from the default
configuration. configuration.
FIXME: These default locations has changed
The location of the config file to use can be specified in various ways: The location of the config file to use can be specified in various ways:
* Define the ``-Dakka.config=...`` system property parameter with a file path to configuration file. * Define the ``-Dakka.config=...`` system property parameter with a file path to configuration file.
@ -44,47 +46,42 @@ Each Akka module has a reference configuration file with the default values.
*akka-actor:* *akka-actor:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-actor-reference.conf .. literalinclude:: ../../akka-actor/src/main/resources/reference.conf
:language: none :language: none
*akka-remote:* *akka-remote:*
.. literalinclude:: ../../akka-remote/src/main/resources/akka-remote-reference.conf .. literalinclude:: ../../akka-remote/src/main/resources/reference.conf
:language: none :language: none
*akka-serialization:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-serialization-reference.conf
:language: none
*akka-testkit:* *akka-testkit:*
.. literalinclude:: ../../akka-testkit/src/main/resources/akka-testkit-reference.conf .. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf
:language: none :language: none
*akka-beanstalk-mailbox:* *akka-beanstalk-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/akka-beanstalk-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-file-mailbox:* *akka-file-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-file-mailbox/src/main/resources/akka-file-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-file-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-mongo-mailbox:* *akka-mongo-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/akka-mongo-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-redis-mailbox:* *akka-redis-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/akka-redis-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-zookeeper-mailbox:* *akka-zookeeper-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/akka-zookeeper-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/reference.conf
:language: none :language: none
A custom ``akka.conf`` might look like this:: A custom ``akka.conf`` might look like this::
@ -121,7 +118,6 @@ A custom ``akka.conf`` might look like this::
} }
} }
.. _-Dakka.mode:
Config file format Config file format
------------------ ------------------
@ -129,9 +125,13 @@ Config file format
The configuration file syntax is described in the `HOCON <https://github.com/havocp/config/blob/master/HOCON.md>`_ The configuration file syntax is described in the `HOCON <https://github.com/havocp/config/blob/master/HOCON.md>`_
specification. Note that it supports three formats; conf, json, and properties. specification. Note that it supports three formats; conf, json, and properties.
.. _-Dakka.mode:
Specifying files for different modes Specifying files for different modes
------------------------------------ ------------------------------------
FIXME: mode doesn't exist, or will it?
You can use different configuration files for different purposes by specifying a mode option, either as You can use different configuration files for different purposes by specifying a mode option, either as
``-Dakka.mode=...`` system property or as ``AKKA_MODE=...`` environment variable. For example using DEBUG log level ``-Dakka.mode=...`` system property or as ``AKKA_MODE=...`` environment variable. For example using DEBUG log level
when in development mode. Run with ``-Dakka.mode=dev`` and place the following ``akka.dev.conf`` in the root of when in development mode. Run with ``-Dakka.mode=dev`` and place the following ``akka.dev.conf`` in the root of
@ -152,6 +152,8 @@ The mode option is not used when specifying the configuration file with ``-Dakka
Including files Including files
--------------- ---------------
FIXME: The include syntax has changed
Sometimes it can be useful to include another configuration file, for example if you have one ``akka.conf`` with all Sometimes it can be useful to include another configuration file, for example if you have one ``akka.conf`` with all
environment independent settings and then override some settings for specific modes. environment independent settings and then override some settings for specific modes.
@ -165,14 +167,14 @@ akka.dev.conf:
loglevel = "DEBUG" loglevel = "DEBUG"
} }
.. _-Dakka.output.config.source: .. _-Dakka.logConfigOnStart:
Showing Configuration Source Logging of Configuration
---------------------------- ------------------------
If the system property ``akka.output.config.source`` is set to anything but If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the
null, then the source from which Akka reads its configuration is printed to the complete configuration at INFO level when the actor system is started. This is useful
console during application startup. when you are uncertain of what configuration is used.
Summary of System Properties Summary of System Properties
---------------------------- ----------------------------
@ -180,4 +182,3 @@ Summary of System Properties
* :ref:`akka.home <-Dakka.home>` (``AKKA_HOME``): where Akka searches for configuration * :ref:`akka.home <-Dakka.home>` (``AKKA_HOME``): where Akka searches for configuration
* :ref:`akka.config <-Dakka.config>`: explicit configuration file location * :ref:`akka.config <-Dakka.config>`: explicit configuration file location
* :ref:`akka.mode <-Dakka.mode>` (``AKKA_MODE``): modify configuration file name for multiple profiles * :ref:`akka.mode <-Dakka.mode>` (``AKKA_MODE``): modify configuration file name for multiple profiles
* :ref:`akka.output.config.source <-Dakka.output.config.source>`: whether to print configuration source to console

View file

@ -4,23 +4,16 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider { object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new BeanstalkMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new BeanstalkMailboxSettings(system.settings.config)
} }
class BeanstalkMailboxSettings(cfg: Config) extends Extension { class BeanstalkMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-beanstalk-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-beanstalk-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -15,8 +15,8 @@ akka {
max-items = 2147483647 max-items = 2147483647
max-item-size = 2147483647 bytes max-item-size = 2147483647 bytes
max-age = 0s max-age = 0s
max-journal-size = 16 megabytes max-journal-size = 16 MiB
max-memory-size = 128 megabytes max-memory-size = 128 MiB
max-journal-overflow = 10 max-journal-overflow = 10
max-journal-size-absolute = 9223372036854775807 bytes max-journal-size-absolute = 9223372036854775807 bytes
discard-old-when-full = on discard-old-when-full = on

View file

@ -4,36 +4,29 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object FileBasedMailboxExtension extends ExtensionId[FileBasedMailboxSettings] with ExtensionIdProvider { object FileBasedMailboxExtension extends ExtensionId[FileBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new FileBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new FileBasedMailboxSettings(system.settings.config)
} }
class FileBasedMailboxSettings(cfg: Config) extends Extension { class FileBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-file-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-file-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._
val QueuePath = getString("akka.actor.mailbox.file-based.directory-path") val QueuePath = getString("akka.actor.mailbox.file-based.directory-path")
val MaxItems = getInt("akka.actor.mailbox.file-based.max-items") val MaxItems = getInt("akka.actor.mailbox.file-based.max-items")
val MaxSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-size") val MaxSize = getBytes("akka.actor.mailbox.file-based.max-size")
val MaxItemSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-item-size") val MaxItemSize = getBytes("akka.actor.mailbox.file-based.max-item-size")
val MaxAge = Duration(getMilliseconds("akka.actor.mailbox.file-based.max-age"), MILLISECONDS) val MaxAge = Duration(getMilliseconds("akka.actor.mailbox.file-based.max-age"), MILLISECONDS)
val MaxJournalSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size") val MaxJournalSize = getBytes("akka.actor.mailbox.file-based.max-journal-size")
val MaxMemorySize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-memory-size") val MaxMemorySize = getBytes("akka.actor.mailbox.file-based.max-memory-size")
val MaxJournalOverflow = getInt("akka.actor.mailbox.file-based.max-journal-overflow") val MaxJournalOverflow = getInt("akka.actor.mailbox.file-based.max-journal-overflow")
val MaxJournalSizeAbsolute = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size-absolute") val MaxJournalSizeAbsolute = getBytes("akka.actor.mailbox.file-based.max-journal-size-absolute")
val DiscardOldWhenFull = getBoolean("akka.actor.mailbox.file-based.discard-old-when-full") val DiscardOldWhenFull = getBoolean("akka.actor.mailbox.file-based.discard-old-when-full")
val KeepJournal = getBoolean("akka.actor.mailbox.file-based.keep-journal") val KeepJournal = getBoolean("akka.actor.mailbox.file-based.keep-journal")
val SyncJournal = getBoolean("akka.actor.mailbox.file-based.sync-journal") val SyncJournal = getBoolean("akka.actor.mailbox.file-based.sync-journal")

View file

@ -4,23 +4,16 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object MongoBasedMailboxExtension extends ExtensionId[MongoBasedMailboxSettings] with ExtensionIdProvider { object MongoBasedMailboxExtension extends ExtensionId[MongoBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new MongoBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new MongoBasedMailboxSettings(system.settings.config)
} }
class MongoBasedMailboxSettings(cfg: Config) extends Extension { class MongoBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-mongo-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-mongo-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -4,21 +4,14 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.actor._ import akka.actor._
object RedisBasedMailboxExtension extends ExtensionId[RedisBasedMailboxSettings] with ExtensionIdProvider { object RedisBasedMailboxExtension extends ExtensionId[RedisBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new RedisBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new RedisBasedMailboxSettings(system.settings.config)
} }
class RedisBasedMailboxSettings(cfg: Config) extends Extension { class RedisBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-redis-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-redis-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -4,22 +4,15 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object ZooKeeperBasedMailboxExtension extends ExtensionId[ZooKeeperBasedMailboxSettings] with ExtensionIdProvider { object ZooKeeperBasedMailboxExtension extends ExtensionId[ZooKeeperBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new ZooKeeperBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new ZooKeeperBasedMailboxSettings(system.settings.config)
} }
class ZooKeeperBasedMailboxSettings(cfg: Config) extends Extension { class ZooKeeperBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-zookeeper-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-zookeeper-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -31,7 +31,7 @@ akka {
server { server {
hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty
port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA) port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA)
message-frame-size = 1048576 # Increase this if you want to be able to send messages with large payloads message-frame-size = 1 MiB # Increase this if you want to be able to send messages with large payloads
connection-timeout = 120s # Timeout duration connection-timeout = 120s # Timeout duration
require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)?
untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect. untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect.
@ -46,7 +46,7 @@ akka {
} }
reconnect-delay = 5s reconnect-delay = 5s
read-timeout = 3600s read-timeout = 3600s
message-frame-size = 1048576 message-frame-size = 1 MiB
reconnection-time-window = 600s # Maximum time window that a client should try to reconnect for reconnection-time-window = 600s # Maximum time window that a client should try to reconnect for
} }
} }

View file

@ -4,9 +4,6 @@
package akka.remote package akka.remote
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import java.net.InetAddress import java.net.InetAddress
@ -18,14 +15,10 @@ import scala.collection.JavaConverters._
object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider { object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.settings.config)
} }
class RemoteExtensionSettings(cfg: Config) extends Extension { class RemoteExtensionSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-remote-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-remote").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._
@ -57,12 +50,12 @@ class RemoteExtensionSettings(cfg: Config) extends Extension {
val ReconnectionTimeWindow = Duration(config.getMilliseconds("akka.remote.client.reconnection-time-window"), MILLISECONDS) val ReconnectionTimeWindow = Duration(config.getMilliseconds("akka.remote.client.reconnection-time-window"), MILLISECONDS)
val ReadTimeout = Duration(config.getMilliseconds("akka.remote.client.read-timeout"), MILLISECONDS) val ReadTimeout = Duration(config.getMilliseconds("akka.remote.client.read-timeout"), MILLISECONDS)
val ReconnectDelay = Duration(config.getMilliseconds("akka.remote.client.reconnect-delay"), MILLISECONDS) val ReconnectDelay = Duration(config.getMilliseconds("akka.remote.client.reconnect-delay"), MILLISECONDS)
val MessageFrameSize = config.getInt("akka.remote.client.message-frame-size") val MessageFrameSize = config.getBytes("akka.remote.client.message-frame-size").toInt
} }
class RemoteServerSettings { class RemoteServerSettings {
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
val MessageFrameSize = config.getInt("akka.remote.server.message-frame-size") val MessageFrameSize = config.getBytes("akka.remote.server.message-frame-size").toInt
val SecureCookie: Option[String] = config.getString("akka.remote.secure-cookie") match { val SecureCookie: Option[String] = config.getString("akka.remote.secure-cookie") match {
case "" None case "" None
case cookie Some(cookie) case cookie Some(cookie)

View file

@ -6,8 +6,27 @@ package akka.remote
import akka.testkit._ import akka.testkit._
import akka.actor.ActorSystemImpl import akka.actor.ActorSystemImpl
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigResolveOptions
import java.io.File
abstract class AkkaRemoteSpec extends AkkaSpec with MultiJvmSync { object AkkaRemoteSpec {
private def configParseOptions = ConfigParseOptions.defaults.setAllowMissing(false)
val testConf: Config = {
System.getProperty("akka.config") match {
case null AkkaSpec.testConf
case location
ConfigFactory.systemProperties
.withFallback(ConfigFactory.parseFileAnySyntax(new File(location), configParseOptions))
.withFallback(ConfigFactory.defaultReference).resolve(ConfigResolveOptions.defaults)
}
}
}
abstract class AkkaRemoteSpec extends AkkaSpec(AkkaRemoteSpec.testConf) with MultiJvmSync {
/** /**
* Helper function for accessing the underlying remoting. * Helper function for accessing the underlying remoting.

View file

@ -19,7 +19,7 @@ class RemoteConfigSpec extends AkkaSpec {
//akka.remote.server //akka.remote.server
getInt("akka.remote.server.port") must equal(2552) getInt("akka.remote.server.port") must equal(2552)
getInt("akka.remote.server.message-frame-size") must equal(1048576) getBytes("akka.remote.server.message-frame-size") must equal(1048576L)
getMilliseconds("akka.remote.server.connection-timeout") must equal(120 * 1000) getMilliseconds("akka.remote.server.connection-timeout") must equal(120 * 1000)
getBoolean("akka.remote.server.require-cookie") must equal(false) getBoolean("akka.remote.server.require-cookie") must equal(false)
getBoolean("akka.remote.server.untrusted-mode") must equal(false) getBoolean("akka.remote.server.untrusted-mode") must equal(false)

View file

@ -10,11 +10,10 @@ import org.scalatest.junit.JUnitRunner
import org.scalatest.matchers.MustMatchers import org.scalatest.matchers.MustMatchers
import akka.actor.ActorSystem import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import akka.testkit.AkkaSpec import akka.testkit.AkkaSpec
@RunWith(classOf[JUnitRunner]) @RunWith(classOf[JUnitRunner])
class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-stm-reference.conf", ConfigParseOptions.defaults)) { class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) {
"The default configuration file (i.e. akka-stm-reference.conf)" should { "The default configuration file (i.e. akka-stm-reference.conf)" should {
"contain all configuration properties for akka-stm that are used in code with their correct defaults" in { "contain all configuration properties for akka-stm that are used in code with their correct defaults" in {

View file

@ -4,22 +4,15 @@
package akka.testkit package akka.testkit
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor.{ ExtensionId, ActorSystem, Extension, ActorSystemImpl } import akka.actor.{ ExtensionId, ActorSystem, Extension, ActorSystemImpl }
object TestKitExtension extends ExtensionId[TestKitSettings] { object TestKitExtension extends ExtensionId[TestKitSettings] {
def createExtension(system: ActorSystemImpl): TestKitSettings = new TestKitSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl): TestKitSettings = new TestKitSettings(system.settings.config)
} }
class TestKitSettings(cfg: Config) extends Extension { class TestKitSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-testkit-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-testkit").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -13,14 +13,12 @@ import akka.util.duration._
import akka.dispatch.FutureTimeoutException import akka.dispatch.FutureTimeoutException
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
object TimingTest extends Tag("timing") object TimingTest extends Tag("timing")
object AkkaSpec { object AkkaSpec {
val testConf = val testConf = {
ActorSystem.DefaultConfigurationLoader.defaultConfig.withFallback( val cfg = ConfigFactory.parseString("""
ConfigFactory.parseString("""
akka { akka {
event-handlers = ["akka.testkit.TestEventListener"] event-handlers = ["akka.testkit.TestEventListener"]
loglevel = "WARNING" loglevel = "WARNING"
@ -32,7 +30,9 @@ object AkkaSpec {
} }
} }
} }
""", ConfigParseOptions.defaults)) """)
ConfigFactory.load(cfg)
}
def mapToConfig(map: Map[String, Any]): Config = { def mapToConfig(map: Map[String, Any]): Config = {
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
@ -64,7 +64,7 @@ abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleNam
def this(config: Config) = this(ActorSystem(getClass.getSimpleName, config.withFallback(AkkaSpec.testConf))) def this(config: Config) = this(ActorSystem(getClass.getSimpleName, config.withFallback(AkkaSpec.testConf)))
def this(s: String) = this(ConfigFactory.parseString(s, ConfigParseOptions.defaults)) def this(s: String) = this(ConfigFactory.parseString(s))
def this(configMap: Map[String, _]) = { def this(configMap: Map[String, _]) = {
this(AkkaSpec.mapToConfig(configMap)) this(AkkaSpec.mapToConfig(configMap))
@ -87,12 +87,11 @@ abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleNam
class AkkaSpecSpec extends WordSpec with MustMatchers { class AkkaSpecSpec extends WordSpec with MustMatchers {
"An AkkaSpec" must { "An AkkaSpec" must {
"terminate all actors" in { "terminate all actors" in {
import ActorSystem.DefaultConfigurationLoader.defaultConfig
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
val conf = Map( val conf = Map(
"akka.actor.debug.lifecycle" -> true, "akka.actor.debug.event-stream" -> true, "akka.actor.debug.lifecycle" -> true, "akka.actor.debug.event-stream" -> true,
"akka.loglevel" -> "DEBUG", "akka.stdout-loglevel" -> "DEBUG") "akka.loglevel" -> "DEBUG", "akka.stdout-loglevel" -> "DEBUG")
val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(defaultConfig)) val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(AkkaSpec.testConf))
val spec = new AkkaSpec(system) { val spec = new AkkaSpec(system) {
val ref = Seq(testActor, system.actorOf(Props.empty, "name")) val ref = Seq(testActor, system.actorOf(Props.empty, "name"))
} }