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 -&gt;
-     *        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"
+    }
+  ]
 }


Reply via email to