SPR-7335: support for expression inline lists and array construction
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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("^"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user