INT-6051 SpEL selection and projection may now be applied to arrays.

This commit is contained in:
Mark Fisher 2009-10-28 03:42:36 +00:00
parent 65f10cceb9
commit 3346752cfd
3 changed files with 327 additions and 8 deletions

View File

@ -16,7 +16,9 @@
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;
@ -27,6 +29,8 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Represents projection, where a given operation is performed on all elements in some input sequence, returning
@ -34,7 +38,8 @@ import org.springframework.expression.spel.SpelMessage;
* "{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}" returns "[n, y, n, y, n, y, n, y, n, y]"
*
* @author Andy Clement
*
* @author Mark Fisher
* @since 3.0
*/
public class Projection extends SpelNodeImpl {
@ -50,6 +55,7 @@ public class Projection extends SpelNodeImpl {
TypedValue op = state.getActiveContextObject();
Object operand = op.getValue();
boolean operandIsArray = ObjectUtils.isArray(operand);
// TypeDescriptor operandTypeDescriptor = op.getTypeDescriptor();
// When the input is a map, we push a special context object on the stack
@ -69,22 +75,36 @@ public class Projection extends SpelNodeImpl {
}
}
return new TypedValue(result,TypeDescriptor.valueOf(List.class)); // TODO unable to build correct type descriptor
} else if (operand instanceof List) {
} else if (operand instanceof List || operandIsArray) {
List<Object> data = new ArrayList<Object>();
data.addAll((Collection<?>) operand);
Collection<?> c = (operand instanceof List) ? (Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand));
data.addAll(c);
List<Object> result = new ArrayList<Object>();
int idx = 0;
Class<?> arrayElementType = null;
for (Object element : data) {
try {
state.pushActiveContextObject(new TypedValue(element,TypeDescriptor.valueOf(op.getTypeDescriptor().getType())));
state.enterScope("index", idx);
result.add(children[0].getValueInternal(state).getValue());
Object value = children[0].getValueInternal(state).getValue();
if (value != null && operandIsArray) {
arrayElementType = this.determineCommonType(arrayElementType, value.getClass());
}
result.add(value);
} finally {
state.exitScope();
state.popActiveContextObject();
}
idx++;
}
if (operandIsArray) {
if (arrayElementType == null) {
arrayElementType = Object.class;
}
Object resultArray = Array.newInstance(arrayElementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray, op.getTypeDescriptor());
}
return new TypedValue(result,op.getTypeDescriptor());
} else {
if (operand==null) {
@ -105,4 +125,27 @@ public class Projection extends SpelNodeImpl {
return sb.append("![").append(getChild(0).toStringAST()).append("]").toString();
}
private Class<?> determineCommonType(Class<?> oldType, Class<?> newType) {
if (oldType == null) {
return newType;
}
if (oldType.isAssignableFrom(newType)) {
return oldType;
}
Class<?> nextType = newType;
while (nextType != Object.class) {
if (nextType.isAssignableFrom(oldType)) {
return nextType;
}
nextType = nextType.getSuperclass();
}
Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(newType);
for (Class<?> nextInterface : interfaces) {
if (nextInterface.isAssignableFrom(oldType)) {
return nextInterface;
}
}
return Object.class;
}
}

View File

@ -16,7 +16,9 @@
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;
@ -28,6 +30,8 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Represents selection over a map or collection. For example: {1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'} returns
@ -37,6 +41,8 @@ import org.springframework.expression.spel.SpelMessage;
* criteria.
*
* @author Andy Clement
* @author Mark Fisher
* @since 3.0
*/
public class Selection extends SpelNodeImpl {
@ -97,9 +103,11 @@ public class Selection extends SpelNodeImpl {
return new TypedValue(resultMap,TypeDescriptor.valueOf(Map.class));
}
return new TypedValue(result,op.getTypeDescriptor());
} else if (operand instanceof Collection) {
} else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
List<Object> data = new ArrayList<Object>();
data.addAll((Collection<?>) operand);
Collection<?> c = (operand instanceof Collection) ?
(Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand));
data.addAll(c);
List<Object> result = new ArrayList<Object>();
int idx = 0;
for (Object element : data) {
@ -130,7 +138,15 @@ public class Selection extends SpelNodeImpl {
if (variant == LAST) {
return new TypedValue(result.get(result.size() - 1),TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType()));
}
return new TypedValue(result,op.getTypeDescriptor());
if (operand instanceof Collection) {
return new TypedValue(result,op.getTypeDescriptor());
}
else {
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementType());
Object resultArray = Array.newInstance(elementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray, op.getTypeDescriptor());
}
} else {
if (operand==null) {
if (nullSafe) {

View File

@ -0,0 +1,260 @@
/*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* @author Mark Fisher
* @since 3.0
*/
public class SelectionAndProjectionTests {
@Test
public void selectionWithList() throws Exception {
Expression expression = new SpelExpressionParser().parse("integers.?[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ListTestBean());
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 selectFirstItemInList() throws Exception {
Expression expression = new SpelExpressionParser().parse("integers.^[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ListTestBean());
Object value = expression.getValue(context);
assertTrue(value instanceof Integer);
assertEquals(0, value);
}
@Test
public void selectLastItemInList() throws Exception {
Expression expression = new SpelExpressionParser().parse("integers.$[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ListTestBean());
Object value = expression.getValue(context);
assertTrue(value instanceof Integer);
assertEquals(4, value);
}
@Test
public void selectionWithArray() throws Exception {
Expression expression = new SpelExpressionParser().parse("integers.?[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean());
Object value = expression.getValue(context);
assertTrue(value.getClass().isArray());
TypedValue typedValue = new TypedValue(value);
assertEquals(Integer.class, typedValue.getTypeDescriptor().getElementType());
Integer[] array = (Integer[]) value;
assertEquals(5, array.length);
assertEquals(new Integer(0), array[0]);
assertEquals(new Integer(1), array[1]);
assertEquals(new Integer(2), array[2]);
assertEquals(new Integer(3), array[3]);
assertEquals(new Integer(4), array[4]);
}
@Test
public void selectFirstItemInArray() throws Exception {
Expression expression = new SpelExpressionParser().parse("integers.^[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean());
Object value = expression.getValue(context);
assertTrue(value instanceof Integer);
assertEquals(0, value);
}
@Test
public void selectLastItemInArray() throws Exception {
Expression expression = new SpelExpressionParser().parse("integers.$[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean());
Object value = expression.getValue(context);
assertTrue(value instanceof Integer);
assertEquals(4, value);
}
@Test
public void selectionWithPrimitiveArray() throws Exception {
Expression expression = new SpelExpressionParser().parse("ints.?[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean());
Object value = expression.getValue(context);
assertTrue(value.getClass().isArray());
TypedValue typedValue = new TypedValue(value);
assertEquals(Integer.class, typedValue.getTypeDescriptor().getElementType());
Integer[] array = (Integer[]) value;
assertEquals(5, array.length);
assertEquals(new Integer(0), array[0]);
assertEquals(new Integer(1), array[1]);
assertEquals(new Integer(2), array[2]);
assertEquals(new Integer(3), array[3]);
assertEquals(new Integer(4), array[4]);
}
@Test
public void selectFirstItemInPrimitiveArray() throws Exception {
Expression expression = new SpelExpressionParser().parse("ints.^[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean());
Object value = expression.getValue(context);
assertTrue(value instanceof Integer);
assertEquals(0, value);
}
@Test
public void selectLastItemInPrimitiveArray() throws Exception {
Expression expression = new SpelExpressionParser().parse("ints.$[#this<5]");
EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean());
Object value = expression.getValue(context);
assertTrue(value instanceof Integer);
assertEquals(4, value);
}
@Test
public void projectionWithList() throws Exception {
Expression expression = new SpelExpressionParser().parse("#testList.![wrapper.value]");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("testList", IntegerTestBean.createList());
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().parse("#testArray.![wrapper.value]");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("testArray", IntegerTestBean.createArray());
Object value = expression.getValue(context);
assertTrue(value.getClass().isArray());
TypedValue typedValue = new TypedValue(value);
assertEquals(Number.class, typedValue.getTypeDescriptor().getElementType());
Number[] array = (Number[]) value;
assertEquals(3, array.length);
assertEquals(new Integer(5), array[0]);
assertEquals(5.9f, array[1]);
assertEquals(new Integer(7), array[2]);
}
static class ListTestBean {
private final List<Integer> integers = new ArrayList<Integer>();
ListTestBean() {
for (int i = 0; i < 10; i++) {
integers.add(i);
}
}
public List<Integer> getIntegers() {
return integers;
}
}
static class ArrayTestBean {
private final int[] ints = new int[10];
private final Integer[] integers = new Integer[10];
ArrayTestBean() {
for (int i = 0; i < 10; i++) {
ints[i] = i;
integers[i] = i;
}
}
public int[] getInts() {
return ints;
}
public Integer[] getIntegers() {
return integers;
}
}
static class IntegerTestBean {
private final IntegerWrapper wrapper;
IntegerTestBean(Number value) {
this.wrapper = new IntegerWrapper(value);
}
public IntegerWrapper getWrapper() {
return this.wrapper;
}
static List<IntegerTestBean> createList() {
List<IntegerTestBean> list = new ArrayList<IntegerTestBean>();
for (int i = 0; i < 3; i++) {
list.add(new IntegerTestBean(i + 5));
}
return list;
}
static IntegerTestBean[] createArray() {
IntegerTestBean[] array = new IntegerTestBean[3];
for (int i = 0; i < 3; i++) {
if (i == 1) {
array[i] = new IntegerTestBean(5.9f);
}
else {
array[i] = new IntegerTestBean(i + 5);
}
}
return array;
}
}
static class IntegerWrapper {
private final Number value;
IntegerWrapper(Number value) {
this.value = value;
}
public Number getValue() {
return this.value;
}
}
}