Author: oheger Date: Tue Jan 2 19:59:02 2018 New Revision: 1819880 URL: http://svn.apache.org/viewvc?rev=1819880&view=rev Log: CONFIGURATION-686: JSON configuration now supports list structures.
Configurations derived from AbstractYAMLBasedConfiguration can now handle list structures that contain complex objects as elements. Modified: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java commons/proper/configuration/trunk/src/test/resources/test.json Modified: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java?rev=1819880&r1=1819879&r2=1819880&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java (original) +++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java Tue Jan 2 19:59:02 2018 @@ -21,7 +21,11 @@ import org.apache.commons.configuration2 import org.apache.commons.configuration2.io.ConfigurationLogger; import org.apache.commons.configuration2.tree.ImmutableNode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -69,9 +73,8 @@ public class AbstractYAMLBasedConfigurat */ protected void load(Map<String, Object> map) { - ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder(); - ImmutableNode top = constructHierarchy(rootBuilder, map); - getNodeModel().setRootNode(top); + List<ImmutableNode> roots = constructHierarchy("", map); + getNodeModel().setRootNode(roots.get(0)); } /** @@ -83,53 +86,116 @@ public class AbstractYAMLBasedConfigurat */ protected Map<String, Object> constructMap(ImmutableNode node) { - Map<String, Object> map = - new HashMap<>(node.getChildren().size()); + Map<String, Object> map = new HashMap<>(node.getChildren().size()); for (ImmutableNode cNode : node.getChildren()) { - if (cNode.getChildren().isEmpty()) - { - map.put(cNode.getNodeName(), cNode.getValue()); - } - else - { - map.put(cNode.getNodeName(), constructMap(cNode)); - } + Object value = cNode.getChildren().isEmpty() ? cNode.getValue() + : constructMap(cNode); + addEntry(map, cNode.getNodeName(), value); } return map; } /** - * Constructs the internal configuration nodes hierarchy. + * Adds a key value pair to a map, taking list structures into account. If a + * key is added which is already present in the map, this method ensures + * that a list is created. + * + * @param map the map + * @param key the key + * @param value the value + */ + private static void addEntry(Map<String, Object> map, String key, + Object value) + { + Object oldValue = map.get(key); + if (oldValue == null) + { + map.put(key, value); + } + else if (oldValue instanceof Collection) + { + // safe case because the collection was created by ourselves + @SuppressWarnings("unchecked") + Collection<Object> values = (Collection<Object>) oldValue; + values.add(value); + } + else + { + Collection<Object> values = new ArrayList<>(); + values.add(oldValue); + values.add(value); + map.put(key, values); + } + } + + /** + * Creates a part of the hierarchical nodes structure of the resulting + * configuration. The passed in element is converted into one or multiple + * configuration nodes. (If list structures are involved, multiple nodes are + * returned.) * - * @param parent The configuration node that is the root of the current - * configuration section. - * @param map The map with the yaml configurations nodes, i.e. String -> - * Object. + * @param key the key of the new node(s) + * @param elem the element to be processed + * @return a list with configuration nodes representing the element */ - private ImmutableNode constructHierarchy(ImmutableNode.Builder parent, - Map<String, Object> map) + private static List<ImmutableNode> constructHierarchy(String key, + Object elem) { + if (elem instanceof Map) + { + return parseMap((Map<String, Object>) elem, key); + } + else if (elem instanceof Collection) + { + return parseCollection((Collection<Object>) elem, key); + } + else + { + return Collections.singletonList( + new ImmutableNode.Builder().name(key).value(elem).create()); + } + } + + /** + * Parses a map structure. The single keys of the map are processed + * recursively. + * + * @param map the map to be processed + * @param key the key under which this map is to be stored + * @return a node representing this map + */ + private static List<ImmutableNode> parseMap(Map<String, Object> map, String key) + { + ImmutableNode.Builder subtree = new ImmutableNode.Builder().name(key); for (Map.Entry<String, Object> entry : map.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - if (value instanceof Map) + List<ImmutableNode> children = + constructHierarchy(entry.getKey(), entry.getValue()); + for (ImmutableNode child : children) { - ImmutableNode.Builder subtree = - new ImmutableNode.Builder().name(key); - ImmutableNode children = - constructHierarchy(subtree, (Map) value); - parent.addChild(children); - } - else - { - ImmutableNode leaf = new ImmutableNode.Builder().name(key) - .value(value).create(); - parent.addChild(leaf); + subtree.addChild(child); } } - return parent.create(); + return Collections.singletonList(subtree.create()); + } + + /** + * Parses a collection structure. The elements of the collection are + * processed recursively. + * + * @param col the collection to be processed + * @param key the key under which this collection is to be stored + * @return a node representing this collection + */ + private static List<ImmutableNode> parseCollection(Collection<Object> col, String key) + { + List<ImmutableNode> nodes = new ArrayList<>(col.size()); + for (Object elem : col) + { + nodes.addAll(constructHierarchy(key, elem)); + } + return nodes; } /** Modified: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java?rev=1819880&r1=1819879&r2=1819880&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java (original) +++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java Tue Jan 2 19:59:02 2018 @@ -108,7 +108,7 @@ public class TestJSONConfiguration MapType type = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class); Map<String, Object> parsed = mapper.readValue(output, type); - assertEquals(6, parsed.entrySet().size()); + assertEquals(7, parsed.entrySet().size()); assertEquals("value1", parsed.get("key1")); Map key2 = (Map) parsed.get("key2"); @@ -119,6 +119,10 @@ public class TestJSONConfiguration assertEquals(2, key5.size()); assertEquals("col1", key5.get(0)); assertEquals("col2", key5.get(1)); + + List<?> capitals = (List<?>) parsed.get("capitals"); + Map<?, ?> capUk = (Map<?, ?>) capitals.get(1); + assertEquals("London", capUk.get("capital")); } @Test @@ -131,6 +135,13 @@ public class TestJSONConfiguration } @Test + public void testGetProperty_dictionaryInList() + { + assertEquals("UK", jsonConfiguration.getString("capitals(1).country")); + assertEquals("Washington", jsonConfiguration.getString("capitals(0).capital")); + } + + @Test public void testCopyConstructor() { BaseHierarchicalConfiguration c = new BaseHierarchicalConfiguration(); Modified: commons/proper/configuration/trunk/src/test/resources/test.json URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/resources/test.json?rev=1819880&r1=1819879&r2=1819880&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/test/resources/test.json (original) +++ commons/proper/configuration/trunk/src/test/resources/test.json Tue Jan 2 19:59:02 2018 @@ -23,5 +23,15 @@ "name": "Martin D'vloper", "job": "Developer", "skill": "Elite" - } + }, + "capitals": [ + { + "country": "USA", + "capital": "Washington" + }, + { + "country": "UK", + "capital": "London" + } + ] }