SPR-7335: support for expression inline lists and array construction

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3473 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Andy Clement 2010-07-06 21:00:54 +00:00
parent 8d0e8fe165
commit 3f09b6a313
15 changed files with 990 additions and 159 deletions

View File

@ -19,6 +19,7 @@ package org.springframework.expression.common;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.util.ClassUtils;
@ -70,4 +71,68 @@ public abstract class ExpressionUtils {
throw new EvaluationException("Cannot convert value '" + value + "' to type '" + targetType.getName() + "'");
}
/**
* Attempt to convert a typed value to an int using the supplied type converter.
*/
public static int toInt(TypeConverter typeConverter, TypedValue typedValue) {
return (Integer) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Integer.class));
}
/**
* Attempt to convert a typed value to a boolean using the supplied type converter.
*/
public static boolean toBoolean(TypeConverter typeConverter, TypedValue typedValue) {
return (Boolean) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Boolean.class));
}
/**
* Attempt to convert a typed value to a double using the supplied type converter.
*/
public static double toDouble(TypeConverter typeConverter, TypedValue typedValue) {
return (Double) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Double.class));
}
/**
* Attempt to convert a typed value to a long using the supplied type converter.
*/
public static long toLong(TypeConverter typeConverter, TypedValue typedValue) {
return (Long) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Long.class));
}
/**
* Attempt to convert a typed value to a char using the supplied type converter.
*/
public static char toChar(TypeConverter typeConverter, TypedValue typedValue) {
return (Character) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Character.class));
}
/**
* Attempt to convert a typed value to a short using the supplied type converter.
*/
public static short toShort(TypeConverter typeConverter, TypedValue typedValue) {
return (Short) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Short.class));
}
/**
* Attempt to convert a typed value to a float using the supplied type converter.
*/
public static float toFloat(TypeConverter typeConverter, TypedValue typedValue) {
return (Float) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Float.class));
}
/**
* Attempt to convert a typed value to a byte using the supplied type converter.
*/
public static byte toByte(TypeConverter typeConverter, TypedValue typedValue) {
return (Byte) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Byte.class));
}
}

View File

@ -95,6 +95,15 @@ public enum SpelMessage {
NO_BEAN_RESOLVER_REGISTERED(Kind.ERROR,1057,"No bean resolver registered in the context to resolve access to bean ''{0}''"),//
EXCEPTION_DURING_BEAN_RESOLUTION(Kind.ERROR, 1058, "A problem occurred when trying to resolve bean ''{0}'':''{1}''"), //
INVALID_BEAN_REFERENCE(Kind.ERROR,1059,"@ can only be followed by an identifier or a quoted name"),//
TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION(Kind.ERROR, 1060,
"Expected the type of the new array to be specified as a String but found ''{0}''"), //
INCORRECT_ELEMENT_TYPE_FOR_ARRAY(Kind.ERROR, 1061,
"The array of type ''{0}'' cannot have an element of type ''{1}'' inserted"), //
MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED(Kind.ERROR, 1062,
"Using an initializer to build a multi-dimensional array is not currently supported"), //
MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), //
INITIALIZER_LENGTH_INCORRECT(
Kind.ERROR, 1064, "array initializer size does not match array dimensions"), //
;
private Kind kind;

View File

