Split Config out of ConfigObject.

This separates ConfigObject, which is:
  - a tree of maps
  - that can contain ConfigValueType.NULL

from Config, which is:
  - a one-level map from path to non-map values
  - which never returns a ConfigValueType.NULL
This commit is contained in:
Havoc Pennington 2011-11-17 16:42:07 -05:00
parent 2381cf7785
commit adb80f6017
29 changed files with 1435 additions and 1342 deletions

View File

@ -1,26 +1,11 @@
package com.typesafe.config;
import java.io.File;
import java.io.Reader;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.ConfigUtil;
import com.typesafe.config.impl.Parseable;
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.
*
* 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.
*
*
* 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
@ -29,485 +14,157 @@ import com.typesafe.config.impl.Parseable;
* 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.
*
*
* 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.
*
* Config is an immutable object and thus safe to use from multiple threads.
*
* The "getters" on a Config all work in the same way. They never return null,
* or a ConfigValue with valueType() of ConfigValueType.NULL. If the value is
* completely absent then ConfigException.Missing will be thrown, and if the
* value is present but null, ConfigException.Null will be thrown.
* ConfigException.Null is a subclass of ConfigException.WrongType, where
* 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.
*
*/
public abstract class Config {
protected Config() {
}
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).
*
* @return
*/
public abstract ConfigObject toObject();
ConfigObject toObject();
public abstract ConfigOrigin origin();
ConfigOrigin origin();
@Override
Config withFallback(ConfigMergeable other);
@Override
Config withFallbacks(ConfigMergeable... others);
@Override
ConfigObject toValue();
/**
* 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.
* 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.
*
* 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.
*
* 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().
*
* @param rootPath
* the configuration "domain"
* @return configuration object for the requested root path
*/
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);
}
/**
* 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()}.
*
* @param rootPath
* @return
*/
public static ConfigRoot loadWithoutResolving(String rootPath) {
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
}
public static ConfigRoot loadWithoutResolving(String rootPath,
ConfigParseOptions options) {
ConfigRoot system = systemPropertiesRoot(rootPath);
ConfigValue mainFiles = parse(rootPath, options);
ConfigValue referenceFiles = parse(rootPath + ".reference", options);
return system.withFallbacks(mainFiles, referenceFiles);
}
public static ConfigRoot emptyRoot(String rootPath) {
return emptyRoot(rootPath, null);
}
public static ConfigObject empty() {
return empty(null);
}
public static ConfigRoot emptyRoot(String rootPath, String originDescription) {
return ConfigImpl.emptyRoot(rootPath, originDescription);
}
public static ConfigObject empty(String originDescription) {
return ConfigImpl.empty(originDescription);
}
public static ConfigRoot systemPropertiesRoot(String rootPath) {
return ConfigImpl.systemPropertiesRoot(rootPath);
}
public static ConfigObject systemProperties() {
return ConfigImpl.systemPropertiesAsConfig();
}
public static ConfigObject 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.
*
* 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.
*
* @param properties
* a Java Properties object
* @param options
* @return
*/
public static ConfigObject parse(Properties properties,
ConfigParseOptions options) {
return Parseable.newProperties(properties, options).parse();
}
public static ConfigObject parse(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse();
}
public static ConfigObject parse(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse();
}
public static ConfigObject parse(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse();
}
public static ConfigObject parse(Class<?> klass, String resource,
ConfigParseOptions options) {
return Parseable.newResource(klass, resource, options).parse();
}
/**
* 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.
* 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.
*
* @param path
* the path expression
* @return true if a non-null value is present at the path
* @throws ConfigException.BadPath
* if the path expression is invalid
*/
boolean hasPath(String path);
boolean isEmpty();
boolean getBoolean(String path);
Number getNumber(String path);
int getInt(String path);
long getLong(String path);
double getDouble(String path);
String getString(String path);
ConfigObject getObject(String path);
Config getConfig(String path);
/**
* Gets the value at the path as an unwrapped Java boxed value (Boolean,
* Integer, Long, etc.)
*/
Object getAnyRef(String path);
/**
* 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.
*
* @param path
* @param options
* @return
*/
public static ConfigObject parse(String path, ConfigParseOptions options) {
// null originDescription is allowed in parseResourcesForPath
return ConfigImpl.parseResourcesForPath(path, options);
}
ConfigValue getValue(String path);
/**
* Creates a ConfigValue from a plain Java boxed value, which may be a
* Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from
* String to more values that can be supplied to fromAnyRef(). An Iterable
* must iterate over more values that can be supplied to fromAnyRef(). A Map
* will become a ConfigObject and an Iterable will become a ConfigList. If
* the Iterable is not an ordered collection, results could be strange,
* since ConfigList is ordered.
*
* In a Map passed to fromAnyRef(), the map's keys are plain keys, not path
* expressions. So if your Map has a key "foo.bar" then you will get one
* object with a key called "foo.bar", rather than an object with a key
* "foo" containing another object with a key "bar".
*
* The originDescription will be used to set the origin() field on the
* ConfigValue. It should normally be the name of the file the values came
* from, or something short describing the value such as "default settings".
* The originDescription is prefixed to error messages so users can tell
* where problematic values are coming from.
*
* Supplying the result of ConfigValue.unwrapped() to this function is
* guaranteed to work and should give you back a ConfigValue that matches
* the one you unwrapped. The re-wrapped ConfigValue will lose some
* information that was present in the original such as its origin, but it
* will have matching values.
*
* This function throws if you supply a value that cannot be converted to a
* ConfigValue, but supplying such a value is a bug in your program, so you
* should never handle the exception. Just fix your program (or report a bug
* against this library).
*
* @param object
* object to convert to ConfigValue
* @param originDescription
* name of origin file or brief description of what the value is
* @return a new value
* 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.
*/
public static ConfigValue fromAnyRef(Object object, String originDescription) {
return ConfigImpl.fromAnyRef(object, originDescription);
}
Long getMemorySizeInBytes(String path);
/**
* See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Map and returns ConfigObject rather than
* ConfigValue.
*
* If your Map has a key "foo.bar" then you will get one object with a key
* called "foo.bar", rather than an object with a key "foo" containing
* another object with a key "bar". The keys in the map are keys; not path
* expressions. That is, the Map corresponds exactly to a single
* 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.
*
* There is a separate fromPathMap() that interprets the keys in the map as
* path expressions.
*
* @param values
* @param originDescription
* @return
* 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"
*/
public static ConfigObject fromMap(Map<String, ? extends Object> values,
String originDescription) {
return (ConfigObject) fromAnyRef(values, originDescription);
}
Long getMilliseconds(String path);
/**
* Similar to fromMap(), but the keys in the map are path expressions,
* rather than keys. This is more convenient if you are writing literal maps
* in code, and less convenient if you are getting your maps from some data
* source such as a parser.
*
* An exception will be thrown (and it is a bug in the caller of the method)
* if a path is both an object and a value, for example if you had both
* "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent
* object of "b". The caller of this method should ensure that doesn't
* happen.
*
* @param values
* @param originDescription
* @return
* 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.
*/
public static ConfigObject fromPathMap(
Map<String, ? extends Object> values, String originDescription) {
return ConfigImpl.fromPathMap(values, originDescription);
}
Long getNanoseconds(String path);
/**
* See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Iterable and returns ConfigList rather than
* ConfigValue.
* 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.
*
* @param values
* @param originDescription
* @return
* @param path
* the path to the list value.
* @return the ConfigList at the path
*/
public static ConfigList fromIterable(Iterable<? extends Object> values,
String originDescription) {
return (ConfigList) fromAnyRef(values, originDescription);
}
ConfigList getList(String path);
/**
* See the other overload of fromAnyRef() for details, this one just uses a
* default origin description.
*
* @param object
* @return
*/
public static ConfigValue fromAnyRef(Object object) {
return fromAnyRef(object, null);
}
List<Boolean> getBooleanList(String path);
/**
* See the other overload of fromMap() for details, this one just uses a
* default origin description.
*
* @param values
* @return
*/
public static ConfigObject fromMap(Map<String, ? extends Object> values) {
return fromMap(values, null);
}
List<Number> getNumberList(String path);
/**
* See the other overload of fromPathMap() for details, this one just uses a
* default origin description.
*
* @param values
* @return
*/
public static ConfigObject fromPathMap(Map<String, ? extends Object> values) {
return fromPathMap(values, null);
}
List<Integer> getIntList(String path);
/**
* See the other overload of fromIterable() for details, this one just uses
* a default origin description.
*
* @param values
* @return
*/
public static ConfigList fromIterable(Collection<? extends Object> values) {
return fromIterable(values, null);
}
List<Long> getLongList(String path);
private static String getUnits(String s) {
int i = s.length() - 1;
while (i >= 0) {
char c = s.charAt(i);
if (!Character.isLetter(c))
break;
i -= 1;
}
return s.substring(i + 1);
}
List<Double> getDoubleList(String path);
/**
* Parses a duration string. If no units are specified in the string, it is
* assumed to be in milliseconds. The returned duration is in nanoseconds.
* The purpose of this function is to implement the duration-related methods
* in the ConfigObject interface.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return duration in nanoseconds
* @throws ConfigException
* if string is invalid
*/
public static long parseDuration(String input,
ConfigOrigin originForException, String pathForException) {
String s = ConfigUtil.unicodeTrim(input);
String originalUnitString = getUnits(s);
String unitString = originalUnitString;
String numberString = ConfigUtil.unicodeTrim(s.substring(0, s.length()
- unitString.length()));
TimeUnit units = null;
List<String> getStringList(String path);
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in duration value '" + input
+ "'");
List<? extends ConfigObject> getObjectList(String path);
if (unitString.length() > 2 && !unitString.endsWith("s"))
unitString = unitString + "s";
List<? extends Config> getConfigList(String path);
// note that this is deliberately case-sensitive
if (unitString.equals("") || unitString.equals("ms")
|| unitString.equals("milliseconds")) {
units = TimeUnit.MILLISECONDS;
} else if (unitString.equals("us") || unitString.equals("microseconds")) {
units = TimeUnit.MICROSECONDS;
} else if (unitString.equals("ns") || unitString.equals("nanoseconds")) {
units = TimeUnit.NANOSECONDS;
} else if (unitString.equals("d") || unitString.equals("days")) {
units = TimeUnit.DAYS;
} else if (unitString.equals("h") || unitString.equals("hours")) {
units = TimeUnit.HOURS;
} else if (unitString.equals("s") || unitString.equals("seconds")) {
units = TimeUnit.SECONDS;
} else if (unitString.equals("m") || unitString.equals("minutes")) {
units = TimeUnit.MINUTES;
} else {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse time unit '"
+ originalUnitString
+ "' (try ns, us, ms, s, m, d)");
}
List<? extends Object> getAnyRefList(String path);
try {
// if the string is purely digits, parse as an integer to avoid
// possible precision loss;
// otherwise as a double.
if (numberString.matches("[0-9]+")) {
return units.toNanos(Long.parseLong(numberString));
} else {
long nanosInUnit = units.toNanos(1);
return (long) (Double.parseDouble(numberString) * nanosInUnit);
}
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse duration number '"
+ numberString + "'");
}
}
List<Long> getMemorySizeInBytesList(String path);
private static enum MemoryUnit {
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES(
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
List<Long> getMillisecondsList(String path);
int bytes;
MemoryUnit(int bytes) {
this.bytes = bytes;
}
}
/**
* 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.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return size in bytes
* @throws ConfigException
* if string is invalid
*/
public static long parseMemorySizeInBytes(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()));
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in size-in-bytes value '"
+ input + "'");
MemoryUnit units = null;
// 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)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// 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 + "'");
}
}
List<Long> getNanosecondsList(String path);
}

View File

