You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2019/03/22 19:26:11 UTC

[metamodel] 01/03: METAMODEL-1172: Improves searching based on an expression in MapValueFunction: Add support in cases that the outer most type is a list/array. Add support for searching in nested lists or multidimensional arrays.

This is an automated email from the ASF dual-hosted git repository.

kaspersor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/metamodel.git

commit f6329988d8d295c235557a28d988e74ca41858a3
Author: George Katiforis <ge...@hotmail.com>
AuthorDate: Tue Mar 19 01:08:45 2019 +0200

    METAMODEL-1172: Improves searching based on an expression in MapValueFunction:
    Add support in cases that the outer most type is a list/array.
    Add support for searching in nested lists or multidimensional arrays.
---
 .../apache/metamodel/query/MapValueFunction.java   |  8 +--
 .../org/apache/metamodel/util/CollectionUtils.java | 81 ++++++++++++++--------
 .../metamodel/query/MapValueFunctionTest.java      | 28 ++++++++
 .../apache/metamodel/util/CollectionUtilsTest.java | 80 +++++++++++++++++++++
 4 files changed, 162 insertions(+), 35 deletions(-)

diff --git a/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java b/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java
index e017747..a1ca40e 100644
--- a/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java
+++ b/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java
@@ -18,8 +18,6 @@
  */
 package org.apache.metamodel.query;
 
-import java.util.Map;
-
 import org.apache.metamodel.data.Row;
 import org.apache.metamodel.schema.ColumnType;
 import org.apache.metamodel.util.CollectionUtils;
@@ -38,11 +36,7 @@ public final class MapValueFunction extends DefaultScalarFunction {
             throw new IllegalArgumentException("Expecting path parameter to MAP_VALUE function");
         }
         final Object value = row.getValue(operandItem);
-        if (value instanceof Map) {
-            final Map<?, ?> map = (Map<?, ?>) value;
-            return CollectionUtils.find(map, (String) parameters[0]);
-        }
-        return null;
+        return CollectionUtils.find(value, (String) parameters[0]);
     }
 
     @Override
diff --git a/core/src/main/java/org/apache/metamodel/util/CollectionUtils.java b/core/src/main/java/org/apache/metamodel/util/CollectionUtils.java
index 5246a3a..3dc0d23 100644
--- a/core/src/main/java/org/apache/metamodel/util/CollectionUtils.java
+++ b/core/src/main/java/org/apache/metamodel/util/CollectionUtils.java
@@ -39,8 +39,9 @@ public final class CollectionUtils {
     }
 
     /**
-     * Searches a map for a given key. The key can be a regular map key, or a
-     * simple expression of the form:
+     * Searches a map, a list or array for a given key.
+     * Also can be used with nested lists and multidimensional arrays.
+     * The key can be a regular map key, or a simple expression of the form:
      * 
      * <ul>
      * <li>foo.bar (will lookup 'foo', and then 'bar' in a potential nested map)
@@ -48,27 +49,39 @@ public final class CollectionUtils {
      * <li>foo.bar[0].baz (will lookup 'foo', then 'bar' in a potential nested
      * map, then pick the first element in case it is a list/array and then pick
      * 'baz' from the potential map at that position).
+     * </li>
+     *  <li>[1]foo.bar[0][1].baz (pick the second element in list/array and then
+     *  lookup 'foo' then 'bar' in a potential nested map, then pick the second
+     *  element in the first row in nested lists or 2 dimensional array and then 'baz'  in
+     *  a potential nested map).
+     * </li>
      * </ul>
      * 
-     * @param map
-     *            the map to search in
+     * @param collection
+     *            the map, list or array to search in
      * @param key
      *            the key to resolve
-     * @return the object in the map with the given key/expression. Or null if
+     * @return the object in the map, list or array with the given key/expression. Or null if
      *         it does not exist.
      */
