Skip to content

Commit 0783a1c

Browse files
committed
SpEL selection/projection works with Iterable as well
Issue: SPR-13231
1 parent ea2a1d3 commit 0783a1c

File tree

3 files changed

+108
-35
lines changed

3 files changed

+108
-35
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.lang.reflect.Array;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22-
import java.util.Collection;
2322
import java.util.List;
2423
import java.util.Map;
2524

@@ -38,6 +37,7 @@
3837
*
3938
* @author Andy Clement
4039
* @author Mark Fisher
40+
* @author Juergen Hoeller
4141
* @since 3.0
4242
*/
4343
public class Projection extends SpelNodeImpl {
@@ -86,9 +86,10 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
8686
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); // TODO unable to build correct type descriptor
8787
}
8888

89-
if (operand instanceof Collection || operandIsArray) {
90-
Collection<?> data = (operand instanceof Collection ? (Collection<?>) operand :
91-
Arrays.asList(ObjectUtils.toObjectArray(operand)));
89+
if (operand instanceof Iterable || operandIsArray) {
90+
Iterable<?> data = (operand instanceof Iterable ?
91+
(Iterable<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)));
92+
9293
List<Object> result = new ArrayList<Object>();
9394
int idx = 0;
9495
Class<?> arrayElementType = null;
@@ -108,6 +109,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
108109
}
109110
idx++;
110111
}
112+
111113
if (operandIsArray) {
112114
if (arrayElementType == null) {
113115
arrayElementType = Object.class;
@@ -116,10 +118,11 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
116118
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
117119
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
118120
}
121+
119122
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
120123
}
121124

122-
if (operand==null) {
125+
if (operand == null) {
123126
if (this.nullSafe) {
124127
return ValueRef.NullValueRef.INSTANCE;
125128
}

spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.lang.reflect.Array;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22-
import java.util.Collection;
2322
import java.util.HashMap;
2423
import java.util.List;
2524
import java.util.Map;
@@ -43,6 +42,7 @@
4342
* @author Andy Clement
4443
* @author Mark Fisher
4544
* @author Sam Brannen
45+
* @author Juergen Hoeller
4646
* @since 3.0
4747
*/
4848
public class Selection extends SpelNodeImpl {
@@ -75,13 +75,14 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
7575
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
7676
TypedValue op = state.getActiveContextObject();
7777
Object operand = op.getValue();
78-
7978
SpelNodeImpl selectionCriteria = this.children[0];
79+
8080
if (operand instanceof Map) {
8181
Map<?, ?> mapdata = (Map<?, ?>) operand;
8282
// TODO don't lose generic info for the new map
8383
Map<Object, Object> result = new HashMap<Object, Object>();
8484
Object lastKey = null;
85+
8586
for (Map.Entry<?, ?> entry : mapdata.entrySet()) {
8687
try {
8788
TypedValue kvPair = new TypedValue(entry);
@@ -108,6 +109,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
108109
state.exitScope();
109110
}
110111
}
112+
111113
if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) {
112114
return new ValueRef.TypedValueHolderValueRef(new TypedValue(null), this);
113115
}
@@ -122,11 +124,10 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
122124
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
123125
}
124126

125-
if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
126-
List<Object> data = new ArrayList<Object>();
127-
Collection<?> coll = (operand instanceof Collection ?
128-
(Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)));
129-
data.addAll(coll);
127+
if (operand instanceof Iterable || ObjectUtils.isArray(operand)) {
128+
Iterable<?> data = (operand instanceof Iterable ?
129+
(Iterable<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)));
130+
130131
List<Object> result = new ArrayList<Object>();
131132
int index = 0;
132133
for (Object element : data) {
@@ -154,22 +155,23 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
154155
}
155156
}
156157

157-
if ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) {
158+
if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) {
158159
return ValueRef.NullValueRef.INSTANCE;
159160
}
160161

161162
if (this.variant == LAST) {
162-
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this);
163+
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)), this);
163164
}
164165