@ -0,0 +1,199 @@
package com.typesafe.config;
import java.io.File;
import java.io.Reader;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable;
/**
* This class contains static methods for creating Config objects.
*
* 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.
*/
public final class 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.
*
* 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().
*
* @param rootPath
* the configuration "domain"
* @return configuration object for the requested root path
*/
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);
}
/**
* 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()}.
*
* @param rootPath
* @return
*/
public static ConfigRoot loadWithoutResolving(String rootPath) {
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
}
public static ConfigRoot loadWithoutResolving(String rootPath,
ConfigParseOptions options) {
ConfigRoot system = systemPropertiesRoot(rootPath);
Config mainFiles = parse(rootPath, options);
Config referenceFiles = parse(rootPath + ".reference", options);
return system.withFallbacks(mainFiles, referenceFiles);
}
public static ConfigRoot emptyRoot(String rootPath) {
return emptyRoot(rootPath, null);
}
public static Config empty() {
return empty(null);
}
public static ConfigRoot emptyRoot(String rootPath, String originDescription) {
return ConfigImpl.emptyRoot(rootPath, originDescription);
}
public static Config empty(String originDescription) {
return ConfigImpl.emptyConfig(originDescription);
}
public static ConfigRoot systemPropertiesRoot(String rootPath) {
return ConfigImpl.systemPropertiesRoot(rootPath);
}
public static Config systemProperties() {
return ConfigImpl.systemPropertiesAsConfig();
}
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.
*
* 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.
*
* @param properties
* a Java Properties object
* @param options
* @return
*/
public static Config parse(Properties properties,
ConfigParseOptions options) {
return Parseable.newProperties(properties, options).parse().toConfig();
}
public static Config parse(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse().toConfig();
}
public static Config parse(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse().toConfig();
}
public static Config parse(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig();
}
public static Config parse(Class<?> klass, String resource,
ConfigParseOptions options) {
return Parseable.newResource(klass, resource, 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 parse(String path, ConfigParseOptions options) {
// null originDescription is allowed in parseResourcesForPath
return ConfigImpl.parseResourcesForPath(path, options).toConfig();
}
/**
* 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.
*
* 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
* object of "b". The caller of this method should ensure that doesn't
* happen.
*
* @param values
* @param originDescription
* description of what this map represents, like a filename, or
* "default settings" (origin description is used in error
* messages)
* @return
*/
public static Config parseMap(Map<String, ? extends Object> values,
String originDescription) {
return ConfigImpl.fromPathMap(values, originDescription).toConfig();
}
/**
* See the other overload of parseMap() for details, this one just uses a
* default origin description.
*
* @param values
* @return
*/
public static Config parseMap(Map<String, ? extends Object> values) {
return parseMap(values, null);
}
}

View File

@ -0,0 +1,38 @@
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.
*/
public interface ConfigMergeable {
/**
* Converts the mergeable to a ConfigValue to be merged.
*
* @return
*/
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.
*
* @param other
* an object whose keys should be used if the keys are not
* present in this one
* @return a new object (or the original one, if the fallback doesn't get
* used)
*/
ConfigMergeable withFallback(ConfigMergeable other);
/**
* Convenience method just calls withFallback() on each of the values;
* earlier values in the list win over later ones.
*
* @param fallbacks
* @return a version of the object with the requested fallbacks merged in
*/
ConfigMergeable withFallbacks(ConfigMergeable... others);
}

View File

@ -1,12 +1,19 @@
package com.typesafe.config;
import java.util.List;
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).
* from the perspective of anyone using this interface) and thus thread-safe.
*
* In most cases you want to use the Config interface rather than this one. Call
* toConfig() to convert a ConfigObject to a config.
*
* 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.
*
* 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
@ -18,29 +25,17 @@ import java.util.Map;
* object a in the root object. Sometimes double quotes are needed around
* special characters in path expressions.
*
* ConfigObject implements java.util.Map<String,ConfigValue>. For all methods
* implementing the Map interface, the keys are just plain keys; not a parseable
* path expression. In methods implementing Map, a ConfigValue with
* ConfigValue.valueType() of ConfigValueType.NULL will be distinct from a
* missing value. java.util.Map.containsKey() returns true if the map contains a
* value of type ConfigValueType.NULL at that key.
*
* ConfigObject has another set of "getters", such as getValue() and getAnyRef()
* and getInt(), with more convenient semantics than java.util.Map.get(). These
* "getters" throw ConfigException.Missing if the value is entirely unset, and
* ConfigException.WrongType if you ask for a type that the value can't be
* converted to. ConfigException.Null is a subclass of ConfigException.WrongType
* thrown if the value is null. These getters also use path expressions, rather
* than keys, as described above.
* ConfigObject implements java.util.Map<String,ConfigValue> and all methods
* work with keys, not path expressions.
*
* While ConfigObject implements the standard Java Map interface, the mutator
* methods all throw UnsupportedOperationException. This Map is immutable.
*
* The Map may contain null values, which will have ConfigValue.valueType() ==
* ConfigValueType.NULL. When using methods from the Map interface, such as
* get() or containsKey(), these null ConfigValue will be visible. But hasPath()
* returns false for null values, and getInt() etc. throw ConfigException.Null
* for null values.
* 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.
*/
public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
@ -48,7 +43,7 @@ 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
* operation (it is not proportional to the size of the object).
*
*
* @return
*/
Config toConfig();
@ -61,113 +56,10 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
Map<String, Object> unwrapped();
@Override
ConfigObject withFallback(ConfigValue other);
ConfigObject withFallback(ConfigMergeable other);
@Override
ConfigObject withFallbacks(ConfigValue... others);
/**
* Checks whether a value is present and non-null at the given path. This
* differs in two ways from 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.
*
* 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.
*
* @param path
* the path expression
* @return true if a non-null value is present at the path
* @throws ConfigException.BadPath
* if the path expression is invalid
*/
boolean hasPath(String path);
boolean getBoolean(String path);
Number getNumber(String path);
int getInt(String path);
long getLong(String path);
double getDouble(String path);
String getString(String path);
ConfigObject getObject(String path);
/**
* Gets the value at the path as an unwrapped Java boxed value (Boolean,
* Integer, Long, etc.)
*/
Object getAnyRef(String path);
/**
* 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.
*
* @param path
* @return
*/
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.
*/
Long getMemorySizeInBytes(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"
*/
Long getMilliseconds(String path);
/**
* 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.
*/
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.
*
* @param path
* the path to the list value.
* @return the ConfigList at the path
*/
ConfigList getList(String path);
List<Boolean> getBooleanList(String path);
List<Number> getNumberList(String path);
List<Integer> getIntList(String path);
List<Long> getLongList(String path);
List<Double> getDoubleList(String path);
List<String> getStringList(String path);
List<? extends ConfigObject> getObjectList(String path);
List<? extends Object> getAnyRefList(String path);
List<Long> getMemorySizeInBytesList(String path);
List<Long> getMillisecondsList(String path);
List<Long> getNanosecondsList(String path);
ConfigObject withFallbacks(ConfigMergeable... others);
/**
* Gets a ConfigValue at the given key, or returns null if there is no

View File

@ -5,13 +5,13 @@ package com.typesafe.config;
* 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 ConfigObject {
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();
@ -19,10 +19,10 @@ public interface ConfigRoot extends ConfigObject {
ConfigRoot resolve(ConfigResolveOptions options);
@Override
ConfigRoot withFallback(ConfigValue fallback);
ConfigRoot withFallback(ConfigMergeable fallback);
@Override
ConfigRoot withFallbacks(ConfigValue... fallbacks);
ConfigRoot withFallbacks(ConfigMergeable... fallbacks);
/**
* Gets the global app name that this root represents.

View File

@ -5,7 +5,7 @@ package com.typesafe.config;
* users of this interface, the object is immutable. It is therefore safe to use
* from multiple threads.
*/
public interface ConfigValue {
public interface ConfigValue extends ConfigMergeable {
/**
* The origin of the value, for debugging and error messages.
*
@ -27,27 +27,9 @@ public interface ConfigValue {
*/
Object unwrapped();
/**
* Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only ConfigObject has
* anything to do in this method (it merges the fallback keys into itself).
* All other values just return the original value, since they automatically
* override any fallback.
*
* @param other
* an object whose keys should be used if the keys are not
* present in this one
* @return a new object (or the original one, if the fallback doesn't get
* used)
*/
ConfigValue withFallback(ConfigValue other);
@Override
ConfigValue withFallback(ConfigMergeable other);
/**
* Convenience method just calls withFallback() on each of the values;
* earlier values in the list win over later ones.
*
* @param fallbacks
* @return a version of the object with the requested fallbacks merged in
*/
ConfigValue withFallbacks(ConfigValue... fallbacks);
@Override
ConfigValue withFallbacks(ConfigMergeable... fallbacks);
}

View File

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

View File

@ -10,11 +10,9 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
@ -31,32 +29,10 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
}
@Override
public Config toConfig() {
public SimpleConfig toConfig() {
return config;
}
/**
* Returns a version of this object that implements the ConfigRoot
* interface.
*
* @return a config root
*/
protected ConfigRootImpl asRoot(Path rootPath) {
return new RootConfigObject(this, rootPath);
}
protected static ConfigRootImpl resolve(ConfigRootImpl root) {
return resolve(root, ConfigResolveOptions.defaults());
}
protected static ConfigRootImpl resolve(ConfigRootImpl root,
ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(
(AbstractConfigValue) root, (AbstractConfigObject) root,
options);
return ((AbstractConfigObject) resolved).asRoot(root.rootPathObject());
}
/**
* This looks up the key with no transformation or type conversion of any
* kind, and returns null if the key is not present.
@ -124,70 +100,17 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return ConfigValueType.OBJECT;
}
@Override
public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
ConfigValue peeked = peekPath(path, null, 0, null);
return peeked != null && peeked.valueType() != ConfigValueType.NULL;
}
protected abstract AbstractConfigObject newCopy(ResolveStatus status);
static private AbstractConfigValue resolve(AbstractConfigObject self,
String pathExpression, ConfigValueType expected,
String originalPath) {
Path path = Path.newPath(pathExpression);
return find(self, path, expected, originalPath);
}
static private AbstractConfigValue findKey(AbstractConfigObject self,
String key, ConfigValueType expected, String originalPath) {
AbstractConfigValue v = self.peek(key);
if (v == null)
throw new ConfigException.Missing(originalPath);
if (expected != null)
v = DefaultTransformer.transform(v, expected);
if (v.valueType() == ConfigValueType.NULL)
throw new ConfigException.Null(v.origin(), originalPath,
expected != null ? expected.name() : null);
else if (expected != null && v.valueType() != expected)
throw new ConfigException.WrongType(v.origin(), originalPath,
expected.name(), v.valueType().name());
else
return v;
}
static private AbstractConfigValue find(AbstractConfigObject self,
Path path, ConfigValueType expected,
String originalPath) {
String key = path.first();
Path next = path.remainder();
if (next == null) {
return findKey(self, key, expected, originalPath);
} else {
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
ConfigValueType.OBJECT, originalPath);
assert (o != null); // missing was supposed to throw
return find(o, next, expected, originalPath);
}
}
AbstractConfigValue find(String pathExpression,
ConfigValueType expected,
String originalPath) {
return resolve(this, pathExpression, expected,
originalPath);
}
@Override
public AbstractConfigObject withFallbacks(ConfigValue... others) {
public AbstractConfigObject withFallbacks(ConfigMergeable... others) {
return (AbstractConfigObject) super.withFallbacks(others);
}
@Override
public AbstractConfigObject withFallback(ConfigValue other) {
public AbstractConfigObject withFallback(ConfigMergeable mergeable) {
ConfigValue other = mergeable.toValue();
if (other instanceof Unmergeable) {
List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
stack.add(this);
@ -361,234 +284,6 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return null;
}
@Override
public AbstractConfigValue getValue(String path) {
return find(path, null, path);
}
@Override
public boolean getBoolean(String path) {
ConfigValue v = find(path, ConfigValueType.BOOLEAN, path);
return (Boolean) v.unwrapped();
}
@Override
public Number getNumber(String path) {
ConfigValue v = find(path, ConfigValueType.NUMBER, path);
return (Number) v.unwrapped();
}
@Override
public int getInt(String path) {
return getNumber(path).intValue();
}
@Override
public long getLong(String path) {
return getNumber(path).longValue();
}
@Override
public double getDouble(String path) {
return getNumber(path).doubleValue();
}
@Override
public String getString(String path) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
return (String) v.unwrapped();
}
@Override
public ConfigList getList(String path) {
AbstractConfigValue v = find(path, ConfigValueType.LIST, path);
return (ConfigList) v;
}
@Override
public AbstractConfigObject getObject(String path) {
AbstractConfigObject obj = (AbstractConfigObject) find(path,
ConfigValueType.OBJECT, path);
return obj;
}
@Override
public Object getAnyRef(String path) {
ConfigValue v = find(path, null, path);
return v.unwrapped();
}
@Override
public Long getMemorySizeInBytes(String path) {
Long size = null;
try {
size = getLong(path);
} catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
size = Config.parseMemorySizeInBytes((String) v.unwrapped(), v.origin(),
path);
}
return size;
}
@Override
public Long getMilliseconds(String path) {
long ns = getNanoseconds(path);
long ms = TimeUnit.NANOSECONDS.toMillis(ns);
return ms;
}
@Override
public Long getNanoseconds(String path) {
Long ns = null;
try {
ns = TimeUnit.MILLISECONDS.toNanos(getLong(path));
} catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
ns = Config.parseDuration((String) v.unwrapped(), v.origin(), path);
}
return ns;
}
@SuppressWarnings("unchecked")
private <T> List<T> getHomogeneousUnwrappedList(String path,
ConfigValueType expected) {
List<T> l = new ArrayList<T>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue cv : list) {
// variance would be nice, but stupid cast will do
AbstractConfigValue v = (AbstractConfigValue) cv;
if (expected != null) {
v = DefaultTransformer.transform(v, expected);
}
if (v.valueType() != expected)
throw new ConfigException.WrongType(v.origin(), path,
"list of " + expected.name(), "list of "
+ v.valueType().name());
l.add((T) v.unwrapped());
}
return l;
}
@Override
public List<Boolean> getBooleanList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.BOOLEAN);
}
@Override
public List<Number> getNumberList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.NUMBER);
}
@Override
public List<Integer> getIntList(String path) {
List<Integer> l = new ArrayList<Integer>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.intValue());
}
return l;
}
@Override
public List<Long> getLongList(String path) {
List<Long> l = new ArrayList<Long>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.longValue());
}
return l;
}
@Override
public List<Double> getDoubleList(String path) {
List<Double> l = new ArrayList<Double>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.doubleValue());
}
return l;
}
@Override
public List<String> getStringList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.STRING);
}
@Override
public List<ConfigObject> getObjectList(String path) {
List<ConfigObject> l = new ArrayList<ConfigObject>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() != ConfigValueType.OBJECT)
throw new ConfigException.WrongType(v.origin(), path,
ConfigValueType.OBJECT.name(), v.valueType().name());
l.add((ConfigObject) v);
}
return l;
}
@Override
public List<? extends Object> getAnyRefList(String path) {
List<Object> l = new ArrayList<Object>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
l.add(v.unwrapped());
}
return l;
}
@Override
public List<Long> getMemorySizeInBytesList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = Config.parseMemorySizeInBytes(s, v.origin(), path);
l.add(n);
} else {
throw new ConfigException.WrongType(v.origin(), path,
"memory size string or number of bytes", v.valueType()
.name());
}
}
return l;
}
@Override
public List<Long> getMillisecondsList(String path) {
List<Long> nanos = getNanosecondsList(path);
List<Long> l = new ArrayList<Long>();
for (Long n : nanos) {
l.add(TimeUnit.NANOSECONDS.toMillis(n));
}
return l;
}
@Override
public List<Long> getNanosecondsList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
l.add(TimeUnit.MILLISECONDS.toNanos(((Number) v.unwrapped())
.longValue()));
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = Config.parseDuration(s, v.origin(), path);
l.add(n);
} else {
throw new ConfigException.WrongType(v.origin(), path,
"duration string or number of nanoseconds", v
.valueType().name());
}
}
return l;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();

View File

@ -1,5 +1,6 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
@ -67,15 +68,23 @@ abstract class AbstractConfigValue implements ConfigValue {
}
@Override
public AbstractConfigValue withFallback(ConfigValue other) {
public AbstractConfigValue toValue() {
return this;
}
@Override
public AbstractConfigValue withFallbacks(ConfigValue... fallbacks) {
public AbstractConfigValue withFallback(ConfigMergeable other) {
return this;
}
@Override
public AbstractConfigValue withFallbacks(ConfigMergeable... fallbacks) {
// note: this is a no-op unless the subclass overrides withFallback().
// But we need to do this because subclass withFallback() may not
// just "return this"
AbstractConfigValue merged = this;
for (ConfigValue f : fallbacks) {
merged = merged.withFallback(f);
for (ConfigMergeable f : fallbacks) {
merged = merged.withFallback(f.toValue());
}
return merged;
}

View File

@ -5,6 +5,7 @@ import java.util.Collection;
import java.util.List;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
@ -100,7 +101,9 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
}
@Override
public AbstractConfigValue withFallback(ConfigValue other) {
public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
ConfigValue other = mergeable.toValue();
if (other instanceof AbstractConfigObject
|| other instanceof Unmergeable) {
// if we turn out to be an object, and the fallback also does,

View File

@ -7,6 +7,7 @@ import java.util.Map;
import java.util.Set;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
@ -30,59 +31,6 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
"created a delayed merge object not guaranteed to be an object");
}
final private static class Root extends ConfigDelayedMergeObject implements
ConfigRootImpl {
final private Path rootPath;
Root(ConfigDelayedMergeObject original, Path rootPath) {
super(original.origin(), original.stack);
this.rootPath = rootPath;
}
@Override
protected Root asRoot(Path newRootPath) {
if (newRootPath.equals(this.rootPath))
return this;
else
return new Root(this, newRootPath);
}
@Override
public ConfigRootImpl resolve() {
return resolve(this);
}
@Override
public ConfigRootImpl resolve(ConfigResolveOptions options) {
return resolve(this, options);
}
@Override
public Root withFallback(ConfigValue value) {
return super.withFallback(value).asRoot(rootPath);
}
@Override
public Root withFallbacks(ConfigValue... values) {
return super.withFallbacks(values).asRoot(rootPath);
}
@Override
public String rootPath() {
return rootPath.render();
}
@Override
public Path rootPathObject() {
return rootPath;
}
}
@Override
protected Root asRoot(Path rootPath) {
return new Root(this, rootPath);
}
@Override
public ConfigDelayedMergeObject newCopy(ResolveStatus status) {
if (status != resolveStatus())
@ -120,7 +68,9 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
}
@Override
public ConfigDelayedMergeObject withFallback(ConfigValue other) {
public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
ConfigValue other = mergeable.toValue();
if (other instanceof AbstractConfigObject
|| other instanceof Unmergeable) {
// since we are an object, and the fallback could be,
@ -142,7 +92,7 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
}
@Override
public ConfigDelayedMergeObject withFallbacks(ConfigValue... others) {
public ConfigDelayedMergeObject withFallbacks(ConfigMergeable... others) {
return (ConfigDelayedMergeObject) super.withFallbacks(others);
}

View File

@ -7,6 +7,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigIncludeContext;
import com.typesafe.config.ConfigIncluder;
@ -119,17 +120,21 @@ public class ConfigImpl {
/** 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 SimpleConfigObject.empty(new SimpleConfigOrigin(desc))
.asRoot(
Parser.parsePath(rootPath));
return emptyObject(desc).toConfig().asRoot(
Path.newPath(rootPath));
}
public static ConfigObject empty(String originDescription) {
static AbstractConfigObject emptyObject(String originDescription) {
ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin(
originDescription) : null;
return emptyObject(origin);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config emptyConfig(String originDescription) {
return emptyObject(originDescription).toConfig();
}
static AbstractConfigObject empty(ConfigOrigin origin) {
return emptyObject(origin);
}
@ -267,9 +272,10 @@ public class ConfigImpl {
public static ConfigRoot systemPropertiesRoot(String rootPath) {
Path path = Parser.parsePath(rootPath);
try {
return systemPropertiesAsConfig().getObject(rootPath).asRoot(path);
return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath)
.asRoot(path);
} catch (ConfigException.Missing e) {
return SimpleConfigObject.empty().asRoot(path);
return emptyObject("system properties").toConfig().asRoot(path);
}
}
@ -329,8 +335,7 @@ public class ConfigImpl {
private static AbstractConfigObject systemProperties = null;
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public synchronized static AbstractConfigObject systemPropertiesAsConfig() {
synchronized static AbstractConfigObject systemPropertiesAsConfigObject() {
if (systemProperties == null) {
systemProperties = loadSystemProperties();
}
@ -344,6 +349,11 @@ public class ConfigImpl {
"system properties")).parse();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config systemPropertiesAsConfig() {
return systemPropertiesAsConfigObject().toConfig();
}
// this is a hack to let us set system props in the test suite
synchronized static void dropSystemPropertiesConfig() {
systemProperties = null;
@ -351,8 +361,7 @@ public class ConfigImpl {
private static AbstractConfigObject envVariables = null;
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public synchronized static AbstractConfigObject envVariablesAsConfig() {
synchronized static AbstractConfigObject envVariablesAsConfigObject() {
if (envVariables == null) {
envVariables = loadEnvVariables();
}
@ -370,4 +379,9 @@ public class ConfigImpl {
return new SimpleConfigObject(new SimpleConfigOrigin("env variables"),
m, ResolveStatus.RESOLVED);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config envVariablesAsConfig() {
return envVariablesAsConfigObject().toConfig();
}
}

View File

@ -1,11 +0,0 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigRoot;
import com.typesafe.config.ConfigValue;
interface ConfigRootImpl extends ConfigRoot {
Path rootPathObject();
@Override
ConfigRootImpl withFallbacks(ConfigValue... fallbacks);
}

View File

@ -6,6 +6,7 @@ import java.util.Collections;
import java.util.List;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
@ -51,7 +52,9 @@ final class ConfigSubstitution extends AbstractConfigValue implements
}
@Override
public AbstractConfigValue withFallback(ConfigValue other) {
public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
ConfigValue other = mergeable.toValue();
if (other instanceof AbstractConfigObject
|| other instanceof Unmergeable) {
// if we turn out to be an object, and the fallback also does,
@ -113,12 +116,12 @@ final class ConfigSubstitution extends AbstractConfigValue implements
Path unprefixed = subst.subPath(prefixLength);
if (result == null && options.getUseSystemProperties()) {
result = findInObject(ConfigImpl.systemPropertiesAsConfig(), null,
result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null,
unprefixed, depth, options);
}
if (result == null && options.getUseSystemEnvironment()) {
result = findInObject(ConfigImpl.envVariablesAsConfig(), null,
result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null,
unprefixed, depth, options);
}

View File

@ -1,92 +0,0 @@
package com.typesafe.config.impl;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import com.typesafe.config.ConfigValue;
abstract class DelegatingConfigObject extends AbstractConfigObject {
final private AbstractConfigObject underlying;
DelegatingConfigObject(AbstractConfigObject underlying) {
super(underlying.origin());
this.underlying = underlying;
}
@Override
protected DelegatingConfigObject newCopy(ResolveStatus newStatus) {
return newCopy(underlying, newStatus);
}
abstract DelegatingConfigObject newCopy(AbstractConfigObject underlying,
ResolveStatus newStatus);
@Override
ResolveStatus resolveStatus() {
return underlying.resolveStatus();
}
@Override
final protected ConfigRootImpl asRoot(Path rootPath) {
return asRoot(underlying, rootPath);
}
protected abstract ConfigRootImpl asRoot(AbstractConfigObject underlying,
Path rootPath);
@Override
public boolean containsKey(Object key) {
return underlying.containsKey(key);
}
@Override
public Set<String> keySet() {
return underlying.keySet();
}
@Override
public Map<String, Object> unwrapped() {
return underlying.unwrapped();
}
@Override
protected AbstractConfigValue peek(String key) {
return underlying.peek(key);
}
@Override
public AbstractConfigObject withFallback(ConfigValue value) {
return underlying.withFallback(value);
}
@Override
public AbstractConfigObject withFallbacks(ConfigValue... values) {
return underlying.withFallbacks(values);
}
@Override
public boolean containsValue(Object value) {
return underlying.containsValue(value);
}
@Override
public Set<java.util.Map.Entry<String, ConfigValue>> entrySet() {
return underlying.entrySet();
}
@Override
public boolean isEmpty() {
return underlying.isEmpty();
}
@Override
public int size() {
return underlying.size();
}
@Override
public Collection<ConfigValue> values() {
return underlying.values();
}
}

View File

@ -0,0 +1,64 @@
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.
SimpleConfig resolved = resolvedObject(options).toConfig();
return resolved.asRoot(rootPath);
}
@Override
public RootConfig withFallback(ConfigMergeable value) {
// this can return "this" if the withFallback does nothing
return super.withFallback(value).asRoot(rootPath);
}
@Override
public RootConfig withFallbacks(ConfigMergeable... values) {
// this can return "this" if the withFallbacks does nothing
return super.withFallbacks(values).asRoot(rootPath);
}
Path rootPathObject() {
return rootPath;
}
@Override
public String rootPath() {
return rootPath.render();
}
@Override
public String toString() {
return "Root" + super.toString();
}
}

View File

@ -1,60 +0,0 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
final class RootConfigObject extends DelegatingConfigObject implements
ConfigRootImpl {
final private Path rootPath;
RootConfigObject(AbstractConfigObject underlying, Path rootPath) {
super(underlying);
this.rootPath = rootPath;
}
@Override
protected ConfigRootImpl asRoot(AbstractConfigObject underlying,
Path newRootPath) {
if (newRootPath.equals(this.rootPath))
return this;
else
return new RootConfigObject(underlying, newRootPath);
}
@Override
public RootConfigObject newCopy(AbstractConfigObject underlying,
ResolveStatus newStatus) {
return new RootConfigObject(underlying.newCopy(newStatus), rootPath);
}
@Override
public ConfigRootImpl resolve() {
return resolve(this);
}
@Override
public ConfigRootImpl resolve(ConfigResolveOptions options) {
return resolve(this, options);
}
@Override
public RootConfigObject withFallback(ConfigValue value) {
return new RootConfigObject(super.withFallback(value), rootPath);
}
@Override
public RootConfigObject withFallbacks(ConfigValue... values) {
return new RootConfigObject(super.withFallbacks(values), rootPath);
}
@Override
public Path rootPathObject() {
return rootPath;
}
@Override
public String rootPath() {
return rootPath.render();
}
}

View File

@ -0,0 +1,589 @@
package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
/**
* One thing to keep in mind in the future: if any Collection-like APIs are
* added here, including iterators or size() or anything, then we'd have to
* grapple with whether ConfigNull values are "in" the Config (probably not) and
* we'd probably want to make the collection look flat - not like a tree. So the
* key-value pairs would be all the tree's leaf values, in a big flat list with
* their full paths.
*/
class SimpleConfig implements Config {
AbstractConfigObject object;
SimpleConfig(AbstractConfigObject object) {
this.object = object;
}
@Override
public AbstractConfigObject toObject() {
return object;
}
@Override
public ConfigOrigin origin() {
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);
}
// RootConfig overrides this to avoid a new object on unchanged path.
protected RootConfig asRoot(AbstractConfigObject underlying,
Path newRootPath) {
return new RootConfig(underlying, newRootPath);
}
protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
object, options);
return (AbstractConfigObject) resolved;
}
@Override
public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
ConfigValue peeked = object.peekPath(path, null, 0, null);
return peeked != null && peeked.valueType() != ConfigValueType.NULL;
}
@Override
public boolean isEmpty() {
return object.isEmpty();
}
static private AbstractConfigValue find(AbstractConfigObject self,
String pathExpression, ConfigValueType expected, String originalPath) {
Path path = Path.newPath(pathExpression);
return find(self, path, expected, originalPath);
}
static private AbstractConfigValue findKey(AbstractConfigObject self,
String key, ConfigValueType expected, String originalPath) {
AbstractConfigValue v = self.peek(key);
if (v == null)
throw new ConfigException.Missing(originalPath);
if (expected != null)
v = DefaultTransformer.transform(v, expected);
if (v.valueType() == ConfigValueType.NULL)
throw new ConfigException.Null(v.origin(), originalPath,
expected != null ? expected.name() : null);
else if (expected != null && v.valueType() != expected)
throw new ConfigException.WrongType(v.origin(), originalPath,
expected.name(), v.valueType().name());
else
return v;
}
static private AbstractConfigValue find(AbstractConfigObject self,
Path path, ConfigValueType expected, String originalPath) {
String key = path.first();
Path next = path.remainder();
if (next == null) {
return findKey(self, key, expected, originalPath);
} else {
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
ConfigValueType.OBJECT, originalPath);
assert (o != null); // missing was supposed to throw
return find(o, next, expected, originalPath);
}
}
AbstractConfigValue find(String pathExpression, ConfigValueType expected,
String originalPath) {
return find(object, pathExpression, expected, originalPath);
}
@Override
public AbstractConfigValue getValue(String path) {
return find(path, null, path);
}
@Override
public boolean getBoolean(String path) {
ConfigValue v = find(path, ConfigValueType.BOOLEAN, path);
return (Boolean) v.unwrapped();
}
@Override
public Number getNumber(String path) {
ConfigValue v = find(path, ConfigValueType.NUMBER, path);
return (Number) v.unwrapped();
}
@Override
public int getInt(String path) {
return getNumber(path).intValue();
}
@Override
public long getLong(String path) {
return getNumber(path).longValue();
}
@Override
public double getDouble(String path) {
return getNumber(path).doubleValue();
}
@Override
public String getString(String path) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
return (String) v.unwrapped();
}
@Override
public ConfigList getList(String path) {
AbstractConfigValue v = find(path, ConfigValueType.LIST, path);
return (ConfigList) v;
}
@Override
public AbstractConfigObject getObject(String path) {
AbstractConfigObject obj = (AbstractConfigObject) find(path,
ConfigValueType.OBJECT, path);
return obj;
}
@Override
public SimpleConfig getConfig(String path) {
return getObject(path).toConfig();
}
@Override
public Object getAnyRef(String path) {
ConfigValue v = find(path, null, path);
return v.unwrapped();
}
@Override
public Long getMemorySizeInBytes(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(),
v.origin(), path);
}
return size;
}
@Override
public Long getMilliseconds(String path) {
long ns = getNanoseconds(path);
long ms = TimeUnit.NANOSECONDS.toMillis(ns);
return ms;
}
@Override
public Long getNanoseconds(String path) {
Long ns = null;
try {
ns = TimeUnit.MILLISECONDS.toNanos(getLong(path));
} catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
ns = parseDuration((String) v.unwrapped(), v.origin(), path);
}
return ns;
}
@SuppressWarnings("unchecked")
private <T> List<T> getHomogeneousUnwrappedList(String path,
ConfigValueType expected) {
List<T> l = new ArrayList<T>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue cv : list) {
// variance would be nice, but stupid cast will do
AbstractConfigValue v = (AbstractConfigValue) cv;
if (expected != null) {
v = DefaultTransformer.transform(v, expected);
}
if (v.valueType() != expected)
throw new ConfigException.WrongType(v.origin(), path,
"list of " + expected.name(), "list of "
+ v.valueType().name());
l.add((T) v.unwrapped());
}
return l;
}
@Override
public List<Boolean> getBooleanList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.BOOLEAN);
}
@Override
public List<Number> getNumberList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.NUMBER);
}
@Override
public List<Integer> getIntList(String path) {
List<Integer> l = new ArrayList<Integer>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.intValue());
}
return l;
}
@Override
public List<Long> getLongList(String path) {
List<Long> l = new ArrayList<Long>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.longValue());
}
return l;
}
@Override
public List<Double> getDoubleList(String path) {
List<Double> l = new ArrayList<Double>();
List<Number> numbers = getNumberList(path);
for (Number n : numbers) {
l.add(n.doubleValue());
}
return l;
}
@Override
public List<String> getStringList(String path) {
return getHomogeneousUnwrappedList(path, ConfigValueType.STRING);
}
@SuppressWarnings("unchecked")
private <T extends ConfigValue> List<T> getHomogeneousWrappedList(
String path, ConfigValueType expected) {
List<T> l = new ArrayList<T>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue cv : list) {
// variance would be nice, but stupid cast will do
AbstractConfigValue v = (AbstractConfigValue) cv;
if (expected != null) {
v = DefaultTransformer.transform(v, expected);
}
if (v.valueType() != expected)
throw new ConfigException.WrongType(v.origin(), path,
"list of " + expected.name(), "list of "
+ v.valueType().name());
l.add((T) v);
}
return l;
}
@Override
public List<ConfigObject> getObjectList(String path) {
return getHomogeneousWrappedList(path, ConfigValueType.OBJECT);
}
@Override
public List<? extends Config> getConfigList(String path) {
List<ConfigObject> objects = getObjectList(path);
List<Config> l = new ArrayList<Config>();
for (ConfigObject o : objects) {
l.add(o.toConfig());
}
return l;
}
@Override
public List<? extends Object> getAnyRefList(String path) {
List<Object> l = new ArrayList<Object>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
l.add(v.unwrapped());
}
return l;
}
@Override
public List<Long> getMemorySizeInBytesList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = parseMemorySizeInBytes(s, v.origin(), path);
l.add(n);
} else {
throw new ConfigException.WrongType(v.origin(), path,
"memory size string or number of bytes", v.valueType()
.name());
}
}
return l;
}
@Override
public List<Long> getMillisecondsList(String path) {
List<Long> nanos = getNanosecondsList(path);
List<Long> l = new ArrayList<Long>();
for (Long n : nanos) {
l.add(TimeUnit.NANOSECONDS.toMillis(n));
}
return l;
}
@Override
public List<Long> getNanosecondsList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
l.add(TimeUnit.MILLISECONDS.toNanos(((Number) v.unwrapped())
.longValue()));
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = parseDuration(s, v.origin(), path);
l.add(n);
} else {
throw new ConfigException.WrongType(v.origin(), path,
"duration string or number of nanoseconds", v
.valueType().name());
}
}
return l;
}
@Override
public AbstractConfigObject toValue() {
return object;
}
@Override
public SimpleConfig withFallback(ConfigMergeable other) {
// this can return "this" if the withFallback doesn't need a new
// ConfigObject
return object.withFallback(other).toConfig();
}
@Override
public SimpleConfig withFallbacks(ConfigMergeable... others) {
// this can return "this" if the withFallbacks doesn't need a new
// ConfigObject
return object.withFallbacks(others).toConfig();
}
@Override
public final boolean equals(Object other) {
if (other instanceof SimpleConfig) {
return object.equals(((SimpleConfig) other).object);
} else {
return false;
}
}
@Override
public final int hashCode() {
// we do the "41*" just so our hash code won't match that of the
// underlying object. there's no real reason it can't match, but
// making it not match might catch some kinds of bug.
return 41 * object.hashCode();
}
@Override
public String toString() {
return "Config(" + object.toString() + ")";
}
private static String getUnits(String s) {
int i = s.length() - 1;
while (i >= 0) {
char c = s.charAt(i);
if (!Character.isLetter(c))
break;
i -= 1;
}
return s.substring(i + 1);
}
/**
* Parses a duration string. If no units are specified in the string, it is
* assumed to be in milliseconds. The returned duration is in nanoseconds.
* The purpose of this function is to implement the duration-related methods
* in the ConfigObject interface.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return duration in nanoseconds
* @throws ConfigException
* if string is invalid
*/
public static long parseDuration(String input,
ConfigOrigin originForException, String pathForException) {
String s = ConfigUtil.unicodeTrim(input);
String originalUnitString = getUnits(s);
String unitString = originalUnitString;
String numberString = ConfigUtil.unicodeTrim(s.substring(0, s.length()
- unitString.length()));
TimeUnit units = null;
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in duration value '" + input
+ "'");
if (unitString.length() > 2 && !unitString.endsWith("s"))
unitString = unitString + "s";
// note that this is deliberately case-sensitive
if (unitString.equals("") || unitString.equals("ms")
|| unitString.equals("milliseconds")) {
units = TimeUnit.MILLISECONDS;
} else if (unitString.equals("us") || unitString.equals("microseconds")) {
units = TimeUnit.MICROSECONDS;
} else if (unitString.equals("ns") || unitString.equals("nanoseconds")) {
units = TimeUnit.NANOSECONDS;
} else if (unitString.equals("d") || unitString.equals("days")) {
units = TimeUnit.DAYS;
} else if (unitString.equals("h") || unitString.equals("hours")) {
units = TimeUnit.HOURS;
} else if (unitString.equals("s") || unitString.equals("seconds")) {
units = TimeUnit.SECONDS;
} else if (unitString.equals("m") || unitString.equals("minutes")) {
units = TimeUnit.MINUTES;
} else {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse time unit '"
+ originalUnitString
+ "' (try ns, us, ms, s, m, d)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// possible precision loss;
// otherwise as a double.
if (numberString.matches("[0-9]+")) {
return units.toNanos(Long.parseLong(numberString));
} else {
long nanosInUnit = units.toNanos(1);
return (long) (Double.parseDouble(numberString) * nanosInUnit);
}
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse duration number '"
+ numberString + "'");
}
}
private static enum MemoryUnit {
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES(
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
int bytes;
MemoryUnit(int bytes) {
this.bytes = bytes;
}
}
/**
* 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.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return size in bytes
* @throws ConfigException
* if string is invalid
*/
public static long parseMemorySizeInBytes(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()));
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in size-in-bytes value '"
+ input + "'");
MemoryUnit units = null;
// 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)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// 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 + "'");
}
}
}

