From f6fd02508e7105cdfea136d5ca28428b49e385ed Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Fri, 2 Dec 2011 18:44:11 -0500 Subject: [PATCH] add Config.entrySet() which returns the set of paths and non-null values --- .../main/java/com/typesafe/config/Config.java | 22 +++++++++-- .../typesafe/config/impl/SimpleConfig.java | 38 ++++++++++++++++--- .../com/typesafe/config/impl/ConfigTest.scala | 10 +++++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 1c7fca50..1f5dc427 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -4,6 +4,8 @@ package com.typesafe.config; import java.util.List; +import java.util.Map; +import java.util.Set; /** * An immutable map from config paths to config values. @@ -54,10 +56,11 @@ import java.util.List; * are performed for you though. * *

- * If you want to iterate over the contents of a {@code Config}, you have to get - * its {@code ConfigObject} with {@link #root()}, and then iterate over the - * {@code ConfigObject}. - * + * If you want to iterate over the contents of a {@code Config}, you can get its + * {@code ConfigObject} with {@link #root()}, and then iterate over the + * {@code ConfigObject} (which implements java.util.Map). Or, you + * can use {@link #entrySet()} which recurses the object tree for you and builds + * up a Set of all path-value pairs where the value is not null. * *

* Do not implement {@code Config}; it should only be implemented by @@ -256,6 +259,17 @@ public interface Config extends ConfigMergeable { */ boolean isEmpty(); + /** + * Returns the set of path-value pairs, excluding any null values, found by + * recursing {@link #root() the root object}. Note that this is very + * different from root().entrySet() which returns the set of + * immediate-child keys in the root object and includes null values. + * + * @return set of paths with non-null values, built up by recursing the + * entire tree of {@link ConfigObject} + */ + Set> entrySet(); + /** * * @param path diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java index 127a98a0..fcc9593b 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -3,10 +3,13 @@ */ package com.typesafe.config.impl; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import com.typesafe.config.Config; @@ -20,12 +23,10 @@ import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; /** - * One thing to keep in mind in the future: if any Collection-like APIs are - * added here, including iterators or size() or anything, then we'd have to - * grapple with whether ConfigNull values are "in" the Config (probably not) and - * we'd probably want to make the collection look flat - not like a tree. So the - * key-value pairs would be all the tree's leaf values, in a big flat list with - * their full paths. + * One thing to keep in mind in the future: as Collection-like APIs are added + * here, including iterators or size() or anything, they should be consistent + * with a one-level java.util.Map from paths to non-null values. Null values are + * not "in" the map. */ final class SimpleConfig implements Config, MergeableValue { @@ -73,6 +74,31 @@ final class SimpleConfig implements Config, MergeableValue { return object.isEmpty(); } + private static void findPaths(Set> entries, Path parent, + AbstractConfigObject obj) { + for (Map.Entry entry : obj.entrySet()) { + String elem = entry.getKey(); + ConfigValue v = entry.getValue(); + Path path = Path.newKey(elem); + if (parent != null) + path = path.prepend(parent); + if (v instanceof AbstractConfigObject) { + findPaths(entries, path, (AbstractConfigObject) v); + } else if (v instanceof ConfigNull) { + // nothing; nulls are conceptually not in a Config + } else { + entries.add(new AbstractMap.SimpleImmutableEntry(path.render(), v)); + } + } + } + + @Override + public Set> entrySet() { + Set> entries = new HashSet>(); + findPaths(entries, null, object); + return entries; + } + static private AbstractConfigValue find(AbstractConfigObject self, String pathExpression, ConfigValueType expected, String originalPath) { Path path = Path.newPath(pathExpression); diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index f1b378b7..38b8d88d 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -782,6 +782,16 @@ class ConfigTest extends TestUtils { assertEquals(-1, o3.lineNumber) } + @Test + def test01EntrySet() { + val conf = ConfigFactory.load("test01") + + val javaEntries = conf.entrySet() + val entries = Map((javaEntries.asScala.toSeq map { e => (e.getKey(), e.getValue()) }): _*) + assertEquals(Some(intValue(42)), entries.get("ints.fortyTwo")) + assertEquals(None, entries.get("nulls.null")) + } + @Test def test02SubstitutionsWithWeirdPaths() { val conf = ConfigFactory.load("test02")