More tests and some fixes. Also created tests based on the documentation examples.
This commit is contained in:
@@ -36,6 +36,13 @@ public interface EvaluationContext {
|
||||
* @return the root context object against which unqualified properties/methods/etc should be resolved
|
||||
*/
|
||||
TypedValue getRootObject();
|
||||
|
||||
|
||||
/**
|
||||
* @param rootObject the root object against which unqualified properties/methods/etc should be resolved
|
||||
*/
|
||||
void setRootObject(Object object);
|
||||
|
||||
|
||||
/**
|
||||
* Set a named variable within this execution context to a specified value.
|
||||
|
||||
@@ -48,17 +48,11 @@ public class ExpressionState {
|
||||
|
||||
private final Stack<TypedValue> contextObjects = new Stack<TypedValue>();
|
||||
|
||||
|
||||
public ExpressionState() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ExpressionState(EvaluationContext context) {
|
||||
this.relatedContext = context;
|
||||
createVariableScope();
|
||||
}
|
||||
|
||||
|
||||
private void createVariableScope() {
|
||||
this.variableScopes.add(new VariableScope()); // create an empty top level VariableScope
|
||||
}
|
||||
@@ -95,7 +89,7 @@ public class ExpressionState {
|
||||
|
||||
public TypedValue lookupVariable(String name) {
|
||||
Object value = this.relatedContext.lookupVariable(name);
|
||||
return new TypedValue(value,TypeDescriptor.valueOf((value==null?null:value.getClass())));
|
||||
return new TypedValue(value,TypeDescriptor.forObject(value));
|
||||
}
|
||||
|
||||
public TypeComparator getTypeComparator() {
|
||||
|
||||
@@ -113,10 +113,10 @@ public enum SpelMessages {
|
||||
"The value ''{0}'' cannot be parsed as a long"), PARSE_PROBLEM(Kind.ERROR, 1066,
|
||||
"Error occurred during expression parse: {0}"), INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR,
|
||||
1067, "First operand to matches operator must be a string. ''{0}'' is not"), INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR(
|
||||
Kind.ERROR, 1068, "Second operand to matches operator must be a string. ''{0}'' is not"), FUNCTION_MUST_BE_STATIC(
|
||||
Kind.ERROR,
|
||||
1069,
|
||||
"Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static.");
|
||||
Kind.ERROR, 1068, "Second operand to matches operator must be a string. ''{0}'' is not"), //
|
||||
FUNCTION_MUST_BE_STATIC(Kind.ERROR, 1069,
|
||||
"Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static."),//
|
||||
CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1070, "Cannot index into a null value");
|
||||
|
||||
private Kind kind;
|
||||
private int code;
|
||||
|
||||
@@ -74,8 +74,9 @@ public class ConstructorReference extends SpelNodeImpl {
|
||||
Class<?>[] argumentTypes = new Class[getChildCount() - 1];
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
TypedValue childValue = getChild(i + 1).getValueInternal(state);
|
||||
arguments[i] = childValue.getValue();
|
||||
argumentTypes[i] = childValue.getValue().getClass();
|
||||
Object value = childValue.getValue();
|
||||
arguments[i] = value;
|
||||
argumentTypes[i] = value==null?Object.class:value.getClass();
|
||||
}
|
||||
|
||||
ConstructorExecutor executorToUse = this.cachedExecutor;
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.expression.spel.ExpressionState;
|
||||
import org.springframework.expression.spel.SpelException;
|
||||
import org.springframework.expression.spel.SpelMessages;
|
||||
import org.springframework.expression.spel.support.ReflectionHelper;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A function reference is of the form "#someFunction(a,b,c)". Functions may be defined in the context prior to the
|
||||
@@ -101,7 +102,9 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
try {
|
||||
return new TypedValue(m.invoke(m.getClass(), functionArgs),new TypeDescriptor(new MethodParameter(m,-1)));
|
||||
ReflectionUtils.makeAccessible(m);
|
||||
Object result = m.invoke(m.getClass(), functionArgs);
|
||||
return new TypedValue(result, new TypeDescriptor(new MethodParameter(m,-1)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SpelException(getCharPositionInLine(), e, SpelMessages.EXCEPTION_DURING_FUNCTION_CALL, name, e
|
||||
.getMessage());
|
||||
|
||||
@@ -60,9 +60,13 @@ public class Indexer extends SpelNodeImpl {
|
||||
|
||||
int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR);
|
||||
|
||||
if (targetObjectTypeDescriptor.isArray()) {
|
||||
if (targetObject == null) {
|
||||
throw new SpelException(SpelMessages.CANNOT_INDEX_INTO_NULL_VALUE);
|
||||
}
|
||||
|
||||
if (targetObject.getClass().isArray()) {
|
||||
return new TypedValue(accessArrayElement(targetObject, idx),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType()));
|
||||
} else if (targetObjectTypeDescriptor.isCollection()) {
|
||||
} else if (targetObject instanceof Collection) {
|
||||
Collection<?> c = (Collection<?>) targetObject;
|
||||
if (idx >= c.size()) {
|
||||
throw new SpelException(SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
||||
@@ -81,7 +85,7 @@ public class Indexer extends SpelNodeImpl {
|
||||
}
|
||||
return new TypedValue(String.valueOf(ctxString.charAt(idx)),STRING_TYPE_DESCRIPTOR);
|
||||
}
|
||||
throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor);
|
||||
throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +102,9 @@ public class Indexer extends SpelNodeImpl {
|
||||
TypeDescriptor targetObjectTypeDescriptor = contextObject.getTypeDescriptor();
|
||||
TypedValue index = getChild(0).getValueInternal(state);
|
||||
|
||||
if (targetObject == null) {
|
||||
throw new SpelException(SpelMessages.CANNOT_INDEX_INTO_NULL_VALUE);
|
||||
}
|
||||
// Indexing into a Map
|
||||
if (targetObjectTypeDescriptor.isMap()) {
|
||||
Map map = (Map)targetObject;
|
||||
|
||||
@@ -54,9 +54,9 @@ public class MethodReference extends SpelNodeImpl {
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
arguments[i] = getChild(i).getValueInternal(state).getValue();
|
||||
}
|
||||
if (currentContext == null) {
|
||||
if (currentContext.getValue() == null) {
|
||||
throw new SpelException(getCharPositionInLine(), SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT,
|
||||
formatMethodForMessage(name, getTypes(arguments)));
|
||||
FormatHelper.formatMethodForMessage(name, getTypes(arguments)));
|
||||
}
|
||||
|
||||
MethodExecutor executorToUse = this.cachedExecutor;
|
||||
@@ -85,12 +85,9 @@ public class MethodReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
private Class<?>[] getTypes(Object... arguments) {
|
||||
if (arguments == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?>[] argumentTypes = new Class[arguments.length];
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
argumentTypes[i] = arguments[i].getClass();
|
||||
argumentTypes[i] = (arguments[i]==null?Object.class:arguments[i].getClass());
|
||||
}
|
||||
return argumentTypes;
|
||||
}
|
||||
@@ -108,38 +105,13 @@ public class MethodReference extends SpelNodeImpl {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a nice string for a given method name with specified arguments.
|
||||
* @param name the name of the method
|
||||
* @param argumentTypes the types of the arguments to the method
|
||||
* @return nicely formatted string, eg. foo(String,int)
|
||||
*/
|
||||
private String formatMethodForMessage(String name, Class<?>... argumentTypes) {
|
||||
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].getClass());
|
||||
}
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
protected MethodExecutor findAccessorForMethod(String name, Class<?>[] argumentTypes, ExpressionState state)
|
||||
throws SpelException {
|
||||
|
||||
TypedValue context = state.getActiveContextObject();
|
||||
Object contextObject = context.getValue();
|
||||
EvaluationContext eContext = state.getEvaluationContext();
|
||||
if (contextObject == null) {
|
||||
throw new SpelException(SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT,
|
||||
FormatHelper.formatMethodForMessage(name, argumentTypes));
|
||||
}
|
||||
|
||||
List<MethodResolver> mResolvers = eContext.getMethodResolvers();
|
||||
if (mResolvers != null) {
|
||||
for (MethodResolver methodResolver : mResolvers) {
|
||||
|
||||
@@ -145,7 +145,6 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||
}
|
||||
}
|
||||
} catch (AccessException ae) {
|
||||
ae.printStackTrace();
|
||||
throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_PROPERTY_WRITE,
|
||||
name, ae.getMessage());
|
||||
}
|
||||
|
||||
@@ -62,8 +62,10 @@ public class Selection extends SpelNodeImpl {
|
||||
Map<?, ?> mapdata = (Map<?, ?>) operand;
|
||||
// TODO don't lose generic info for the new map
|
||||
Map<Object,Object> result = new HashMap<Object,Object>();
|
||||
Object lastKey = null;
|
||||
for (Object k : mapdata.keySet()) {
|
||||
try {
|
||||
lastKey = k;
|
||||
KeyValuePair kvp = new KeyValuePair(k,mapdata.get(k));
|
||||
TypedValue kvpair = new TypedValue(kvp,TypeDescriptor.valueOf(KeyValuePair.class));
|
||||
state.pushActiveContextObject(kvpair);
|
||||
@@ -83,12 +85,15 @@ public class Selection extends SpelNodeImpl {
|
||||
} finally {
|
||||
state.popActiveContextObject();
|
||||
}
|
||||
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
if (variant == LAST) {
|
||||
return new TypedValue(result.get(result.size() - 1),TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType()));
|
||||
}
|
||||
}
|
||||
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
|
||||
return new TypedValue(null,TypeDescriptor.NULL_TYPE_DESCRIPTOR);
|
||||
}
|
||||
if (variant == LAST) {
|
||||
Object lastValue = result.get(lastKey);
|
||||
Map resultMap = new HashMap();
|
||||
resultMap.put(lastKey,result.get(lastKey));
|
||||
return new TypedValue(resultMap,TypeDescriptor.valueOf(Map.class));
|
||||
}
|
||||
return new TypedValue(result,op.getTypeDescriptor());
|
||||
} else if (operand instanceof Collection) {
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
|
||||
package org.springframework.expression.spel.ast;
|
||||
|
||||
enum TypeCode {
|
||||
public enum TypeCode {
|
||||
|
||||
OBJECT(0, Object.class), BOOLEAN(1, Boolean.TYPE), BYTE(1, Byte.TYPE), CHAR(1, Character.TYPE), SHORT(2, Short.TYPE), INT(
|
||||
3, Integer.TYPE), LONG(4, Long.TYPE), FLOAT(5, Float.TYPE), DOUBLE(6, Double.TYPE);
|
||||
OBJECT(Object.class), BOOLEAN(Boolean.TYPE), BYTE(Byte.TYPE), CHAR(Character.TYPE), //
|
||||
SHORT(Short.TYPE), INT(Integer.TYPE), LONG(Long.TYPE), FLOAT(Float.TYPE), DOUBLE(Double.TYPE);
|
||||
|
||||
private int code;
|
||||
private Class<?> type;
|
||||
|
||||
TypeCode(int code, Class<?> type) {
|
||||
this.code = code;
|
||||
TypeCode(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@@ -33,72 +31,15 @@ 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 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;
|
||||
// }
|
||||
|
||||
/**
|
||||
* For a primitive name this will determine the typecode value - supports
|
||||
* int,byte,char,short,long,double,float,boolean
|
||||
*/
|
||||
public static TypeCode forName(String name) {
|
||||
if (name.equals("int"))
|
||||
return TypeCode.INT;
|
||||
else if (name.equals("boolean"))
|
||||
return TypeCode.BOOLEAN;
|
||||
else if (name.equals("char"))
|
||||
return TypeCode.CHAR;
|
||||
else if (name.equals("long"))
|
||||
return TypeCode.LONG;
|
||||
else if (name.equals("float"))
|
||||
return TypeCode.FLOAT;
|
||||
else if (name.equals("double"))
|
||||
return TypeCode.DOUBLE;
|
||||
else if (name.equals("short"))
|
||||
return TypeCode.SHORT;
|
||||
else if (name.equals("byte"))
|
||||
return TypeCode.BYTE;
|
||||
return TypeCode.OBJECT;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public Object coerce(TypeCode fromTypeCode, Object fromObject) {
|
||||
if (this == TypeCode.INT) {
|
||||
switch (fromTypeCode) {
|
||||
case BOOLEAN:
|
||||
return ((Boolean) fromObject).booleanValue() ? 1 : 0;
|
||||
}
|
||||
}
|
||||
//
|
||||
// return Integer.valueOf
|
||||
// } else if (this==TypeCode.BOOLEAN) {
|
||||
// return new Boolean(left).intValue();
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TypeCode forValue(Number op1) {
|
||||
return forClass(op1.getClass());
|
||||
}
|
||||
|
||||
public boolean isDouble() {
|
||||
return this == DOUBLE;
|
||||
}
|
||||
|
||||
public boolean isFloat() {
|
||||
return this == FLOAT;
|
||||
}
|
||||
|
||||
public boolean isLong() {
|
||||
return this == LONG;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class TypeReference extends SpelNodeImpl {
|
||||
// TODO possible optimization here if we cache the discovered type reference, but can we do that?
|
||||
String typename = (String) getChild(0).getValueInternal(state).getValue();
|
||||
if (typename.indexOf(".") == -1 && Character.isLowerCase(typename.charAt(0))) {
|
||||
TypeCode tc = TypeCode.forName(typename);
|
||||
TypeCode tc = TypeCode.valueOf(typename.toUpperCase());
|
||||
if (tc != TypeCode.OBJECT) {
|
||||
// it is a primitive type
|
||||
return new TypedValue(tc.getType(),CLASS_TYPE_DESCRIPTOR);
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
|
||||
/**
|
||||
* Represents a variable reference, eg. #someVar. Note this is different to a *local* variable like $someVar
|
||||
@@ -52,9 +51,7 @@ public class VariableReference extends SpelNodeImpl {
|
||||
return state.getRootContextObject();
|
||||
}
|
||||
TypedValue result = state.lookupVariable(this.name);
|
||||
if (result == null) {
|
||||
throw new SpelException(getCharPositionInLine(), SpelMessages.VARIABLE_NOT_FOUND, this.name);
|
||||
}
|
||||
// a null value will mean either the value was null or the variable was not found
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -64,7 +61,7 @@ public class VariableReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toStringAST() {
|
||||
public String toStringAST() {
|
||||
return "#" + this.name;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,11 +70,9 @@ public class ReflectiveConstructorResolver implements ConstructorResolver {
|
||||
if (matchInfo != null) {
|
||||
if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.EXACT) {
|
||||
return new ReflectiveConstructorExecutor(ctor, null);
|
||||
}
|
||||
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
|
||||
} else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
|
||||
closeMatch = ctor;
|
||||
}
|
||||
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) {
|
||||
} else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) {
|
||||
argsToConvert = matchInfo.argsRequiringConversion;
|
||||
matchRequiringConversion = ctor;
|
||||
}
|
||||
@@ -82,11 +80,9 @@ public class ReflectiveConstructorResolver implements ConstructorResolver {
|
||||
}
|
||||
if (closeMatch != null) {
|
||||
return new ReflectiveConstructorExecutor(closeMatch, null);
|
||||
}
|
||||
else if (matchRequiringConversion != null) {
|
||||
} else if (matchRequiringConversion != null) {
|
||||
return new ReflectiveConstructorExecutor(matchRequiringConversion, argsToConvert);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
|
||||
Method method = findGetterForProperty(name, type, target instanceof Class);
|
||||
if (method != null) {
|
||||
this.readerCache.put(cacheKey, method);
|
||||
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,0)));
|
||||
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,-1)));
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -85,8 +85,8 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
this.variables.put(name, value);
|
||||
}
|
||||
|
||||
public void registerFunction(String name, Method m) {
|
||||
this.variables.put(name, m);
|
||||
public void registerFunction(String name, Method method) {
|
||||
this.variables.put(name, method);
|
||||
}
|
||||
|
||||
public Object lookupVariable(String name) {
|
||||
|
||||
Reference in New Issue
Block a user