提交 cfc65b0c 编写于 作者: A Andy Clement

Removed unnecessary code. Increased test coverage from 70>75% - still some way to go

上级 30bed7b5
......@@ -57,5 +57,11 @@ public class TypedValue {
public TypeDescriptor getTypeDescriptor() {
return this.typeDescriptor;
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
str.append("TypedValue: ").append(value).append(" of type "+typeDescriptor.asString());
return str.toString();
}
}
......@@ -20,7 +20,6 @@ import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* Represents assignment. An alternative to calling setValue() for an expression is to use an assign.
......@@ -49,9 +48,4 @@ public class Assign extends SpelNodeImpl {
.toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
}
......@@ -16,11 +16,9 @@
package org.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import java.util.List;
import org.antlr.runtime.Token;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.ConstructorResolver;
......@@ -31,6 +29,7 @@ import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.SpelMessages;
// TODO asc array constructor call logic has been removed for now
/**
* 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.
......@@ -46,201 +45,22 @@ import org.springframework.expression.spel.SpelMessages;
*/
public class ConstructorReference extends SpelNodeImpl {
/**
* If true then this is an array constructor, for example, 'new String[]', rather than a simple constructor 'new
* String()'
*/
private final boolean isArrayConstructor;
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
/**
* The cached executor that may be reused on subsequent evaluations.
*/
private ConstructorExecutor cachedExecutor;
public ConstructorReference(Token payload, boolean isArrayConstructor) {
public ConstructorReference(Token payload) {
super(payload);
this.isArrayConstructor = isArrayConstructor;
}
/**
* Implements getValue() - delegating to the code for building an array or a simple type.
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
if (this.isArrayConstructor) {
return createArray(state);
}
else {
return createNewInstance(state);
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("new ");
int index = 0;
sb.append(getChild(index++).toStringAST());
if (!this.isArrayConstructor) {
sb.append("(");
for (int i = index; i < getChildCount(); i++) {
if (i > index)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
}
else {
// Next child is EXPRESSIONLIST token with children that are the
// expressions giving array size
sb.append("[");
SpelNodeImpl arrayRank = getChild(index++);
for (int i = 0; i < arrayRank.getChildCount(); i++) {
if (i > 0)
sb.append(",");
sb.append(arrayRank.getChild(i).toStringAST());
}
sb.append("]");
if (index < getChildCount())
sb.append(" ").append(getChild(index).toStringAST());
}
return sb.toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
/**
* Create an array and return it. The children of this node indicate the type of array, the array ranks and any
* optional initializer that might have been supplied.
* @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 {
TypedValue intendedArrayType = getChild(0).getValueInternal(state);
if (!(intendedArrayType.getValue() instanceof String)) {
throw new SpelException(getChild(0).getCharPositionInLine(),
SpelMessages.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION,
FormatHelper.formatClassNameForMessage(intendedArrayType.getClass()));
}
String type = (String) intendedArrayType.getValue();
Class<?> componentType = null;
TypeCode arrayTypeCode = TypeCode.forName(type);
if (arrayTypeCode == TypeCode.OBJECT) {
componentType = state.findType(type);
} else {
componentType = arrayTypeCode.getType();
}
Object newArray = null;
TypeDescriptor newArrayTypeDescriptor = null;
if (getChild(1).getChildCount() == 0) { // are the array ranks defined?
if (getChildCount() < 3) {
throw new SpelException(getCharPositionInLine(),
SpelMessages.NO_SIZE_OR_INITIALIZER_FOR_ARRAY_CONSTRUCTION);
}
// no array ranks so use the size of the initializer to determine array size
int arraySize = getChild(2).getChildCount();
newArray = Array.newInstance(componentType, arraySize);
newArrayTypeDescriptor = TypeDescriptor.valueOf(newArray.getClass());
}
else {
// Array ranks are specified but is it a single or multiple dimension array?
int dimensions = getChild(1).getChildCount();
if (dimensions == 1) {
Object o = getChild(1).getValueInternal(state);
int arraySize = state.convertValue(o, INTEGER_TYPE_DESCRIPTOR);
if (getChildCount() == 3) {
// Check initializer length matches array size length
int initializerLength = getChild(2).getChildCount();
if (initializerLength != arraySize) {
throw new SpelException(getChild(2).getCharPositionInLine(),
SpelMessages.INITIALIZER_LENGTH_INCORRECT, initializerLength, arraySize);
}
}
newArray = Array.newInstance(componentType, arraySize);
newArrayTypeDescriptor = TypeDescriptor.valueOf(newArray.getClass());
}
else {
// Multi-dimensional - hold onto your hat !
int[] dims = new int[dimensions];
for (int d = 0; d < dimensions; d++) {
dims[d] = state.convertValue(getChild(1).getChild(d).getValueInternal(state), INTEGER_TYPE_DESCRIPTOR);
}
newArray = Array.newInstance(componentType, dims);
newArrayTypeDescriptor = TypeDescriptor.valueOf(newArray.getClass());
// TODO check any specified initializer for the multidim array matches
}
}
// Populate the array using the initializer if one is specified
if (getChildCount() == 3) {
SpelNodeImpl initializer = getChild(2);
if (arrayTypeCode == TypeCode.OBJECT) {
Object[] newObjectArray = (Object[]) newArray;
for (int i = 0; i < newObjectArray.length; i++) {
SpelNodeImpl elementNode = initializer.getChild(i);
Object arrayEntry = elementNode.getValueInternal(state);
if (!componentType.isAssignableFrom(arrayEntry.getClass())) {
throw new SpelException(elementNode.getCharPositionInLine(),
SpelMessages.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, componentType.getName(), arrayEntry
.getClass().getName());
}
newObjectArray[i] = arrayEntry;
}
} else if (arrayTypeCode == TypeCode.INT) {
int[] newIntArray = (int[]) newArray;
for (int i = 0; i < newIntArray.length; i++) {
newIntArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), INTEGER_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.BOOLEAN) {
boolean[] newBooleanArray = (boolean[]) newArray;
for (int i = 0; i < newBooleanArray.length; i++) {
newBooleanArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.CHAR) {
char[] newCharArray = (char[]) newArray;
for (int i = 0; i < newCharArray.length; i++) {
newCharArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), CHARACTER_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.SHORT) {
short[] newShortArray = (short[]) newArray;
for (int i = 0; i < newShortArray.length; i++) {
newShortArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), SHORT_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.LONG) {
long[] newLongArray = (long[]) newArray;
for (int i = 0; i < newLongArray.length; i++) {
newLongArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), LONG_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.FLOAT) {
float[] newFloatArray = (float[]) newArray;
for (int i = 0; i < newFloatArray.length; i++) {
newFloatArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), FLOAT_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.DOUBLE) {
double[] newDoubleArray = (double[]) newArray;
for (int i = 0; i < newDoubleArray.length; i++) {
newDoubleArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), DOUBLE_TYPE_DESCRIPTOR);
}
} else if (arrayTypeCode == TypeCode.BYTE) {
byte[] newByteArray = (byte[]) newArray;
for (int i = 0; i < newByteArray.length; i++) {
newByteArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), BYTE_TYPE_DESCRIPTOR);
}
}
}
return new TypedValue(newArray, newArrayTypeDescriptor);
return createNewInstance(state);
}
/**
......@@ -274,9 +94,10 @@ public class ConstructorReference extends SpelNodeImpl {
String typename = (String) getChild(0).getValueInternal(state).getValue();
executorToUse = findExecutorForConstructor(typename, argumentTypes, state);
try {
return executorToUse.execute(state.getEvaluationContext(), arguments);
}
catch (AccessException ae) {
this.cachedExecutor = executorToUse;
TypedValue result = executorToUse.execute(state.getEvaluationContext(), arguments);
return result;
} catch (AccessException ae) {
throw new SpelException(ae, SpelMessages.EXCEPTION_DURING_CONSTRUCTOR_INVOCATION, typename, ae.getMessage());
}
}
......@@ -314,4 +135,22 @@ public class ConstructorReference extends SpelNodeImpl {
argumentTypes));
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("new ");
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(")");
return sb.toString();
}
}
......@@ -21,7 +21,7 @@ package org.springframework.expression.spel.ast;
*
* @author Andy Clement
*/
class FormatHelper {
public class FormatHelper {
/**
* Produce a nice string for a given method name with specified arguments.
......@@ -33,13 +33,11 @@ class FormatHelper {
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append("(");
if (argumentTypes != null) {
for (int i = 0; i < argumentTypes.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(argumentTypes[i].getName());
for (int i = 0; i < argumentTypes.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(formatClassNameForMessage(argumentTypes[i]));
}
sb.append(")");
return sb.toString();
......
......@@ -128,10 +128,6 @@ public class FunctionReference extends SpelNodeImpl {
}
// to 'assign' to a function don't use the () suffix and so it is just a variable reference
@Override
public boolean isWritable(ExpressionState expressionState) throws EvaluationException {
return false;
}
/**
* Compute the arguments to the function, they are the children of this expression node.
......
......@@ -52,11 +52,6 @@ public abstract class Literal extends SpelNodeImpl {
return toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
/**
* Process the string form of a number, using the specified base if supplied and return an appropriate literal to
* hold it. Any suffix to indicate a long will be taken into account (either 'l' or 'L' is supported).
......@@ -71,9 +66,7 @@ public abstract class Literal extends SpelNodeImpl {
boolean isLong = false;
boolean isHex = (radix == 16);
if (numberString.length() > 0) {
isLong = numberString.endsWith("L") || numberString.endsWith("l");
}
isLong = numberString.endsWith("L") || numberString.endsWith("l");
if (isLong || isHex) { // needs to be chopped up a little
int len = numberString.length();
......
......@@ -78,10 +78,9 @@ public class MethodReference extends SpelNodeImpl {
try {
return executorToUse.execute(
state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments);
}
catch (AccessException ae) {
} catch (AccessException ae) {
throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_METHOD_INVOCATION,
this.name, state.getActiveContextObject().getClass().getName(), ae.getMessage());
this.name, state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage());
}
}
......@@ -130,11 +129,6 @@ public class MethodReference extends SpelNodeImpl {
return sb.toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
protected MethodExecutor findAccessorForMethod(String name, Class<?>[] argumentTypes, ExpressionState state)
throws SpelException {
......
......@@ -17,8 +17,6 @@
package org.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.ExpressionState;
/**
* Common supertype for operators that operate on either one or two operands. In the case of multiply or divide there
......@@ -32,15 +30,7 @@ public abstract class Operator extends SpelNodeImpl {
public Operator(Token payload) {
super(payload);
}
/**
* Operator expressions can never be written to
*/
@Override
public final boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
public SpelNodeImpl getLeftOperand() {
return getChild(0);
}
......@@ -57,17 +47,13 @@ public abstract class Operator extends SpelNodeImpl {
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
if (getChildCount() > 0) {
sb.append("(");
}
sb.append("(");
sb.append(getChild(0).toStringAST());
for (int i = 1; i < getChildCount(); i++) {
sb.append(" ").append(getOperatorName()).append(" ");
sb.append(getChild(i).toStringAST());
}
if (getChildCount() > 0) {
sb.append(")");
}
sb.append(")");
return sb.toString();
}
......
......@@ -53,9 +53,4 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do
return sb.toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
}
......@@ -28,11 +28,10 @@ import org.springframework.expression.spel.ExpressionState;
* <li>add doubles (floats are represented as doubles)
* <li>add longs
* <li>add integers
* <li>add a string of one character and a number (effectively increasing that character), so 'a'+3='d'
* <li>concatenate strings
* </ul>
* It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed
* when the operand types vary (double+int=double).
* For other options it defers to the registered overloader.
* when the operand types vary (double+int=double). For other options it defers to the registered overloader.
*
* @author Andy Clement
* @since 3.0
......@@ -75,16 +74,6 @@ public class OperatorPlus extends Operator {
}
} else if (operandOne instanceof String && operandTwo instanceof String) {
return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString(),STRING_TYPE_DESCRIPTOR);
} else if (operandOne instanceof String && operandTwo instanceof Integer && ((String)operandOne).length()==1) {
String theString = (String) operandOne;
Integer theInteger = (Integer) operandTwo;
// implements character + int (ie. a + 1 = b)
return new TypedValue(Character.toString((char) (theString.charAt(0) + theInteger)),STRING_TYPE_DESCRIPTOR);
} else if (operandOne instanceof Integer && ((operandTwo instanceof String) && ((String)operandTwo).length()==1)) {
String theString = (String) operandTwo;
Integer theInteger = (Integer) operandOne;
// implements character + int (ie. 1 + a = b)
return new TypedValue(Character.toString((char) (theString.charAt(0) + theInteger)),STRING_TYPE_DESCRIPTOR);
}
return state.operate(Operation.ADD, operandOne, operandTwo);
}
......
......@@ -61,7 +61,7 @@ public class Projection extends SpelNodeImpl {
for (Object k : mapdata.keySet()) {
try {
state.pushActiveContextObject(new TypedValue(new KeyValuePair(k, mapdata.get(k)),TypeDescriptor.valueOf(KeyValuePair.class)));
result.add(getChild(0).getValueInternal(state));
result.add(getChild(0).getValueInternal(state).getValue());
} finally {
state.popActiveContextObject();
}
......@@ -76,7 +76,7 @@ public class Projection extends SpelNodeImpl {
try {
state.pushActiveContextObject(new TypedValue(element,TypeDescriptor.valueOf(op.getTypeDescriptor().getType())));
state.enterScope("index", idx);
result.add(getChild(0).getValueInternal(state));
result.add(getChild(0).getValueInternal(state).getValue());
} finally {
state.exitScope();
state.popActiveContextObject();
......@@ -95,9 +95,4 @@ public class Projection extends SpelNodeImpl {
return sb.append("!{").append(getChild(0).toStringAST()).append("}").toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
}
......@@ -145,6 +145,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
}
} catch (AccessException ae) {
ae.printStackTrace();
throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_PROPERTY_WRITE,
name, ae.getMessage());
}
......
......@@ -31,7 +31,7 @@ import org.springframework.expression.spel.ExpressionState;
*/
public class QualifiedIdentifier extends SpelNodeImpl {
private String value;
private TypedValue value;
public QualifiedIdentifier(Token payload) {
super(payload);
......@@ -49,18 +49,17 @@ public class QualifiedIdentifier extends SpelNodeImpl {
}
sb.append(getChild(i).getValueInternal(state).getValue());
}
this.value = sb.toString();
this.value = new TypedValue(sb.toString(),STRING_TYPE_DESCRIPTOR);
}
return new TypedValue(this.value,STRING_TYPE_DESCRIPTOR);
return this.value;
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
if (this.value != null) {
sb.append(this.value);
}
else {
sb.append(this.value.getValue());
} else {
for (int i = 0; i < getChildCount(); i++) {
if (i > 0) {
sb.append(".");
......
......@@ -18,6 +18,7 @@ package org.springframework.expression.spel.ast;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -59,17 +60,21 @@ public class Selection extends SpelNodeImpl {
SpelNodeImpl selectionCriteria = getChild(0);
if (operand instanceof Map) {
Map<?, ?> mapdata = (Map<?, ?>) operand;
List<Object> result = new ArrayList<Object>();
// TODO don't lose generic info for the new map
Map<Object,Object> result = new HashMap<Object,Object>();
for (Object k : mapdata.keySet()) {
try {
TypedValue kvpair = new TypedValue(new KeyValuePair(k, mapdata.get(k)),TypeDescriptor.valueOf(KeyValuePair.class));
KeyValuePair kvp = new KeyValuePair(k,mapdata.get(k));
TypedValue kvpair = new TypedValue(kvp,TypeDescriptor.valueOf(KeyValuePair.class));
state.pushActiveContextObject(kvpair);
Object o = selectionCriteria.getValueInternal(state);
Object o = selectionCriteria.getValueInternal(state).getValue();
if (o instanceof Boolean) {
if (((Boolean) o).booleanValue() == true) {
if (variant == FIRST)
return kvpair;
result.add(kvpair);
if (variant == FIRST) {
result.put(kvp.key,kvp.value);
return new TypedValue(result);
}
result.put(kvp.key,kvp.value);
}
} else {
throw new SpelException(selectionCriteria.getCharPositionInLine(),
......@@ -142,9 +147,4 @@ public class Selection extends SpelNodeImpl {
return sb.append(getChild(0).toStringAST()).append("}").toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
}
......@@ -55,6 +55,7 @@ public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Seria
}
}
// by default Ast nodes are not writable
public boolean isWritable(ExpressionState expressionState) throws EvaluationException {
return false;
}
......
......@@ -18,7 +18,6 @@ package org.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.antlr.runtime.tree.CommonTreeAdaptor;
import org.springframework.expression.spel.generated.SpringExpressionsLexer;
/**
......@@ -95,7 +94,7 @@ public class SpelTreeAdaptor extends CommonTreeAdaptor {
return new CompoundExpression(payload);
case SpringExpressionsLexer.CONSTRUCTOR:
return new ConstructorReference(payload, false);
return new ConstructorReference(payload);
case SpringExpressionsLexer.VARIABLEREF:
return new VariableReference(payload);
case SpringExpressionsLexer.FUNCTIONREF:
......@@ -123,11 +122,6 @@ public class SpelTreeAdaptor extends CommonTreeAdaptor {
case SpringExpressionsLexer.INSTANCEOF:
return new OperatorInstanceof(payload);
case SpringExpressionsLexer.RPAREN:
return new Placeholder(payload);
case SpringExpressionsLexer.COLON:
return new Placeholder(payload);
case SpringExpressionsLexer.DOT:
return new Dot(payload);
......
......@@ -20,7 +20,6 @@ import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* Represents a ternary expression, for example: "someCheck()?true:false".
......@@ -57,9 +56,4 @@ public class Ternary extends SpelNodeImpl {
.append(" : ").append(getChild(2).toStringAST()).toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
}
......@@ -20,7 +20,6 @@ import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* Represents a reference to a type, for example "T(String)" or "T(com.somewhere.Foo)"
......@@ -56,9 +55,4 @@ public class TypeReference extends SpelNodeImpl {
return sb.toString();
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelException {
return false;
}
}
......@@ -56,8 +56,7 @@ class ReflectiveMethodExecutor implements MethodExecutor {
}
ReflectionUtils.makeAccessible(this.method);
return new TypedValue(this.method.invoke(target, arguments), new TypeDescriptor(new MethodParameter(method,-1)));
}
catch (Exception ex) {
} catch (Exception ex) {
throw new AccessException("Problem invoking method: " + this.method, ex);
}
}
......
......@@ -17,6 +17,7 @@
package org.springframework.expression.spel.support;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.service.DefaultConversionService;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelException;
......@@ -31,8 +32,16 @@ import org.springframework.util.NumberUtils;
*/
public class StandardTypeConverter implements TypeConverter {
DefaultConversionService conversionService;
StandardTypeConverter() {
conversionService = new DefaultConversionService();
}
@SuppressWarnings("unchecked")
public <T> T convertValue(Object value, Class<T> targetType) throws EvaluationException {
// For activation when conversion service available - this replaces the rest of the method (probably...)
// return (T)convertValue(value,TypeDescriptor.valueOf(targetType));
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
......@@ -78,10 +87,20 @@ public class StandardTypeConverter implements TypeConverter {
@SuppressWarnings("unchecked")
public <T> T convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException {
// For activation when conversion service available - this replaces the rest of the method (probably...)
// try {
// return (T)conversionService.executeConversion(value, typeDescriptor);
// } catch (ConversionExecutorNotFoundException cenfe) {
// throw new SpelException(cenfe, SpelMessages.TYPE_CONVERSION_ERROR, value.getClass(), typeDescriptor.asString());
// } catch (ConversionException ce) {
// throw new SpelException(ce, SpelMessages.TYPE_CONVERSION_ERROR, value.getClass(), typeDescriptor.asString());
// }
return (T)convertValue(value,typeDescriptor.getType());
}
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
// For activation when conversion service available - this replaces the rest of the method (probably...)
// return canConvert(sourceType,TypeDescriptor.valueOf(targetType));
if (ClassUtils.isAssignable(targetType, sourceType) || String.class.equals(targetType)) {
return true;
}
......@@ -92,6 +111,12 @@ public class StandardTypeConverter implements TypeConverter {
}
public boolean canConvert(Class<?> sourceType, TypeDescriptor typeDescriptor) {
// For activation when conversion service available - this replaces the rest of the method (probably...)
// try {
// return conversionService.getConversionExecutor(sourceType, typeDescriptor)!=null;
// } catch (ConversionExecutorNotFoundException cenfe) {
// return false;
// }
return canConvert(sourceType,typeDescriptor.getType());
}
......
......@@ -19,6 +19,7 @@ package org.springframework.expression.spel;
import java.util.ArrayList;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Tests the evaluation of real expressions in a real context.
......@@ -208,6 +209,22 @@ public class EvaluationTests extends ExpressionTestCase {
public void testConstructorInvocation05() {
evaluate("new java.lang.String('foobar')", "foobar", String.class);
}
public void testConstructorInvocation06() throws Exception {
// repeated evaluation to drive use of cached executor
SpelExpression expr = (SpelExpression)parser.parseExpression("new String('wibble')");
String newString = expr.getValue(String.class);
assertEquals("wibble",newString);
newString = expr.getValue(String.class);
assertEquals("wibble",newString);
// not writable
assertFalse(expr.isWritable(new StandardEvaluationContext()));
// ast
assertEquals("new String('wibble')",expr.toStringAST());
}
// array construction
// public void testArrayConstruction01() {
......@@ -275,14 +292,18 @@ public class EvaluationTests extends ExpressionTestCase {
// }
// projection and selection
// public void testProjection01() {
// 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);
// }
//
// public void testProjection02() {
// evaluate("#{'a':'y','b':'n','c':'y'}.!{value=='y'?key:null}.nonnull().sort()", "[a, c]", ArrayList.class);
// }
//
public void testProjection01() {
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);
}
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);
}
// public void testProjection03() {
// evaluate("{1,2,3,4,5,6,7,8,9,10}.!{#this>5}",
// "[false, false, false, false, false, true, true, true, true, true]", ArrayList.class);
......@@ -291,11 +312,21 @@ public class EvaluationTests extends ExpressionTestCase {
// public void testProjection04() {
// evaluate("{1,2,3,4,5,6,7,8,9,10}.!{$index>5?'y':'n'}", "[n, n, n, n, n, n, y, y, y, y]", 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 testProjection05() {
evaluateAndCheckError("'abc'.!{true}", SpelMessages.PROJECTION_NOT_SUPPORTED_ON_TYPE);
}
public void testProjection06() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'.!{true}");
assertEquals("'abc'.!{true}",expr.toStringAST());
assertFalse(expr.isWritable(new StandardEvaluationContext()));
}
//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 testSelection02() {
evaluate("testMap.keySet().?{#this matches '.*o.*'}", "[monday]", ArrayList.class);
......@@ -303,23 +334,40 @@ public class EvaluationTests extends ExpressionTestCase {
evaluate("testMap.keySet().?{#this matches '.*r.*'}.size()", "3", Integer.class);
}
// public void testSelectionError_NonBooleanSelectionCriteria() {
// evaluateAndCheckError("{1,2,3,4,5,6,7,8,9,10}.?{'nonboolean'}",
// SpelMessages.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
// }
public void testSelectionError_NonBooleanSelectionCriteria() {
evaluateAndCheckError("listOfNumbersUpToTen.?{'nonboolean'}",
SpelMessages.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
}
// public void testSelectionUsingIndex() {
// evaluate("{1,2,3,4,5,6,7,8,9,10}.?{$index > 5 }", "[7, 8, 9, 10]", ArrayList.class);
// }
public void testSelection03() {
evaluate("mapOfNumbersUpToTen.?{key>5}.size()", "5", Integer.class);
}
// public void testSelectionFirst01() {
// evaluate("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}", "2", Integer.class);
// }
//
// public void testSelectionLast01() {
// evaluate("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}", "10", Integer.class);
// }
public void testSelectionFirst01() {
evaluate("listOfNumbersUpToTen.^{#isEven(#this) == 'y'}", "2", Integer.class);
}
public void testSelectionLast01() {
evaluate("listOfNumbersUpToTen.${#isEven(#this) == 'y'}", "10", Integer.class);
}
public void testSelectionAST() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'.^{true}");
assertEquals("'abc'.^{true}",expr.toStringAST());
assertFalse(expr.isWritable(new StandardEvaluationContext()));
expr = (SpelExpression)parser.parseExpression("'abc'.?{true}");
assertEquals("'abc'.?{true}",expr.toStringAST());
assertFalse(expr.isWritable(new StandardEvaluationContext()));
expr = (SpelExpression)parser.parseExpression("'abc'.${true}");
assertEquals("'abc'.${true}",expr.toStringAST());
assertFalse(expr.isWritable(new StandardEvaluationContext()));
}
// assignment
public void testAssignmentToVariables01() {
evaluate("#var1='value1'", "value1", String.class);
......@@ -451,6 +499,16 @@ public class EvaluationTests extends ExpressionTestCase {
public void testTypeReferences01() {
evaluate("T(java.lang.String)", "class java.lang.String", Class.class);
}
public void testTypeReferencesAndQualifiedIdentifierCaching() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("T(java.lang.String)");
assertFalse(expr.isWritable(new StandardEvaluationContext()));
assertEquals("T(java.lang.String)",expr.toStringAST());
assertEquals(String.class,expr.getValue(Class.class));
// use cached QualifiedIdentifier:
assertEquals("T(java.lang.String)",expr.toStringAST());
assertEquals(String.class,expr.getValue(Class.class));
}
public void testTypeReferencesPrimitive() {
evaluate("T(int)", "int", Class.class);
......
......@@ -13,36 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
package org.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.SpelMessages;
import org.springframework.expression.spel.ast.FormatHelper;
/**
* PlaceHolder nodes are created for tokens that come through from the grammar purely to give us additional positional
* information for messages/etc.
* Tests for any helper code.
*
* @author Andy Clement
* @since 3.0
*/
public class Placeholder extends SpelNodeImpl {
public class HelperTests extends ExpressionTestCase {
public Placeholder(Token payload) {
super(payload);
public void testFormatHelperForClassName() {
assertEquals("java.lang.String",FormatHelper.formatClassNameForMessage(String.class));
assertEquals("java.lang.String[]",FormatHelper.formatClassNameForMessage(new String[1].getClass()));
assertEquals("int[]",FormatHelper.formatClassNameForMessage(new int[1].getClass()));
assertEquals("int[][]",FormatHelper.formatClassNameForMessage(new int[1][2].getClass()));
assertEquals("null",FormatHelper.formatClassNameForMessage(null));
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws SpelException {
throw new SpelException(getCharPositionInLine(), SpelMessages.PLACEHOLDER_SHOULD_NEVER_BE_EVALUATED);
public void testFormatHelperForMethod() {
assertEquals("foo(java.lang.String)",FormatHelper.formatMethodForMessage("foo", String.class));
assertEquals("goo(java.lang.String,int[])",FormatHelper.formatMethodForMessage("goo", String.class,new int[1].getClass()));
assertEquals("boo()",FormatHelper.formatMethodForMessage("boo"));
}
@Override
public String toStringAST() {
return getText();
}
}
......@@ -16,6 +16,8 @@
package org.springframework.expression.spel;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Tests the evaluation of basic literals: boolean, integer, hex integer, long, real, null, date
*
......@@ -134,4 +136,13 @@ public class LiteralTests extends ExpressionTestCase {
evaluate("new Integer(37).byteValue()", (byte) 37, Byte.class); // calling byteValue() on Integer.class
evaluateAndAskForReturnType("new Integer(37)", (byte) 37, Byte.class); // relying on registered type converters
}
public void testNotWritable() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("37");
assertFalse(expr.isWritable(new StandardEvaluationContext()));
expr = (SpelExpression)parser.parseExpression("37L");
assertFalse(expr.isWritable(new StandardEvaluationContext()));
expr = (SpelExpression)parser.parseExpression("true");
assertFalse(expr.isWritable(new StandardEvaluationContext()));
}
}
......@@ -63,6 +63,8 @@ public class OperatorTests extends ExpressionTestCase {
evaluate("3 == 5", false, Boolean.class);
evaluate("5 == 3", false, Boolean.class);
evaluate("6 == 6", true, Boolean.class);
evaluate("3.0f == 5.0f", false, Boolean.class);
evaluate("3.0f == 3.0f", true, Boolean.class);
evaluate("'abc' == null", false, Boolean.class);
}
......@@ -70,6 +72,8 @@ public class OperatorTests extends ExpressionTestCase {
evaluate("3 != 5", true, Boolean.class);
evaluate("5 != 3", true, Boolean.class);
evaluate("6 != 6", false, Boolean.class);
evaluate("3.0f != 5.0f", true, Boolean.class);
evaluate("3.0f != 3.0f", false, Boolean.class);
}
public void testGreaterThanOrEqual() {
......@@ -122,23 +126,36 @@ public class OperatorTests extends ExpressionTestCase {
}
public void testPlus() throws Exception {
evaluate("'a' + 2", "c", String.class);
evaluate("7 + 2", "9", Integer.class);
evaluate("3.0f + 5.0f", 8.0d, Double.class);
evaluate("3.0d + 5.0d", 8.0d, Double.class);
evaluateAndCheckError("'ab' + 2", SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluate("2+'a' ", "c", String.class);
evaluateAndCheckError("2+'a' ", SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluateAndCheckError("2+'ab'",SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
// AST:
SpelExpression expr = (SpelExpression)parser.parseExpression("+3");
assertEquals("+3",expr.toStringAST());
expr = (SpelExpression)parser.parseExpression("2+3");
assertEquals("(2 + 3)",expr.toStringAST());
// use as a unary operator
evaluate("+5d",5d,Double.class);
evaluate("+5L",5L,Long.class);
evaluate("+5",5,Integer.class);
evaluateAndCheckError("+'abc'",SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
// string concatenation
evaluate("'abc'+'def'","abcdef",String.class);
//
evaluate("5 + new Integer('37')",42,Integer.class);
}
public void testMinus() throws Exception {
evaluate("'c' - 2", "a", String.class);
evaluate("3.0f - 5.0f", -2.0d, Double.class);
evaluateAndCheckError("'ab' - 2", SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluateAndCheckError("2-'ab'",SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
SpelExpression expr = (SpelExpression)parser.parseExpression("-3");
......@@ -156,11 +173,14 @@ public class OperatorTests extends ExpressionTestCase {
evaluate("3%2",1,Integer.class);
evaluate("3L%2L",1L,Long.class);
evaluate("3.0d%2.0d",1d,Double.class);
evaluate("5.0f % 3.1f", 1.9d, Double.class);
evaluateAndCheckError("'abc'%'def'",SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
public void testDivide() {
evaluate("3.0f / 5.0f", 0.6d, Double.class);
evaluate("4L/2L",2L,Long.class);
evaluateAndCheckError("'abc'/'def'",SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
public void testMathOperatorDivide_ConvertToDouble() {
......@@ -182,20 +202,8 @@ public class OperatorTests extends ExpressionTestCase {
evaluate("3.0d / 5.0d", 0.6d, Double.class);
evaluate("6.0d % 3.5d", 2.5d, Double.class);
}
public void testFloats() {
evaluate("3.0f == 5.0f", false, Boolean.class);
evaluate("3.0f == 3.0f", true, Boolean.class);
evaluate("3.0f != 5.0f", true, Boolean.class);
evaluate("3.0f != 3.0f", false, Boolean.class);
evaluate("3.0f + 5.0f", 8.0d, Double.class);
evaluate("3.0f - 5.0f", -2.0d, Double.class);
evaluate("3.0f * 5.0f", 15.0d, Double.class);
evaluate("3.0f / 5.0f", 0.6d, Double.class);
evaluate("5.0f % 3.1f", 1.9d, Double.class);
}
public void testOperatorStrings() throws Exception {
public void testOperatorNames() throws Exception {
Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3"));
assertEquals("==",node.getOperatorName());
......@@ -266,6 +274,8 @@ public class OperatorTests extends ExpressionTestCase {
evaluate("3L - 50L", -47L, Long.class);
}
// ---
private Operator getOperatorNode(SpelExpression e) {
SpelNode node = e.getAST();
return (Operator)findNode(node,Operator.class);
......
......@@ -29,6 +29,7 @@ public class ParserErrorMessagesTests extends ExpressionTestCase {
// will not fit into an int, needs L suffix
parseAndCheckError("0xCAFEBABE", SpelMessages.NOT_AN_INTEGER);
evaluate("0xCAFEBABEL", 0xCAFEBABEL, Long.class);
parseAndCheckError("0xCAFEBABECAFEBABEL", SpelMessages.NOT_A_LONG);
}
public void testBrokenExpression02() {
......
......@@ -19,6 +19,7 @@ package org.springframework.expression.spel;
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Tests the evaluation of expressions that access variables and functions (lambda/java).
*
......@@ -26,10 +27,15 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
*/
public class VariableAndFunctionTests extends ExpressionTestCase {
public void testVariableAccess() {
public void testVariableAccess01() {
evaluate("#answer", "42", Integer.class, SHOULD_BE_WRITABLE);
evaluate("#answer / 2", 21, Integer.class, SHOULD_NOT_BE_WRITABLE);
}
public void testVariableAccess_WellKnownVariables() {
evaluate("#this.getName()","Nikola Tesla",String.class);
evaluate("#root.getName()","Nikola Tesla",String.class);
}
public void testFunctionAccess01() {
evaluate("#reverseInt(1,2,3)", "int[3]{3,2,1}", int[].class);
......@@ -71,7 +77,7 @@ public class VariableAndFunctionTests extends ExpressionTestCase {
}
}
}
// this method is used by the test above
public void nonStatic() {
}
......
......@@ -3,6 +3,7 @@
*/
package org.springframework.expression.spel.testresources;
///CLOVER:OFF
public class Company {
String address;
......
......@@ -5,6 +5,7 @@ package org.springframework.expression.spel.testresources;
import java.awt.Color;
///CLOVER:OFF
public class Fruit {
public String name; // accessible as property field
public Color color; // accessible as property through getter/setter
......
......@@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
///CLOVER:OFF
@SuppressWarnings("unused")
public class Inventor {
private String name;
......@@ -26,6 +27,8 @@ public class Inventor {
public List<Integer> listOfInteger = new ArrayList<Integer>();
public List<Boolean> booleanList = new ArrayList<Boolean>();
public Map<String,Boolean> mapOfStringToBoolean = new HashMap<String,Boolean>();
public Map<Integer,String> mapOfNumbersUpToTen = new HashMap<Integer,String>();
public List<Integer> listOfNumbersUpToTen = new ArrayList<Integer>();
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
......@@ -42,6 +45,26 @@ public class Inventor {
testMap.put("sunday", "sonntag");
booleanList.add(false);
booleanList.add(false);
listOfNumbersUpToTen.add(1);
listOfNumbersUpToTen.add(2);
listOfNumbersUpToTen.add(3);
listOfNumbersUpToTen.add(4);
listOfNumbersUpToTen.add(5);
listOfNumbersUpToTen.add(6);
listOfNumbersUpToTen.add(7);
listOfNumbersUpToTen.add(8);
listOfNumbersUpToTen.add(9);
listOfNumbersUpToTen.add(10);
mapOfNumbersUpToTen.put(1,"one");
mapOfNumbersUpToTen.put(2,"two");
mapOfNumbersUpToTen.put(3,"three");
mapOfNumbersUpToTen.put(4,"four");
mapOfNumbersUpToTen.put(5,"five");
mapOfNumbersUpToTen.put(6,"six");
mapOfNumbersUpToTen.put(7,"seven");
mapOfNumbersUpToTen.put(8,"eight");
mapOfNumbersUpToTen.put(9,"nine");
mapOfNumbersUpToTen.put(10,"ten");
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth2) {
......
package org.springframework.expression.spel.testresources;
///CLOVER:OFF
public class Person {
private String privateName;
Company company;
......
package org.springframework.expression.spel.testresources;
///CLOVER:OFF
public class PlaceOfBirth {
private String city;
......
......@@ -6,6 +6,7 @@ Import-Template:
org.springframework.util;version="[3.0.0, 3.0.1)",
org.springframework.core;version="[3.0.0, 3.0.1)",
org.springframework.core.convert;version="[3.0.0, 3.0.1)",
org.springframework.core.convert.service;version="[3.0.0, 3.0.1)",
org.apache.commons.logging;version="[1.1.1, 2.0.0)",
org.antlr.runtime;version="[3.0.1,4.0.0)",
org.antlr.runtime.tree;version="[3.0.1,4.0.0)"
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册