diff --git a/config/src/main/java/com/typesafe/config/impl/DefaultTransformer.java b/config/src/main/java/com/typesafe/config/impl/DefaultTransformer.java index 9a9bf5c6..f82e1361 100644 --- a/config/src/main/java/com/typesafe/config/impl/DefaultTransformer.java +++ b/config/src/main/java/com/typesafe/config/impl/DefaultTransformer.java @@ -3,6 +3,13 @@ */ package com.typesafe.config.impl; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.typesafe.config.ConfigValueType; /** @@ -74,6 +81,49 @@ final class DefaultTransformer { // no-op STRING to STRING break; } + } else if (requested == ConfigValueType.LIST && value.valueType() == ConfigValueType.OBJECT) { + // attempt to convert an array-like (numeric indices) object to a + // list. This would be used with .properties syntax for example: + // -Dfoo.0=bar -Dfoo.1=baz + // To ensure we still throw type errors for objects treated + // as lists in most cases, we'll refuse to convert if the object + // does not contain any numeric keys. This means we don't allow + // empty objects here though :-/ + AbstractConfigObject o = (AbstractConfigObject) value; + Map values = new HashMap(); + for (String key : o.keySet()) { + int i; + try { + i = Integer.parseInt(key, 10); + if (i < 0) + continue; + values.put(i, o.get(key)); + } catch (NumberFormatException e) { + continue; + } + } + if (!values.isEmpty()) { + ArrayList> entryList = new ArrayList>( + values.entrySet()); + // sort by numeric index + Collections.sort(entryList, + new Comparator>() { + @Override + public int compare(Map.Entry a, + Map.Entry b) { + // Integer.compare was added in 1.7 so not using + // it here yet + return Integer.valueOf(a.getKey()).compareTo(b.getKey()); + } + }); + // drop the indices (we allow gaps in the indices, for better or + // worse) + ArrayList list = new ArrayList(); + for (Map.Entry entry : entryList) { + list.add(entry.getValue()); + } + return new SimpleConfigList(value.origin(), list); + } } return value; diff --git a/config/src/test/scala/com/typesafe/config/impl/PropertiesTest.scala b/config/src/test/scala/com/typesafe/config/impl/PropertiesTest.scala index eb42691d..1088abf2 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PropertiesTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PropertiesTest.scala @@ -9,6 +9,7 @@ import java.util.Properties import com.typesafe.config.Config import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigException class PropertiesTest extends TestUtils { @Test @@ -92,4 +93,63 @@ class PropertiesTest extends TestUtils { assertEquals("foo", conf.getString("a.b")) assertEquals("foo", conf.getString("x.y.z")) } + + @Test + def makeListWithNumericKeys() { + import scala.collection.JavaConverters._ + + val props = new Properties() + props.setProperty("a.0", "0") + props.setProperty("a.1", "1") + props.setProperty("a.2", "2") + props.setProperty("a.3", "3") + props.setProperty("a.4", "4") + + val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) + assertEquals(Seq(0, 1, 2, 3, 4), conf.getIntList("a").asScala.toSeq) + } + + @Test + def makeListWithNumericKeysWithGaps() { + import scala.collection.JavaConverters._ + + val props = new Properties() + props.setProperty("a.1", "0") + props.setProperty("a.2", "1") + props.setProperty("a.4", "2") + + val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) + assertEquals(Seq(0, 1, 2), conf.getIntList("a").asScala.toSeq) + } + + @Test + def makeListWithNumericKeysWithNoise() { + import scala.collection.JavaConverters._ + + val props = new Properties() + props.setProperty("a.-1", "-1") + props.setProperty("a.foo", "-2") + props.setProperty("a.0", "0") + props.setProperty("a.1", "1") + props.setProperty("a.2", "2") + props.setProperty("a.3", "3") + props.setProperty("a.4", "4") + + val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) + assertEquals(Seq(0, 1, 2, 3, 4), conf.getIntList("a").asScala.toSeq) + } + + @Test + def noNumericKeysAsListFails() { + import scala.collection.JavaConverters._ + + val props = new Properties() + props.setProperty("a.bar", "0") + + val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) + val e = intercept[ConfigException.WrongType] { + conf.getList("a") + } + assertTrue("expected exception thrown", e.getMessage.contains("LIST")) + } }