diff --git a/src/main/java/com/typesafe/config/ConfigList.java b/src/main/java/com/typesafe/config/ConfigList.java new file mode 100644 index 00000000..5efe7014 --- /dev/null +++ b/src/main/java/com/typesafe/config/ConfigList.java @@ -0,0 +1,20 @@ +package com.typesafe.config; + +import java.util.List; + +/** + * A list (aka array) value corresponding to ConfigValueType.LIST or JSON's + * "[1,2,3]" value. Implements java.util.List so you can use it + * like a regular Java list. + * + */ +public interface ConfigList extends List, ConfigValue { + + /** + * Recursively unwraps the list, returning a list of plain Java values such + * as Integer or String or whatever is in the list. + */ + @Override + List unwrapped(); + +} diff --git a/src/main/java/com/typesafe/config/ConfigObject.java b/src/main/java/com/typesafe/config/ConfigObject.java index 17f3106b..101d5289 100644 --- a/src/main/java/com/typesafe/config/ConfigObject.java +++ b/src/main/java/com/typesafe/config/ConfigObject.java @@ -7,7 +7,7 @@ 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). - * + * * The getters all have the same semantics; they 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 @@ -16,10 +16,10 @@ import java.util.Map; * path "a.b.c" looks for key c in object b in object a in the root object. (The * syntax for paths is the same as in ${} substitution expressions in config * files, sometimes double quotes are needed around special characters.) - * + * * ConfigObject implements the standard Java Map interface, but the mutator * methods all throw UnsupportedOperationException. - * + * * TODO add OrNull variants of all these getters? Or better to avoid convenience * API for that? */ @@ -74,8 +74,16 @@ public interface ConfigObject extends ConfigValue, Map { */ Long getNanoseconds(String path); - /* TODO should this return an iterator instead? */ - List getList(String path); + /** + * Gets a list value (with any element type) as a ConfigList, which + * implements java.util.List. Throws if the path is unset or + * null. + * + * @param path + * the path to the list value. + * @return the ConfigList at the path + */ + ConfigList getList(String path); List getBooleanList(String path); diff --git a/src/main/java/com/typesafe/config/ConfigValue.java b/src/main/java/com/typesafe/config/ConfigValue.java index 5b3a445a..3dd40a53 100644 --- a/src/main/java/com/typesafe/config/ConfigValue.java +++ b/src/main/java/com/typesafe/config/ConfigValue.java @@ -8,21 +8,22 @@ package com.typesafe.config; public interface ConfigValue { /** * The origin of the value, for debugging and error messages. - * + * * @return where the value came from */ ConfigOrigin origin(); /** * The type of the value; matches the JSON type schema. - * + * * @return value's type */ ConfigValueType valueType(); /** * Returns the config value as a plain Java boxed value, should be a String, - * Number, etc. matching the valueType() of the ConfigValue. + * Number, etc. matching the valueType() of the ConfigValue. If the value is + * a ConfigObject or ConfigList, it is recursively unwrapped. */ Object unwrapped(); } diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index bc19433a..e6900898 100644 --- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValue; @@ -293,7 +294,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements } @Override - public List getList(String path) { + public ConfigList getList(String path) { AbstractConfigValue v = find(path, ConfigValueType.LIST, path); return (ConfigList) v; } diff --git a/src/main/java/com/typesafe/config/impl/Parser.java b/src/main/java/com/typesafe/config/impl/Parser.java index c820f724..e7788e2c 100644 --- a/src/main/java/com/typesafe/config/impl/Parser.java +++ b/src/main/java/com/typesafe/config/impl/Parser.java @@ -322,7 +322,7 @@ final class Parser { return new SimpleConfigObject(objectOrigin, null, values); } - private ConfigList parseArray() { + private SimpleConfigList parseArray() { // invoked just after the OPEN_SQUARE ConfigOrigin arrayOrigin = lineOrigin(); List values = new ArrayList(); @@ -333,7 +333,7 @@ final class Parser { // special-case the first element if (t == Tokens.CLOSE_SQUARE) { - return new ConfigList(arrayOrigin, + return new SimpleConfigList(arrayOrigin, Collections. emptyList()); } else if (Tokens.isValue(t)) { values.add(parseValue(t)); @@ -351,7 +351,7 @@ final class Parser { // just after a value t = nextTokenIgnoringNewline(); if (t == Tokens.CLOSE_SQUARE) { - return new ConfigList(arrayOrigin, values); + return new SimpleConfigList(arrayOrigin, values); } else if (t == Tokens.COMMA) { // OK } else { diff --git a/src/main/java/com/typesafe/config/impl/ConfigList.java b/src/main/java/com/typesafe/config/impl/SimpleConfigList.java similarity index 93% rename from src/main/java/com/typesafe/config/impl/ConfigList.java rename to src/main/java/com/typesafe/config/impl/SimpleConfigList.java index 649d0ad7..70940f21 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigList.java +++ b/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -7,15 +7,16 @@ import java.util.List; import java.util.ListIterator; import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; -final class ConfigList extends AbstractConfigValue implements List { +final class SimpleConfigList extends AbstractConfigValue implements ConfigList { private List value; - ConfigList(ConfigOrigin origin, List value) { + SimpleConfigList(ConfigOrigin origin, List value) { super(origin); this.value = value; } @@ -35,7 +36,7 @@ final class ConfigList extends AbstractConfigValue implements List } @Override - ConfigList resolveSubstitutions(SubstitutionResolver resolver, int depth, + SimpleConfigList resolveSubstitutions(SubstitutionResolver resolver, int depth, boolean withFallbacks) { // lazy-create for optimization List changed = null; @@ -65,7 +66,7 @@ final class ConfigList extends AbstractConfigValue implements List if (changed.size() != value.size()) throw new ConfigException.BugOrBroken( "substituted list's size doesn't match"); - return new ConfigList(origin(), changed); + return new SimpleConfigList(origin(), changed); } else { return this; } @@ -73,15 +74,15 @@ final class ConfigList extends AbstractConfigValue implements List @Override protected boolean canEqual(Object other) { - return other instanceof ConfigList; + return other instanceof SimpleConfigList; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality - if (other instanceof ConfigList) { + if (other instanceof SimpleConfigList) { // optimization to avoid unwrapped() for two ConfigList - return canEqual(other) && value.equals(((ConfigList) other).value); + return canEqual(other) && value.equals(((SimpleConfigList) other).value); } else { return false; } diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index 58d32875..a8762b98 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -6,6 +6,7 @@ import com.typesafe.config.ConfigValue import java.util.Collections import scala.collection.JavaConverters._ import com.typesafe.config.ConfigObject +import com.typesafe.config.ConfigList class ConfigValueTest extends TestUtils { @@ -69,10 +70,10 @@ class ConfigValueTest extends TestUtils { @Test def configListEquality() { val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue } - val aList = new ConfigList(fakeOrigin(), aScalaSeq.asJava) - val sameAsAList = new ConfigList(fakeOrigin(), aScalaSeq.asJava) + val aList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava) + val sameAsAList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava) val bScalaSeq = Seq(4, 5, 6) map { intValue(_): AbstractConfigValue } - val bList = new ConfigList(fakeOrigin(), bScalaSeq.asJava) + val bList = new SimpleConfigList(fakeOrigin(), bScalaSeq.asJava) checkEqualObjects(aList, aList) checkEqualObjects(aList, sameAsAList) @@ -101,7 +102,7 @@ class ConfigValueTest extends TestUtils { nullValue().toString() boolValue(true).toString() (new SimpleConfigObject(fakeOrigin(), null, Collections.emptyMap[String, AbstractConfigValue]())).toString() - (new ConfigList(fakeOrigin(), Collections.emptyList[AbstractConfigValue]())).toString() + (new SimpleConfigList(fakeOrigin(), Collections.emptyList[AbstractConfigValue]())).toString() subst("a").toString() substInString("b").toString() } @@ -153,8 +154,9 @@ class ConfigValueTest extends TestUtils { @Test def configListImplementsList() { - val l: ConfigList = new ConfigList(fakeOrigin(), List[AbstractConfigValue](stringValue("a"), stringValue("b"), stringValue("c")).asJava) - val scalaSeq = Seq(stringValue("a"), stringValue("b"), stringValue("c")) + val scalaSeq = Seq[AbstractConfigValue](stringValue("a"), stringValue("b"), stringValue("c")) + val l: ConfigList = new SimpleConfigList(fakeOrigin(), + scalaSeq.asJava) assertEquals(scalaSeq(0), l.get(0)) assertEquals(scalaSeq(1), l.get(1)) diff --git a/src/test/scala/com/typesafe/config/impl/JsonTest.scala b/src/test/scala/com/typesafe/config/impl/JsonTest.scala index f50d36ff..25a91e18 100644 --- a/src/test/scala/com/typesafe/config/impl/JsonTest.scala +++ b/src/test/scala/com/typesafe/config/impl/JsonTest.scala @@ -51,7 +51,7 @@ class JsonTest extends TestUtils { fields.foreach({ field => m.put(field.name, fromLift(field.value)) }) new SimpleConfigObject(fakeOrigin(), null, m) case lift.JArray(values) => - new ConfigList(fakeOrigin(), values.map(fromLift(_)).asJava) + new SimpleConfigList(fakeOrigin(), values.map(fromLift(_)).asJava) case lift.JField(name, value) => throw new IllegalStateException("either JField was a toplevel from lift-json or this function is buggy") case lift.JInt(i) =>