Merge pull request #129 from jboner/wip-config-patriknw

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:
patriknw 2011-12-01 23:53:49 -08:00
commit b70faa4e42
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.Config;
import com.typesafe.config.ConfigParseOptions;
import static org.junit.Assert.*;
@ -26,14 +25,14 @@ public class JavaExtension {
}
static class TestExtension implements Extension {
public final ActorSystemImpl system;
public TestExtension(ActorSystemImpl i) {
system = i;
}
public final ActorSystemImpl system;
public TestExtension(ActorSystemImpl i) {
system = i;
}
}
private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]",
ConfigParseOptions.defaults());
private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]");
private ActorSystem system = ActorSystem.create("JavaExtension", c);

View file

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

View file

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

View file

@ -6,13 +6,12 @@ package akka.config
import akka.testkit.AkkaSpec
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import akka.util.duration._
import akka.util.Duration
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-actor-reference.conf", ConfigParseOptions.defaults)) {
class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) {
"The default configuration file (i.e. akka-actor-reference.conf)" must {
"contain all configuration properties for akka-actor that are used in code with their correct defaults" in {

View file

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

View file

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

View file

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

View file

@ -6,51 +6,82 @@ package com.typesafe.config;
import java.util.List;
/**
* This class represents an immutable map from config paths to config values. It
* also contains some static methods for creating configs.
* An immutable map from config paths to config values.
*
* <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
* is a key in a JSON object; it's just a string that's the key in a map. A
* "path" is a parseable expression with a syntax and it refers to a series of
* keys. Path expressions are described in the spec for "HOCON", which can be
* found at https://github.com/havocp/config/blob/master/HOCON.md; in brief, a
* path is period-separated so "a.b.c" looks for key c in object b in object a
* in the root object. Sometimes double quotes are needed around special
* characters in path expressions.
* keys. Path expressions are described in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">spec for
* Human-Optimized Config Object Notation</a>. In brief, a path is
* period-separated so "a.b.c" looks for key c in object b in object a in the
* root object. Sometimes double quotes are needed around special characters in
* path expressions.
*
* The API for a Config is in terms of path expressions, while the API for a
* ConfigObject is in terms of keys. Conceptually, Config is a one-level map
* from paths to values, while a ConfigObject is a tree of maps from keys to
* values.
* <p>
* The API for a {@code Config} is in terms of path expressions, while the API
* for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config}
* is a one-level map from <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,
* ConfigValue with valueType() of ConfigValueType.NULL exist in a ConfigObject,
* while a Config treats null values as if they were missing.
* <p>
* Another difference between {@code Config} and {@code ConfigObject} is that
* conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType()
* valueType()} of {@link ConfigValueType#NULL NULL} exist in a
* {@code ConfigObject}, while a {@code Config} treats null values as if they
* were missing.
*
* Config is an immutable object and thus safe to use from multiple threads.
* <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,
* nor do they return a ConfigValue with valueType() of ConfigValueType.NULL.
* Instead, they throw ConfigException.Missing if the value is completely absent
* or set to null. If the value is set to null, a subtype of
* ConfigException.Missing called ConfigException.Null will be thrown.
* ConfigException.WrongType will be thrown anytime you ask for a type and the
* value has an incompatible type. Reasonable type conversions are performed for
* you though.
* <p>
* The "getters" on a {@code Config} all work in the same way. They never return
* null, nor do they return a {@code ConfigValue} with
* {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL
* NULL}. Instead, they throw {@link ConfigException.Missing} if the value is
* completely absent or set to null. If the value is set to null, a subtype of
* {@code ConfigException.Missing} called {@link ConfigException.Null} will be
* thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for
* a type and the value has an incompatible type. Reasonable type conversions
* are performed for you though.
*
* If you want to iterate over the contents of a Config, you have to get its
* ConfigObject with toObject, and then iterate over the ConfigObject.
* <p>
* 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 {
/**
* Gets the config as a tree of ConfigObject. This is a constant-time
* operation (it is not proportional to the number of values in the Config).
* Gets the {@code Config} as a tree of {@link ConfigObject}. This is a
* constant-time operation (it is not proportional to the number of values
* in the {@code Config}).
*
* @return
* @return the root object in the configuration
*/
ConfigObject toObject();
ConfigObject root();
/**
* Gets the origin of the {@code Config}, which may be a file, or a file
* with a line number, or just a descriptive phrase.
*
* @return the origin of the {@code Config} for use in error messages
*/
ConfigOrigin origin();
@Override
@ -60,15 +91,157 @@ public interface Config extends ConfigMergeable {
ConfigObject toValue();
/**
* Checks whether a value is present and non-null at the given path. This
* differs in two ways from ConfigObject.containsKey(): it looks for a path
* expression, not a key; and it returns false for null values, while
* containsKey() returns true indicating that the object contains a null
* value for the key.
* Returns a replacement config with all substitutions (the
* <code>${foo.bar}</code> syntax, see <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">the
* spec</a>) resolved. Substitutions are looked up using this
* <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
* an exception. However, the typed getters, such as getInt(), will still
* throw if the value is not convertible to the requested type.
* <p>
* This method uses {@link ConfigResolveOptions#defaults()}, there is
* 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
* the path expression
@ -78,12 +251,19 @@ public interface Config extends ConfigMergeable {
*/
boolean hasPath(String path);
/**
* Returns true if the {@code Config}'s root object contains no key-value
* pairs.
*
* @return true if the configuration is empty
*/
boolean isEmpty();
/**
*
* @param path
* @return
* path expression
* @return the boolean value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -93,7 +273,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the numeric value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -103,17 +284,20 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the 32-bit integer value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to an int
* if value is not convertible to an int (for example it is out
* of range, or it's a boolean value)
*/
int getInt(String path);
/**
* @param path
* @return
* path expression
* @return the 64-bit long value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -123,7 +307,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the floating-point value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -133,7 +318,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the string value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -143,7 +329,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the {@link ConfigObject} value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -153,7 +340,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the nested {@code Config} value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -162,9 +350,13 @@ public interface Config extends ConfigMergeable {
Config getConfig(String path);
/**
* Gets the value at the path as an unwrapped Java boxed value (Boolean,
* Integer, Long, etc.)
* Gets the value at the path as an unwrapped Java boxed value (
* {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and
* so on - see {@link ConfigValue#unwrapped()}).
*
* @param path
* path expression
* @return the unwrapped value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
*/
@ -172,35 +364,47 @@ public interface Config extends ConfigMergeable {
/**
* Gets the value at the given path, unless the value is a null value or
* missing, in which case it throws just like the other getters. Use get()
* from the Map interface if you want an unprocessed value.
* missing, in which case it throws just like the other getters. Use
* {@code get()} from the {@link java.util.Map Map} interface if you want an
* unprocessed value.
*
* @param path
* @return
* path expression
* @return the value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
*/
ConfigValue getValue(String path);
/**
* Get value as a size in bytes (parses special strings like "128M"). The
* size units are interpreted as for memory, not as for disk space, so they
* are in powers of two.
* Gets a value as a size in bytes (parses special strings like "128M"). If
* the value is already a number, then it's left alone; if it's a string,
* it's parsed understanding unit suffixes such as "128K", as documented in
* the <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
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to Long or String
* @throws ConfigException.BadValue
* if value cannot be parsed as a memory size
* if value cannot be parsed as a size in bytes
*/
Long getMemorySizeInBytes(String path);
Long getBytes(String path);
/**
* Get value as a duration in milliseconds. If the value is already a
* number, then it's left alone; if it's a string, it's parsed understanding
* units suffixes like "10m" or "5ns"
* units suffixes like "10m" or "5ns" as documented in the <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
* if value is absent or null
* @throws ConfigException.WrongType
@ -213,8 +417,12 @@ public interface Config extends ConfigMergeable {
/**
* Get value as a duration in nanoseconds. If the value is already a number
* it's taken as milliseconds and converted to nanoseconds. If it's a
* string, it's parsed understanding unit suffixes.
* string, it's parsed understanding unit suffixes, as for
* {@link #getMilliseconds(String)}.
*
* @param path
* path expression
* @return the duration value at the requested path, in nanoseconds
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -225,13 +433,13 @@ public interface Config extends ConfigMergeable {
Long getNanoseconds(String path);
/**
* Gets a list value (with any element type) as a ConfigList, which
* implements java.util.List<ConfigValue>. Throws if the path is unset or
* null.
* Gets a list value (with any element type) as a {@link ConfigList}, which
* implements {@code java.util.List<ConfigValue>}. Throws if the path is
* unset or null.
*
* @param path
* the path to the list value.
* @return the ConfigList at the path
* @return the {@link ConfigList} at the path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType

View file

@ -3,15 +3,19 @@
*/
package com.typesafe.config;
/**
* All exceptions thrown by the library are subclasses of ConfigException.
*/
public class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L;
final private ConfigOrigin origin;
protected ConfigException(ConfigOrigin origin, String message,
Throwable cause) {
super(origin.description() + ": " + message, cause);
this.origin = origin;
}
protected ConfigException(ConfigOrigin origin, String message) {
@ -20,12 +24,26 @@ public class ConfigException extends RuntimeException {
protected ConfigException(String message, Throwable cause) {
super(message, cause);
this.origin = null;
}
protected ConfigException(String message) {
this(message, null);
}
/**
* Returns an "origin" (such as a filename and line number) for the
* exception, or null if none is available. If there's no sensible origin
* for a given exception, or the kind of exception doesn't meaningfully
* relate to a particular origin file, this returns null. Never assume this
* will return non-null, it can always return null.
*
* @return origin of the problem, or null if unknown/inapplicable
*/
public ConfigOrigin origin() {
return origin;
}
/**
* Exception indicating that the type of a value does not match the type you
* requested.
@ -163,10 +181,11 @@ public class ConfigException extends RuntimeException {
}
/**
* Exception indicating that there's a bug in something or the runtime
* environment is broken. This exception should never be handled; instead,
* something should be fixed to keep the exception from occurring.
*
* Exception indicating that there's a bug in something (possibly the
* library itself) or the runtime environment is broken. This exception
* should never be handled; instead, something should be fixed to keep the
* exception from occurring. This exception can be thrown by any method in
* the library.
*/
public static class BugOrBroken extends ConfigException {
private static final long serialVersionUID = 1L;
@ -212,12 +231,29 @@ public class ConfigException extends RuntimeException {
}
}
/**
* Exception indicating that a substitution did not resolve to anything.
* Thrown by {@link Config#resolve}.
*/
public static class UnresolvedSubstitution extends Parse {
private static final long serialVersionUID = 1L;
public UnresolvedSubstitution(ConfigOrigin origin, String expression, Throwable cause) {
super(origin, "Could not resolve substitution to a value: " + expression, cause);
}
public UnresolvedSubstitution(ConfigOrigin origin, String expression) {
this(origin, expression, null);
}
}
/**
* Exception indicating that you tried to use a function that requires
* substitutions to be resolved, but substitutions have not been resolved.
* This is always a bug in either application code or the library; it's
* wrong to write a handler for this exception because you should be able to
* fix the code to avoid it.
* substitutions to be resolved, but substitutions have not been resolved
* (that is, {@link Config#resolve} was not called). This is always a bug in
* either application code or the library; it's wrong to write a handler for
* this exception because you should be able to fix the code to avoid it by
* adding calls to {@link Config#resolve}.
*/
public static class NotResolved extends BugOrBroken {
private static final long serialVersionUID = 1L;
@ -230,4 +266,59 @@ public class ConfigException extends RuntimeException {
this(message, null);
}
}
public static class ValidationProblem {
final private String path;
final private ConfigOrigin origin;
final private String problem;
public ValidationProblem(String path, ConfigOrigin origin, String problem) {
this.path = path;
this.origin = origin;
this.problem = problem;
}
public String path() {
return path;
}
public ConfigOrigin origin() {
return origin;
}
public String problem() {
return problem;
}
}
public static class ValidationFailed extends ConfigException {
private static final long serialVersionUID = 1L;
final private Iterable<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;
/**
* 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
* operation potentially parsing multiple resources and resolving substitutions,
* while the ones with "parse" in the name just create a ConfigValue from a
* resource and nothing else.
* while the ones with "parse" in the name just create a {@link ConfigValue}
* from a resource and nothing else.
*/
public final class ConfigFactory {
private ConfigFactory() {
}
/**
* Loads a configuration for the given root path in a "standard" way.
* Oversimplified, if your root path is foo.bar then this will load files
* from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties,
* foo-bar-reference.conf, foo-bar-reference.json,
* foo-bar-reference.properties. It will override all those files with any
* system properties that begin with "foo.bar.", as well.
*
* The root path should be a path expression, usually just a single short
* word, that scopes the package being configured; typically it's the
* package name or something similar. System properties overriding values in
* the configuration will have to be prefixed with the root path. The root
* path may have periods in it if you like but other punctuation or
* whitespace will probably cause you headaches. Example root paths: "akka",
* "sbt", "jsoup", "heroku", "mongo", etc.
* Loads an application's configuration from the given classpath resource or
* classpath resource basename, sandwiches it between default reference
* config and default overrides, and then resolves it. The classpath
* resource is "raw" (it should have no "/" prefix, and is not made relative
* to any package, so it's like {@link ClassLoader#getResource} not
* {@link Class#getResource}).
*
* <p>
* The loaded object will already be resolved (substitutions have already
* been processed). As a result, if you add more fallbacks then they won't
* be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
* you want to parse additional files or something then you need to use
* loadWithoutResolving().
* {@link #load(Config)}.
*
* @param rootPath
* the configuration "domain"
* @return configuration object for the requested root path
* @param resourceBasename
* name (optionally without extension) of a resource on classpath
* @return configuration for an application
*/
public static ConfigRoot load(String rootPath) {
return loadWithoutResolving(rootPath).resolve();
}
public static ConfigRoot load(String rootPath,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return loadWithoutResolving(rootPath, parseOptions).resolve(
resolveOptions);
public static Config load(String resourceBasename) {
return load(resourceBasename, ConfigParseOptions.defaults(),
ConfigResolveOptions.defaults());
}
/**
* Like load() but does not resolve the object, so you can go ahead and add
* more fallbacks and stuff and have them seen by substitutions when you do
* call {@link ConfigRoot.resolve()}.
* Like {@link #load(String)} but allows you to specify parse and resolve
* options.
*
* @param rootPath
* @return
* <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 resourceBasename
* the classpath resource name with optional extension
* @param parseOptions
* options to use when parsing the resource
* @param resolveOptions
* options to use when resolving the stack
* @return configuration for an application
*/
public static ConfigRoot loadWithoutResolving(String rootPath) {
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ resourceBasename, parseOptions);
return load(appConfig, resolveOptions);
}
public static ConfigRoot loadWithoutResolving(String rootPath,
ConfigParseOptions options) {
ConfigRoot system = systemPropertiesRoot(rootPath);
Config mainFiles = parseResourcesForPath(rootPath, options);
Config referenceFiles = parseResourcesForPath(rootPath + ".reference",
options);
return system.withFallback(mainFiles).withFallback(referenceFiles);
/**
* Assembles a standard configuration using a custom <code>Config</code>
* object rather than loading "application.conf". The <code>Config</code>
* object will be sandwiched between the default reference config and
* default overrides and then resolved.
*
* @param config
* the application's portion of the configuration
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config) {
return load(config, ConfigResolveOptions.defaults());
}
public static ConfigRoot emptyRoot(String rootPath) {
return emptyRoot(rootPath, null);
/**
* Like {@link #load(Config)} but allows you to specify
* {@link ConfigResolveOptions}.
*
* <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() {
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) {
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() {
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() {
return ConfigImpl.envVariablesAsConfig();
}
/**
* Converts a Java Properties object to a ConfigObject using the rules
* documented in https://github.com/havocp/config/blob/master/HOCON.md The
* keys in the Properties object are split on the period character '.' and
* treated as paths. The values will all end up as string values. If you
* have both "a=foo" and "a.b=bar" in your properties file, so "a" is both
* the object containing "b" and the string "foo", then the string value is
* dropped.
* Converts a Java {@link java.util.Properties} object to a
* {@link ConfigObject} using the rules documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON
* spec</a>. The keys in the <code>Properties</code> object are split on the
* period character '.' and treated as paths. The values will all end up as
* string values. If you have both "a=foo" and "a.b=bar" in your properties
* file, so "a" is both the object containing "b" and the string "foo", then
* the string value is dropped.
*
* If you want to get System.getProperties() as a ConfigObject, it's better
* to use the systemProperties() or systemPropertiesRoot() methods. Those
* methods are able to use a cached global singleton ConfigObject for the
* system properties.
* <p>
* If you want to have <code>System.getProperties()</code> as a
* ConfigObject, it's better to use the {@link #systemProperties()} method
* which returns a cached global singleton.
*
* @param properties
* a Java Properties object
* @param options
* @return
* @return the parsed configuration
*/
public static Config parseProperties(Properties properties,
ConfigParseOptions options) {
return Parseable.newProperties(properties, options).parse().toConfig();
}
public static Config parseProperties(Properties properties) {
return parseProperties(properties, ConfigParseOptions.defaults());
}
public static Config parseReader(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse().toConfig();
}
public static Config parseReader(Reader reader) {
return parseReader(reader, ConfigParseOptions.defaults());
}
public static Config parseURL(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse().toConfig();
}
public static Config parseURL(URL url) {
return parseURL(url, ConfigParseOptions.defaults());
}
public static Config parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig();
}
public static Config parseFile(File file) {
return parseFile(file, ConfigParseOptions.defaults());
}
/**
* Parses a file. If the fileBasename already ends in a known extension,
* just parses it according to that extension. If the fileBasename does not
* end in an extension, then parse all known extensions and merge whatever
* is found. If options force a specific syntax, only parse files with an
* extension matching that syntax. If options.getAllowMissing() is true,
* then no files have to exist; if false, then at least one file has to
* exist.
* Parses a file with a flexible extension. If the <code>fileBasename</code>
* already ends in a known extension, this method parses it according to
* that extension (the file's syntax must match its extension). If the
* <code>fileBasename</code> does not end in an extension, it parses files
* with all known extensions and merges whatever is found.
*
* <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
* a filename with or without extension
* @param options
* @return
* parse options
* @return the parsed configuration
*/
public static Config parseFileAnySyntax(File fileBasename,
ConfigParseOptions options) {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
}
public static Config parseResource(Class<?> klass, String resource,
ConfigParseOptions options) {
return Parseable.newResource(klass, resource, options).parse()
.toConfig();
public static Config parseFileAnySyntax(File fileBasename) {
return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
}
/**
* Same behavior as parseFileAnySyntax() but for classpath resources
* instead.
* Parses all resources on the classpath with the given name and merges them
* into a single <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 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
* @return
* parse options
* @return the parsed configuration
*/
public static Config parseResourceAnySyntax(Class<?> klass, String resourceBasename,
public static Config parseResources(Class<?> klass, String resource,
ConfigParseOptions options) {
return ConfigImpl.parseResourceAnySyntax(klass, resourceBasename,
return Parseable.newResources(klass, resource, options).parse()
.toConfig();
}
public static Config parseResources(Class<?> klass, String resource) {
return parseResources(klass, resource, ConfigParseOptions.defaults());
}
/**
* Parses classpath resources with a flexible extension. In general, this
* method has the same behavior as
* {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath
* resources instead, as in {@link #parseResources}.
*
* <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();
}
public static Config parseResourcesAnySyntax(Class<?> klass, String resourceBasename) {
return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
}
public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig();
}
/**
* Parses classpath resources corresponding to this path expression.
* Essentially if the path is "foo.bar" then the resources are
* "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than
* one of those exists, they are merged.
*
* @param path
* @param options
* @return
*/
public static Config parseResourcesForPath(String rootPath,
ConfigParseOptions options) {
// null originDescription is allowed in parseResourcesForPath
return ConfigImpl.parseResourcesForPath(rootPath, options).toConfig();
public static Config parseString(String s) {
return parseString(s, ConfigParseOptions.defaults());
}
/**
* Similar to ConfigValueFactory.fromMap(), but the keys in the map are path
* expressions, rather than keys; and correspondingly it returns a Config
* instead of a ConfigObject. This is more convenient if you are writing
* literal maps in code, and less convenient if you are getting your maps
* from some data source such as a parser.
* Creates a {@code Config} based on a {@link java.util.Map} from paths to
* plain Java values. Similar to
* {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the
* map are path expressions, rather than keys; and correspondingly it
* returns a {@code Config} instead of a {@code ConfigObject}. This is more
* convenient if you are writing literal maps in code, and less convenient
* if you are getting your maps from some data source such as a parser.
*
* <p>
* An exception will be thrown (and it is a bug in the caller of the method)
* if a path is both an object and a value, for example if you had both
* "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent
@ -221,7 +448,7 @@ public final class ConfigFactory {
* description of what this map represents, like a filename, or
* "default settings" (origin description is used in error
* messages)
* @return
* @return the map converted to a {@code Config}
*/
public static Config parseMap(Map<String, ? extends Object> values,
String originDescription) {
@ -229,11 +456,11 @@ public final class ConfigFactory {
}
/**
* See the other overload of parseMap() for details, this one just uses a
* default origin description.
* See the other overload of {@link #parseMap(Map, String)} for details,
* this one just uses a default origin description.
*
* @param values
* @return
* @return the map converted to a {@code Config}
*/
public static Config parseMap(Map<String, ? extends Object> values) {
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
* intended for apps to implement.
* Context provided to a {@link ConfigIncluder}; this interface is only useful
* inside a {@code ConfigIncluder} implementation, and is not intended for apps
* to implement.
*/
public interface ConfigIncludeContext {
/**
@ -15,7 +16,7 @@ public interface ConfigIncludeContext {
* null if it can't meaningfully create a relative name. The returned
* parseable may not exist; this function is not required to do any IO, just
* compute what the name would be.
*
*
* The passed-in filename has to be a complete name (with extension), not
* just a basename. (Include statements in config files are allowed to give
* just a basename.)

View file

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

View file

@ -6,9 +6,30 @@ package com.typesafe.config;
import java.util.List;
/**
* A list (aka array) value corresponding to ConfigValueType.LIST or JSON's
* "[1,2,3]" value. Implements java.util.List<ConfigValue> so you can use it
* like a regular Java list.
* Subtype of {@link ConfigValue} representing a list value, as in JSON's
* {@code [1,2,3]} syntax.
*
* <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 {

View file

@ -4,33 +4,48 @@
package com.typesafe.config;
/**
* This is a marker for types that can be merged as a fallback into a Config or
* a ConfigValue. Both Config and ConfigValue are mergeable.
* Marker for types whose instances can be merged, that is {@link Config} and
* {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can
* be combined into a single new instance using the
* {@link ConfigMergeable#withFallback withFallback()} method.
*
* <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 {
/**
* Converts the mergeable to a ConfigValue to be merged.
* Converts this instance to a {@link ConfigValue}. If called on a
* {@code ConfigValue} it returns {@code this}, if called on a
* {@link Config} it's equivalent to {@link Config#root()}.
*
* @return
* @return this instance as a {@code ConfigValue}
*/
ConfigValue toValue();
/**
* Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only ConfigObject and
* Config instances do anything in this method (they need to merge the
* fallback keys into themselves). All other values just return the original
* value, since they automatically override any fallback.
*
* The semantics of merging are described in
* https://github.com/havocp/config/blob/master/HOCON.md
*
* Note that objects do not merge "across" non-objects; if you do
* keys in this value "winning" over the other one. Only
* {@link ConfigObject} and {@link Config} instances do anything in this
* method (they need to merge the fallback keys into themselves). All other
* values just return the original value, since they automatically override
* any fallback.
*
* <p>
* The semantics of merging are described in the <a
* 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>,
* then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works. Both non-objects, and any object
* which has fallen back to a non-object, block subsequent fallbacks.
*
*
* @param other
* an object whose keys should be used if the keys are not
* present in this one

View file

@ -6,54 +6,67 @@ package com.typesafe.config;
import java.util.Map;
/**
* A ConfigObject is a read-only configuration object, which may have nested
* child objects. Implementations of ConfigObject should be immutable (at least
* from the perspective of anyone using this interface) and thus thread-safe.
* Subtype of {@link ConfigValue} representing an object (dictionary, map)
* value, as in JSON's <code>{ "a" : 42 }</code> syntax.
*
* In most cases you want to use the Config interface rather than this one. Call
* toConfig() to convert a ConfigObject to a config.
* <p>
* {@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
* in terms of path expressions. Conceptually, ConfigObject is a tree of maps
* from keys to values, while a ConfigObject is a one-level map from paths to
* values.
* <p>
* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* This makes it threadsafe and you never have to create "defensive copies." The
* mutator methods from {@link java.util.Map} all throw
* {@link java.lang.UnsupportedOperationException}.
*
* Throughout the API, there is a distinction between "keys" and "paths". A key
* is a key in a JSON object; it's just a string that's the key in a map. A
* "path" is a parseable expression with a syntax and it refers to a series of
* keys. A path is used to traverse nested ConfigObject by looking up each key
* in the path. Path expressions are described in the spec for "HOCON", which
* can be found at https://github.com/havocp/config/blob/master/HOCON.md; in
* brief, a path is period-separated so "a.b.c" looks for key c in object b in
* object a in the root object. Sometimes double quotes are needed around
* special characters in path expressions.
* <p>
* The {@link ConfigValue#valueType} method on an object returns
* {@link ConfigValueType#OBJECT}.
*
* ConfigObject implements java.util.Map<String,ConfigValue> and all methods
* work with keys, not path expressions.
* <p>
* In most cases you want to use the {@link Config} interface rather than this
* one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a
* {@code Config}.
*
* While ConfigObject implements the standard Java Map interface, the mutator
* methods all throw UnsupportedOperationException. This Map is immutable.
* <p>
* The API for a {@code ConfigObject} is in terms of keys, while the API for a
* {@link Config} is in terms of path expressions. Conceptually,
* {@code ConfigObject} is a tree of maps from keys to values, while a
* {@code ConfigObject} is a one-level map from paths to values.
*
* The Map may contain null values, which will have ConfigValue.valueType() ==
* ConfigValueType.NULL. If get() returns Java's null then the key was not
* present in the parsed file (or wherever this value tree came from). If get()
* returns a ConfigValue with type ConfigValueType.NULL then the key was set to
* null explicitly.
* <p>
* A {@code ConfigObject} may contain null values, which will have
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
* {@code get()} returns Java's null then the key was not present in the parsed
* file (or wherever this value tree came from). If {@code get()} returns a
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was
* set to null explicitly in the config file.
*
* <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> {
/**
* Converts this object to a Config instance, enabling you to use path
* expressions to find values in the object. This is a constant-time
* Converts this object to a {@link Config} instance, enabling you to use
* path expressions to find values in the object. This is a constant-time
* operation (it is not proportional to the size of the object).
*
* @return
* @return a {@link Config} with this object as its root
*/
Config toConfig();
/**
* Recursively unwraps the object, returning a map from String to whatever
* plain Java values are unwrapped from the object's values.
*
* @return a {@link java.util.Map} containing plain Java objects
*/
@Override
Map<String, Object> unwrapped();
@ -62,10 +75,15 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
ConfigObject withFallback(ConfigMergeable other);
/**
* Gets a ConfigValue at the given key, or returns null if there is no
* value. The returned ConfigValue may have ConfigValueType.NULL or any
* other type, and the passed-in key must be a key in this object, rather
* than a path expression.
* Gets a {@link ConfigValue} at the given key, or returns null if there is
* no value. The returned {@link ConfigValue} may have
* {@link ConfigValueType#NULL} or any other type, and the passed-in key
* must be a key in this object, rather than a path expression.
*
* @param key
* key to look up
*
* @return the value at the key or null if none
*/
@Override
ConfigValue get(Object key);

View file

@ -3,10 +3,67 @@
*/
package com.typesafe.config;
import java.net.URL;
/**
* ConfigOrigin is used to track the origin (such as filename and line number)
* of a ConfigValue or other object. The origin is used in error messages.
* Represents the origin (such as filename and line number) of a
* {@link ConfigValue} for use in error messages. Obtain the origin of a value
* with {@link ConfigValue#origin}. Exceptions may have an origin, see
* {@link ConfigException#origin}, but be careful because
* <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 {
/**
* Returns a string describing the origin of a value or exception. This will
* never return null.
*
* @return string describing the origin
*/
public String description();
/**
* Returns a filename describing the origin. This will return null if the
* origin was not a file.
*
* @return filename of the origin or null
*/
public String filename();
/**
* Returns a URL describing the origin. This will return null if the origin
* has no meaningful URL.
*
* @return url of the origin or null
*/
public URL url();
/**
* Returns a classpath resource name describing the origin. This will return
* null if the origin was not a classpath resource.
*
* @return resource name of the origin or null
*/
public String resource();
/**
* Returns a line number where the value or exception originated. This will
* return -1 if there's no meaningful line number.
*
* @return line number or -1 if none is available
*/
public int lineNumber();
}

View file

@ -4,6 +4,22 @@
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 {
final ConfigSyntax syntax;
final String originDescription;
@ -24,10 +40,11 @@ public final class ConfigParseOptions {
/**
* Set the file format. If set to null, try to guess from any available
* filename extension; if guessing fails, assume ConfigSyntax.CONF.
*
* filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}.
*
* @param syntax
* @return
* a syntax or {@code null} for best guess
* @return options with the syntax set
*/
public ConfigParseOptions setSyntax(ConfigSyntax syntax) {
if (this.syntax == syntax)
@ -45,10 +62,11 @@ public final class ConfigParseOptions {
* Set a description for the thing being parsed. In most cases this will be
* set up for you to something like the filename, but if you provide just an
* input stream you might want to improve on it. Set to null to allow the
* library to come up with something automatically.
* library to come up with something automatically. This description is the
* basis for the {@link ConfigOrigin} of the parsed values.
*
* @param originDescription
* @return
* @return options with the origin description set
*/
public ConfigParseOptions setOriginDescription(String originDescription) {
if (this.originDescription == originDescription)
@ -79,7 +97,7 @@ public final class ConfigParseOptions {
* case.
*
* @param allowMissing
* @return
* @return options with the "allow missing" flag set
*/
public ConfigParseOptions setAllowMissing(boolean allowMissing) {
if (this.allowMissing == allowMissing)

View file

@ -3,21 +3,40 @@
*/
package com.typesafe.config;
import java.net.URL;
/** An opaque handle to something that can be parsed. */
/**
* An opaque handle to something that can be parsed, obtained from
* {@link ConfigIncludeContext}.
*
* <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 {
/**
* Parse whatever it is.
*
* Parse whatever it is. The options should come from
* {@link ConfigParseable#options options()} but you could tweak them if you
* like.
*
* @param options
* parse options, should be based on the ones from options()
* parse options, should be based on the ones from
* {@link ConfigParseable#options options()}
*/
ConfigObject parse(ConfigParseOptions options);
/** Possibly return a URL representing the resource; this may return null. */
URL url();
/**
* Returns a {@link ConfigOrigin} describing the origin of the parseable
* item.
*/
ConfigOrigin origin();
/** Get the initial options, which can be modified then passed to parse(). */
/**
* Get the initial options, which can be modified then passed to parse().
* These options will have the right description, includer, and other
* parameters already set up.
*/
ConfigParseOptions options();
}

View file

@ -3,6 +3,28 @@
*/
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 {
private final boolean useSystemProperties;
private final boolean useSystemEnvironment;
@ -13,26 +35,68 @@ public final class ConfigResolveOptions {
this.useSystemEnvironment = useSystemEnvironment;
}
/**
* Returns the default resolve options.
*
* @return the default resolve options
*/
public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, true);
}
/**
* Returns resolve options that disable any reference to "system" data
* (system properties or environment variables).
*
* @return the resolve options with system properties and env variables
* disabled
*/
public static ConfigResolveOptions noSystem() {
return new ConfigResolveOptions(false, false);
return defaults().setUseSystemEnvironment(false).setUseSystemProperties(false);
}
/**
* Returns options with use of Java system properties set to the given
* value.
*
* @param value
* true to resolve substitutions falling back to Java system
* properties.
* @return options with requested setting for use of system properties
*/
public ConfigResolveOptions setUseSystemProperties(boolean value) {
return new ConfigResolveOptions(value, useSystemEnvironment);
}
/**
* Returns options with use of environment variables set to the given value.
*
* @param value
* true to resolve substitutions falling back to environment
* variables.
* @return options with requested setting for use of environment variables
*/
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(useSystemProperties, value);
}
/**
* Returns whether the options enable use of system properties. This method
* is mostly used by the config lib internally, not by applications.
*
* @return true if system properties should be used
*/
public boolean getUseSystemProperties() {
return useSystemProperties;
}
/**
* Returns whether the options enable use of system environment variables.
* This method is mostly used by the config lib internally, not by
* applications.
*
* @return true if environment variables should be used
*/
public boolean getUseSystemEnvironment() {
return useSystemEnvironment;
}

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;
/**
* 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 {
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;
/**
* Interface implemented by any configuration value. From the perspective of
* users of this interface, the object is immutable. It is therefore safe to use
* from multiple threads.
* An immutable value, following the <a href="http://json.org">JSON</a> type
* schema.
*
* <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 {
/**
* The origin of the value, for debugging and error messages.
* The origin of the value (file, line number, etc.), for debugging and
* error messages.
*
* @return where the value came from
*/
ConfigOrigin origin();
/**
* The type of the value; matches the JSON type schema.
* The {@link ConfigValueType} of the value; matches the JSON type schema.
*
* @return value's type
*/
ConfigValueType valueType();
/**
* Returns the config value as a plain Java boxed value, should be a String,
* Number, etc. matching the valueType() of the ConfigValue. If the value is
* a ConfigObject or ConfigList, it is recursively unwrapped.
* Returns the value as a plain Java boxed value, that is, a {@code String},
* {@code Number}, {@code Boolean}, {@code Map<String,Object>},
* {@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();
/**
* 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
ConfigValue withFallback(ConfigMergeable other);
}

View file

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

View file

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

View file

@ -162,8 +162,39 @@ abstract class AbstractConfigValue implements ConfigValue {
}
@Override
public String toString() {
return valueType().name() + "(" + unwrapped() + ")";
public final String toString() {
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

View file

@ -5,6 +5,7 @@ package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.typesafe.config.ConfigException;
@ -76,10 +77,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
AbstractConfigValue merged = null;
for (AbstractConfigValue v : stack) {
AbstractConfigValue resolved = resolver.resolve(v, depth, options);
if (merged == null)
merged = resolved;
else
merged = merged.withFallback(resolved);
if (resolved != null) {
if (merged == null)
merged = resolved;
else
merged = merged.withFallback(resolved);
}
}
return merged;
@ -160,16 +163,58 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("DELAYED_MERGE");
sb.append("(");
for (Object s : stack) {
sb.append(s.toString());
sb.append(",");
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
render(stack, sb, indent, atKey, formatted);
}
// static method also used by ConfigDelayedMergeObject.
static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, String atKey,
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
public String toString() {
StringBuilder sb = new StringBuilder();
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();
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
ConfigDelayedMerge.render(stack, sb, indent, atKey, formatted);
}
private static ConfigException notResolved() {

View file

@ -19,7 +19,6 @@ import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable;
import com.typesafe.config.ConfigRoot;
import com.typesafe.config.ConfigSyntax;
import com.typesafe.config.ConfigValue;
@ -45,8 +44,7 @@ public class ConfigImpl {
obj = p.parse(p.options().setAllowMissing(
options.getAllowMissing()));
} else {
obj = SimpleConfigObject.emptyMissing(new SimpleConfigOrigin(
name));
obj = SimpleConfigObject.emptyMissing(SimpleConfigOrigin.newSimple(name));
}
} else {
ConfigParseable confHandle = source.nameToParseable(name + ".conf");
@ -56,13 +54,13 @@ public class ConfigImpl {
if (!options.getAllowMissing() && confHandle == 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");
}
ConfigSyntax syntax = options.getSyntax();
obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name));
obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name));
if (confHandle != null
&& (syntax == null || syntax == ConfigSyntax.CONF)) {
obj = confHandle.parse(confHandle.options()
@ -89,39 +87,13 @@ public class ConfigImpl {
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 */
public static ConfigObject parseResourcesForPath(String expression,
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,
public static ConfigObject parseResourcesAnySyntax(final Class<?> klass,
String resourceBasename, final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
return Parseable.newResource(klass, name, baseOptions);
return Parseable.newResources(klass, name, baseOptions);
}
};
return fromBasename(source, resourceBasename, baseOptions);
@ -139,16 +111,9 @@ public class ConfigImpl {
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) {
ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin(
originDescription) : null;
ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin
.newSimple(originDescription) : null;
return emptyObject(origin);
}
@ -162,8 +127,8 @@ public class ConfigImpl {
}
// default origin for values created with fromAnyRef and no origin specified
final private static ConfigOrigin defaultValueOrigin = new SimpleConfigOrigin(
"hardcoded value");
final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin
.newSimple("hardcoded value");
final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
defaultValueOrigin, true);
final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
@ -196,7 +161,7 @@ public class ConfigImpl {
if (originDescription == null)
return defaultValueOrigin;
else
return new SimpleConfigOrigin(originDescription);
return SimpleConfigOrigin.newSimple(originDescription);
}
/** 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 ConfigIncluder fallback;
@ -346,29 +300,26 @@ public class ConfigImpl {
}
}
private static ConfigIncluder defaultIncluder = null;
synchronized static ConfigIncluder defaultIncluder() {
if (defaultIncluder == null) {
defaultIncluder = new SimpleIncluder(null);
}
return defaultIncluder;
private static class DefaultIncluderHolder {
static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
}
private static AbstractConfigObject systemProperties = null;
synchronized static AbstractConfigObject systemPropertiesAsConfigObject() {
if (systemProperties == null) {
systemProperties = loadSystemProperties();
}
return systemProperties;
static ConfigIncluder defaultIncluder() {
return DefaultIncluderHolder.defaultIncluder;
}
private static AbstractConfigObject loadSystemProperties() {
return (AbstractConfigObject) Parseable.newProperties(
System.getProperties(),
ConfigParseOptions.defaults().setOriginDescription(
"system properties")).parse();
return (AbstractConfigObject) Parseable.newProperties(System.getProperties(),
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 */
@ -376,18 +327,10 @@ public class ConfigImpl {
return systemPropertiesAsConfigObject().toConfig();
}
// this is a hack to let us set system props in the test suite
synchronized static void dropSystemPropertiesConfig() {
systemProperties = null;
}
private static AbstractConfigObject envVariables = null;
synchronized static AbstractConfigObject envVariablesAsConfigObject() {
if (envVariables == null) {
envVariables = loadEnvVariables();
}
return envVariables;
// this is a hack to let us set system props in the test suite.
// obviously not thread-safe.
static void reloadSystemPropertiesConfig() {
SystemPropertiesHolder.systemProperties = loadSystemProperties();
}
private static AbstractConfigObject loadEnvVariables() {
@ -395,15 +338,37 @@ public class ConfigImpl {
Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>();
for (Map.Entry<String, String> entry : env.entrySet()) {
String key = entry.getKey();
m.put(key, new ConfigString(
new SimpleConfigOrigin("env var " + key), entry.getValue()));
m.put(key,
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 */);
}
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 */
public static Config envVariablesAsConfig() {
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() {
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() {
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
Unmergeable {
// this is a list of String and Path where the Path
// have to be resolved to values, then if there's more
// this is a list of String and SubstitutionExpression where the
// SubstitutionExpression has to be resolved to values, then if there's more
// than one piece everything is stringified and concatenated
final private List<Object> pieces;
// the length of any prefixes added with relativized()
@ -40,19 +40,24 @@ final class ConfigSubstitution extends AbstractConfigValue implements
this.pieces = pieces;
this.prefixLength = prefixLength;
this.ignoresFallbacks = ignoresFallbacks;
for (Object p : pieces) {
if (p instanceof Path)
throw new RuntimeException("broken here");
}
}
@Override
public ConfigValueType valueType() {
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);
}
@Override
public Object unwrapped() {
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
@ -124,15 +129,15 @@ final class ConfigSubstitution extends AbstractConfigValue implements
return result;
}
private ConfigValue resolve(SubstitutionResolver resolver, Path subst,
private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst,
int depth, ConfigResolveOptions options) {
ConfigValue result = findInObject(resolver.root(), resolver, subst,
ConfigValue result = findInObject(resolver.root(), resolver, subst.path(),
depth, options);
// when looking up system props and env variables,
// we don't want the prefix that was added when
// we were included in another file.
Path unprefixed = subst.subPath(prefixLength);
Path unprefixed = subst.path().subPath(prefixLength);
if (result == null && options.getUseSystemProperties()) {
result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null,
@ -144,10 +149,6 @@ final class ConfigSubstitution extends AbstractConfigValue implements
unprefixed, depth, options);
}
if (result == null) {
result = new ConfigNull(origin());
}
return result;
}
@ -160,28 +161,40 @@ final class ConfigSubstitution extends AbstractConfigValue implements
if (p instanceof String) {
sb.append((String) p);
} else {
ConfigValue v = resolve(resolver, (Path) p, depth, options);
switch (v.valueType()) {
case NULL:
// nothing; becomes empty string
break;
case LIST:
case OBJECT:
// cannot substitute lists and objects into strings
throw new ConfigException.WrongType(v.origin(),
((Path) p).render(),
"not a list or object", v.valueType().name());
default:
sb.append(((AbstractConfigValue) v).transformToString());
SubstitutionExpression exp = (SubstitutionExpression) p;
ConfigValue v = resolve(resolver, exp, depth, options);
if (v == null) {
if (exp.optional()) {
// append nothing to StringBuilder
} else {
throw new ConfigException.UnresolvedSubstitution(origin(),
exp.toString());
}
} else {
switch (v.valueType()) {
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());
} else {
if (!(pieces.get(0) instanceof Path))
if (!(pieces.get(0) instanceof SubstitutionExpression))
throw new ConfigException.BugOrBroken(
"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) {
List<Object> newPieces = new ArrayList<Object>();
for (Object p : pieces) {
if (p instanceof Path) {
newPieces.add(((Path) p).prepend(prefix));
if (p instanceof SubstitutionExpression) {
SubstitutionExpression exp = (SubstitutionExpression) p;
newPieces.add(exp.changePath(exp.path().prepend(prefix)));
} else {
newPieces.add(p);
}
@ -243,16 +258,13 @@ final class ConfigSubstitution extends AbstractConfigValue implements
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SUBST");
sb.append("(");
protected void render(StringBuilder sb, int indent, boolean formatted) {
for (Object p : pieces) {
sb.append(p.toString());
sb.append(",");
if (p instanceof SubstitutionExpression) {
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.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
@ -39,6 +40,7 @@ import com.typesafe.config.ConfigValue;
public abstract class Parseable implements ConfigParseable {
private ConfigIncludeContext includeContext;
private ConfigParseOptions initialOptions;
private ConfigOrigin initialOrigin;
protected Parseable() {
@ -54,9 +56,6 @@ public abstract class Parseable implements ConfigParseable {
}
ConfigParseOptions modified = baseOptions.setSyntax(syntax);
if (modified.getOriginDescription() == null)
modified = modified.setOriginDescription(originDescription());
modified = modified.appendIncluder(ConfigImpl.defaultIncluder());
return modified;
@ -71,6 +70,11 @@ public abstract class Parseable implements ConfigParseable {
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
@ -108,33 +112,26 @@ public abstract class Parseable implements ConfigParseable {
return forceParsedToObject(parseValue(baseOptions));
}
AbstractConfigValue parseValue(ConfigParseOptions baseOptions) {
// note that we are NOT using our "options" and "origin" fields,
final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) {
// note that we are NOT using our "initialOptions",
// but using the ones from the passed-in options. The idea is that
// callers can get our original options and then parse with different
// ones if they want.
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);
}
protected AbstractConfigValue parseValue(ConfigOrigin origin,
final private AbstractConfigValue parseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) {
try {
Reader reader = reader();
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();
}
return rawParseValue(origin, finalOptions);
} catch (IOException e) {
if (finalOptions.getAllowMissing()) {
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() {
return forceParsedToObject(parseValue(options()));
}
@ -152,13 +171,13 @@ public abstract class Parseable implements ConfigParseable {
return parseValue(options());
}
abstract String originDescription();
@Override
public URL url() {
return null;
public final ConfigOrigin origin() {
return initialOrigin;
}
protected abstract ConfigOrigin createOrigin();
@Override
public ConfigParseOptions options() {
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 {
final private Reader reader;
@ -270,8 +261,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return "Reader";
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newSimple("Reader");
}
}
@ -297,8 +288,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return "String";
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newSimple("String");
}
}
@ -335,13 +326,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return input.toExternalForm();
}
@Override
public URL url() {
return input;
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newURL(input);
}
@Override
@ -387,17 +373,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return input.getPath();
}
@Override
public URL url() {
try {
return input.toURI().toURL();
} catch (MalformedURLException e) {
return null;
}
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newFile(input.getPath());
}
@Override
@ -410,25 +387,61 @@ public abstract class Parseable implements ConfigParseable {
return new ParseableFile(input, options);
}
private final static class ParseableResource extends Parseable {
final private Class<?> klass;
private final static class ParseableResources extends Parseable {
final private ClassLoader loader;
final private String resource;
ParseableResource(Class<?> klass, String resource,
ParseableResources(ClassLoader loader, String resource,
ConfigParseOptions options) {
this.klass = klass;
this.loader = loader;
this.resource = resource;
postConstruct(options);
}
@Override
protected Reader reader() throws IOException {
InputStream stream = klass.getResourceAsStream(resource);
if (stream == null) {
throw new IOException("resource not found on classpath: "
+ resource);
throw new ConfigException.BugOrBroken(
"reader() should not be called on resources");
}
@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
@ -436,48 +449,76 @@ public abstract class Parseable implements ConfigParseable {
return syntaxFromExtension(resource);
}
@Override
ConfigParseable relativeTo(String filename) {
// not using File.isAbsolute because resource paths always use '/'
// (?)
if (filename.startsWith("/"))
static String parent(String resource) {
int i = resource.lastIndexOf('/');
if (i < 0) {
return null;
} else {
return resource.substring(0, i);
}
}
@Override
ConfigParseable relativeTo(String sibling) {
// here we want to build a new resource name and let
// the class loader have it, rather than getting the
// url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to
// search a classpath.
File parent = new File(resource).getParentFile();
String parent = parent(resource);
if (parent == null)
return newResource(klass, "/" + filename, options()
return newResources(loader, sibling, options()
.setOriginDescription(null));
else
return newResource(klass, new File(parent, filename).getPath(),
return newResources(loader, parent + "/" + sibling,
options().setOriginDescription(null));
}
@Override
String originDescription() {
return resource + " on classpath";
}
@Override
public URL url() {
return klass.getResource(resource);
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newResource(resource);
}
@Override
public String toString() {
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) {
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 {
@ -495,7 +536,7 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
protected AbstractConfigObject parseValue(ConfigOrigin origin,
protected AbstractConfigObject rawParseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) {
return PropertiesParser.fromProperties(origin, props);
}
@ -506,13 +547,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return "properties";
}
@Override
public URL url() {
return null;
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newSimple("properties");
}
@Override

View file

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

View file

@ -152,7 +152,7 @@ final class Path {
}
private void appendToStringBuilder(StringBuilder sb) {
if (hasFunkyChars(first))
if (hasFunkyChars(first) || first.isEmpty())
sb.append(ConfigUtil.renderJsonString(first));
else
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config;
@ -34,7 +36,7 @@ class SimpleConfig implements Config {
}
@Override
public AbstractConfigObject toObject() {
public AbstractConfigObject root() {
return object;
}
@ -43,35 +45,22 @@ class SimpleConfig implements Config {
return object.origin();
}
/**
* Returns a version of this config that implements the ConfigRoot
* interface.
*
* @return a config root
*/
RootConfig asRoot(Path rootPath) {
return asRoot(object, rootPath);
@Override
public SimpleConfig resolve() {
return resolve(ConfigResolveOptions.defaults());
}
// RootConfig overrides this to avoid a new object on unchanged path.
protected RootConfig asRoot(AbstractConfigObject underlying,
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) {
@Override
public SimpleConfig resolve(ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
object, options);
return (AbstractConfigObject) resolved;
if (resolved == object)
return this;
else
return new SimpleConfig((AbstractConfigObject) resolved);
}
@Override
public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
@ -196,13 +185,13 @@ class SimpleConfig implements Config {
}
@Override
public Long getMemorySizeInBytes(String path) {
public Long getBytes(String path) {
Long size = null;
try {
size = getLong(path);
} catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
size = parseMemorySizeInBytes((String) v.unwrapped(),
size = parseBytes((String) v.unwrapped(),
v.origin(), path);
}
return size;
@ -346,7 +335,7 @@ class SimpleConfig implements Config {
l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = parseMemorySizeInBytes(s, v.origin(), path);
Long n = parseBytes(s, v.origin(), path);
l.add(n);
} else {
throw new ConfigException.WrongType(v.origin(), path,
@ -509,23 +498,87 @@ class SimpleConfig implements Config {
}
private static enum MemoryUnit {
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES(
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
BYTES("", 1024, 0),
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;
}
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
* is assumed to be in bytes. The returned value is in bytes. The purpose of
* this function is to implement the memory-size-related methods in the
* ConfigObject interface. The units parsed are interpreted as powers of
* two, that is, the convention for memory rather than the convention for
* disk space.
* Parses a size-in-bytes string. If no units are specified in the string,
* it is assumed to be in bytes. The returned value is in bytes. The purpose
* of this function is to implement the size-in-bytes-related methods in the
* Config interface.
*
* @param input
* the string to parse
@ -537,19 +590,12 @@ class SimpleConfig implements Config {
* @throws ConfigException
* if string is invalid
*/
public static long parseMemorySizeInBytes(String input,
ConfigOrigin originForException, String pathForException) {
public static long parseBytes(String input, ConfigOrigin originForException,
String pathForException) {
String s = ConfigUtil.unicodeTrim(input);
String unitStringMaybePlural = getUnits(s);
String unitString;
if (unitStringMaybePlural.endsWith("s"))
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()));
String unitString = getUnits(s);
String numberString = ConfigUtil.unicodeTrim(s.substring(0,
s.length() - unitString.length()));
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
@ -558,40 +604,197 @@ class SimpleConfig implements Config {
pathForException, "No number in size-in-bytes value '"
+ input + "'");
MemoryUnit units = null;
MemoryUnit units = MemoryUnit.parseUnit(unitString);
// the short abbreviations are case-insensitive but you can't write the
// long form words in all caps.
if (unitString.equals("") || unitStringLower.equals("b")
|| unitString.equals("byte")) {
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)");
if (units == null) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes unit '" + unitString
+ "' (try k, K, kB, KiB, kilobytes, kibibytes)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// possible precision loss;
// otherwise as a double.
// possible precision loss; otherwise as a double.
if (numberString.matches("[0-9]+")) {
return Long.parseLong(numberString) * units.bytes;
} else {
return (long) (Double.parseDouble(numberString) * units.bytes);
}
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse memory size number '"
+ numberString + "'");
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes number '" + 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.ListIterator;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
@ -68,8 +67,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
// once the new list is created, all elements
// have to go in it.
if (changed != null) {
// have to go in it. if modifyChild returned
// null, we drop that element.
if (changed != null && modified != null) {
changed.add(modified);
}
@ -77,9 +77,6 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
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);
} else {
return this;
@ -135,18 +132,34 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(valueType().name());
sb.append("(");
for (ConfigValue e : value) {
sb.append(e.toString());
sb.append(",");
protected void render(StringBuilder sb, int indent, boolean formatted) {
if (value.isEmpty()) {
sb.append("[]");
} else {
sb.append("[");
if (formatted)
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
@ -160,7 +173,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
@Override
public ConfigValue get(int index) {
public AbstractConfigValue get(int 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 SimpleConfigObject emptyInstance = empty(new SimpleConfigOrigin(
EMPTY_NAME));
final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin
.newSimple(EMPTY_NAME));
final static SimpleConfigObject empty() {
return emptyInstance;
@ -128,7 +128,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
}
final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) {
return new SimpleConfigObject(new SimpleConfigOrigin(
return new SimpleConfigObject(SimpleConfigOrigin.newSimple(
baseOrigin.description() + " (not found)"),
Collections.<String, AbstractConfigValue> emptyMap());
}

View file

@ -3,26 +3,101 @@
*/
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;
// 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 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.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
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
public boolean equals(Object other) {
if (other instanceof SimpleConfigOrigin) {
return this.description
.equals(((SimpleConfigOrigin) other).description);
SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other;
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 {
return false;
}
@ -30,11 +105,201 @@ final class SimpleConfigOrigin implements ConfigOrigin {
@Override
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
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 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;
SubstitutionResolver(AbstractConfigObject root) {
@ -31,9 +33,11 @@ final class SubstitutionResolver {
} else {
AbstractConfigValue resolved = original.resolveSubstitutions(this,
depth, options);
if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
if (resolved != null) {
if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
}
memos.put(original, resolved);
return resolved;
}

View file

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

View file

@ -125,11 +125,13 @@ final class Tokens {
// This is not a Value, because it requires special processing
static private class Substitution extends Token {
final private ConfigOrigin origin;
final private boolean optional;
final private List<Token> value;
Substitution(ConfigOrigin origin, List<Token> expression) {
Substitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
super(TokenType.SUBSTITUTION);
this.origin = origin;
this.optional = optional;
this.value = expression;
}
@ -137,6 +139,10 @@ final class Tokens {
return origin;
}
boolean optional() {
return optional;
}
List<Token> 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 END = new Token(TokenType.END);
final static Token COMMA = new Token(TokenType.COMMA);
@ -255,8 +270,8 @@ final class Tokens {
return new UnquotedText(origin, s);
}
static Token newSubstitution(ConfigOrigin origin, List<Token> expression) {
return new Substitution(origin, expression);
static Token newSubstitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
return new Substitution(origin, optional, expression);
}
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
# this level is used by the configured loggers (see "event-handlers") as soon
# 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?
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
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
ticksPerWheel = 512
}
}

View file

@ -13,9 +13,10 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.NANOSECONDS
import java.io.File
import com.typesafe.config.Config
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
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 akka.util.{ Helpers, Duration, ReflectiveAccess }
import java.util.concurrent.atomic.AtomicLong
@ -43,17 +44,32 @@ object ActorSystem {
def create(name: String, config: Config): ActorSystem = apply(name, config)
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 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 apply(): ActorSystem = apply("default")
class Settings(cfg: Config) {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-actor-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-actor").withFallback(cfg).withFallback(referenceConfig).resolve()
// Verify that the Config is sane and has our reference config.
val config: Config =
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 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
val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka")
@ -130,7 +150,7 @@ object ActorSystem {
private def fromClasspath = try {
Option(ConfigFactory.systemProperties.withFallback(
ConfigFactory.parseResourceAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions)))
ConfigFactory.parseResourcesAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions)))
} catch { case _ None }
private def fromHome = try {
@ -273,7 +293,7 @@ abstract class ActorSystem extends ActorRefFactory {
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._

View file

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

View file

@ -7,8 +7,7 @@ package akka.serialization
import akka.AkkaException
import akka.util.ReflectiveAccess
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.actor.{ Extension, ActorSystem, ActorSystemImpl }
@ -19,11 +18,7 @@ object Serialization {
// TODO ensure that these are always set (i.e. withValue()) when doing deserialization
val currentSystem = new DynamicVariable[ActorSystemImpl](null)
class Settings(cfg: 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()
class Settings(val config: Config) {
import scala.collection.JavaConverters._
import config._
@ -37,7 +32,7 @@ object Serialization {
hasPath(configPath) match {
case false Map()
case true
val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toObject.unwrapped.asScala.toMap.map {
val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).root.unwrapped.asScala.toMap.map {
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))
}
@ -47,7 +42,7 @@ object Serialization {
}
private def toStringMap(mapConfig: Config): Map[String, String] =
mapConfig.toObject.unwrapped.asScala.toMap.map { case (k, v) (k, v.toString) }
mapConfig.root.unwrapped.asScala.toMap.map { case (k, v) (k, v.toString) }
}
}
@ -58,7 +53,7 @@ object Serialization {
class Serialization(val system: ActorSystemImpl) extends Extension {
import Serialization._
val settings = new Settings(system.applicationConfig)
val settings = new Settings(system.settings.config)
//TODO document me
def serialize(o: AnyRef): Either[Exception, Array[Byte]] =

View file

@ -6,7 +6,6 @@ import org.scalatest.matchers.MustMatchers
//#imports
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
//#imports
@ -21,7 +20,7 @@ class ConfigDocSpec extends WordSpec {
nr-of-instances = 3
}
}
""", ConfigParseOptions.defaults)
""")
val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf))
//#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
configuration.
FIXME: These default locations has changed
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.
@ -44,47 +46,42 @@ Each Akka module has a reference configuration file with the default values.
*akka-actor:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-actor-reference.conf
.. literalinclude:: ../../akka-actor/src/main/resources/reference.conf
:language: none
*akka-remote:*
.. literalinclude:: ../../akka-remote/src/main/resources/akka-remote-reference.conf
.. literalinclude:: ../../akka-remote/src/main/resources/reference.conf
:language: none
*akka-serialization:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-serialization-reference.conf
:language: none
*akka-testkit:*
.. literalinclude:: ../../akka-testkit/src/main/resources/akka-testkit-reference.conf
.. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf
:language: none
*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
*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
*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
*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
*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
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
------------------
@ -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>`_
specification. Note that it supports three formats; conf, json, and properties.
.. _-Dakka.mode:
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
``-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
@ -152,6 +152,8 @@ The mode option is not used when specifying the configuration file with ``-Dakka
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
environment independent settings and then override some settings for specific modes.
@ -165,14 +167,14 @@ akka.dev.conf:
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
null, then the source from which Akka reads its configuration is printed to the
console during application startup.
If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the
complete configuration at INFO level when the actor system is started. This is useful
when you are uncertain of what configuration is used.
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.config <-Dakka.config>`: explicit configuration file location
* :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
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 java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._
object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider {
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 {
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()
class BeanstalkMailboxSettings(val config: Config) extends Extension {
import config._

View file

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

View file

@ -4,36 +4,29 @@
package akka.actor.mailbox
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 java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._
object FileBasedMailboxExtension extends ExtensionId[FileBasedMailboxSettings] with ExtensionIdProvider {
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 {
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()
class FileBasedMailboxSettings(val config: Config) extends Extension {
import config._
val QueuePath = getString("akka.actor.mailbox.file-based.directory-path")
val MaxItems = getInt("akka.actor.mailbox.file-based.max-items")
val MaxSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-size")
val MaxItemSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-item-size")
val MaxSize = getBytes("akka.actor.mailbox.file-based.max-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 MaxJournalSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size")
val MaxMemorySize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-memory-size")
val MaxJournalSize = getBytes("akka.actor.mailbox.file-based.max-journal-size")
val MaxMemorySize = getBytes("akka.actor.mailbox.file-based.max-memory-size")
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 KeepJournal = getBoolean("akka.actor.mailbox.file-based.keep-journal")
val SyncJournal = getBoolean("akka.actor.mailbox.file-based.sync-journal")

View file

@ -4,23 +4,16 @@
package akka.actor.mailbox
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 java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._
object MongoBasedMailboxExtension extends ExtensionId[MongoBasedMailboxSettings] with ExtensionIdProvider {
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 {
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()
class MongoBasedMailboxSettings(val config: Config) extends Extension {
import config._

View file

@ -4,21 +4,14 @@
package akka.actor.mailbox
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.actor._
object RedisBasedMailboxExtension extends ExtensionId[RedisBasedMailboxSettings] with ExtensionIdProvider {
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 {
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()
class RedisBasedMailboxSettings(val config: Config) extends Extension {
import config._

View file

@ -4,22 +4,15 @@
package akka.actor.mailbox
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 java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._
object ZooKeeperBasedMailboxExtension extends ExtensionId[ZooKeeperBasedMailboxSettings] with ExtensionIdProvider {
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 {
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()
class ZooKeeperBasedMailboxSettings(val config: Config) extends Extension {
import config._

View file

@ -31,7 +31,7 @@ akka {
server {
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)
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
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.
@ -46,7 +46,7 @@ akka {
}
reconnect-delay = 5s
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
}
}

View file

@ -4,9 +4,6 @@
package akka.remote
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 java.util.concurrent.TimeUnit.MILLISECONDS
import java.net.InetAddress
@ -18,14 +15,10 @@ import scala.collection.JavaConverters._
object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider {
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 {
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()
class RemoteExtensionSettings(val config: Config) extends Extension {
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 ReadTimeout = Duration(config.getMilliseconds("akka.remote.client.read-timeout"), 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 {
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 {
case "" None
case cookie Some(cookie)

View file

@ -6,8 +6,27 @@ package akka.remote
import akka.testkit._
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.

View file

@ -19,7 +19,7 @@ class RemoteConfigSpec extends AkkaSpec {
//akka.remote.server
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)
getBoolean("akka.remote.server.require-cookie") 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 akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import akka.testkit.AkkaSpec
@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 {
"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
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 java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor.{ ExtensionId, ActorSystem, Extension, ActorSystemImpl }
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 {
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()
class TestKitSettings(val config: Config) extends Extension {
import config._

View file

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