mirror of
https://github.com/lightbend/config.git
synced 2025-01-15 23:01:05 +08:00
Implement fromAnyRef and friends to wrap values in ConfigValue.
This commit is contained in:
parent
3bdaaabf02
commit
41b9d5c68b
@ -96,7 +96,6 @@ public final class Config {
|
||||
return ConfigImpl.empty(originDescription);
|
||||
}
|
||||
|
||||
|
||||
public static ConfigRoot systemPropertiesRoot(String rootPath) {
|
||||
return ConfigImpl.systemPropertiesRoot(rootPath);
|
||||
}
|
||||
@ -146,16 +145,101 @@ public final class Config {
|
||||
return ConfigImpl.parseResourcesForPath(path, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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) {
|
||||
throw new UnsupportedOperationException("not implemented yet");
|
||||
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) {
|
||||
throw new UnsupportedOperationException("not implemented yet");
|
||||
return fromMap(values, null);
|
||||
}
|
||||
|
||||
public static ConfigList fromCollection(Collection<? extends Object> values) {
|
||||
throw new UnsupportedOperationException("not implemented yet");
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
private static String getUnits(String s) {
|
||||
@ -205,7 +289,8 @@ public final class Config {
|
||||
unitString = unitString + "s";
|
||||
|
||||
// note that this is deliberately case-sensitive
|
||||
if (unitString.equals("") || unitString.equals("ms") || unitString.equals("milliseconds")) {
|
||||
if (unitString.equals("") || unitString.equals("ms")
|
||||
|| unitString.equals("milliseconds")) {
|
||||
units = TimeUnit.MILLISECONDS;
|
||||
} else if (unitString.equals("us") || unitString.equals("microseconds")) {
|
||||
units = TimeUnit.MICROSECONDS;
|
||||
@ -227,7 +312,8 @@ public final class Config {
|
||||
}
|
||||
|
||||
try {
|
||||
// if the string is purely digits, parse as an integer to avoid possible precision loss;
|
||||
// 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));
|
||||
@ -247,6 +333,7 @@ public final class Config {
|
||||
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
|
||||
|
||||
int bytes;
|
||||
|
||||
MemoryUnit(int bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
@ -324,8 +411,7 @@ public final class Config {
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse memory size number '"
|
||||
+ numberString
|
||||
+ "'");
|
||||
+ numberString + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,22 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigIncludeContext;
|
||||
import com.typesafe.config.ConfigIncluder;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigParseOptions;
|
||||
import com.typesafe.config.ConfigParseable;
|
||||
import com.typesafe.config.ConfigRoot;
|
||||
import com.typesafe.config.ConfigSyntax;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
|
||||
/** This is public but is only supposed to be used by the "config" package */
|
||||
public class ConfigImpl {
|
||||
@ -119,9 +125,123 @@ public class ConfigImpl {
|
||||
}
|
||||
|
||||
public static ConfigObject empty(String originDescription) {
|
||||
return SimpleConfigObject
|
||||
.empty(originDescription != null ? new SimpleConfigOrigin(
|
||||
originDescription) : null);
|
||||
ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin(
|
||||
originDescription) : null;
|
||||
return emptyObject(origin);
|
||||
}
|
||||
|
||||
static AbstractConfigObject empty(ConfigOrigin origin) {
|
||||
return emptyObject(origin);
|
||||
}
|
||||
|
||||
// default origin for values created with fromAnyRef and no origin specified
|
||||
final private static ConfigOrigin defaultValueOrigin = new SimpleConfigOrigin(
|
||||
"hardcoded value");
|
||||
final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
|
||||
defaultValueOrigin, true);
|
||||
final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
|
||||
defaultValueOrigin, false);
|
||||
final private static ConfigNull defaultNullValue = new ConfigNull(
|
||||
defaultValueOrigin);
|
||||
final private static SimpleConfigList defaultEmptyList = new SimpleConfigList(
|
||||
defaultValueOrigin, Collections.<AbstractConfigValue> emptyList());
|
||||
final private static SimpleConfigObject defaultEmptyObject = SimpleConfigObject
|
||||
.empty(defaultValueOrigin);
|
||||
|
||||
private static SimpleConfigList emptyList(ConfigOrigin origin) {
|
||||
if (origin == null || origin == defaultValueOrigin)
|
||||
return defaultEmptyList;
|
||||
else
|
||||
return new SimpleConfigList(origin,
|
||||
Collections.<AbstractConfigValue> emptyList());
|
||||
}
|
||||
|
||||
private static AbstractConfigObject emptyObject(ConfigOrigin origin) {
|
||||
// we want null origin to go to SimpleConfigObject.empty() to get the
|
||||
// origin "empty config" rather than "hardcoded value"
|
||||
if (origin == defaultValueOrigin)
|
||||
return defaultEmptyObject;
|
||||
else
|
||||
return SimpleConfigObject.empty(origin);
|
||||
}
|
||||
|
||||
private static ConfigOrigin valueOrigin(String originDescription) {
|
||||
if (originDescription == null)
|
||||
return defaultValueOrigin;
|
||||
else
|
||||
return new SimpleConfigOrigin(originDescription);
|
||||
}
|
||||
|
||||
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
|
||||
public static ConfigValue fromAnyRef(Object object, String originDescription) {
|
||||
ConfigOrigin origin = valueOrigin(originDescription);
|
||||
return fromAnyRef(object, origin);
|
||||
}
|
||||
|
||||
static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin) {
|
||||
if (origin == null)
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"origin not supposed to be null");
|
||||
|
||||
if (object == null) {
|
||||
if (origin != defaultValueOrigin)
|
||||
return new ConfigNull(origin);
|
||||
else
|
||||
return defaultNullValue;
|
||||
} else if (object instanceof Boolean) {
|
||||
if (origin != defaultValueOrigin) {
|
||||
return new ConfigBoolean(origin, (Boolean) object);
|
||||
} else if ((Boolean) object) {
|
||||
return defaultTrueValue;
|
||||
} else {
|
||||
return defaultFalseValue;
|
||||
}
|
||||
} else if (object instanceof String) {
|
||||
return new ConfigString(origin, (String) object);
|
||||
} else if (object instanceof Number) {
|
||||
if (object instanceof Double) {
|
||||
return new ConfigDouble(origin, (Double) object, null);
|
||||
} else if (object instanceof Integer) {
|
||||
return new ConfigInt(origin, (Integer) object, null);
|
||||
} else if (object instanceof Long) {
|
||||
return new ConfigLong(origin, (Long) object, null);
|
||||
} else {
|
||||
return new ConfigDouble(origin,
|
||||
((Number) object).doubleValue(), null);
|
||||
}
|
||||
} else if (object instanceof Map) {
|
||||
if (((Map<?, ?>) object).isEmpty())
|
||||
return emptyObject(origin);
|
||||
|
||||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||
for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
if (!(key instanceof String))
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"bug in method caller: not valid to create ConfigObject from map with non-String key: "
|
||||
+ key);
|
||||
AbstractConfigValue value = fromAnyRef(entry.getValue(), origin);
|
||||
values.put((String) key, value);
|
||||
}
|
||||
|
||||
return new SimpleConfigObject(origin, values);
|
||||
} else if (object instanceof Iterable) {
|
||||
Iterator<?> i = ((Iterable<?>) object).iterator();
|
||||
if (!i.hasNext())
|
||||
return emptyList(origin);
|
||||
|
||||
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
|
||||
while (i.hasNext()) {
|
||||
AbstractConfigValue v = fromAnyRef(i.next(), origin);
|
||||
values.add(v);
|
||||
}
|
||||
|
||||
return new SimpleConfigList(origin, values);
|
||||
} else {
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"bug in method caller: not valid to create ConfigValue from: "
|
||||
+ object);
|
||||
}
|
||||
}
|
||||
|
||||
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
|
||||
|
@ -4,6 +4,8 @@ import org.junit.Assert._
|
||||
import org.junit._
|
||||
import scala.collection.JavaConverters._
|
||||
import com.typesafe.config._
|
||||
import java.util.Collections
|
||||
import java.util.TreeSet
|
||||
|
||||
class PublicApiTest extends TestUtils {
|
||||
@Test
|
||||
@ -75,9 +77,100 @@ 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())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@Test
|
||||
def fromJavaBoolean() {
|
||||
testFromValue(boolValue(true), true: java.lang.Boolean)
|
||||
testFromValue(boolValue(false), false: java.lang.Boolean)
|
||||
}
|
||||
|
||||
@Test
|
||||
def fromJavaNull() {
|
||||
testFromValue(nullValue, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
def fromJavaNumbers() {
|
||||
testFromValue(intValue(5), 5: java.lang.Integer)
|
||||
testFromValue(longValue(6), 6: java.lang.Long)
|
||||
testFromValue(doubleValue(3.14), 3.14: java.lang.Double)
|
||||
|
||||
class WeirdNumber(v: Double) extends java.lang.Number {
|
||||
override def doubleValue = v
|
||||
override def intValue = v.intValue
|
||||
override def longValue = v.longValue
|
||||
override def floatValue = v.floatValue
|
||||
}
|
||||
|
||||
val weirdNumber = new WeirdNumber(5.1);
|
||||
testFromValue(doubleValue(5.1), weirdNumber)
|
||||
}
|
||||
|
||||
@Test
|
||||
def fromJavaString() {
|
||||
testFromValue(stringValue("hello world"), "hello world")
|
||||
}
|
||||
|
||||
@Test
|
||||
def fromJavaMap() {
|
||||
val emptyMapValue = Collections.emptyMap[String, AbstractConfigValue]
|
||||
val aMapValue = Map("a" -> 1, "b" -> 2, "c" -> 3).mapValues(intValue(_): AbstractConfigValue).asJava
|
||||
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())
|
||||
}
|
||||
|
||||
@Test
|
||||
def fromJavaCollection() {
|
||||
val emptyListValue = Collections.emptyList[AbstractConfigValue]
|
||||
val aListValue = List(1, 2, 3).map(intValue(_): AbstractConfigValue).asJava
|
||||
|
||||
testFromValue(new SimpleConfigList(fakeOrigin(), emptyListValue), Nil.asJava)
|
||||
testFromValue(new SimpleConfigList(fakeOrigin(), aListValue), List(1, 2, 3).asJava)
|
||||
|
||||
// test with a non-List (but has to be ordered)
|
||||
val treeSet = new TreeSet[Int]();
|
||||
treeSet.add(1)
|
||||
treeSet.add(2)
|
||||
treeSet.add(3)
|
||||
|
||||
testFromValue(new SimpleConfigList(fakeOrigin(), emptyListValue), Set.empty[String].asJava)
|
||||
testFromValue(new SimpleConfigList(fakeOrigin(), aListValue), treeSet)
|
||||
|
||||
// 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("hardcoded value", Config.fromIterable(List(1, 2, 3).asJava).origin().description())
|
||||
assertEquals("foo", Config.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 reunwrapped = rewrapped.unwrapped()
|
||||
assertEquals(conf, rewrapped)
|
||||
assertEquals(reunwrapped, unwrapped)
|
||||
}
|
||||
}
|
||||
|
@ -316,6 +316,10 @@ abstract trait TestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// it's important that these do NOT use the public API to create the
|
||||
// instances, because we may be testing that the public API returns the
|
||||
// right instance by comparing to these, so using public API here would
|
||||
// make the test compare public API to itself.
|
||||
protected def intValue(i: Int) = new ConfigInt(fakeOrigin(), i, null)
|
||||
protected def longValue(l: Long) = new ConfigLong(fakeOrigin(), l, null)
|
||||
protected def boolValue(b: Boolean) = new ConfigBoolean(fakeOrigin(), b)
|
||||
|
Loading…
Reference in New Issue
Block a user