@ -16,20 +16,23 @@
package org.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
// TODO asc array constructor call logic has been removed for now
// TODO make this like the method referencing one
/**
* Represents the invocation of a constructor. Either a constructor on a regular type or construction of an array. When
* an array is constructed, an initializer can be specified.
@ -42,7 +45,7 @@ import org.springframework.expression.spel.SpelMessage;
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
*/
public class ConstructorReference extends SpelNodeImpl {
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
@ -51,13 +54,26 @@ public class ConstructorReference extends SpelNodeImpl {
*/
private volatile ConstructorExecutor cachedExecutor;
private boolean isArrayConstructor = false;
private SpelNodeImpl[] dimensions;
/**
* Create a constructor reference. The first argument is the type, the rest are the parameters to the
* constructor call
* Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
* call
*/
public ConstructorReference(int pos, SpelNodeImpl... arguments) {
super(pos,arguments);
super(pos, arguments);
this.isArrayConstructor = false;
}
/**
* Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
* call
*/
public ConstructorReference(int pos, SpelNodeImpl[] dimensions, SpelNodeImpl... arguments) {
super(pos, arguments);
this.isArrayConstructor = true;
this.dimensions = dimensions;
}
/**
@ -65,11 +81,16 @@ public class ConstructorReference extends SpelNodeImpl {
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return createNewInstance(state);
if (isArrayConstructor) {
return createArray(state);
} else {
return createNewInstance(state);
}
}
/**
* Create a new ordinary object and return it.
*
* @param state the expression state within which this expression is being evaluated
* @return the new object
* @throws EvaluationException if there is a problem creating the object
@ -81,40 +102,40 @@ public class ConstructorReference extends SpelNodeImpl {
TypedValue childValue = children[i + 1].getValueInternal(state);
Object value = childValue.getValue();
arguments[i] = value;
argumentTypes[i] = (value==null?null:value.getClass());
argumentTypes[i] = (value == null ? null : value.getClass());
}
ConstructorExecutor executorToUse = this.cachedExecutor;
if (executorToUse != null) {
try {
return executorToUse.execute(state.getEvaluationContext(), arguments);
}
catch (AccessException ae) {
} catch (AccessException ae) {
// Two reasons this can occur:
// 1. the method invoked actually threw a real exception
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
// In the first case we should not retry, in the second case we should see if there is a
// In the first case we should not retry, in the second case we should see if there is a
// better suited method.
// To determine which situation it is, the AccessException will contain a cause - this
// will be the exception thrown by the reflective invocation. Inside this exception there
// may or may not be a root cause. If there is a root cause it is a user created exception.
// will be the exception thrown by the reflective invocation. Inside this exception there
// may or may not be a root cause. If there is a root cause it is a user created exception.
// If there is no root cause it was a reflective invocation problem.
Throwable causeOfAccessException = ae.getCause();
Throwable rootCause = (causeOfAccessException==null?null:causeOfAccessException.getCause());
if (rootCause!=null) {
Throwable rootCause = (causeOfAccessException == null ? null : causeOfAccessException.getCause());
if (rootCause != null) {
// User exception was the root cause - exit now
if (rootCause instanceof RuntimeException) {
throw (RuntimeException)rootCause;
throw (RuntimeException) rootCause;
} else {
String typename = (String) children[0].getValueInternal(state).getValue();
throw new SpelEvaluationException(getStartPosition(), rootCause, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM,
typename,FormatHelper.formatMethodForMessage("", argumentTypes));
throw new SpelEvaluationException(getStartPosition(), rootCause,
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, FormatHelper
.formatMethodForMessage("", argumentTypes));
}
}
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
this.cachedExecutor = null;
}
@ -128,8 +149,8 @@ public class ConstructorReference extends SpelNodeImpl {
TypedValue result = executorToUse.execute(state.getEvaluationContext(), arguments);
return result;
} catch (AccessException ae) {
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename,
FormatHelper.formatMethodForMessage("", argumentTypes));
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM,
typename, FormatHelper.formatMethodForMessage("", argumentTypes));
}
}
@ -137,14 +158,15 @@ public class ConstructorReference extends SpelNodeImpl {
/**
* Go through the list of registered constructor resolvers and see if any can find a constructor that takes the
* specified set of arguments.
*
* @param typename the type trying to be constructed
* @param argumentTypes the types of the arguments supplied that the constructor must take
* @param state the current state of the expression
* @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
* @throws SpelEvaluationException if there is a problem locating the constructor
*/
private ConstructorExecutor findExecutorForConstructor(
String typename, Class<?>[] argumentTypes, ExpressionState state) throws SpelEvaluationException {
private ConstructorExecutor findExecutorForConstructor(String typename, Class<?>[] argumentTypes,
ExpressionState state) throws SpelEvaluationException {
EvaluationContext eContext = state.getEvaluationContext();
List<ConstructorResolver> cResolvers = eContext.getConstructorResolvers();
@ -157,13 +179,14 @@ public class ConstructorReference extends SpelNodeImpl {
return cEx;
}
} catch (AccessException ex) {
throw new SpelEvaluationException(getStartPosition(),ex, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename,
FormatHelper.formatMethodForMessage("", argumentTypes));
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, FormatHelper.formatMethodForMessage(
"", argumentTypes));
}
}
}
throw new SpelEvaluationException(getStartPosition(),SpelMessage.CONSTRUCTOR_NOT_FOUND, typename, FormatHelper.formatMethodForMessage("",
argumentTypes));
throw new SpelEvaluationException(getStartPosition(), SpelMessage.CONSTRUCTOR_NOT_FOUND, typename, FormatHelper
.formatMethodForMessage("", argumentTypes));
}
@Override
@ -173,15 +196,201 @@ public class ConstructorReference extends SpelNodeImpl {
int index = 0;
sb.append(getChild(index++).toStringAST());
sb.append("(");
for (int i = index; i < getChildCount(); i++) {
if (i > index)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
sb.append("(");
for (int i = index; i < getChildCount(); i++) {
if (i > index)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
return sb.toString();
}
/**
* Create an array and return it.
*
* @param state the expression state within which this expression is being evaluated
* @return the new array
* @throws EvaluationException if there is a problem creating the array
*/
private TypedValue createArray(ExpressionState state) throws EvaluationException {
// First child gives us the array type which will either be a primitive or reference type
Object intendedArrayType = getChild(0).getValue(state);
if (!(intendedArrayType instanceof String)) {
throw new SpelEvaluationException(getChild(0).getStartPosition(),
SpelMessage.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION, FormatHelper
.formatClassNameForMessage(intendedArrayType.getClass()));
}
String type = (String) intendedArrayType;
Class<?> componentType = null;
TypeCode arrayTypeCode = TypeCode.forName(type);
if (arrayTypeCode == TypeCode.OBJECT) {
componentType = state.findType(type);
} else {
componentType = arrayTypeCode.getType();
}
TypeDescriptor td = TypeDescriptor.valueOf(componentType);
Object newArray = null;
if (!hasInitializer()) {
// Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension)
for (int i = 0; i < dimensions.length; i++) {
if (dimensions[i] == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
}
}
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
// Shortcut for 1 dimensional
if (dimensions.length == 1) {
TypedValue o = dimensions[0].getTypedValue(state);
int arraySize = ExpressionUtils.toInt(typeConverter, o);
newArray = Array.newInstance(componentType, arraySize);
} else {
// Multi-dimensional - hold onto your hat!
int[] dims = new int[dimensions.length];
for (int d = 0; d < dimensions.length; d++) {
TypedValue o = dimensions[d].getTypedValue(state);
dims[d] = ExpressionUtils.toInt(typeConverter, o);
}
newArray = Array.newInstance(componentType, dims);
}
} else {
// There is an initializer
if (dimensions.length > 1) {
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
// is not currently supported
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
}
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
InlineList initializer = (InlineList) getChild(1);
// If a dimension was specified, check it matches the initializer length
if (dimensions[0] != null) {
TypedValue dValue = dimensions[0].getTypedValue(state);
int i = ExpressionUtils.toInt(typeConverter, dValue);
if (i != initializer.getChildCount()) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INITIALIZER_LENGTH_INCORRECT);
}
}
// Build the array and populate it
int arraySize = initializer.getChildCount();
newArray = Array.newInstance(componentType, arraySize);
if (arrayTypeCode == TypeCode.OBJECT) {
populateReferenceTypeArray(state, newArray, typeConverter, initializer, componentType);
} else if (arrayTypeCode == TypeCode.INT) {
populateIntArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.BOOLEAN) {
populateBooleanArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.CHAR) {
populateCharArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.LONG) {
populateLongArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.SHORT) {
populateShortArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.DOUBLE) {
populateDoubleArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.FLOAT) {
populateFloatArray(state, newArray, typeConverter, initializer);
} else if (arrayTypeCode == TypeCode.BYTE) {
populateByteArray(state, newArray, typeConverter, initializer);
} else {
throw new IllegalStateException(arrayTypeCode.name());
}
}
return new TypedValue(newArray, td);
}
private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer, Class<?> componentType) {
TypeDescriptor toTypeDescriptor = TypeDescriptor.valueOf(componentType);
Object[] newObjectArray = (Object[]) newArray;
for (int i = 0; i < newObjectArray.length; i++) {
SpelNode elementNode = initializer.getChild(i);
Object arrayEntry = elementNode.getValue(state);
newObjectArray[i] = typeConverter.convertValue(arrayEntry, TypeDescriptor.forObject(arrayEntry),
toTypeDescriptor);
}
}
private void populateByteArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
byte[] newByteArray = (byte[]) newArray;
for (int i = 0; i < newByteArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newByteArray[i] = ExpressionUtils.toByte(typeConverter, typedValue);
}
}
private void populateFloatArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
float[] newFloatArray = (float[]) newArray;
for (int i = 0; i < newFloatArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newFloatArray[i] = ExpressionUtils.toFloat(typeConverter, typedValue);
}
}
private void populateDoubleArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
double[] newDoubleArray = (double[]) newArray;
for (int i = 0; i < newDoubleArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newDoubleArray[i] = ExpressionUtils.toDouble(typeConverter, typedValue);
}
}
private void populateShortArray(ExpressionState state, Object newArray,
TypeConverter typeConverter, InlineList initializer) {
short[] newShortArray = (short[]) newArray;
for (int i = 0; i < newShortArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newShortArray[i] = ExpressionUtils.toShort(typeConverter, typedValue);
}
}
private void populateLongArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
long[] newLongArray = (long[]) newArray;
for (int i = 0; i < newLongArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newLongArray[i] = ExpressionUtils.toLong(typeConverter, typedValue);
}
}
private void populateCharArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
char[] newCharArray = (char[]) newArray;
for (int i = 0; i < newCharArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newCharArray[i] = ExpressionUtils.toChar(typeConverter, typedValue);
}
}
private void populateBooleanArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
boolean[] newBooleanArray = (boolean[]) newArray;
for (int i = 0; i < newBooleanArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newBooleanArray[i] = ExpressionUtils.toBoolean(typeConverter, typedValue);
}
}
private void populateIntArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
int[] newIntArray = (int[]) newArray;
for (int i = 0; i < newIntArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newIntArray[i] = ExpressionUtils.toInt(typeConverter, typedValue);
}
}
private boolean hasInitializer() {
return getChildCount() > 1;
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2002-2010 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.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
/**
* Represent a list in an expression, e.g. '{1,2,3}'
*
* @author Andy Clement
* @since 3.0.4
*/
public class InlineList extends SpelNodeImpl {
// if the list is purely literals, it is a constant value and can be computed and cached
TypedValue constant = null; // TODO must be immutable list
public InlineList(int pos, SpelNodeImpl... args) {
super(pos, args);
checkIfConstant();
}
/**
* If all the components of the list are constants, or lists that themselves contain constants, then a constant list
* can be built to represent this node. This will speed up later getValue calls and reduce the amount of garbage
* created.
*/
private void checkIfConstant() {
boolean isConstant = true;
for (int c = 0, max = getChildCount(); c < max; c++) {
SpelNode child = getChild(c);
if (!(child instanceof Literal)) {
if (child instanceof InlineList) {
InlineList inlineList = (InlineList) child;
if (!inlineList.isConstant()) {
isConstant = false;
}
} else {
isConstant = false;
}
}
}
if (isConstant) {
List<Object> constantList = new ArrayList<Object>();
int childcount = getChildCount();
for (int c = 0; c < childcount; c++) {
SpelNode child = getChild(c);
if ((child instanceof Literal)) {
constantList.add(((Literal) child).getLiteralValue().getValue());
} else if (child instanceof InlineList) {
constantList.add(((InlineList) child).getConstantValue());
}
}
this.constant = new TypedValue(Collections.unmodifiableList(constantList), TypeDescriptor
.valueOf(List.class));
}
}
@Override
public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
if (constant != null) {
return constant;
} else {
List<Object> returnValue = new ArrayList<Object>();
int childcount = getChildCount();
for (int c = 0; c < childcount; c++) {
returnValue.add(getChild(c).getValue(expressionState));
}
return new TypedValue(returnValue, TypeDescriptor.valueOf(List.class));
}
}
@Override
public String toStringAST() {
StringBuilder s = new StringBuilder();
// string ast matches input string, not the 'toString()' of the resultant collection, which would use []
s.append('{');
int count = getChildCount();
for (int c = 0; c < count; c++) {
if (c > 0) {
s.append(',');
}
s.append(getChild(c).toStringAST());
}
s.append('}');
return s.toString();
}
/**
* @return whether this list is a constant value
*/
public boolean isConstant() {
return constant != null;
}
@SuppressWarnings("unchecked")
private List<Object> getConstantValue() {
return (List<Object>) constant.getValue();
}
}