View File

@ -12,31 +12,45 @@ import scala.collection.mutable
class ApiExamples {
@Test
def readSomeConfig() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
// you don't have to write the types explicitly of course,
// just doing that to show what they are.
val a: Int = conf.getInt("ints.fortyTwo")
val obj: ConfigObject = conf.getObject("ints")
val c: Int = obj.getInt("fortyTwo")
val child: Config = conf.getConfig("ints")
val b: Int = child.getInt("fortyTwo")
val ms: Long = conf.getMilliseconds("durations.halfSecond")
// a Config has an associated tree of values, with a ConfigObject
// at the root. The ConfigObject implements java.util.Map
val obj: ConfigObject = conf.toObject
// this is how you do conf.getInt "manually" on the value tree, if you
// were so inclined. (This is not a good approach vs. conf.getInt() above,
// just showing how ConfigObject stores a tree of values.)
val c: Int = obj.get("ints")
.asInstanceOf[ConfigObject]
.get("fortyTwo").unwrapped()
.asInstanceOf[java.lang.Integer]
// this is unfortunate; asScala creates a mutable map but
// the ConfigObject is in fact immutable.
// a ConfigObject is in fact immutable.
val objAsScalaMap: mutable.Map[String, ConfigValue] = obj.asScala
// this is sort of ugly...
// this is sort of ugly, but you could improve it
// with a Scala implicit wrapper, see below...
val d: Int = conf.getAnyRef("ints.fortyTwo") match {
case x: java.lang.Integer => x
case x: java.lang.Long => x.intValue
}
class EnhancedConfigObject(obj: ConfigObject) {
def getAny(path: String): Any = obj.getAnyRef(path)
// our implicit wrapper: do stuff like this to get a nicer Scala API
class EnhancedConfig(c: Config) {
def getAny(path: String): Any = c.getAnyRef(path)
}
implicit def obj2enhanced(obj: ConfigObject) = new EnhancedConfigObject(obj)
implicit def config2enhanced(c: Config) = new EnhancedConfig(c)
// somewhat nicer
// somewhat nicer now
val e: Int = conf.getAny("ints.fortyTwo") match {
case x: Int => x
case x: Long => x.intValue

View File

@ -1,4 +1,5 @@
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
object Util {
def time(body: () => Unit, iterations: Int): Long = {
// warm up
@ -26,7 +27,7 @@ object Util {
object FileLoad extends App {
def task() {
val conf = Config.load("test04")
val conf = ConfigFactory.load("test04")
if (!"2.0-SNAPSHOT".equals(conf.getString("akka.version"))) {
throw new Exception("broken file load")
}
@ -39,7 +40,7 @@ object FileLoad extends App {
}
object Resolve extends App {
val conf = Config.load("test02")
val conf = ConfigFactory.load("test02")
def task() {
conf.resolve()

View File

@ -153,17 +153,17 @@ class ConfParserTest extends TestUtils {
@Test
def duplicateKeyLastWins() {
val obj = parseObject("""{ "a" : 10, "a" : 11 } """)
val obj = parseConfig("""{ "a" : 10, "a" : 11 } """)
assertEquals(1, obj.size())
assertEquals(1, obj.toObject.size())
assertEquals(11, obj.getInt("a"))
}
@Test
def duplicateKeyObjectsMerged() {
val obj = parseObject("""{ "a" : { "x" : 1, "y" : 2 }, "a" : { "x" : 42, "z" : 100 } }""")
val obj = parseConfig("""{ "a" : { "x" : 1, "y" : 2 }, "a" : { "x" : 42, "z" : 100 } }""")
assertEquals(1, obj.size())
assertEquals(1, obj.toObject.size())
assertEquals(3, obj.getObject("a").size())
assertEquals(42, obj.getInt("a.x"))
assertEquals(2, obj.getInt("a.y"))
@ -172,9 +172,9 @@ class ConfParserTest extends TestUtils {
@Test
def duplicateKeyObjectsMergedRecursively() {
val obj = parseObject("""{ "a" : { "b" : { "x" : 1, "y" : 2 } }, "a" : { "b" : { "x" : 42, "z" : 100 } } }""")
val obj = parseConfig("""{ "a" : { "b" : { "x" : 1, "y" : 2 } }, "a" : { "b" : { "x" : 42, "z" : 100 } } }""")
assertEquals(1, obj.size())
assertEquals(1, obj.toObject.size())
assertEquals(1, obj.getObject("a").size())
assertEquals(3, obj.getObject("a.b").size())
assertEquals(42, obj.getInt("a.b.x"))
@ -184,9 +184,9 @@ class ConfParserTest extends TestUtils {
@Test
def duplicateKeyObjectsMergedRecursivelyDeeper() {
val obj = parseObject("""{ "a" : { "b" : { "c" : { "x" : 1, "y" : 2 } } }, "a" : { "b" : { "c" : { "x" : 42, "z" : 100 } } } }""")
val obj = parseConfig("""{ "a" : { "b" : { "c" : { "x" : 1, "y" : 2 } } }, "a" : { "b" : { "c" : { "x" : 42, "z" : 100 } } } }""")
assertEquals(1, obj.size())
assertEquals(1, obj.toObject.size())
assertEquals(1, obj.getObject("a").size())
assertEquals(1, obj.getObject("a.b").size())
assertEquals(3, obj.getObject("a.b.c").size())
@ -198,16 +198,16 @@ class ConfParserTest extends TestUtils {
@Test
def duplicateKeyObjectNullObject() {
// null is supposed to "reset" the object at key "a"
val obj = parseObject("""{ a : { b : 1 }, a : null, a : { c : 2 } }""")
assertEquals(1, obj.size())
val obj = parseConfig("""{ a : { b : 1 }, a : null, a : { c : 2 } }""")
assertEquals(1, obj.toObject.size())
assertEquals(1, obj.getObject("a").size())
assertEquals(2, obj.getInt("a.c"))
}
@Test
def duplicateKeyObjectNumberObject() {
val obj = parseObject("""{ a : { b : 1 }, a : 42, a : { c : 2 } }""")
assertEquals(1, obj.size())
val obj = parseConfig("""{ a : { b : 1 }, a : 42, a : { c : 2 } }""")
assertEquals(1, obj.toObject.size())
assertEquals(1, obj.getObject("a").size())
assertEquals(2, obj.getInt("a.c"))
}
@ -263,8 +263,8 @@ class ConfParserTest extends TestUtils {
var tested = 0;
for (v <- valids; change <- changes) {
tested += 1;
val obj = parseObject(change(v))
assertEquals(3, obj.size())
val obj = parseConfig(change(v))
assertEquals(3, obj.toObject.size())
assertEquals("y", obj.getString("a"))
assertEquals("z", obj.getString("b"))
assertEquals(Seq(1, 2, 3), obj.getIntList("c").asScala)
@ -273,37 +273,37 @@ class ConfParserTest extends TestUtils {
assertEquals(valids.length * changes.length, tested)
// with no newline or comma, we do value concatenation
val noNewlineInArray = parseObject(" { c : [ 1 2 3 ] } ")
val noNewlineInArray = parseConfig(" { c : [ 1 2 3 ] } ")
assertEquals(Seq("1 2 3"), noNewlineInArray.getStringList("c").asScala)
val noNewlineInArrayWithQuoted = parseObject(""" { c : [ "4" "5" "6" ] } """)
val noNewlineInArrayWithQuoted = parseConfig(""" { c : [ "4" "5" "6" ] } """)
assertEquals(Seq("4 5 6"), noNewlineInArrayWithQuoted.getStringList("c").asScala)
val noNewlineInObject = parseObject(" { a : b c } ")
val noNewlineInObject = parseConfig(" { a : b c } ")
assertEquals("b c", noNewlineInObject.getString("a"))
val noNewlineAtEnd = parseObject("a : b")
val noNewlineAtEnd = parseConfig("a : b")
assertEquals("b", noNewlineAtEnd.getString("a"))
intercept[ConfigException] {
parseObject("{ a : y b : z }")
parseConfig("{ a : y b : z }")
}
intercept[ConfigException] {
parseObject("""{ "a" : "y" "b" : "z" }""")
parseConfig("""{ "a" : "y" "b" : "z" }""")
}
}
@Test
def keysWithSlash() {
val obj = parseObject("""/a/b/c=42, x/y/z : 32""")
val obj = parseConfig("""/a/b/c=42, x/y/z : 32""")
assertEquals(42, obj.getInt("/a/b/c"))
assertEquals(32, obj.getInt("x/y/z"))
}
private def lineNumberTest(num: Int, text: String) {
val e = intercept[ConfigException] {
parseObject(text)
parseConfig(text)
}
if (!e.getMessage.contains(num + ":"))
throw new Exception("error message did not contain line '" + num + "' '" + text.replace("\n", "\\n") + "'", e)

View File

@ -6,12 +6,13 @@ import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigResolveOptions
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
class ConfigSubstitutionTest extends TestUtils {
private def resolveWithoutFallbacks(v: AbstractConfigObject) = {
val options = ConfigResolveOptions.noSystem()
SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject]
SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject].toConfig
}
private def resolveWithoutFallbacks(s: ConfigSubstitution, root: AbstractConfigObject) = {
val options = ConfigResolveOptions.noSystem()
@ -20,7 +21,7 @@ class ConfigSubstitutionTest extends TestUtils {
private def resolve(v: AbstractConfigObject) = {
val options = ConfigResolveOptions.defaults()
SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject]
SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject].toConfig
}
private def resolve(s: ConfigSubstitution, root: AbstractConfigObject) = {
val options = ConfigResolveOptions.defaults()
@ -106,7 +107,7 @@ class ConfigSubstitutionTest extends TestUtils {
assertEquals(stringValue("start<>end"), v)
// but when null is NOT a subst, it should not become empty, incidentally
val o = parseObject("""{ "a" : null foo bar }""")
val o = parseConfig("""{ "a" : null foo bar }""")
assertEquals("null foo bar", o.getString("a"))
}
@ -281,13 +282,13 @@ class ConfigSubstitutionTest extends TestUtils {
val resolved = resolve(substEnvVarObject)
var existed = 0
for (k <- resolved.keySet().asScala) {
for (k <- resolved.toObject.keySet().asScala) {
val e = System.getenv(k.toUpperCase());
if (e != null) {
existed += 1
assertEquals(e, resolved.getString(k))
} else {
assertEquals(nullValue, resolved.get(k))
assertEquals(nullValue, resolved.toObject.get(k))
}
}
if (existed == 0) {
@ -307,13 +308,13 @@ class ConfigSubstitutionTest extends TestUtils {
for (k <- substEnvVarObject.keySet().asScala) {
nullsMap.put(k.toUpperCase(), null);
}
val nulls = Config.fromMap(nullsMap, "nulls map")
val nulls = ConfigFactory.parseMap(nullsMap, "nulls map")
val resolved = resolve(substEnvVarObject.withFallback(nulls))
for (k <- resolved.keySet().asScala) {
assertNotNull(resolved.get(k))
assertEquals(nullValue, resolved.get(k))
for (k <- resolved.toObject.keySet().asScala) {
assertNotNull(resolved.toObject.get(k))
assertEquals(nullValue, resolved.toObject.get(k))
}
}
@ -332,7 +333,7 @@ class ConfigSubstitutionTest extends TestUtils {
val e = System.getenv(k.toUpperCase());
if (e != null) {
existed += 1
assertEquals(e, resolved.getObject("a").getString(k))
assertEquals(e, resolved.getConfig("a").getString(k))
} else {
assertEquals(nullValue, resolved.getObject("a").get(k))
}

View File

@ -11,6 +11,7 @@ import scala.collection.JavaConverters._
import com.typesafe.config.ConfigResolveOptions
import java.io.File
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigFactory
class ConfigTest extends TestUtils {
@ -23,7 +24,7 @@ class ConfigTest extends TestUtils {
// a non-object value follows, and then an object after that;
// in that case, if an association starts with the non-object
// value, then the value after the non-object value gets lost.
private def associativeMerge(allObjects: Seq[AbstractConfigObject])(assertions: AbstractConfigObject => Unit) {
private def associativeMerge(allObjects: Seq[AbstractConfigObject])(assertions: SimpleConfig => Unit) {
def m(toMerge: AbstractConfigObject*) = merge(toMerge: _*)
def makeTrees(objects: Seq[AbstractConfigObject]): Iterator[AbstractConfigObject] = {
@ -63,7 +64,7 @@ class ConfigTest extends TestUtils {
}
for (tree <- trees) {
assertions(tree)
assertions(tree.toConfig)
}
}
@ -71,42 +72,42 @@ class ConfigTest extends TestUtils {
def mergeTrivial() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ "b" : 2 }""")
val merged = merge(obj1, obj2)
val merged = merge(obj1, obj2).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(2, merged.getInt("b"))
assertEquals(2, merged.keySet().size)
assertEquals(2, merged.toObject.size)
}
@Test
def mergeEmpty() {
val merged = merge()
val merged = merge().toConfig
assertEquals(0, merged.keySet().size)
assertEquals(0, merged.toObject.size)
}
@Test
def mergeOne() {
val obj1 = parseObject("""{ "a" : 1 }""")
val merged = merge(obj1)
val merged = merge(obj1).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
assertEquals(1, merged.toObject.size)
}
@Test
def mergeOverride() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ "a" : 2 }""")
val merged = merge(obj1, obj2)
val merged = merge(obj1, obj2).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
assertEquals(1, merged.toObject.size)
val merged2 = merge(obj2, obj1)
val merged2 = merge(obj2, obj1).toConfig
assertEquals(2, merged2.getInt("a"))
assertEquals(1, merged2.keySet().size)
assertEquals(1, merged2.toObject.size)
}
@Test
@ -115,13 +116,13 @@ class ConfigTest extends TestUtils {
val obj2 = parseObject("""{ "b" : 2 }""")
val obj3 = parseObject("""{ "c" : 3 }""")
val obj4 = parseObject("""{ "d" : 4 }""")
val merged = merge(obj1, obj2, obj3, obj4)
val merged = merge(obj1, obj2, obj3, obj4).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(2, merged.getInt("b"))
assertEquals(3, merged.getInt("c"))
assertEquals(4, merged.getInt("d"))
assertEquals(4, merged.keySet().size)
assertEquals(4, merged.toObject.size)
}
@Test
@ -130,60 +131,60 @@ class ConfigTest extends TestUtils {
val obj2 = parseObject("""{ "a" : 2 }""")
val obj3 = parseObject("""{ "a" : 3 }""")
val obj4 = parseObject("""{ "a" : 4 }""")
val merged = merge(obj1, obj2, obj3, obj4)
val merged = merge(obj1, obj2, obj3, obj4).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
assertEquals(1, merged.toObject.size)
val merged2 = merge(obj4, obj3, obj2, obj1)
val merged2 = merge(obj4, obj3, obj2, obj1).toConfig
assertEquals(4, merged2.getInt("a"))
assertEquals(1, merged2.keySet().size)
assertEquals(1, merged2.toObject.size)
}
@Test
def mergeNested() {
val obj1 = parseObject("""{ "root" : { "a" : 1, "z" : 101 } }""")
val obj2 = parseObject("""{ "root" : { "b" : 2, "z" : 102 } }""")
val merged = merge(obj1, obj2)
val merged = merge(obj1, obj2).toConfig
assertEquals(1, merged.getInt("root.a"))
assertEquals(2, merged.getInt("root.b"))
assertEquals(101, merged.getInt("root.z"))
assertEquals(1, merged.keySet().size)
assertEquals(3, merged.getObject("root").keySet().size)
assertEquals(1, merged.toObject.size)
assertEquals(3, merged.getConfig("root").toObject.size)
}
@Test
def mergeWithEmpty() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ }""")
val merged = merge(obj1, obj2)
val merged = merge(obj1, obj2).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
assertEquals(1, merged.toObject.size)
val merged2 = merge(obj2, obj1)
val merged2 = merge(obj2, obj1).toConfig
assertEquals(1, merged2.getInt("a"))
assertEquals(1, merged2.keySet().size)
assertEquals(1, merged2.toObject.size)
}
@Test
def mergeOverrideObjectAndPrimitive() {
val obj1 = parseObject("""{ "a" : 1 }""")
val obj2 = parseObject("""{ "a" : { "b" : 42 } }""")
val merged = merge(obj1, obj2)
val merged = merge(obj1, obj2).toConfig
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
assertEquals(1, merged.toObject.size)
val merged2 = merge(obj2, obj1)
val merged2 = merge(obj2, obj1).toConfig
assertEquals(42, merged2.getObject("a").getInt("b"))
assertEquals(42, merged2.getConfig("a").getInt("b"))
assertEquals(42, merged2.getInt("a.b"))
assertEquals(1, merged2.keySet().size)
assertEquals(1, merged2.getObject("a").keySet().size)
assertEquals(1, merged2.toObject.size)
assertEquals(1, merged2.getObject("a").size)
}
@Test
@ -195,15 +196,17 @@ class ConfigTest extends TestUtils {
val obj2 = parseObject("""{ "a" : 2 }""")
val obj3 = parseObject("""{ "a" : { "b" : 43, "c" : 44 } }""")
val merged = merge(obj1, obj2, obj3)
val merged = merge(obj1, obj2, obj3).toConfig
assertEquals(42, merged.getInt("a.b"))
assertEquals(1, merged.size)
assertEquals(1, merged.toObject.size)
assertEquals(1, merged.getObject("a").size())
val merged2 = merge(obj3, obj2, obj1)
val merged2 = merge(obj3, obj2, obj1).toConfig
assertEquals(43, merged2.getInt("a.b"))
assertEquals(44, merged2.getInt("a.c"))
assertEquals(1, merged2.size)
assertEquals(1, merged2.toObject.size)
assertEquals(2, merged2.getObject("a").size())
}
@ -216,7 +219,7 @@ class ConfigTest extends TestUtils {
associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
assertEquals(1, merged.getInt("a"))
assertEquals(1, merged.keySet().size)
assertEquals(1, merged.toObject.size)
}
}
@ -224,15 +227,20 @@ class ConfigTest extends TestUtils {
SubstitutionResolver.resolve(v, root, ConfigResolveOptions.noSystem())
}
private def resolveNoSystem(v: SimpleConfig, root: SimpleConfig) = {
SubstitutionResolver.resolve(v.toObject, root.toObject,
ConfigResolveOptions.noSystem()).asInstanceOf[AbstractConfigObject].toConfig
}
@Test
def mergeSubstitutedValues() {
val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : ${a} }""")
val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""")
val merged = merge(obj1, obj2)
val resolved = resolveNoSystem(merged, merged) match {
val resolved = (resolveNoSystem(merged, merged) match {
case x: ConfigObject => x
}
}).toConfig
assertEquals(3, resolved.getObject("c").size())
assertEquals(1, resolved.getInt("c.x"))
@ -246,18 +254,18 @@ class ConfigTest extends TestUtils {
val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""")
val merged = merge(obj1, obj2)
val resolved = resolveNoSystem(merged, merged) match {
val resolved = (resolveNoSystem(merged, merged) match {
case x: ConfigObject => x
}
}).toConfig
assertEquals(2, resolved.getObject("c").size())
assertEquals(2, resolved.getInt("c.y"))
assertEquals(42, resolved.getInt("c.z"))
val merged2 = merge(obj2, obj1)
val resolved2 = resolveNoSystem(merged2, merged2) match {
val resolved2 = (resolveNoSystem(merged2, merged2) match {
case x: ConfigObject => x
}
}).toConfig
assertEquals(2, resolved2.getObject("c").size())
assertEquals(2, resolved2.getInt("c.y"))
@ -315,9 +323,7 @@ class ConfigTest extends TestUtils {
val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""")
associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
val resolved = resolveNoSystem(merged, merged) match {
case x: ConfigObject => x
}
val resolved = resolveNoSystem(merged, merged)
assertEquals(4, resolved.getObject("j").size())
assertEquals(1, resolved.getInt("j.x"))
@ -334,11 +340,9 @@ class ConfigTest extends TestUtils {
val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""")
associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
val resolved = resolveNoSystem(merged, merged) match {
case x: ConfigObject => x
}
val resolved = resolveNoSystem(merged, merged)
assertEquals(3, resolved.size())
assertEquals(3, resolved.toObject.size())
assertEquals(42, resolved.getInt("j"));
assertEquals(2, resolved.getInt("b.y"))
assertEquals(3, resolved.getInt("c.z"))
@ -352,9 +356,7 @@ class ConfigTest extends TestUtils {
val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""")
associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
val resolved = resolveNoSystem(merged, merged) match {
case x: ConfigObject => x
}
val resolved = resolveNoSystem(merged, merged)
assertEquals(4, resolved.getObject("j").size())
assertEquals(1, resolved.getInt("j.x"))
@ -372,9 +374,7 @@ class ConfigTest extends TestUtils {
val obj4 = parseObject("""{ "c" : { "z" : 4, "q" : 8 }, "j" : ${c} }""")
associativeMerge(Seq(obj1, obj2, obj3, obj4)) { merged =>
val resolved = resolveNoSystem(merged, merged) match {
case x: ConfigObject => x
}
val resolved = resolveNoSystem(merged, merged)
assertEquals(5, resolved.getObject("j").size())
assertEquals(1, resolved.getInt("j.w"))
@ -387,7 +387,7 @@ class ConfigTest extends TestUtils {
@Test
def test01Getting() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
// get all the primitive types
assertEquals(42, conf.getInt("ints.fortyTwo"))
@ -404,7 +404,7 @@ class ConfigTest extends TestUtils {
// to get null we have to use the get() method from Map,
// which takes a key and not a path
assertEquals(nullValue(), conf.getObject("nulls").get("null"))
assertNull(conf.get("notinthefile"))
assertNull(conf.toObject.get("notinthefile"))
// get stuff with getValue
assertEquals(intValue(42), conf.getValue("ints.fortyTwo"))
@ -447,7 +447,7 @@ class ConfigTest extends TestUtils {
@Test
def test01Exceptions() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
// should throw Missing if key doesn't exist
intercept[ConfigException.Missing] {
@ -534,7 +534,7 @@ class ConfigTest extends TestUtils {
@Test
def test01Conversions() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
// should convert numbers to string
assertEquals("42", conf.getString("ints.fortyTwo"))
@ -597,7 +597,7 @@ class ConfigTest extends TestUtils {
@Test
def test01MergingOtherFormats() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
// should have loaded stuff from .json
assertEquals(1, conf.getInt("fromJson1"))
@ -611,7 +611,7 @@ class ConfigTest extends TestUtils {
@Test
def test01ToString() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
// toString() on conf objects doesn't throw (toString is just a debug string so not testing its result)
conf.toString()
@ -619,7 +619,7 @@ class ConfigTest extends TestUtils {
@Test
def test01SystemFallbacks() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
val jv = System.getProperty("java.version")
assertNotNull(jv)
assertEquals(jv, conf.getString("system.javaversion"))
@ -627,13 +627,13 @@ class ConfigTest extends TestUtils {
if (home != null) {
assertEquals(home, conf.getString("system.home"))
} else {
assertEquals(nullValue, conf.get("system.home"))
assertEquals(nullValue, conf.getObject("system").get("home"))
}
}
@Test
def test02SubstitutionsWithWeirdPaths() {
val conf = Config.load("test02")
val conf = ConfigFactory.load("test02")
assertEquals(42, conf.getInt("42_a"))
assertEquals(42, conf.getInt("42_b"))
@ -645,7 +645,7 @@ class ConfigTest extends TestUtils {
@Test
def test02UseWeirdPathsWithConfigObject() {
val conf = Config.load("test02")
val conf = ConfigFactory.load("test02")
// we're checking that the getters in ConfigObject support
// these weird path expressions
@ -657,7 +657,7 @@ class ConfigTest extends TestUtils {
@Test
def test03Includes() {
val conf = Config.load("test03")
val conf = ConfigFactory.load("test03")
// include should have overridden the "ints" value in test03
assertEquals(42, conf.getInt("test01.ints.fortyTwo"))
@ -685,7 +685,7 @@ class ConfigTest extends TestUtils {
if (home != null) {
assertEquals(home, conf.getString("test01.system.home"))
} else {
assertEquals(nullValue, conf.get("test01.system.home"))
assertEquals(nullValue, conf.getObject("test01.system").get("home"))
}
val concatenated = conf.getString("test01.system.concatenated")
assertTrue(concatenated.contains("Your Java version"))
@ -695,7 +695,7 @@ class ConfigTest extends TestUtils {
@Test
def test04LoadAkkaReference() {
val conf = Config.load("test04")
val conf = ConfigFactory.load("test04")
// Note, test04 is an unmodified old-style akka.conf,
// which means it has an outer akka{} namespace.
@ -710,7 +710,7 @@ class ConfigTest extends TestUtils {
@Test
def test05LoadPlayApplicationConf() {
val conf = Config.load("test05")
val conf = ConfigFactory.load("test05")
assertEquals("prod", conf.getString("%prod.application.mode"))
assertEquals("Yet another blog", conf.getString("blog.title"))

View File

@ -87,10 +87,23 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, c)
checkNotEqualObjects(b, c)
val root = a.asRoot(path("foo"))
checkEqualObjects(a, root)
checkNotEqualObjects(root, b)
checkNotEqualObjects(root, c)
// the config for an equal object is also equal
val config = a.toConfig()
checkEqualObjects(config, config)
checkEqualObjects(config, sameAsA.toConfig())
checkEqualObjects(a.toConfig(), config)
checkNotEqualObjects(config, b.toConfig())
checkNotEqualObjects(config, c.toConfig())
// configs are not equal to objects
checkNotEqualObjects(a, a.toConfig())
checkNotEqualObjects(b, b.toConfig())
// configs are equal to the same config as a root
val root = config.asRoot(path("foo"))
checkEqualObjects(config, root)
checkNotEqualObjects(b.toConfig(), root)
checkNotEqualObjects(c.toConfig(), root)
}
@Test
@ -321,7 +334,7 @@ class ConfigValueTest extends TestUtils {
unresolved { dmo.keySet() }
unresolved { dmo.size() }
unresolved { dmo.values() }
unresolved { dmo.getInt("foo") }
unresolved { dmo.toConfig.getInt("foo") }
}
@Test
@ -335,12 +348,12 @@ class ConfigValueTest extends TestUtils {
// formats as 1E100 (capital E)
val d = "1e100"
val obj = parseObject("{ a : " + a + ", b : " + b + ", c : " + c + ", d : " + d + "}")
val obj = parseConfig("{ a : " + a + ", b : " + b + ", c : " + c + ", d : " + d + "}")
assertEquals(Seq(a, b, c, d),
Seq("a", "b", "c", "d") map { obj.getString(_) })
// make sure it still works if we're doing concatenation
val obj2 = parseObject("{ a : xx " + a + " yy, b : xx " + b + " yy, c : xx " + c + " yy, d : xx " + d + " yy}")
val obj2 = parseConfig("{ a : xx " + a + " yy, b : xx " + b + " yy, c : xx " + c + " yy, d : xx " + d + " yy}")
assertEquals(Seq(a, b, c, d) map { "xx " + _ + " yy" },
Seq("a", "b", "c", "d") map { obj2.getString(_) })
}
@ -374,11 +387,11 @@ class ConfigValueTest extends TestUtils {
@Test
def hasPathWorks() {
val empty = parseObject("{}")
val empty = parseConfig("{}")
assertFalse(empty.hasPath("foo"))
val obj = parseObject("a=null, b.c.d=11, foo=bar")
val obj = parseConfig("a=null, b.c.d=11, foo=bar")
// returns true for the non-null values
assertTrue(obj.hasPath("foo"))
@ -387,12 +400,12 @@ class ConfigValueTest extends TestUtils {
assertTrue(obj.hasPath("b"))
// hasPath() is false for null values but containsKey is true
assertEquals(nullValue(), obj.get("a"))
assertTrue(obj.containsKey("a"))
assertEquals(nullValue(), obj.toObject.get("a"))
assertTrue(obj.toObject.containsKey("a"))
assertFalse(obj.hasPath("a"))
// false for totally absent values
assertFalse(obj.containsKey("notinhere"))
assertFalse(obj.toObject.containsKey("notinhere"))
assertFalse(obj.hasPath("notinhere"))
// throws proper exceptions
@ -425,12 +438,12 @@ class ConfigValueTest extends TestUtils {
@Test
def automaticBooleanConversions() {
val trues = parseObject("{ a=true, b=yes, c=on }")
val trues = parseObject("{ a=true, b=yes, c=on }").toConfig
assertEquals(true, trues.getBoolean("a"))
assertEquals(true, trues.getBoolean("b"))
assertEquals(true, trues.getBoolean("c"))
val falses = parseObject("{ a=false, b=no, c=off }")
val falses = parseObject("{ a=false, b=no, c=off }").toConfig
assertEquals(false, falses.getBoolean("a"))
assertEquals(false, falses.getBoolean("b"))
assertEquals(false, falses.getBoolean("c"))

View File

@ -42,12 +42,12 @@ class EquivalentsTest extends TestUtils {
private def parse(flavor: ConfigSyntax, f: File) = {
val options = ConfigParseOptions.defaults().setSyntax(flavor)
postParse(Config.parse(f, options))
postParse(ConfigFactory.parse(f, options).toObject)
}
private def parse(f: File) = {
val options = ConfigParseOptions.defaults()
postParse(Config.parse(f, options))
postParse(ConfigFactory.parse(f, options).toObject)
}
// would like each "equivNN" directory to be a suite and each file in the dir

View File

@ -5,6 +5,7 @@ import org.junit._
import java.util.Properties
import com.typesafe.config.Config
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigFactory
class PropertiesTest extends TestUtils {
@Test
@ -50,7 +51,7 @@ class PropertiesTest extends TestUtils {
props.setProperty(propsPath, propsPath)
val conf = Config.parse(props, ConfigParseOptions.defaults())
val conf = ConfigFactory.parse(props, ConfigParseOptions.defaults())
assertEquals(propsPath, conf.getString(confPath))
}
@ -82,9 +83,9 @@ class PropertiesTest extends TestUtils {
props.setProperty("x.y", "bar")
props.setProperty("x.y.z", "foo")
val conf = Config.parse(props, ConfigParseOptions.defaults())
val conf = ConfigFactory.parse(props, ConfigParseOptions.defaults())
assertEquals(2, conf.size())
assertEquals(2, conf.toObject.size())
assertEquals("foo", conf.getString("a.b"))
assertEquals("foo", conf.getString("x.y.z"))
}

View File

@ -12,11 +12,11 @@ import scala.collection.mutable
class PublicApiTest extends TestUtils {
@Test
def basicLoadAndGet() {
val conf = Config.load("test01")
val conf = ConfigFactory.load("test01")
val a = conf.getInt("ints.fortyTwo")
val obj = conf.getObject("ints")
val c = obj.getInt("fortyTwo")
val child = conf.getConfig("ints")
val c = child.getInt("fortyTwo")
val ms = conf.getMilliseconds("durations.halfSecond")
// should have used system variables
@ -29,7 +29,7 @@ class PublicApiTest extends TestUtils {
@Test
def noSystemVariables() {
// should not have used system variables
val conf = Config.load("test01", ConfigParseOptions.defaults(),
val conf = ConfigFactory.load("test01", ConfigParseOptions.defaults(),
ConfigResolveOptions.noSystem())
intercept[ConfigException.Null] {
@ -43,7 +43,7 @@ class PublicApiTest extends TestUtils {
@Test
def canLimitLoadToJson {
val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON);
val conf = Config.load("test01", options, ConfigResolveOptions.defaults())
val conf = ConfigFactory.load("test01", options, ConfigResolveOptions.defaults())
assertEquals(1, conf.getInt("fromJson1"))
intercept[ConfigException.Missing] {
@ -54,7 +54,7 @@ class PublicApiTest extends TestUtils {
@Test
def canLimitLoadToProperties {
val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.PROPERTIES);
val conf = Config.load("test01", options, ConfigResolveOptions.defaults())
val conf = ConfigFactory.load("test01", options, ConfigResolveOptions.defaults())
assertEquals(1, conf.getInt("fromProps.one"))
intercept[ConfigException.Missing] {
@ -65,7 +65,7 @@ class PublicApiTest extends TestUtils {
@Test
def canLimitLoadToConf {
val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF);
val conf = Config.load("test01", options, ConfigResolveOptions.defaults())
val conf = ConfigFactory.load("test01", options, ConfigResolveOptions.defaults())
assertEquals(42, conf.getInt("ints.fortyTwo"))
intercept[ConfigException.Missing] {
@ -77,22 +77,22 @@ class PublicApiTest extends TestUtils {
}
@Test
def emptyObjects() {
assertEquals(0, Config.empty().size())
assertEquals("empty config", Config.empty().origin().description())
assertEquals(0, Config.empty("foo").size())
assertEquals("foo", Config.empty("foo").origin().description())
assertEquals(0, Config.emptyRoot("foo.bar").size())
assertEquals("foo.bar", Config.emptyRoot("foo.bar").origin().description())
def emptyConfigs() {
assertTrue(ConfigFactory.empty().isEmpty())
assertEquals("empty config", ConfigFactory.empty().origin().description())
assertTrue(ConfigFactory.empty("foo").isEmpty())
assertEquals("foo", ConfigFactory.empty("foo").origin().description())
assertTrue(ConfigFactory.emptyRoot("foo.bar").isEmpty())
assertEquals("foo.bar", ConfigFactory.emptyRoot("foo.bar").origin().description())
}
private val defaultValueDesc = "hardcoded value";
private def testFromValue(expectedValue: ConfigValue, createFrom: AnyRef) {
assertEquals(expectedValue, Config.fromAnyRef(createFrom))
assertEquals(defaultValueDesc, Config.fromAnyRef(createFrom).origin().description())
assertEquals(expectedValue, Config.fromAnyRef(createFrom, "foo"))
assertEquals("foo", Config.fromAnyRef(createFrom, "foo").origin().description())
assertEquals(expectedValue, ConfigValueFactory.fromAnyRef(createFrom))
assertEquals(defaultValueDesc, ConfigValueFactory.fromAnyRef(createFrom).origin().description())
assertEquals(expectedValue, ConfigValueFactory.fromAnyRef(createFrom, "foo"))
assertEquals("foo", ConfigValueFactory.fromAnyRef(createFrom, "foo").origin().description())
}
@Test
@ -135,8 +135,8 @@ class PublicApiTest extends TestUtils {
testFromValue(new SimpleConfigObject(fakeOrigin(), emptyMapValue), Collections.emptyMap[String, Int])
testFromValue(new SimpleConfigObject(fakeOrigin(), aMapValue), Map("a" -> 1, "b" -> 2, "c" -> 3).asJava)
assertEquals("hardcoded value", Config.fromMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava).origin().description())
assertEquals("foo", Config.fromMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava, "foo").origin().description())
assertEquals("hardcoded value", ConfigValueFactory.fromMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava).origin().description())
assertEquals("foo", ConfigValueFactory.fromMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava, "foo").origin().description())
}
@Test
@ -158,34 +158,34 @@ class PublicApiTest extends TestUtils {
// testFromValue doesn't test the fromIterable public wrapper around fromAnyRef,
// do so here.
assertEquals(new SimpleConfigList(fakeOrigin(), aListValue), Config.fromIterable(List(1, 2, 3).asJava))
assertEquals(new SimpleConfigList(fakeOrigin(), aListValue), Config.fromIterable(treeSet))
assertEquals(new SimpleConfigList(fakeOrigin(), aListValue), ConfigValueFactory.fromIterable(List(1, 2, 3).asJava))
assertEquals(new SimpleConfigList(fakeOrigin(), aListValue), ConfigValueFactory.fromIterable(treeSet))
assertEquals("hardcoded value", Config.fromIterable(List(1, 2, 3).asJava).origin().description())
assertEquals("foo", Config.fromIterable(treeSet, "foo").origin().description())
assertEquals("hardcoded value", ConfigValueFactory.fromIterable(List(1, 2, 3).asJava).origin().description())
assertEquals("foo", ConfigValueFactory.fromIterable(treeSet, "foo").origin().description())
}
@Test
def roundTripUnwrap() {
val conf = Config.load("test01")
assertTrue(conf.size() > 4) // "has a lot of stuff in it"
val unwrapped = conf.unwrapped()
val rewrapped = Config.fromMap(unwrapped, conf.origin().description())
val conf = ConfigFactory.load("test01")
assertTrue(conf.toObject.size > 4) // "has a lot of stuff in it"
val unwrapped = conf.toObject.unwrapped()
val rewrapped = ConfigValueFactory.fromMap(unwrapped, conf.origin().description())
val reunwrapped = rewrapped.unwrapped()
assertEquals(conf, rewrapped)
assertEquals(conf.toObject, rewrapped)
assertEquals(reunwrapped, unwrapped)
}
private def testFromPathMap(expectedValue: ConfigObject, createFrom: java.util.Map[String, Object]) {
assertEquals(expectedValue, Config.fromPathMap(createFrom))
assertEquals(defaultValueDesc, Config.fromPathMap(createFrom).origin().description())
assertEquals(expectedValue, Config.fromPathMap(createFrom, "foo"))
assertEquals("foo", Config.fromPathMap(createFrom, "foo").origin().description())
assertEquals(expectedValue, ConfigFactory.parseMap(createFrom).toObject)
assertEquals(defaultValueDesc, ConfigFactory.parseMap(createFrom).origin().description())
assertEquals(expectedValue, ConfigFactory.parseMap(createFrom, "foo").toObject)
assertEquals("foo", ConfigFactory.parseMap(createFrom, "foo").origin().description())
}
@Test
def fromJavaPathMap() {
// first the same tests as with fromMap
// first the same tests as with fromMap, but use parseMap
val emptyMapValue = Collections.emptyMap[String, AbstractConfigValue]
val aMapValue = Map("a" -> 1, "b" -> 2, "c" -> 3).mapValues(intValue(_): AbstractConfigValue).asJava
testFromPathMap(new SimpleConfigObject(fakeOrigin(), emptyMapValue),
@ -193,19 +193,19 @@ class PublicApiTest extends TestUtils {
testFromPathMap(new SimpleConfigObject(fakeOrigin(), aMapValue),
Map("a" -> 1, "b" -> 2, "c" -> 3).asInstanceOf[Map[String, AnyRef]].asJava)
assertEquals("hardcoded value", Config.fromPathMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava).origin().description())
assertEquals("foo", Config.fromPathMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava, "foo").origin().description())
assertEquals("hardcoded value", ConfigFactory.parseMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava).origin().description())
assertEquals("foo", ConfigFactory.parseMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava, "foo").origin().description())
// now some tests with paths; be sure to test nested path maps
val simplePathMapValue = Map("x.y" -> 4, "z" -> 5).asInstanceOf[Map[String, AnyRef]].asJava
val pathMapValue = Map("a.c" -> 1, "b" -> simplePathMapValue).asInstanceOf[Map[String, AnyRef]].asJava
val obj = Config.fromPathMap(pathMapValue)
val conf = ConfigFactory.parseMap(pathMapValue)
assertEquals(2, obj.size)
assertEquals(4, obj.getInt("b.x.y"))
assertEquals(5, obj.getInt("b.z"))
assertEquals(1, obj.getInt("a.c"))
assertEquals(2, conf.toObject.size)
assertEquals(4, conf.getInt("b.x.y"))
assertEquals(5, conf.getInt("b.z"))
assertEquals(1, conf.getInt("a.c"))
}
@Test
@ -213,7 +213,7 @@ class PublicApiTest extends TestUtils {
// "a" is both number 1 and an object
val pathMapValue = Map("a" -> 1, "a.b" -> 2).asInstanceOf[Map[String, AnyRef]].asJava
intercept[ConfigException.BugOrBroken] {
Config.fromPathMap(pathMapValue)
ConfigFactory.parseMap(pathMapValue)
}
}
@ -236,11 +236,11 @@ class PublicApiTest extends TestUtils {
@Test
def allowMissing() {
val e = intercept[ConfigException.IO] {
Config.parse(resource("nonexistent.conf"), ConfigParseOptions.defaults().setAllowMissing(false))
ConfigFactory.parse(resource("nonexistent.conf"), ConfigParseOptions.defaults().setAllowMissing(false))
}
assertTrue(e.getMessage.contains("No such"))
val conf = Config.parse(resource("nonexistent.conf"), ConfigParseOptions.defaults().setAllowMissing(true))
val conf = ConfigFactory.parse(resource("nonexistent.conf"), ConfigParseOptions.defaults().setAllowMissing(true))
assertTrue(conf.isEmpty())
}
@ -251,10 +251,10 @@ class PublicApiTest extends TestUtils {
// change that the includes are allowed to be missing.
// This can break because some options might "propagate" through
// to includes, but we don't want them all to do so.
val conf = Config.parse(resource("test03.conf"), ConfigParseOptions.defaults().setAllowMissing(false))
val conf = ConfigFactory.parse(resource("test03.conf"), ConfigParseOptions.defaults().setAllowMissing(false))
assertEquals(42, conf.getInt("test01.booleans"))
val conf2 = Config.parse(resource("test03.conf"), ConfigParseOptions.defaults().setAllowMissing(true))
val conf2 = ConfigFactory.parse(resource("test03.conf"), ConfigParseOptions.defaults().setAllowMissing(true))
assertEquals(conf, conf2)
}
@ -277,7 +277,7 @@ class PublicApiTest extends TestUtils {
}
}
private def whatWasIncluded(parser: ConfigParseOptions => ConfigObject): List[Included] = {
private def whatWasIncluded(parser: ConfigParseOptions => Config): List[Included] = {
val included = mutable.ListBuffer[Included]()
val includer = new RecordingIncluder(null, included)
@ -288,7 +288,7 @@ class PublicApiTest extends TestUtils {
@Test
def includersAreUsedWithFiles() {
val included = whatWasIncluded(Config.parse(resource("test03.conf"), _))
val included = whatWasIncluded(ConfigFactory.parse(resource("test03.conf"), _))
assertEquals(List("test01", "test02.conf", "equiv01/original.json",
"nothere", "nothere.conf", "nothere.json", "nothere.properties"),
@ -298,7 +298,7 @@ class PublicApiTest extends TestUtils {
@Test
def includersAreUsedRecursivelyWithFiles() {
// includes.conf has recursive includes in it
val included = whatWasIncluded(Config.parse(resource("equiv03/includes.conf"), _))
val included = whatWasIncluded(ConfigFactory.parse(resource("equiv03/includes.conf"), _))
assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c"),
included.map(_.name))
@ -306,7 +306,7 @@ class PublicApiTest extends TestUtils {
@Test
def includersAreUsedWithClasspath() {
val included = whatWasIncluded(Config.parse(classOf[PublicApiTest], "/test03.conf", _))
val included = whatWasIncluded(ConfigFactory.parse(classOf[PublicApiTest], "/test03.conf", _))
assertEquals(List("test01", "test02.conf", "equiv01/original.json",
"nothere", "nothere.conf", "nothere.json", "nothere.properties"),
@ -316,7 +316,7 @@ class PublicApiTest extends TestUtils {
@Test
def includersAreUsedRecursivelyWithClasspath() {
// includes.conf has recursive includes in it
val included = whatWasIncluded(Config.parse(classOf[PublicApiTest], "/equiv03/includes.conf", _))
val included = whatWasIncluded(ConfigFactory.parse(classOf[PublicApiTest], "/equiv03/includes.conf", _))
assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c"),
included.map(_.name))

View File

@ -8,6 +8,7 @@ import java.io.StringReader
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.Config
import com.typesafe.config.ConfigSyntax
import com.typesafe.config.ConfigFactory
abstract trait TestUtils {
protected def intercept[E <: Throwable: Manifest](block: => Unit): E = {
@ -332,10 +333,14 @@ abstract trait TestUtils {
protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d, null)
protected def parseObject(s: String) = {
parseConfig(s).toObject
}
protected def parseConfig(s: String) = {
val options = ConfigParseOptions.defaults().
setOriginDescription("test string").
setSyntax(ConfigSyntax.CONF);
Config.parse(new StringReader(s), options).asInstanceOf[AbstractConfigObject]
ConfigFactory.parse(new StringReader(s), options).asInstanceOf[SimpleConfig]
}
protected def subst(ref: String) = {

View File

@ -20,19 +20,19 @@ class UnitParserTest extends TestUtils {
"1.1574074074074073e-05d", "1.1574074074074073e-05 days", "1.1574074074074073e-05day")
val oneSecInNanos = TimeUnit.SECONDS.toNanos(1)
for (s <- oneSecs) {
val result = Config.parseDuration(s, fakeOrigin(), "test")
val result = SimpleConfig.parseDuration(s, fakeOrigin(), "test")
assertEquals(oneSecInNanos, result)
}
// bad units
val e = intercept[ConfigException.BadValue] {
Config.parseDuration("100 dollars", fakeOrigin(), "test")
SimpleConfig.parseDuration("100 dollars", fakeOrigin(), "test")
}
assertTrue(e.getMessage().contains("time unit"))
// bad number
val e2 = intercept[ConfigException.BadValue] {
Config.parseDuration("1 00 seconds", fakeOrigin(), "test")
SimpleConfig.parseDuration("1 00 seconds", fakeOrigin(), "test")
}
assertTrue(e2.getMessage().contains("duration number"))
}
@ -47,7 +47,7 @@ class UnitParserTest extends TestUtils {
"1m", "1M", "1 M", "1 megabytes", "1 megabyte",
"0.0009765625g", "0.0009765625G", "0.0009765625 gigabytes", "0.0009765625 gigabyte")
def parseMem(s: String) = Config.parseMemorySizeInBytes(s, fakeOrigin(), "test")
def parseMem(s: String) = SimpleConfig.parseMemorySizeInBytes(s, fakeOrigin(), "test")
for (s <- oneMegs) {
val result = parseMem(s)
@ -61,13 +61,13 @@ class UnitParserTest extends TestUtils {
// bad units
val e = intercept[ConfigException.BadValue] {
Config.parseMemorySizeInBytes("100 dollars", fakeOrigin(), "test")
SimpleConfig.parseMemorySizeInBytes("100 dollars", fakeOrigin(), "test")
}
assertTrue(e.getMessage().contains("size unit"))
// bad number
val e2 = intercept[ConfigException.BadValue] {
Config.parseMemorySizeInBytes("1 00 bytes", fakeOrigin(), "test")
SimpleConfig.parseMemorySizeInBytes("1 00 bytes", fakeOrigin(), "test")
}
assertTrue(e2.getMessage().contains("size number"))
}