-    public static Object find(Map<?, ?> map, String key) {
-        if (map == null || key == null) {
+    public static Object find(Object collection, String key) {
+        if (collection == null || key == null) {
             return null;
         }
-        final Object result = map.get(key);
-        if (result == null) {
-            return find(map, key, 0);
+        if(collection instanceof Map){
+           Map<?, ?> map  = (Map<?, ?>) collection;
+            final Object result = map.get(key);
+            if (result == null) {
+                return find(map, key, 0);
+            }
+            return result;
+        } else if(collection instanceof List || collection.getClass().isArray()){
+            return find( collection, key, 0);
         }
-        return result;
+       return null;
     }
 
-    private static Object find(Map<?, ?> map, String key, int fromIndex) {
+    private static Object find(final Object collection, String key, int fromIndex) {
         final int indexOfDot = key.indexOf('.', fromIndex);
         final int indexOfBracket = key.indexOf('[', fromIndex);
         int indexOfEndBracket = -1;
@@ -79,12 +92,28 @@ public final class CollectionUtils {
 
         if (hasBracket) {
             // also check that there is an end-bracket
-            indexOfEndBracket = key.indexOf("]", indexOfBracket);
+            indexOfEndBracket = key.indexOf(']', indexOfBracket);
             hasBracket = indexOfEndBracket != -1;
             if (hasBracket) {
                 final String indexString = key.substring(indexOfBracket + 1, indexOfEndBracket);
                 try {
                     arrayIndex = Integer.parseInt(indexString);
+                    if(collection instanceof List || collection.getClass().isArray()){
+                        Object obj = null;
+                        if(collection instanceof List){
+                            obj = ((List) collection).get(arrayIndex);
+                        } else if (collection.getClass().isArray()) {
+                            obj = Array.get(collection, arrayIndex);
+                        }
+                        key = key.substring( indexOfEndBracket+1, key.length());
+                        if(key.startsWith(".")){
+                           key = key.substring(1, key.length());
+                        }
+                        if(key.isEmpty()){
+                            return obj;
+                        }
+                        return find(obj, key );
+                    }
                 } catch (NumberFormatException e) {
                     // not a valid array/list index
                     hasBracket = false;
@@ -102,9 +131,9 @@ public final class CollectionUtils {
 
         if (hasDot) {
             final String prefix = key.substring(0, indexOfDot);
-            final Object nestedObject = map.get(prefix);
+            final Object nestedObject = ((Map<?,?>)collection).get(prefix);
             if (nestedObject == null) {
-                return find(map, key, indexOfDot + 1);
+                return find(collection, key, indexOfDot + 1);
             }
             if (nestedObject instanceof Map) {
                 final String remainingPart = key.substring(indexOfDot + 1);
@@ -115,10 +144,13 @@ public final class CollectionUtils {
         }
 
         if (hasBracket) {
-            final String prefix = key.substring(0, indexOfBracket);
-            final Object nestedObject = map.get(prefix);
-            if (nestedObject == null) {
-                return find(map, key, indexOfBracket + 1);
+             Object nestedObject = null;
+            if(collection instanceof Map){
+                final String prefix = key.substring(0, indexOfBracket);
+                  nestedObject = ((Map<?,?>)collection).get(prefix);
+                if (nestedObject == null) {
+                    return find(collection, key, indexOfBracket + 1);
+                }
             }
 
             String remainingPart = key.substring(indexOfEndBracket + 1);
@@ -127,7 +159,7 @@ public final class CollectionUtils {
                 final Object valueAtIndex;
                 if (nestedObject instanceof List) {
                     valueAtIndex = ((List<?>) nestedObject).get(arrayIndex);
-                } else if (nestedObject.getClass().isArray()) {
+                } else if (nestedObject!= null && nestedObject.getClass().isArray()) {
                     valueAtIndex = Array.get(nestedObject, arrayIndex);
                 } else {
                     // no way to extract from a non-array and non-list
@@ -143,14 +175,7 @@ public final class CollectionUtils {
                         return valueAtIndex;
                     }
 
-                    if (valueAtIndex instanceof Map) {
-                        @SuppressWarnings("unchecked")
-                        final Map<String, ?> nestedMap = (Map<String, ?>) valueAtIndex;
-                        return find(nestedMap, remainingPart);
-                    } else {
-                        // not traversing any further. Should we want to add
-                        // support for double-sided arrays, we could do it here.
-                    }
+                    return find(valueAtIndex, remainingPart);
                 }
 
             } catch (IndexOutOfBoundsException e) {
diff --git a/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java b/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java
index 0d87c0c..bbf39c5 100644
--- a/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java
+++ b/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java
@@ -20,7 +20,9 @@ package org.apache.metamodel.query;
 
 import static org.junit.Assert.assertEquals;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.metamodel.data.DefaultRow;
@@ -46,6 +48,32 @@ public class MapValueFunctionTest {
     }
 
     @Test
+    public void testGetValueFromList() throws Exception {
+        final List<Map<String, Object>> value = new ArrayList<>();
+        final Map<String, Object> innerMap = new HashMap<>();
+        innerMap.put("bar", "baz");
+        value.add(innerMap);
+        final SelectItem operandItem = new SelectItem("foo", "f");
+        final Row row = new DefaultRow(new SimpleDataSetHeader(new SelectItem[] { operandItem }),
+                new Object[] { value });
+        final Object v1 = function.evaluate(row, new Object[] { "[0]bar" }, operandItem);
+        assertEquals("baz", v1.toString());
+    }
+
+    @Test
+    public void testGetValueFromArray() throws Exception {
+
+        final Map<String, Object> innerMap = new HashMap<>();
+        innerMap.put("bar", "baz");
+        final Object[] value = {innerMap};
+        final SelectItem operandItem = new SelectItem("foo", "f");
+        final Row row = new DefaultRow(new SimpleDataSetHeader(new SelectItem[] { operandItem }),
+                new Object[] { value });
+        final Object v1 = function.evaluate(row, new Object[] { "[0]bar" }, operandItem);
+        assertEquals("baz", v1.toString());
+    }
+
+    @Test
     public void testNotAMap() throws Exception {
         final SelectItem operandItem = new SelectItem("foo", "f");
         final Row row = new DefaultRow(new SimpleDataSetHeader(new SelectItem[] { operandItem }),
diff --git a/core/src/test/java/org/apache/metamodel/util/CollectionUtilsTest.java b/core/src/test/java/org/apache/metamodel/util/CollectionUtilsTest.java
index c5a5a6f..3dbcb83 100644
--- a/core/src/test/java/org/apache/metamodel/util/CollectionUtilsTest.java
+++ b/core/src/test/java/org/apache/metamodel/util/CollectionUtilsTest.java
@@ -56,6 +56,86 @@ public class CollectionUtilsTest extends TestCase {
         assertEquals(null, CollectionUtils.find(map, "Interests[2]"));
     }
 
+    public void testFindWithNestedTripleListsAndMaps() throws Exception {
+        final Map<String, Object> map3 = new HashMap<>();
+        map3.put("opt.ion1", "W");
+        final List nestedList = Arrays.asList(Arrays.asList("G", "H", map3), "K");
+        final List tripleNestedList = Arrays.asList(Arrays.asList("L", nestedList), "O");
+
+        final Map<String, Object> nestedMap= new HashMap<>();
+        nestedMap.put("option1", "F");
+        nestedMap.put("option2",  nestedList);
+        nestedMap.put("option3",  tripleNestedList);
+
+        final Map<String, Object> map= new HashMap<>();
+        map.put("option1", "A");
+        map.put("option2", "B");
+        map.put("option3", nestedMap);
+
+        final Map<String, Object> map2 = new HashMap<>();
+        map2.put("option1", "C");
+
+        List list =  Arrays.asList(map, map2);
+
+        final Map<String, Object> options4 = new HashMap<>();
+        options4.put("list", list);
+
+        assertEquals(map, CollectionUtils.find(list, "[0]"));
+        assertEquals("B", CollectionUtils.find(list, "[0].option2"));
+        assertEquals("B", CollectionUtils.find(options4, "list[0].option2"));
+        assertEquals("C", CollectionUtils.find(options4, "list[1].option1"));
+        assertEquals("F", CollectionUtils.find(list, "[0].option3.option1"));
+        assertEquals( nestedList, CollectionUtils.find(list, "[0].option3.option2"));
+        assertEquals( "K", CollectionUtils.find(list, "[0].option3.option2[1]"));
+        assertEquals("G", CollectionUtils.find(list, "[0].option3.option2[0][0]"));
+        assertEquals("H", CollectionUtils.find(list, "[0].option3.option2[0][1]"));
+        assertEquals("K", CollectionUtils.find(list, "[0].option3.option3[0][1][1]"));
+        assertEquals(map3, CollectionUtils.find(list, "[0].option3.option3[0][1][0][2]"));
+        assertEquals("W", CollectionUtils.find(list, "[0].option3.option3[0][1][0][2].opt.ion1"));
+        assertEquals(null, CollectionUtils.find(list, "[0].option3.option3[0][1][0][2].opt.ion2"));
+    }
+
+    public void testFindWithNestedArraysAndMaps() throws Exception {
+        final Map<String, Object> map3 = new HashMap<>();
+        map3.put("opt.ion1", "W");
+        final Object nestedList[] = {"G", "H", map3, "K"};
+        final Object twoDimensionalArray[][] = {{"L", nestedList},
+                                                {"M", nestedList, "L"},};
+
+        final Map<String, Object> nestedMap= new HashMap<>();
+        nestedMap.put("option1", "F");
+        nestedMap.put("option2",  nestedList);
+        nestedMap.put("option3",  twoDimensionalArray);
+
+        final Map<String, Object> map= new HashMap<>();
+        map.put("option1", "A");
+        map.put("option2", "B");
+        map.put("option3", nestedMap);
+
+        final Map<String, Object> map2 = new HashMap<>();
+        map2.put("option1", "C");
+
+        Object list[] =  {map, map2};
+
+        final Map<String, Object> options4 = new HashMap<>();
+        options4.put("list", list);
+
+        assertEquals(map, CollectionUtils.find(list, "[0]"));
+        assertEquals("B", CollectionUtils.find(list, "[0].option2"));
+        assertEquals("B", CollectionUtils.find(options4, "list[0].option2"));
+        assertEquals("C", CollectionUtils.find(options4, "list[1].option1"));
+        assertEquals("F", CollectionUtils.find(list, "[0].option3.option1"));
+        assertEquals( nestedList, CollectionUtils.find(list, "[0].option3.option2"));
+        assertEquals( "H", CollectionUtils.find(list, "[0].option3.option2[1]"));
+        assertEquals("H", CollectionUtils.find(list, "[0].option3.option3[0][1][1]"));
+        assertEquals("L", CollectionUtils.find(list, "[0].option3.option3[1][2]"));
+        assertEquals(map3, CollectionUtils.find(list, "[0].option3.option3[0][1][2]"));
+        assertEquals("W", CollectionUtils.find(list, "[0].option3.option3[0][1][2].opt.ion1"));
+        assertEquals(map3, CollectionUtils.find(list, "[0].option3.option3[0][1][2]"));
+        assertEquals(null, CollectionUtils.find(list, "[0].option3.option3[0][1][2].opt.ion2"));
+    }
+
+
     public void testFindWithNestedListsAndMaps() throws Exception {
         final Map<String, Object> address1 = new LinkedHashMap<String, Object>();
         address1.put("city", "Stockholm");