165-
if (operand instanceof Collection) {
166-
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
166+
if (operand instanceof Iterable) {
167+
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this);
167168
}
169+
168170
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(
169171
op.getTypeDescriptor().getElementTypeDescriptor().getType());
170172
Object resultArray = Array.newInstance(elementType, result.size());
171173
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
172-
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
174+
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray), this);
173175
}
174176
if (operand == null) {
175177
if (this.nullSafe) {

spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.expression.spel;
1818

1919
import java.util.ArrayList;
20+
import java.util.Iterator;
2021
import java.util.LinkedHashSet;
2122
import java.util.List;
2223
import java.util.Map;
@@ -37,6 +38,7 @@
3738
/**
3839
* @author Mark Fisher
3940
* @author Sam Brannen
41+
* @author Juergen Hoeller
4042
*/
4143
public class SelectionAndProjectionTests {
4244

@@ -106,6 +108,21 @@ public void selectLastItemInSet() throws Exception {
106108
assertEquals(4, value);
107109
}
108110

111+
@Test
112+
public void selectionWithIterable() throws Exception {
113+
Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]");
114+
EvaluationContext context = new StandardEvaluationContext(new IterableTestBean());
115+
Object value = expression.getValue(context);
116+
assertTrue(value instanceof List);
117+
List<?> list = (List<?>) value;
118+
assertEquals(5, list.size());
119+
assertEquals(0, list.get(0));
120+
assertEquals(1, list.get(1));
121+
assertEquals(2, list.get(2));
122+
assertEquals(3, list.get(3));
123+
assertEquals(4, list.get(4));
124+
}
125+
109126
@Test
110127
public void selectionWithArray() throws Exception {
111128
Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]");
@@ -242,6 +259,20 @@ public void projectionWithSet() throws Exception {
242259
assertEquals(7, list.get(2));
243260
}
244261

262+
@Test
263+
public void projectionWithIterable() throws Exception {
264+
Expression expression = new SpelExpressionParser().parseRaw("#testList.![wrapper.value]");
265+
EvaluationContext context = new StandardEvaluationContext();
266+
context.setVariable("testList", IntegerTestBean.createIterable());
267+
Object value = expression.getValue(context);
268+
assertTrue(value instanceof List);
269+
List<?> list = (List<?>) value;
270+
assertEquals(3, list.size());
271+
assertEquals(5, list.get(0));
272+
assertEquals(6, list.get(1));
273+
assertEquals(7, list.get(2));
274+
}
275+
245276
@Test
246277
public void projectionWithArray() throws Exception {
247278
Expression expression = new SpelExpressionParser().parseRaw("#testArray.![wrapper.value]");
@@ -258,23 +289,6 @@ public void projectionWithArray() throws Exception {
258289
assertEquals(new Integer(7), array[2]);
259290
}
260291

261-
static class MapTestBean {
262-
263-
private final Map<String, String> colors = new TreeMap<String, String>();
264-
265-
MapTestBean() {
266-
// colors.put("black", "schwarz");
267-
colors.put("red", "rot");
268-
colors.put("brown", "braun");
269-
colors.put("blue", "blau");
270-
colors.put("yellow", "gelb");
271-
colors.put("beige", "beige");
272-
}
273-
274-
public Map<String, String> getColors() {
275-
return colors;
276-
}
277-
}
278292

279293
static class ListTestBean {
280294

@@ -291,6 +305,7 @@ public List<Integer> getIntegers() {
291305
}
292306
}
293307

308+
294309
static class SetTestBean {
295310

296311
private final Set<Integer> integers = new LinkedHashSet<Integer>();
@@ -306,6 +321,28 @@ public Set<Integer> getIntegers() {
306321
}
307322
}
308323

324+
325+
static class IterableTestBean {
326+
327+
private final Set<Integer> integers = new LinkedHashSet<Integer>();
328+
329+
IterableTestBean() {
330+
for (int i = 0; i < 10; i++) {
331+
integers.add(i);
332+
}
333+
}
334+
335+
public Iterable<Integer> getIntegers() {
336+
return new Iterable<Integer>() {
337+
@Override
338+
public Iterator<Integer> iterator() {
339+
return integers.iterator();
340+
}
341+
};
342+
}
343+
}
344+
345+
309346
static class ArrayTestBean {
310347

311348
private final int[] ints = new int[10];
@@ -328,6 +365,26 @@ public Integer[] getIntegers() {
328365
}
329366
}
330367

368+
369+
static class MapTestBean {
370+
371+
private final Map<String, String> colors = new TreeMap<String, String>();
372+
373+
MapTestBean() {
374+
// colors.put("black", "schwarz");
375+
colors.put("red", "rot");
376+
colors.put("brown", "braun");
377+
colors.put("blue", "blau");
378+
colors.put("yellow", "gelb");
379+
colors.put("beige", "beige");
380+
}
381+
382+
public Map<String, String> getColors() {
383+
return colors;
384+
}
385+
}
386+
387+
331388
static class IntegerTestBean {
332389

333390
private final IntegerWrapper wrapper;
@@ -356,6 +413,16 @@ static Set<IntegerTestBean> createSet() {
356413
return set;
357414
}
358415

416+
static Iterable<IntegerTestBean> createIterable() {
417+
final Set<IntegerTestBean> set = createSet();
418+
return new Iterable<IntegerTestBean>() {
419+
@Override
420+
public Iterator<IntegerTestBean> iterator() {
421+
return set.iterator();
422+
}
423+
};
424+
}
425+
359426
static IntegerTestBean[] createArray() {
360427
IntegerTestBean[] array = new IntegerTestBean[3];
361428
for (int i = 0; i < 3; i++) {
@@ -369,6 +436,7 @@ static IntegerTestBean[] createArray() {
369436
}
370437
}
371438

439+
372440
static class IntegerWrapper {
373441

374442
private final Number value;

0 commit comments

Comments
 (0)