View File

@ -13,9 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.ast;
/**
* Captures primitive types and their corresponding class objects, plus one special entry that represents all reference
* (non-primitive) types.
*
* @author Andy Clement
*/
public enum TypeCode {
OBJECT(Object.class), BOOLEAN(Boolean.TYPE), BYTE(Byte.TYPE), CHAR(Character.TYPE), //
@ -31,15 +36,26 @@ public enum TypeCode {
return type;
}
// public static TypeCode forClass(Class<?> c) {
// TypeCode[] allValues = TypeCode.values();
// for (int i = 0; i < allValues.length; i++) {
// TypeCode typeCode = allValues[i];
// if (c == typeCode.getType()) {
// return typeCode;
// }
// }
// return OBJECT;
// }
public static TypeCode forName(String name) {
String searchingFor = name.toUpperCase();
TypeCode[] tcs = values();
for (int i = 1; i < tcs.length; i++) {
if (tcs[i].name().equals(searchingFor)) {
return tcs[i];
}
}
return TypeCode.OBJECT;
}
public static TypeCode forClass(Class<?> c) {
TypeCode[] allValues = TypeCode.values();
for (int i = 0; i < allValues.length; i++) {
TypeCode typeCode = allValues[i];
if (c == typeCode.getType()) {
return typeCode;
}
}
return OBJECT;
}
}

