make ConfigObject implement java.util.Map<String, ConfigValue>

This commit is contained in:
Havoc Pennington 2011-11-10 16:46:44 -05:00
parent c677a23b3b
commit be63b620f6
6 changed files with 186 additions and 25 deletions

View File

@ -1,7 +1,7 @@
package com.typesafe.config;
import java.util.List;
import java.util.Set;
import java.util.Map;
/**
* A ConfigObject is a read-only configuration object, which may have nested
@ -17,13 +17,13 @@ import java.util.Set;
* 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?
*
* TODO should it implement Map<String, ? extends ConfigValue> with the mutators
* throwing ?
*/
public interface ConfigObject extends ConfigValue {
public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
boolean getBoolean(String path);
@ -46,16 +46,14 @@ public interface ConfigObject extends ConfigValue {
Object getAny(String path);
/**
* Gets the value at the path as a ConfigValue.
*
* TODO conceptually if we want to match a read-only subset of the
* Map<String, ? extends ConfigValue> interface, we would need to take a key
* instead of a path here and return null instead of throwing an exception.
* 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 get(String path);
ConfigValue getValue(String path);
/** Get value as a size in bytes (parses special strings like "128M") */
// rename getSizeInBytes ? clearer. allows a megabyte version
@ -76,6 +74,7 @@ public interface ConfigObject extends ConfigValue {
*/
Long getNanoseconds(String path);
/* TODO should this return an iterator instead? */
List<? extends ConfigValue> getList(String path);
List<Boolean> getBooleanList(String path);
@ -100,7 +99,12 @@ public interface ConfigObject extends ConfigValue {
List<Long> getNanosecondsList(String path);
boolean containsKey(String key);
Set<String> keySet();
/**
* Gets a ConfigValue at the given key, or returns null if there is no
* value. The returned ConfigValue may have ConfigValueType.NULL or any
* other type, and the passed-in key must be a key in this object, rather
* than a path expression.
*/
@Override
ConfigValue get(Object key);
}

View File

@ -24,6 +24,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
this.transformer = transformer;
}
@Override
abstract public Map<String, Object> unwrapped();
/**
* This looks up the key with no transformation or type conversion of any
* kind, and returns null if the key is not present.
@ -244,7 +247,15 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
}
@Override
public ConfigValue get(String path) {
public ConfigValue get(Object key) {
if (key instanceof String)
return peek((String) key);
else
return null;
}
@Override
public ConfigValue getValue(String path) {
return find(path, null, path);
}
@ -487,4 +498,30 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
sb.append(")");
return sb.toString();
}
private static UnsupportedOperationException weAreImmutable(String method) {
return new UnsupportedOperationException(
"ConfigObject is immutable, you can't call Map.'" + method
+ "'");
}
@Override
public void clear() {
throw weAreImmutable("clear");
}
@Override
public ConfigValue put(String arg0, ConfigValue arg1) {
throw weAreImmutable("put");
}
@Override
public void putAll(Map<? extends String, ? extends ConfigValue> arg0) {
throw weAreImmutable("putAll");
}
@Override
public ConfigValue remove(Object arg0) {
throw weAreImmutable("remove");
}
}

View File

@ -1,8 +1,11 @@
package com.typesafe.config.impl;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -10,6 +13,7 @@ import java.util.Set;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;
class SimpleConfigObject extends AbstractConfigObject {
@ -31,16 +35,16 @@ class SimpleConfigObject extends AbstractConfigObject {
}
@Override
public Object unwrapped() {
public Map<String, Object> unwrapped() {
Map<String, Object> m = new HashMap<String, Object>();
for (String k : value.keySet()) {
m.put(k, value.get(k).unwrapped());
for (Map.Entry<String, AbstractConfigValue> e : value.entrySet()) {
m.put(e.getKey(), e.getValue().unwrapped());
}
return m;
}
@Override
public boolean containsKey(String key) {
public boolean containsKey(Object key) {
return value.containsKey(key);
}
@ -104,4 +108,37 @@ class SimpleConfigObject extends AbstractConfigObject {
// note that "origin" is deliberately NOT part of equality
return mapHash(this.value);
}
@Override
public boolean containsValue(Object v) {
return value.containsValue(v);
}
@Override
public Set<Map.Entry<String, ConfigValue>> entrySet() {
// total bloat just to work around lack of type variance
HashSet<java.util.Map.Entry<String, ConfigValue>> entries = new HashSet<Map.Entry<String, ConfigValue>>();
for (Map.Entry<String, AbstractConfigValue> e : value.entrySet()) {
entries.add(new AbstractMap.SimpleImmutableEntry<String, ConfigValue>(
e.getKey(), e
.getValue()));
}
return entries;
}
@Override
public boolean isEmpty() {
return value.isEmpty();
}
@Override
public int size() {
return value.size();
}
@Override
public Collection<ConfigValue> values() {
return new HashSet<ConfigValue>(value.values());
}
}

View File

@ -1,7 +1,11 @@
package com.typesafe.config.impl;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import com.typesafe.config.ConfigValue;
class TransformedConfigObject extends AbstractConfigObject {
private AbstractConfigObject underlying;
@ -13,7 +17,7 @@ class TransformedConfigObject extends AbstractConfigObject {
}
@Override
public boolean containsKey(String key) {
public boolean containsKey(Object key) {
return underlying.containsKey(key);
}
@ -23,7 +27,7 @@ class TransformedConfigObject extends AbstractConfigObject {
}
@Override
public Object unwrapped() {
public Map<String, Object> unwrapped() {
return underlying.unwrapped();
}
@ -31,4 +35,29 @@ class TransformedConfigObject extends AbstractConfigObject {
protected AbstractConfigValue peek(String key) {
return underlying.peek(key);
}
@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

@ -156,8 +156,15 @@ class ConfigTest extends TestUtils {
assertEquals("null bar 42 baz true 3.14 hi", conf.getString("strings.concatenated"))
assertEquals(true, conf.getBoolean("booleans.trueAgain"))
assertEquals(false, conf.getBoolean("booleans.falseAgain"))
// FIXME need to add a way to get a null
//assertEquals(null, conf.getAny("nulls.null"))
// 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"))
// get stuff with getValue
assertEquals(intValue(42), conf.getValue("ints.fortyTwo"))
assertEquals(stringValue("abcd"), conf.getValue("strings.abcd"))
// get stuff with getAny
assertEquals(42L, conf.getAny("ints.fortyTwo"))

View File

@ -4,6 +4,8 @@ import org.junit.Assert._
import org.junit._
import com.typesafe.config.ConfigValue
import java.util.Collections
import scala.collection.JavaConverters._
import com.typesafe.config.ConfigObject
class ConfigValueTest extends TestUtils {
@ -90,4 +92,49 @@ class ConfigValueTest extends TestUtils {
subst("a").toString()
substInString("b").toString()
}
private def unsupported(body: => Unit) {
intercept[UnsupportedOperationException] {
body
}
}
@Test
def configObjectUnwraps() {
val m = new SimpleConfigObject(fakeOrigin(), null, configMap("a" -> 1, "b" -> 2, "c" -> 3))
assertEquals(Map("a" -> 1, "b" -> 2, "c" -> 3), m.unwrapped().asScala)
}
@Test
def configObjectImplementsMap() {
val m: ConfigObject = new SimpleConfigObject(fakeOrigin(), null, configMap("a" -> 1, "b" -> 2, "c" -> 3))
assertEquals(intValue(1), m.get("a"))
assertEquals(intValue(2), m.get("b"))
assertEquals(intValue(3), m.get("c"))
assertNull(m.get("d"))
assertTrue(m.containsKey("a"))
assertFalse(m.containsKey("z"))
assertTrue(m.containsValue(intValue(1)))
assertFalse(m.containsValue(intValue(10)))
assertFalse(m.isEmpty())
assertEquals(3, m.size())
val values = Set(intValue(1), intValue(2), intValue(3))
assertEquals(values, m.values().asScala.toSet)
assertEquals(values, m.entrySet().asScala map { _.getValue() } toSet)
val keys = Set("a", "b", "c")
assertEquals(keys, m.keySet().asScala.toSet)
assertEquals(keys, m.entrySet().asScala map { _.getKey() } toSet)
unsupported { m.clear() }
unsupported { m.put("hello", intValue(42)) }
unsupported { m.putAll(Collections.emptyMap[String, AbstractConfigValue]()) }
unsupported { m.remove("a") }
}
}