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 <[email protected]> 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");
