Try to convert objects with numeric keys to lists

This is mostly intended to provide a way to specify lists
on the command line via Java properties, like -Dfoo.0 -Dfoo.1

Fixes #69
This commit is contained in:
Havoc Pennington 2013-05-07 11:02:55 -04:00
parent 145a9be612
commit 338150f281
2 changed files with 110 additions and 0 deletions

View File

@ -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<Integer, AbstractConfigValue> values = new HashMap<Integer, AbstractConfigValue>();
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<Map.Entry<Integer, AbstractConfigValue>> entryList = new ArrayList<Map.Entry<Integer, AbstractConfigValue>>(
values.entrySet());
// sort by numeric index
Collections.sort(entryList,
new Comparator<Map.Entry<Integer, AbstractConfigValue>>() {
@Override
public int compare(Map.Entry<Integer, AbstractConfigValue> a,
Map.Entry<Integer, AbstractConfigValue> 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<AbstractConfigValue> list = new ArrayList<AbstractConfigValue>();
for (Map.Entry<Integer, AbstractConfigValue> entry : entryList) {
list.add(entry.getValue());
}
return new SimpleConfigList(value.origin(), list);
}
}
return value;

View File

@ -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"))
}
}