diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index 2b23219ad7c..1473f52e0e1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -19,7 +19,6 @@ package org.springframework.expression.spel.ast; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; @@ -38,6 +37,7 @@ import org.springframework.util.ObjectUtils; * * @author Andy Clement * @author Mark Fisher + * @author Juergen Hoeller * @since 3.0 */ public class Projection extends SpelNodeImpl { @@ -86,9 +86,10 @@ public class Projection extends SpelNodeImpl { return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); // TODO unable to build correct type descriptor } - if (operand instanceof Collection || operandIsArray) { - Collection data = (operand instanceof Collection ? (Collection) operand : - Arrays.asList(ObjectUtils.toObjectArray(operand))); + if (operand instanceof Iterable || operandIsArray) { + Iterable data = (operand instanceof Iterable ? + (Iterable) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); + List result = new ArrayList(); int idx = 0; Class arrayElementType = null; @@ -108,6 +109,7 @@ public class Projection extends SpelNodeImpl { } idx++; } + if (operandIsArray) { if (arrayElementType == null) { arrayElementType = Object.class; @@ -116,10 +118,11 @@ public class Projection extends SpelNodeImpl { System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); } + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } - if (operand==null) { + if (operand == null) { if (this.nullSafe) { return ValueRef.NullValueRef.INSTANCE; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 81523e505ad..232613398e1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -19,7 +19,6 @@ package org.springframework.expression.spel.ast; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +42,7 @@ import org.springframework.util.ObjectUtils; * @author Andy Clement * @author Mark Fisher * @author Sam Brannen + * @author Juergen Hoeller * @since 3.0 */ public class Selection extends SpelNodeImpl { @@ -75,13 +75,14 @@ public class Selection extends SpelNodeImpl { protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { TypedValue op = state.getActiveContextObject(); Object operand = op.getValue(); - SpelNodeImpl selectionCriteria = this.children[0]; + if (operand instanceof Map) { Map mapdata = (Map) operand; // TODO don't lose generic info for the new map Map result = new HashMap(); Object lastKey = null; + for (Map.Entry entry : mapdata.entrySet()) { try { TypedValue kvPair = new TypedValue(entry); @@ -108,6 +109,7 @@ public class Selection extends SpelNodeImpl { state.exitScope(); } } + if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(null), this); } @@ -122,11 +124,10 @@ public class Selection extends SpelNodeImpl { return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } - if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) { - List data = new ArrayList(); - Collection coll = (operand instanceof Collection ? - (Collection) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); - data.addAll(coll); + if (operand instanceof Iterable || ObjectUtils.isArray(operand)) { + Iterable data = (operand instanceof Iterable ? + (Iterable) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); + List result = new ArrayList(); int index = 0; for (Object element : data) { @@ -154,22 +155,23 @@ public class Selection extends SpelNodeImpl { } } - if ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) { + if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) { return ValueRef.NullValueRef.INSTANCE; } if (this.variant == LAST) { - return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)), this); } - if (operand instanceof Collection) { - return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); + if (operand instanceof Iterable) { + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); } + Class elementType = ClassUtils.resolvePrimitiveIfNecessary( op.getTypeDescriptor().getElementTypeDescriptor().getType()); Object resultArray = Array.newInstance(elementType, result.size()); System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); - return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray), this); } if (operand == null) { if (this.nullSafe) { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java index 3a7ed5e749c..eb318762938 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.expression.spel; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -37,6 +38,7 @@ import static org.junit.Assert.*; /** * @author Mark Fisher * @author Sam Brannen + * @author Juergen Hoeller */ public class SelectionAndProjectionTests { @@ -106,6 +108,21 @@ public class SelectionAndProjectionTests { assertEquals(4, value); } + @Test + public void selectionWithIterable() throws Exception { + Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]"); + EvaluationContext context = new StandardEvaluationContext(new IterableTestBean()); + Object value = expression.getValue(context); + assertTrue(value instanceof List); + List list = (List) value; + assertEquals(5, list.size()); + assertEquals(0, list.get(0)); + assertEquals(1, list.get(1)); + assertEquals(2, list.get(2)); + assertEquals(3, list.get(3)); + assertEquals(4, list.get(4)); + } + @Test public void selectionWithArray() throws Exception { Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]"); @@ -242,6 +259,20 @@ public class SelectionAndProjectionTests { assertEquals(7, list.get(2)); } + @Test + public void projectionWithIterable() throws Exception { + Expression expression = new SpelExpressionParser().parseRaw("#testList.![wrapper.value]"); + EvaluationContext context = new StandardEvaluationContext(); + context.setVariable("testList", IntegerTestBean.createIterable()); + Object value = expression.getValue(context); + assertTrue(value instanceof List); + List list = (List) value; + assertEquals(3, list.size()); + assertEquals(5, list.get(0)); + assertEquals(6, list.get(1)); + assertEquals(7, list.get(2)); + } + @Test public void projectionWithArray() throws Exception { Expression expression = new SpelExpressionParser().parseRaw("#testArray.![wrapper.value]"); @@ -258,23 +289,6 @@ public class SelectionAndProjectionTests { assertEquals(new Integer(7), array[2]); } - static class MapTestBean { - - private final Map colors = new TreeMap(); - - MapTestBean() { - // colors.put("black", "schwarz"); - colors.put("red", "rot"); - colors.put("brown", "braun"); - colors.put("blue", "blau"); - colors.put("yellow", "gelb"); - colors.put("beige", "beige"); - } - - public Map getColors() { - return colors; - } - } static class ListTestBean { @@ -291,6 +305,7 @@ public class SelectionAndProjectionTests { } } + static class SetTestBean { private final Set integers = new LinkedHashSet(); @@ -306,6 +321,28 @@ public class SelectionAndProjectionTests { } } + + static class IterableTestBean { + + private final Set integers = new LinkedHashSet(); + + IterableTestBean() { + for (int i = 0; i < 10; i++) { + integers.add(i); + } + } + + public Iterable getIntegers() { + return new Iterable() { + @Override + public Iterator iterator() { + return integers.iterator(); + } + }; + } + } + + static class ArrayTestBean { private final int[] ints = new int[10]; @@ -328,6 +365,26 @@ public class SelectionAndProjectionTests { } } + + static class MapTestBean { + + private final Map colors = new TreeMap(); + + MapTestBean() { + // colors.put("black", "schwarz"); + colors.put("red", "rot"); + colors.put("brown", "braun"); + colors.put("blue", "blau"); + colors.put("yellow", "gelb"); + colors.put("beige", "beige"); + } + + public Map getColors() { + return colors; + } + } + + static class IntegerTestBean { private final IntegerWrapper wrapper; @@ -356,6 +413,16 @@ public class SelectionAndProjectionTests { return set; } + static Iterable createIterable() { + final Set set = createSet(); + return new Iterable() { + @Override + public Iterator iterator() { + return set.iterator(); + } + }; + } + static IntegerTestBean[] createArray() { IntegerTestBean[] array = new IntegerTestBean[3]; for (int i = 0; i < 3; i++) { @@ -369,6 +436,7 @@ public class SelectionAndProjectionTests { } } + static class IntegerWrapper { private final Number value;