View File

@ -36,6 +36,7 @@ import org.springframework.expression.spel.ast.Elvis;
import org.springframework.expression.spel.ast.FunctionReference;
import org.springframework.expression.spel.ast.Identifier;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.Literal;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
@ -454,6 +455,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return pop();
} else if (maybeEatProjection(false) || maybeEatSelection(false) || maybeEatIndexer()) {
return pop();
} else if (maybeEatInlineList()) {
return pop();
} else {
return null;
}
@ -526,6 +529,29 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return true;
}
// list = LCURLY (element (COMMA element)*) RCURLY
private boolean maybeEatInlineList() {
Token t = peekToken();
if (!peekToken(TokenKind.LCURLY,true)) {
return false;
}
SpelNodeImpl expr = null;
Token closingCurly = peekToken();
if (peekToken(TokenKind.RCURLY,true)) {
// empty list '[]'
expr = new InlineList(toPos(t.startpos,closingCurly.endpos));
} else {
List<SpelNodeImpl> listElements = new ArrayList<SpelNodeImpl>();
do {
listElements.add(eatExpression());
} while (peekToken(TokenKind.COMMA,true));
closingCurly = eatToken(TokenKind.RCURLY);
expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()]));
}
constructedNodes.push(expr);
return true;
}
private boolean maybeEatIndexer() {
Token t = peekToken();
if (!peekToken(TokenKind.LSQUARE,true)) {
@ -599,14 +625,33 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId();
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
nodes.add(possiblyQualifiedConstructorName);
eatConstructorArgs(nodes);
push(new ConstructorReference(toPos(newToken),nodes.toArray(new SpelNodeImpl[nodes.size()]))); // TODO correct end position?
if (peekToken(TokenKind.LSQUARE)) {
// array initializer
List<SpelNodeImpl> dimensions = new ArrayList<SpelNodeImpl>();
while (peekToken(TokenKind.LSQUARE,true)) {
if (!peekToken(TokenKind.RSQUARE)) {
dimensions.add(eatExpression());
} else {
dimensions.add(null);
}
eatToken(TokenKind.RSQUARE);
}
if (maybeEatInlineList()) {
nodes.add(pop());
}
push(new ConstructorReference(toPos(newToken), dimensions.toArray(new SpelNodeImpl[dimensions.size()]),
nodes.toArray(new SpelNodeImpl[nodes.size()])));
} else {
// regular constructor invocation
eatConstructorArgs(nodes);
// TODO correct end position?
push(new ConstructorReference(toPos(newToken), nodes.toArray(new SpelNodeImpl[nodes.size()])));
}
return true;
}
return false;
}
private void push(SpelNodeImpl newNode) {
constructedNodes.push(newNode);
}
@ -691,7 +736,6 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
private Token eatToken(TokenKind expectedKind) {
Assert.isTrue(moreTokens());
Token t = nextToken();
if (t==null) {
raiseInternalException( expressionString.length(), SpelMessage.OOD);

View File

@ -24,6 +24,7 @@ enum TokenKind {
LITERAL_INT, LITERAL_LONG, LITERAL_HEXINT, LITERAL_HEXLONG, LITERAL_STRING, LITERAL_REAL, LITERAL_REAL_FLOAT,
LPAREN("("), RPAREN(")"), COMMA(","), IDENTIFIER,
COLON(":"),HASH("#"),RSQUARE("]"), LSQUARE("["),
LCURLY("{"),RCURLY("}"),
DOT("."), PLUS("+"), STAR("*"), DIV("/"), NOT("!"), MINUS("-"), SELECT_FIRST("^["), SELECT_LAST("$["), QMARK("?"), PROJECT("!["),
GE(">="),GT(">"),LE("<="),LT("<"),EQ("=="),NE("!="),ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"),
SELECT("?["), MOD("%"), POWER("^"),

View File

@ -96,6 +96,12 @@ class Tokenizer {
case ']':
pushCharToken(TokenKind.RSQUARE);
break;
case '{':
pushCharToken(TokenKind.LCURLY);
break;
case '}':
pushCharToken(TokenKind.RCURLY);
break;
case '@':
pushCharToken(TokenKind.BEAN_REF);
break;

View File

@ -61,8 +61,12 @@ public class StandardTypeComparator implements TypeComparator {
}
}
if (left instanceof Comparable) {
return ((Comparable) left).compareTo(right);
try {
if (left instanceof Comparable) {
return ((Comparable) left).compareTo(right);
}
} catch (ClassCastException cce) {
throw new SpelEvaluationException(cce, SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass());
}
throw new SpelEvaluationException(SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass());

View File

@ -0,0 +1,195 @@
/*
* Copyright 2002-2010 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 org.junit.Assert;
import org.junit.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Test construction of arrays.
*
* @author Andy Clement
*/
public class ArrayConstructorTests extends ExpressionTestCase {
@Test
public void testSimpleArrayWithInitializer() {
evaluateArrayBuildingExpression("new int[]{1,2,3}", "[1,2,3]");
evaluateArrayBuildingExpression("new int[]{}", "[]");
evaluate("new int[]{}.length", "0", Integer.class);
}
@Test
public void testConversion() {
evaluate("new String[]{1,2,3}[0]", "1", String.class);
evaluate("new int[]{'123'}[0]", 123, Integer.class);
}
@Test
public void testMultidimensionalArrays() {
evaluateAndCheckError("new int[][]{{1,2},{3,4}}", SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
evaluateAndCheckError("new int[3][]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new int[]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new String[]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new int[][1]", SpelMessage.MISSING_ARRAY_DIMENSION);
}
@Test
public void testPrimitiveTypeArrayConstructors() {
evaluateArrayBuildingExpression("new int[]{1,2,3,4}", "[1,2,3,4]");
evaluateArrayBuildingExpression("new boolean[]{true,false,true}", "[true,false,true]");
evaluateArrayBuildingExpression("new char[]{'a','b','c'}", "[a,b,c]");
evaluateArrayBuildingExpression("new long[]{1,2,3,4,5}", "[1,2,3,4,5]");
evaluateArrayBuildingExpression("new short[]{2,3,4,5,6}", "[2,3,4,5,6]");
evaluateArrayBuildingExpression("new double[]{1d,2d,3d,4d}", "[1.0,2.0,3.0,4.0]");
evaluateArrayBuildingExpression("new float[]{1f,2f,3f,4f}", "[1.0,2.0,3.0,4.0]");
evaluateArrayBuildingExpression("new byte[]{1,2,3,4}", "[1,2,3,4]");
}
@Test
public void testPrimitiveTypeArrayConstructorsElements() {
evaluate("new int[]{1,2,3,4}[0]", 1, Integer.class);
evaluate("new boolean[]{true,false,true}[0]", true, Boolean.class);
evaluate("new char[]{'a','b','c'}[0]", 'a', Character.class);
evaluate("new long[]{1,2,3,4,5}[0]", 1L, Long.class);
evaluate("new short[]{2,3,4,5,6}[0]", (short) 2, Short.class);
evaluate("new double[]{1d,2d,3d,4d}[0]", (double) 1, Double.class);
evaluate("new float[]{1f,2f,3f,4f}[0]", (float) 1, Float.class);
evaluate("new byte[]{1,2,3,4}[0]", (byte) 1, Byte.class);
evaluate("new String(new char[]{'h','e','l','l','o'})", "hello", String.class);
}
@Test
public void testErrorCases() {
evaluateAndCheckError("new char[7]{'a','c','d','e'}", SpelMessage.INITIALIZER_LENGTH_INCORRECT);
evaluateAndCheckError("new char[3]{'a','c','d','e'}", SpelMessage.INITIALIZER_LENGTH_INCORRECT);
evaluateAndCheckError("new char[2]{'hello','world'}", SpelMessage.TYPE_CONVERSION_ERROR);
evaluateAndCheckError("new String('a','c','d')", SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
}
@Test
public void testTypeArrayConstructors() {
evaluate("new String[]{'a','b','c','d'}[1]", "b", String.class);
evaluateAndCheckError("new String[]{'a','b','c','d'}.size()", SpelMessage.METHOD_NOT_FOUND, 30, "size()",
"java.lang.String[]");
evaluate("new String[]{'a','b','c','d'}.length", 4, Integer.class);
}
@Test
public void testBasicArray() {
evaluate("new String[3]", "java.lang.String[3]{null,null,null}", String[].class);
}
@Test
public void testMultiDimensionalArray() {
evaluate("new String[2][2]", "[Ljava.lang.String;[2]{[2]{null,null},[2]{null,null}}", String[][].class);
evaluate("new String[3][2][1]",
"[[Ljava.lang.String;[3]{[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}}}",
String[][][].class);
}
@Test
public void testConstructorInvocation03() {
evaluateAndCheckError("new String[]", SpelMessage.MISSING_ARRAY_DIMENSION);
}
public void testConstructorInvocation04() {
evaluateAndCheckError("new Integer[3]{'3','ghi','5'}", SpelMessage.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, 4);
}
private String evaluateArrayBuildingExpression(String expression, String expectedToString) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression e = parser.parseExpression(expression);
Object o = e.getValue();
Assert.assertNotNull(o);
Assert.assertTrue(o.getClass().isArray());
StringBuilder s = new StringBuilder();
s.append('[');
if (o instanceof int[]) {
int[] array = (int[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof boolean[]) {
boolean[] array = (boolean[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof char[]) {
char[] array = (char[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof long[]) {
long[] array = (long[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof short[]) {
short[] array = (short[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof double[]) {
double[] array = (double[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof float[]) {
float[] array = (float[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else if (o instanceof byte[]) {
byte[] array = (byte[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
} else {
Assert.fail("Not supported " + o.getClass());
}
s.append(']');
Assert.assertEquals(expectedToString, s.toString());
return s.toString();
}
}

View File

@ -338,14 +338,17 @@ public abstract class ExpressionTestCase {
}
}
}
public static String stringValueOf(Object value) {
return stringValueOf(value, false);
}
/**
* Produce a nice string representation of the input object.
*
* @param value object to be formatted
* @return a nice string
*/
public static String stringValueOf(Object value) {
public static String stringValueOf(Object value, boolean isNested) {
// do something nice for arrays
if (value == null) {
return "null";
@ -378,9 +381,27 @@ public abstract class ExpressionTestCase {
throw new RuntimeException("Please implement support for type " + primitiveType.getName()
+ " in ExpressionTestCase.stringValueOf()");
}
} else if (value.getClass().getComponentType().isArray()) {
List<Object> l = Arrays.asList((Object[]) value);
if (!isNested) {
sb.append(value.getClass().getComponentType().getName());
}
sb.append("[").append(l.size()).append("]{");
int i = 0;
for (Object object : l) {
if (i > 0) {
sb.append(",");
}
i++;
sb.append(stringValueOf(object, true));
}
sb.append("}");
} else {
List<Object> l = Arrays.asList((Object[]) value);
sb.append(value.getClass().getComponentType().getName()).append("[").append(l.size()).append("]{");
if (!isNested) {
sb.append(value.getClass().getComponentType().getName());
}
sb.append("[").append(l.size()).append("]{");
int i = 0;
for (Object object : l) {
if (i > 0) {

View File

@ -16,15 +16,17 @@
package org.springframework.expression.spel;
import java.util.ArrayList;
import java.util.HashMap;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* These are tests for language features that are not yet considered 'live'. Either missing implementation or documentation.
* These are tests for language features that are not yet considered 'live'. Either missing implementation or
* documentation.
*
* Where implementation is missing the tests are commented out.
*
@ -37,71 +39,73 @@ public class InProgressTests extends ExpressionTestCase {
evaluate("1 between listOneFive", "true", Boolean.class);
// evaluate("1 between {1, 5}", "true", Boolean.class); // no inline list building at the moment
}
@Test
public void testRelOperatorsBetweenErrors01() {
evaluateAndCheckError("1 between T(String)", SpelMessage.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST, 10);
evaluateAndCheckError("1 between T(String)", SpelMessage.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST, 10);
}
@Test
public void testRelOperatorsBetweenErrors03() {
evaluateAndCheckError("1 between listOfNumbersUpToTen", SpelMessage.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST, 10);
evaluateAndCheckError("1 between listOfNumbersUpToTen",
SpelMessage.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST, 10);
}
// PROJECTION
// PROJECTION
@Test
public void testProjection01() {
evaluate("listOfNumbersUpToTen.![#this<5?'y':'n']","[y, y, y, y, n, n, n, n, n, n]",ArrayList.class);
evaluate("listOfNumbersUpToTen.![#this<5?'y':'n']", "[y, y, y, y, n, n, n, n, n, n]", ArrayList.class);
// inline list creation not supported at the moment
// evaluate("{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}", "[n, y, n, y, n, y, n, y, n, y]", ArrayList.class);
// evaluate("{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}", "[n, y, n, y, n, y, n, y, n, y]", ArrayList.class);
}
@Test
public void testProjection02() {
// inline map creation not supported at the moment
// evaluate("#{'a':'y','b':'n','c':'y'}.![value=='y'?key:null].nonnull().sort()", "[a, c]", ArrayList.class);
evaluate("mapOfNumbersUpToTen.![key>5?value:null]", "[null, null, null, null, null, six, seven, eight, nine, ten]", ArrayList.class);
evaluate("mapOfNumbersUpToTen.![key>5?value:null]",
"[null, null, null, null, null, six, seven, eight, nine, ten]", ArrayList.class);
}
@Test
public void testProjection05() {
evaluateAndCheckError("'abc'.![true]", SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE);
evaluateAndCheckError("null.![true]", SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE);
evaluate("null?.![true]", null, null);
}
@Test
public void testProjection06() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'.![true]");
Assert.assertEquals("'abc'.![true]",expr.toStringAST());
SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.![true]");
Assert.assertEquals("'abc'.![true]", expr.toStringAST());
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
}
// SELECTION
@Test
public void testSelection02() {
evaluate("testMap.keySet().?[#this matches '.*o.*']", "[monday]", ArrayList.class);
evaluate("testMap.keySet().?[#this matches '.*r.*'].contains('saturday')", "true", Boolean.class);
evaluate("testMap.keySet().?[#this matches '.*r.*'].size()", "3", Integer.class);
evaluate("testMap.keySet().?[#this matches '.*o.*']", "[monday]", ArrayList.class);
evaluate("testMap.keySet().?[#this matches '.*r.*'].contains('saturday')", "true", Boolean.class);
evaluate("testMap.keySet().?[#this matches '.*r.*'].size()", "3", Integer.class);
}
@Test
public void testSelectionError_NonBooleanSelectionCriteria() {
evaluateAndCheckError("listOfNumbersUpToTen.?['nonboolean']",
SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
}
public void testSelectionError_NonBooleanSelectionCriteria() {
evaluateAndCheckError("listOfNumbersUpToTen.?['nonboolean']",
SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
}
@Test
public void testSelection03() {
evaluate("mapOfNumbersUpToTen.?[key>5].size()", "5", Integer.class);
// evaluate("listOfNumbersUpToTen.?{#this>5}", "5", ArrayList.class);
// evaluate("listOfNumbersUpToTen.?{#this>5}", "5", ArrayList.class);
}
@Test
public void testSelection04() {
evaluateAndCheckError("mapOfNumbersUpToTen.?['hello'].size()",SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
evaluateAndCheckError("mapOfNumbersUpToTen.?['hello'].size()",
SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
}
@Test
@ -131,23 +135,25 @@ public class InProgressTests extends ExpressionTestCase {
@Test
public void testSelectionLast02() {
evaluate("mapOfNumbersUpToTen.$[key>5]", "{10=ten}", HashMap.class);
evaluate("mapOfNumbersUpToTen.$[key>5].size()", "1", Integer.class);
}
@Test
public void testSelectionAST() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'.^[true]");
Assert.assertEquals("'abc'.^[true]",expr.toStringAST());
SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.^[true]");
Assert.assertEquals("'abc'.^[true]", expr.toStringAST());
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
expr = (SpelExpression)parser.parseExpression("'abc'.?[true]");
Assert.assertEquals("'abc'.?[true]",expr.toStringAST());
expr = (SpelExpression) parser.parseExpression("'abc'.?[true]");
Assert.assertEquals("'abc'.?[true]", expr.toStringAST());
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
expr = (SpelExpression)parser.parseExpression("'abc'.$[true]");
Assert.assertEquals("'abc'.$[true]",expr.toStringAST());
expr = (SpelExpression) parser.parseExpression("'abc'.$[true]");
Assert.assertEquals("'abc'.$[true]", expr.toStringAST());
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
}
// Constructor invocation
// public void testPrimitiveTypeArrayConstructors() {
// evaluate("new int[]{1,2,3,4}.count()", 4, Integer.class);
// evaluate("new boolean[]{true,false,true}.count()", 3, Integer.class);
@ -254,52 +260,41 @@ public class InProgressTests extends ExpressionTestCase {
// evaluate("(#answer=42;#answer)", "42", Integer.class, true);
// evaluate("($answer=42;$answer)", "42", Integer.class, true);
// }
//
// // inline map creation
// @Test
// public void testInlineMapCreation01() {
// evaluate("#{'key1':'Value 1', 'today':'Monday'}", "{key1=Value 1, today=Monday}", HashMap.class);
// }
//
// public void testRelOperatorsIs02() {
// evaluate("{1, 2, 3, 4, 5} instanceof T(List)", "true", Boolean.class);
// }
// @Test
// public void testInlineMapCreation02() {
// // "{2=February, 1=January, 3=March}", HashMap.class);
// evaluate("#{1:'January', 2:'February', 3:'March'}.size()", 3, Integer.class);
// }
//
// public void testRelOperatorsIs03() {
// evaluate("{1, 2, 3, 4, 5} instanceof T(List)", "true", Boolean.class);
// }
// @Test
// public void testInlineMapCreation03() {
// evaluate("#{'key1':'Value 1', 'today':'Monday'}['key1']", "Value 1", String.class);
// }
//
// @Test
// public void testInlineMapCreation04() {
// evaluate("#{1:'January', 2:'February', 3:'March'}[3]", "March", String.class);
// }
//
// @Test
// public void testInlineMapCreation05() {
// evaluate("#{1:'January', 2:'February', 3:'March'}.get(2)", "February", String.class);
// }
// set construction
@Test
public void testSetConstruction01() {
evaluate("new java.util.HashSet().addAll({'a','b','c'})", "true", Boolean.class);
}
//
// inline list creation
// public void testInlineListCreation01() {
// evaluate("{1, 2, 3, 4, 5}", "[1, 2, 3, 4, 5]", ArrayList.class);
// }
//
// public void testInlineListCreation02() {
// evaluate("{'abc', 'xyz'}", "[abc, xyz]", ArrayList.class);
// }
//
// // inline map creation
// public void testInlineMapCreation01() {
// evaluate("#{'key1':'Value 1', 'today':'Monday'}", "{key1=Value 1, today=Monday}", HashMap.class);
// }
//
// public void testInlineMapCreation02() {
// evaluate("#{1:'January', 2:'February', 3:'March'}.size()", 3, Integer.class);// "{2=February, 1=January,
// // 3=March}", HashMap.class);
// }
//
// public void testInlineMapCreation03() {
// evaluate("#{'key1':'Value 1', 'today':'Monday'}['key1']", "Value 1", String.class);
// }
//
// public void testInlineMapCreation04() {
// evaluate("#{1:'January', 2:'February', 3:'March'}[3]", "March", String.class);
// }
//
// public void testInlineMapCreation05() {
// evaluate("#{1:'January', 2:'February', 3:'March'}.get(2)", "February", String.class);
// }
//
// // set construction
// public void testSetConstruction01() {
// evaluate("new HashSet().addAll({'a','b','c'})", "true", Boolean.class);
// }
//
// public void testConstructorInvocation02() {
// evaluate("new String[3]", "java.lang.String[3]{null,null,null}", String[].class);
// }
@ -311,7 +306,8 @@ public class InProgressTests extends ExpressionTestCase {
// public void testConstructorInvocation04() {
// evaluateAndCheckError("new String[3]{'abc',3,'def'}", SpelMessages.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, 4);
// }
// // array construction
// array construction
// @Test
// public void testArrayConstruction01() {
// evaluate("new int[] {1, 2, 3, 4, 5}", "int[5]{1,2,3,4,5}", int[].class);
// }
@ -418,13 +414,13 @@ public class InProgressTests extends ExpressionTestCase {
// public void testSelectionUsingIndex() {
// evaluate("{1,2,3,4,5,6,7,8,9,10}.?{$index > 5 }", "[7, 8, 9, 10]", ArrayList.class);
// }
//public void testSelection01() {
// inline list creation not supported:
// evaluate("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}", "[2, 4, 6, 8, 10]", ArrayList.class);
//}
// public void testSelection01() {
// inline list creation not supported:
// evaluate("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}", "[2, 4, 6, 8, 10]", ArrayList.class);
// }
//
// public void testSelectionUsingIndex() {
// evaluate("listOfNumbersUpToTen.?[#index > 5 ]", "[7, 8, 9, 10]", ArrayList.class);
//}
// public void testSelectionUsingIndex() {
// evaluate("listOfNumbersUpToTen.?[#index > 5 ]", "[7, 8, 9, 10]", ArrayList.class);
// }
}

View File

@ -0,0 +1,149 @@
/*
* Copyright 2002-2010 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 java.util.ArrayList;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Test usage of inline lists.
*
* @author Andy Clement
* @since 3.0.4
*/
public class ListTests extends ExpressionTestCase {
// if the list is full of literals then it will be of the type unmodifiableClass rather than ArrayList
Class<?> unmodifiableClass = Collections.unmodifiableList(new ArrayList<Object>()).getClass();
@Test
public void testInlineListCreation01() {
evaluate("{1, 2, 3, 4, 5}", "[1, 2, 3, 4, 5]", unmodifiableClass);
}
@Test
public void testInlineListCreation02() {
evaluate("{'abc', 'xyz'}", "[abc, xyz]", unmodifiableClass);
}
@Test
public void testInlineListCreation03() {
evaluate("{}", "[]", unmodifiableClass);
}
@Test
public void testInlineListCreation04() {
evaluate("{'abc'=='xyz'}", "[false]", ArrayList.class);
}
@Test
public void testInlineListAndNesting() {
evaluate("{{1,2,3},{4,5,6}}", "[[1, 2, 3], [4, 5, 6]]", unmodifiableClass);
evaluate("{{1,'2',3},{4,{'a','b'},5,6}}", "[[1, 2, 3], [4, [a, b], 5, 6]]", unmodifiableClass);
}
@Test
public void testInlineListError() {
parseAndCheckError("{'abc'", SpelMessage.OOD);
}
@Test
public void testRelOperatorsIs02() {
evaluate("{1, 2, 3, 4, 5} instanceof T(java.util.List)", "true", Boolean.class);
}
@Test
public void testInlineListCreation05() {
evaluate("3 between {1,5}", "true", Boolean.class);
}
@Test
public void testInlineListCreation06() {
evaluate("8 between {1,5}", "false", Boolean.class);
}
@Test
public void testInlineListAndProjectionSelection() {
evaluate("{1,2,3,4,5,6}.![#this>3]", "[false, false, false, true, true, true]", ArrayList.class);
evaluate("{1,2,3,4,5,6}.?[#this>3]", "[4, 5, 6]", ArrayList.class);
evaluate("{1,2,3,4,5,6,7,8,9,10}.?[#isEven(#this) == 'y']", "[2, 4, 6, 8, 10]", ArrayList.class);
}
@Test
public void testSetConstruction01() {
evaluate("new java.util.HashSet().addAll({'a','b','c'})", "true", Boolean.class);
}
@Test
public void testRelOperatorsBetween01() {
evaluate("32 between {32, 42}", "true", Boolean.class);
}
@Test
public void testRelOperatorsBetween02() {
evaluate("'efg' between {'abc', 'xyz'}", "true", Boolean.class);
}
@Test
public void testRelOperatorsBetween03() {
evaluate("42 between {32, 42}", "true", Boolean.class);
}
@Test
public void testRelOperatorsBetweenErrors02() {
evaluateAndCheckError("'abc' between {5,7}", SpelMessage.NOT_COMPARABLE, 6);
}
@Test
public void testConstantRepresentation1() {
checkConstantList("{1,2,3,4,5}", true);
checkConstantList("{'abc'}", true);
checkConstantList("{}", true);
checkConstantList("{#a,2,3}", false);
checkConstantList("{1,2,Integer.valueOf(4)}", false);
checkConstantList("{1,2,{#a}}", false);
}
private void checkConstantList(String expressionText, boolean expectedToBeConstant) {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expression = (SpelExpression) parser.parseExpression(expressionText);
SpelNode node = expression.getAST();
Assert.assertTrue(node instanceof InlineList);
InlineList inlineList = (InlineList) node;
if (expectedToBeConstant) {
Assert.assertTrue(inlineList.isConstant());
} else {
Assert.assertFalse(inlineList.isConstant());
}
}
@Test
public void testInlineListWriting() {
// list should be unmodifiable
try {
evaluate("{1, 2, 3, 4, 5}[0]=6", "[1, 2, 3, 4, 5]", unmodifiableClass);
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
}
}

View File

@ -432,7 +432,12 @@ public class ParsingTests {
public void testTypeReferences02() {
parseCheck("T(String)");
}
@Test
public void testInlineList1() {
parseCheck("{1,2,3,4}");
}
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
* as the original expression.

View File

@ -17,13 +17,12 @@
package org.springframework.expression.spel.standard;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.SpelParseException;
@ -111,16 +110,6 @@ public class SpelParserTests {
@Test
public void generalExpressions() throws Exception {
try {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String[3]");
Assert.fail();
} catch (ParseException e) {
Assert.assertTrue(e instanceof SpelParseException);
SpelParseException spe = (SpelParseException)e;
Assert.assertEquals(SpelMessage.MISSING_CONSTRUCTOR_ARGS,spe.getMessageCode());
Assert.assertEquals(10,spe.getPosition());
}
try {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String");