SPR-6230: SpEL improvements

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@2095 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Andy Clement 2009-10-13 18:11:34 +00:00
parent cd37a26586
commit fd15a9a822
19 changed files with 848 additions and 77 deletions

View File

@ -33,7 +33,7 @@ import java.util.List;
public interface EvaluationContext {
/**
* @return the root context object against which unqualified properties/methods/etc should be resolved
* @return the default root context object against which unqualified properties/methods/etc should be resolved. This can be overridden when evaluating an expression.
*/
TypedValue getRootObject();

View File

@ -36,8 +36,17 @@ public interface Expression {
*/
public Object getValue() throws EvaluationException;
/**
* Evaluate this expression against the specified root object
*
* @param rootObject the root object against which properties/etc will be resolved
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
public Object getValue(Object rootObject) throws EvaluationException;
/**
* Evaluate the expression in the default standard context. If the result of the evaluation does not match (and
* Evaluate the expression in the default context. If the result of the evaluation does not match (and
* cannot be converted to) the expected result type then an exception will be returned.
*
* @param desiredResultType the class the caller would like the result to be
@ -45,6 +54,18 @@ public interface Expression {
* @throws EvaluationException if there is a problem during evaluation
*/
public <T> T getValue(Class<T> desiredResultType) throws EvaluationException;
/**
* Evaluate the expression in the default context against the specified root object. If the
* result of the evaluation does not match (and cannot be converted to) the expected result type
* then an exception will be returned.
*
* @param rootObject the root object against which properties/etc will be resolved
* @param desiredResultType the class the caller would like the result to be
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException;
/**
* Evaluate this expression in the provided context and return the result of evaluation.
@ -55,6 +76,17 @@ public interface Expression {
*/
public Object getValue(EvaluationContext context) throws EvaluationException;
/**
* Evaluate this expression in the provided context and return the result of evaluation, but use
* the supplied root context as an override for any default root object specified in the context.
*
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which properties/etc will be resolved
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Evaluate the expression in a specified context which can resolve references to properties, methods, types, etc -
* the type of the evaluation result is expected to be of a particular class and an exception will be thrown if it
@ -67,6 +99,20 @@ public interface Expression {
*/
public <T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException;
/**
* Evaluate the expression in a specified context which can resolve references to properties, methods, types, etc -
* the type of the evaluation result is expected to be of a particular class and an exception will be thrown if it
* is not and cannot be converted to that type. The supplied root object overrides any default specified on the
* supplied context.
*
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which properties/etc will be resolved
* @param desiredResultType the class the caller would like the result to be
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
* the default context.
@ -76,6 +122,16 @@ public interface Expression {
*/
public Class getValueType() throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
* the default context.
*
* @param rootObject the root object against which to evaluate the expression
* @return the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
public Class getValueType(Object rootObject) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
* the given context.
@ -85,6 +141,17 @@ public interface Expression {
* @throws EvaluationException if there is a problem determining the type
*/
public Class getValueType(EvaluationContext context) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
* the given context. The supplied root object overrides any specified in the context.
*
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which to evaluate the expression
* @return the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
@ -94,6 +161,16 @@ public interface Expression {
* @throws EvaluationException if there is a problem determining the type
*/
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
* the default context.
*
* @param rootObject the root object against which to evaluate the expression
* @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
@ -105,6 +182,17 @@ public interface Expression {
*/
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
* the given context. The supplied root object overrides any specified in the context.
*
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which to evaluate the expression
* @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
*
@ -113,7 +201,27 @@ public interface Expression {
* @throws EvaluationException if there is a problem determining if it is writable
*/
public boolean isWritable(EvaluationContext context) throws EvaluationException;
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
* The supplied root object overrides any specified in the context.
*
* @param context the context in which the expression should be checked
* @param rootObject the root object against which to evaluate the expression
* @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
*
* @param rootObject the root object against which to evaluate the expression
* @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/
public boolean isWritable(Object rootObject) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
*
@ -122,7 +230,27 @@ public interface Expression {
* @throws EvaluationException if there is a problem during evaluation
*/
public void setValue(EvaluationContext context, Object value) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
*
* @param rootObject the root object against which to evaluate the expression
* @param value the new value
* @throws EvaluationException if there is a problem during evaluation
*/
public void setValue(Object rootObject, Object value) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
* The supplied root object overrides any specified in the context.
*
* @param context the context in which to set the value of the expression
* @param rootObject the root object against which to evaluate the expression
* @param value the new value
* @throws EvaluationException if there is a problem during evaluation
*/
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException;
/**
* Returns the original string used to create this expression, unmodified.
*

View File

@ -23,7 +23,7 @@ package org.springframework.expression;
* @author Andy Clement
* @since 3.0
*/
public class ExpressionException extends Exception {
public class ExpressionException extends RuntimeException {
protected String expressionString;
protected int position; // -1 if not known - but should be known in all reasonable cases

View File

@ -25,11 +25,6 @@ package org.springframework.expression;
* to determine if it can read or write them. Property resolvers are considered to be ordered and each will be called in
* turn. The only rule that affects the call order is that any naming the target class directly in
* getSpecifiedTargetClasses() will be called first, before the general resolvers.
*
* <p>If the cost of locating the property is expensive, in relation to actually retrieving its value, consider extending
* CacheablePropertyAccessor rather than directly implementing PropertyAccessor. A CacheablePropertyAccessor enables the
* discovery (resolution) of the property to be done once and then an object (an executor) returned and cached by the
* infrastructure that can be used repeatedly to retrieve the property value.
*
* @author Andy Clement
* @since 3.0

View File

@ -33,7 +33,7 @@ public class TypedValue {
private final Object value;
private final TypeDescriptor typeDescriptor;
private TypeDescriptor typeDescriptor;
/**
@ -43,7 +43,7 @@ public class TypedValue {
*/
public TypedValue(Object value) {
this.value = value;
this.typeDescriptor = TypeDescriptor.forObject(value);
this.typeDescriptor = null;// initialized when/if requested
}
/**
@ -62,6 +62,9 @@ public class TypedValue {
}
public TypeDescriptor getTypeDescriptor() {
if (this.typeDescriptor==null) {
this.typeDescriptor = TypeDescriptor.forObject(value);
}
return this.typeDescriptor;
}

View File

@ -70,6 +70,15 @@ public class CompositeStringExpression implements Expression {
}
return sb.toString();
}
public String getValue(Object rootObject) throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : this.expressions) {
sb.append(ObjectUtils.getDisplayString(expression.getValue(rootObject)));
}
return sb.toString();
}
public Class getValueType(EvaluationContext context) {
return String.class;
@ -109,4 +118,57 @@ public class CompositeStringExpression implements Expression {
return expressions;
}
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
Object value = getValue(rootObject);
return ExpressionUtils.convert(null, value, desiredResultType);
}
public String getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : this.expressions) {
sb.append(ObjectUtils.getDisplayString(expression.getValue(context,rootObject)));
}
return sb.toString();
}
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType)
throws EvaluationException {
Object value = getValue(context,rootObject);
return ExpressionUtils.convert(context, value, desiredResultType);
}
public Class getValueType(Object rootObject) throws EvaluationException {
return String.class;
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
return String.class;
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject)
throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
return false;
}
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression");
}
public boolean isWritable(Object rootObject) throws EvaluationException {
return false;
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression");
}
}

View File

@ -51,6 +51,10 @@ public class LiteralExpression implements Expression {
public String getValue(EvaluationContext context) {
return this.literalValue;
}
public String getValue(Object rootObject) {
return this.literalValue;
}
public Class getValueType(EvaluationContext context) {
return String.class;
@ -86,4 +90,61 @@ public class LiteralExpression implements Expression {
return String.class;
}
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
Object value = getValue(rootObject);
return ExpressionUtils.convert(null, value, desiredResultType);
}
public String getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
return this.literalValue;
}
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType)
throws EvaluationException {
Object value = getValue(context, rootObject);
return ExpressionUtils.convert(null, value, desiredResultType);
}
public Class getValueType(Object rootObject) throws EvaluationException {
return String.class;
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
return String.class;
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject)
throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
return false;
}
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression");
}
public boolean isWritable(Object rootObject) throws EvaluationException {
return false;
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression");
}
}

View File

@ -46,60 +46,71 @@ public class ExpressionState {
private final EvaluationContext relatedContext;
private final Stack<VariableScope> variableScopes = new Stack<VariableScope>();
private Stack<VariableScope> variableScopes;
private final Stack<TypedValue> contextObjects = new Stack<TypedValue>();
private Stack<TypedValue> contextObjects;
private final TypedValue rootObject;
private int configuration = 0;
public ExpressionState(EvaluationContext context) {
this.relatedContext = context;
createVariableScope();
this.rootObject = context.getRootObject();
}
public ExpressionState(EvaluationContext context, int configuration) {
this.relatedContext = context;
this.configuration = configuration;
createVariableScope();
this.rootObject = context.getRootObject();
}
// create an empty top level VariableScope
private void createVariableScope() {
this.variableScopes.add(new VariableScope());
public ExpressionState(EvaluationContext context, TypedValue rootObject) {
this.relatedContext = context;
this.rootObject = rootObject;
}
public ExpressionState(EvaluationContext context, TypedValue rootObject, int configuration) {
this.relatedContext = context;
this.configuration = configuration;
this.rootObject = rootObject;
}
private void ensureVariableScopesInitialized() {
if (variableScopes == null) {
this.variableScopes = new Stack<VariableScope>();
// top level empty variable scope
this.variableScopes.add(new VariableScope());
}
}
/**
* The active context object is what unqualified references to properties/etc are resolved against.
*/
public TypedValue getActiveContextObject() {
if (this.contextObjects.isEmpty()) {
TypedValue rootObject = this.relatedContext.getRootObject();
if (rootObject == null) {
return TypedValue.NULL_TYPED_VALUE;
}
else {
return rootObject;
}
if (this.contextObjects==null || this.contextObjects.isEmpty()) {
return this.rootObject;
}
return this.contextObjects.peek();
}
public void pushActiveContextObject(TypedValue obj) {
if (this.contextObjects==null) {
this.contextObjects = new Stack<TypedValue>();
}
this.contextObjects.push(obj);
}
public void popActiveContextObject() {
if (this.contextObjects==null) {
this.contextObjects = new Stack<TypedValue>();
}
this.contextObjects.pop();
}
public TypedValue getRootContextObject() {
TypedValue root = this.relatedContext.getRootObject();
if (root == null) {
return TypedValue.NULL_TYPED_VALUE;
}
else {
return root;
}
return this.rootObject;
}
public void setVariable(String name, Object value) {
@ -137,22 +148,27 @@ public class ExpressionState {
*/
public void enterScope(Map<String, Object> argMap) {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(argMap));
}
public void enterScope(String name, Object value) {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(name, value));
}
public void exitScope() {
ensureVariableScopesInitialized();
this.variableScopes.pop();
}
public void setLocalVariable(String name, Object value) {
ensureVariableScopesInitialized();
this.variableScopes.peek().setVariable(name, value);
}
public Object lookupLocalVariable(String name) {
ensureVariableScopesInitialized();
int scopeNumber = this.variableScopes.size() - 1;
for (int i = scopeNumber; i >= 0; i--) {
if (this.variableScopes.get(i).definesVariable(name)) {

View File

@ -20,6 +20,7 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@ -37,6 +38,9 @@ public class SpelExpression implements Expression {
private final String expression;
// the default context is used if no override is supplied by the user
private transient EvaluationContext defaultContext;
public final SpelNodeImpl ast;
public final int configuration;
@ -53,20 +57,36 @@ public class SpelExpression implements Expression {
// implementing Expression
public Object getValue() throws EvaluationException {
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(), configuration);
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), configuration);
return ast.getValue(expressionState);
}
public Object getValue(Object rootObject) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration);
return ast.getValue(expressionState);
}
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(), configuration);
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), configuration);
Object result = ast.getValue(expressionState);
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
}
public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration);
Object result = ast.getValue(expressionState);
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
}
public Object getValue(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.getValue(new ExpressionState(context, configuration));
}
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.getValue(new ExpressionState(context, toTypedValue(rootObject), configuration));
}
@SuppressWarnings("unchecked")
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
@ -80,9 +100,23 @@ public class SpelExpression implements Expression {
}
return (T) result;
}
@SuppressWarnings("unchecked")
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) throws EvaluationException {
Object result = ast.getValue(new ExpressionState(context, toTypedValue(rootObject), configuration));
if (result != null && expectedResultType != null) {
Class<?> resultType = result.getClass();
if (!expectedResultType.isAssignableFrom(resultType)) {
// Attempt conversion to the requested type, may throw an exception
result = context.getTypeConverter().convertValue(result, TypeDescriptor.valueOf(expectedResultType));
}
}
return (T) result;
}
public Class getValueType() throws EvaluationException {
return ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(), configuration)).getTypeDescriptor().getType();
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor().getType();
}
public Class getValueType(EvaluationContext context) throws EvaluationException {
@ -92,8 +126,23 @@ public class SpelExpression implements Expression {
return typeDescriptor.getType();
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor.getType();
}
public Class getValueType(Object rootObject) throws EvaluationException {
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor().getType();
}
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
return ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(), configuration)).getTypeDescriptor();
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor();
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
ExpressionState eState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration);
return ast.getValueInternal(eState).getTypeDescriptor();
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
@ -102,6 +151,12 @@ public class SpelExpression implements Expression {
return ast.getValueInternal(eState).getTypeDescriptor();
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
return ast.getValueInternal(eState).getTypeDescriptor();
}
public String getExpressionString() {
return expression;
}
@ -111,10 +166,29 @@ public class SpelExpression implements Expression {
return ast.isWritable(new ExpressionState(context, configuration));
}
public boolean isWritable(Object rootObject) throws EvaluationException {
return ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration));
}
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), configuration));
}
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ast.setValue(new ExpressionState(context, configuration), value);
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration), value);
}
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ast.setValue(new ExpressionState(context, toTypedValue(rootObject), configuration), value);
}
// impl only
@ -135,5 +209,33 @@ public class SpelExpression implements Expression {
public String toStringAST() {
return ast.toStringAST();
}
/**
* Return the default evaluation context that will be used if none is supplied on an evaluation call
* @return the default evaluation context
*/
public EvaluationContext getEvaluationContext() {
if (defaultContext==null) {
defaultContext = new StandardEvaluationContext();
}
return defaultContext;
}
/**
* Set the evaluation context that will be used if none is specified on an evaluation call.
*
* @param context an evaluation context
*/
public void setEvaluationContext(EvaluationContext context) {
this.defaultContext = context;
}
private TypedValue toTypedValue(Object object) {
if (object==null) {
return TypedValue.NULL_TYPED_VALUE;
} else {
return new TypedValue(object);
}
}
}

View File

@ -206,7 +206,6 @@ public class Indexer extends SpelNodeImpl {
return sb.toString();
}
@SuppressWarnings("unchecked")
private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue, Class clazz) throws EvaluationException {
Class<?> arrayComponentType = clazz;
if (arrayComponentType == Integer.TYPE) {

View File

@ -52,7 +52,6 @@ public class MethodReference extends SpelNodeImpl {
TypedValue currentContext = state.getActiveContextObject();
Object[] arguments = new Object[getChildCount()];
for (int i = 0; i < arguments.length; i++) {
// System.out.println(i);
arguments[i] = children[i].getValueInternal(state).getValue();
}
if (currentContext.getValue() == null) {

View File

@ -45,7 +45,6 @@ public class Projection extends SpelNodeImpl {
this.nullSafe = nullSafe;
}
@SuppressWarnings("unchecked")
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue op = state.getActiveContextObject();

View File

@ -30,6 +30,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectivePropertyResolver;
/**
* Represents a simple property or field reference.
@ -57,9 +58,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue result = readProperty(state, this.name);
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
// Dynamically create the objects if the user has requested that optional behaviour
if (result.getValue()==null && state.configuredToDynamicallyCreateNullObjects() && nextChildIs(Indexer.class,PropertyOrFieldReference.class)) {
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
// Creating lists and maps
if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
// Create a new collection or map ready for the indexer
@ -130,7 +132,6 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
*/
private TypedValue readProperty(ExpressionState state, String name) throws EvaluationException {
TypedValue contextObject = state.getActiveContextObject();
EvaluationContext eContext = state.getEvaluationContext();
Object targetObject = contextObject.getValue();
if (targetObject == null && nullSafe) {
@ -151,6 +152,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
EvaluationContext eContext = state.getEvaluationContext();
// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
// get the accessor and use it. If they are not cacheable but report they can read the property
@ -159,6 +161,9 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
try {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canRead(eContext, contextObject.getValue(), name)) {
if (accessor instanceof ReflectivePropertyResolver) {
accessor = ((ReflectivePropertyResolver)accessor).createOptimalAccessor(eContext, contextObject.getValue(), name);
}
this.cachedReadAccessor = accessor;
return accessor.read(eContext, contextObject.getValue(), name);
}
@ -224,7 +229,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
Object contextObject = state.getActiveContextObject().getValue();
TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
EvaluationContext eContext = state.getEvaluationContext();
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);

View File

@ -44,11 +44,11 @@ import org.springframework.util.StringUtils;
*/
public class ReflectivePropertyResolver implements PropertyAccessor {
protected final Map<CacheKey, Member> readerCache = new ConcurrentHashMap<CacheKey, Member>();
protected Map<CacheKey, InvokerPair> readerCache;
protected final Map<CacheKey, Member> writerCache = new ConcurrentHashMap<CacheKey, Member>();
protected Map<CacheKey, Member> writerCache;
protected final Map<CacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<CacheKey,TypeDescriptor>();
protected Map<CacheKey, TypeDescriptor> typeDescriptorCache;
/**
* @return null which means this is a general purpose accessor
@ -65,21 +65,29 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
if ((type.isArray() && name.equals("length"))) {
return true;
}
if (this.readerCache==null) {
this.readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>();
if (this.typeDescriptorCache == null) {
this.typeDescriptorCache = new ConcurrentHashMap<CacheKey,TypeDescriptor>();
}
}
CacheKey cacheKey = new CacheKey(type, name);
if (this.readerCache.containsKey(cacheKey)) {
return true;
}
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,-1)));
TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method,-1));
this.readerCache.put(cacheKey, new InvokerPair(method,typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
if (field != null) {
this.readerCache.put(cacheKey, field);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
TypeDescriptor typeDescriptor = new TypeDescriptor(field);
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true;
}
}
@ -99,23 +107,25 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
return new TypedValue(Array.getLength(target),TypeDescriptor.valueOf(Integer.TYPE));
}
if (this.readerCache==null) {
this.readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>();
}
CacheKey cacheKey = new CacheKey(type, name);
Member cachedMember = this.readerCache.get(cacheKey);
InvokerPair invoker = this.readerCache.get(cacheKey);
if (cachedMember == null || cachedMember instanceof Method) {
Method method = (Method) cachedMember;
if (invoker == null || invoker.member instanceof Method) {
Method method = (Method) (invoker==null?null:invoker.member);
if (method == null) {
method = findGetterForProperty(name, type, target instanceof Class);
if (method != null) {
cachedMember = method;
this.readerCache.put(cacheKey, cachedMember);
invoker = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
this.readerCache.put(cacheKey, invoker);
}
}
if (method != null) {
try {
ReflectionUtils.makeAccessible(method);
TypeDescriptor resultTypeDescriptor = new TypeDescriptor(new MethodParameter(method,-1));
return new TypedValue(method.invoke(target),resultTypeDescriptor);
return new TypedValue(method.invoke(target),invoker.typeDescriptor);
}
catch (Exception ex) {
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
@ -123,19 +133,19 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
}
}
if (cachedMember == null || cachedMember instanceof Field) {
Field field = (Field) cachedMember;
if (invoker == null || invoker.member instanceof Field) {
Field field = (Field) (invoker==null?null:invoker.member);
if (field == null) {
field = findField(name, type, target instanceof Class);
if (field != null) {
cachedMember = field;
this.readerCache.put(cacheKey, cachedMember);
invoker = new InvokerPair(field, new TypeDescriptor(field));
this.readerCache.put(cacheKey, invoker);
}
}
if (field != null) {
try {
ReflectionUtils.makeAccessible(field);
return new TypedValue(field.get(target),new TypeDescriptor(field));
return new TypedValue(field.get(target),invoker.typeDescriptor);
}
catch (Exception ex) {
throw new AccessException("Unable to access field: " + name, ex);
@ -151,6 +161,12 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
return false;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (this.writerCache == null) {
this.writerCache = new ConcurrentHashMap<CacheKey, Member>();
if (this.typeDescriptorCache == null) {
this.typeDescriptorCache = new ConcurrentHashMap<CacheKey,TypeDescriptor>();
}
}
CacheKey cacheKey = new CacheKey(type, name);
if (this.writerCache.containsKey(cacheKey)) {
return true;
@ -187,7 +203,9 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
throw new AccessException("Type conversion failure",evaluationException);
}
}
if (this.writerCache == null) {
this.writerCache = new ConcurrentHashMap<CacheKey, Member>();
}
CacheKey cacheKey = new CacheKey(type, name);
Member cachedMember = this.writerCache.get(cacheKey);
@ -218,7 +236,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
field = findField(name, type, target instanceof Class);
if (field != null) {
cachedMember = field;
this.readerCache.put(cacheKey, cachedMember);
this.writerCache.put(cacheKey, cachedMember);
}
}
if (field != null) {
@ -315,7 +333,23 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
}
return null;
}
/**
* Captures the member (method/field) to call reflectively to access a property value and the type descriptor for the
* value returned by the reflective call.
*/
private static class InvokerPair {
final Member member;
final TypeDescriptor typeDescriptor;
public InvokerPair(Member member, TypeDescriptor typeDescriptor) {
this.member = member;
this.typeDescriptor = typeDescriptor;
}
}
private static class CacheKey {
@ -346,4 +380,148 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
}
}
/**
* Attempt to create an optimized property accessor tailored for a property of a particular name on
* a particular class. The general ReflectivePropertyAccessor will always work but is not optimal
* due to the need to lookup which reflective member (method/field) to use each time read() is called.
* This method will just return the ReflectivePropertyAccessor instance if it is unable to build
* something more optimal.
*/
public PropertyAccessor createOptimalAccessor(EvaluationContext eContext, Object target, String name) {
// Don't be clever for arrays or null target
if (target==null) {
return this;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray()) {
return this;
}
CacheKey cacheKey = new CacheKey(type, name);
if (this.readerCache==null) {
this.readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>();
if (this.typeDescriptorCache == null) {
this.typeDescriptorCache = new ConcurrentHashMap<CacheKey,TypeDescriptor>();
}
}
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
if (invocationTarget == null || invocationTarget.member instanceof Method) {
Method method = (Method) (invocationTarget==null?null:invocationTarget.member);
if (method == null) {
method = findGetterForProperty(name, type, target instanceof Class);
if (method != null) {
invocationTarget = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
ReflectionUtils.makeAccessible(method);
this.readerCache.put(cacheKey, invocationTarget);
}
}
if (method != null) {
return new OptimalPropertyAccessor(invocationTarget);
}
}
if (invocationTarget == null || invocationTarget.member instanceof Field) {
Field field = (Field) (invocationTarget==null?null:invocationTarget.member);
if (field == null) {
field = findField(name, type, target instanceof Class);
if (field != null) {
invocationTarget = new InvokerPair(field, new TypeDescriptor(field));
ReflectionUtils.makeAccessible(field);
this.readerCache.put(cacheKey, invocationTarget);
}
}
if (field != null) {
return new OptimalPropertyAccessor(invocationTarget);
}
}
return this;
}
/**
* An optimized form of a PropertyAccessor that will use reflection but only knows how to access a particular property
* on a particular class. This is unlike the general ReflectivePropertyResolver which manages a cache of methods/fields that
* may be invoked to access different properties on different classes. This optimal accessor exists because looking up
* the appropriate reflective object by class/name on each read is not cheap.
*/
static class OptimalPropertyAccessor implements PropertyAccessor {
private final Member member;
private final TypeDescriptor typeDescriptor;
private final boolean needsToBeMadeAccessible;
OptimalPropertyAccessor(InvokerPair target) {
this.member = target.member;
this.typeDescriptor = target.typeDescriptor;
if (this.member instanceof Field) {
Field field = (Field)member;
needsToBeMadeAccessible = (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()))
&& !field.isAccessible();
} else {
Method method = (Method)member;
needsToBeMadeAccessible = ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible());
}
}
public Class[] getSpecificTargetClasses() {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
if (target == null) {
return false;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray()) {
return false;
}
if (member instanceof Method) {
Method method = (Method)member;
String getterName = "get" + StringUtils.capitalize(name);
if (getterName.equals(method.getName())) {
return true;
}
getterName = "is" + StringUtils.capitalize(name);
return getterName.equals(method.getName());
} else {
Field field = (Field)member;
return field.getName().equals(name);
}
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
if (member instanceof Method) {
try {
if (needsToBeMadeAccessible) {
ReflectionUtils.makeAccessible((Method)member);
}
return new TypedValue(((Method)member).invoke(target),typeDescriptor);
} catch (Exception e) {
throw new AccessException("Unable to access property '" + name + "' through getter", e);
}
}
if (member instanceof Field) {
try {
if (needsToBeMadeAccessible) {
ReflectionUtils.makeAccessible((Field)member);
}
return new TypedValue(((Field)member).get(target),typeDescriptor);
}
catch (Exception ex) {
throw new AccessException("Unable to access field: " + name, ex);
}
}
throw new AccessException("Neither getter nor field found for property '" + name + "'");
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
public void write(EvaluationContext context, Object target, String name, Object newValue)
throws AccessException {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
}
}

View File

@ -47,15 +47,15 @@ public class StandardEvaluationContext implements EvaluationContext {
private TypedValue rootObject;
private final List<ConstructorResolver> constructorResolvers = new ArrayList<ConstructorResolver>();
private List<ConstructorResolver> constructorResolvers;
private final List<MethodResolver> methodResolvers = new ArrayList<MethodResolver>();
private List<MethodResolver> methodResolvers;
private final List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
private List<PropertyAccessor> propertyAccessors;
private TypeLocator typeLocator = new StandardTypeLocator();
private TypeLocator typeLocator;
private TypeConverter typeConverter = new StandardTypeConverter();
private TypeConverter typeConverter;
private TypeComparator typeComparator = new StandardTypeComparator();
@ -65,9 +65,7 @@ public class StandardEvaluationContext implements EvaluationContext {
public StandardEvaluationContext() {
this.methodResolvers.add(new ReflectiveMethodResolver());
this.constructorResolvers.add(new ReflectiveConstructorResolver());
this.propertyAccessors.add(new ReflectivePropertyResolver());
setRootObject(null);
}
public StandardEvaluationContext(Object rootObject) {
@ -77,7 +75,11 @@ public class StandardEvaluationContext implements EvaluationContext {
public void setRootObject(Object rootObject) {
this.rootObject = new TypedValue(rootObject, TypeDescriptor.forObject(rootObject));
if (this.rootObject == null) {
this.rootObject = TypedValue.NULL_TYPED_VALUE;
} else {
this.rootObject = new TypedValue(rootObject);//, TypeDescriptor.forObject(rootObject));
}
}
public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor) {
@ -89,35 +91,65 @@ public class StandardEvaluationContext implements EvaluationContext {
}
public void addConstructorResolver(ConstructorResolver resolver) {
ensureConstructorResolversInitialized();
this.constructorResolvers.add(this.constructorResolvers.size() - 1, resolver);
}
public List<ConstructorResolver> getConstructorResolvers() {
ensureConstructorResolversInitialized();
return this.constructorResolvers;
}
private void ensureConstructorResolversInitialized() {
if (this.constructorResolvers == null) {
this.constructorResolvers = new ArrayList<ConstructorResolver>();
this.constructorResolvers.add(new ReflectiveConstructorResolver());
}
}
public void addMethodResolver(MethodResolver resolver) {
ensureMethodResolversInitialized();
this.methodResolvers.add(this.methodResolvers.size() - 1, resolver);
}
public List<MethodResolver> getMethodResolvers() {
ensureMethodResolversInitialized();
return this.methodResolvers;
}
private void ensureMethodResolversInitialized() {
if (this.methodResolvers == null) {
this.methodResolvers = new ArrayList<MethodResolver>();
this.methodResolvers.add(new ReflectiveMethodResolver());
}
}
public void addPropertyAccessor(PropertyAccessor accessor) {
ensurePropertyAccessorsInitialized();
this.propertyAccessors.add(this.propertyAccessors.size() - 1, accessor);
}
public List<PropertyAccessor> getPropertyAccessors() {
ensurePropertyAccessorsInitialized();
return this.propertyAccessors;
}
private void ensurePropertyAccessorsInitialized() {
if (this.propertyAccessors == null) {
this.propertyAccessors = new ArrayList<PropertyAccessor>();
this.propertyAccessors.add(new ReflectivePropertyResolver());
}
}
public void setTypeLocator(TypeLocator typeLocator) {
Assert.notNull(typeLocator, "TypeLocator must not be null");
this.typeLocator = typeLocator;
}
public TypeLocator getTypeLocator() {
if (this.typeLocator == null) {
this.typeLocator = new StandardTypeLocator();
}
return this.typeLocator;
}
@ -127,6 +159,9 @@ public class StandardEvaluationContext implements EvaluationContext {
}
public TypeConverter getTypeConverter() {
if (this.typeConverter == null) {
this.typeConverter = new StandardTypeConverter();
}
return this.typeConverter;
}

View File

@ -16,6 +16,7 @@
package org.springframework.expression.spel;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Map;
@ -117,8 +118,10 @@ public class ExpressionStateTests extends ExpressionTestCase {
ExpressionState state = getState();
Assert.assertEquals(Inventor.class,state.getRootContextObject().getValue().getClass());
// although the root object is being set on the evaluation context, the value in the 'state' remains what it was when constructed
((StandardEvaluationContext) state.getEvaluationContext()).setRootObject(null);
Assert.assertEquals(null, state.getRootContextObject().getValue());
Assert.assertEquals(Inventor.class,state.getRootContextObject().getValue().getClass());
// Assert.assertEquals(null, state.getRootContextObject().getValue());
state = new ExpressionState(new StandardEvaluationContext());
Assert.assertEquals(TypedValue.NULL_TYPED_VALUE,state.getRootContextObject());
@ -133,6 +136,13 @@ public class ExpressionStateTests extends ExpressionTestCase {
ExpressionState state = getState();
Assert.assertEquals(state.getRootContextObject().getValue(),state.getActiveContextObject().getValue());
try {
state.popActiveContextObject();
Assert.fail("stack should be empty...");
} catch (EmptyStackException ese) {
// success
}
state.pushActiveContextObject(new TypedValue(34));
Assert.assertEquals(34,state.getActiveContextObject().getValue());
@ -168,6 +178,17 @@ public class ExpressionStateTests extends ExpressionTestCase {
Assert.assertNull(state.lookupLocalVariable("goo"));
}
@Test
public void testRootObjectConstructor() {
EvaluationContext ctx = getContext();
// TypedValue root = ctx.getRootObject();
// supplied should override root on context
ExpressionState state = new ExpressionState(ctx,new TypedValue("i am a string"));
TypedValue stateRoot = state.getRootContextObject();
Assert.assertEquals(String.class,stateRoot.getTypeDescriptor().getType());
Assert.assertEquals("i am a string",stateRoot.getValue());
}
@Test
public void testPopulatedNestedScopesMap() {
ExpressionState state = getState();
@ -257,5 +278,9 @@ public class ExpressionStateTests extends ExpressionTestCase {
ExpressionState state = new ExpressionState(context);
return state;
}
private EvaluationContext getContext() {
return TestScenarioCreator.getTestEvaluationContext();
}
}

View File

@ -26,6 +26,7 @@ import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ParseException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ast.FormatHelper;
import org.springframework.expression.spel.support.ReflectionHelper;
@ -307,6 +308,82 @@ public class HelperTests extends ExpressionTestCase {
// Assert.assertEquals(0,rpr.read(ctx,t,"field3").getValue());
Assert.assertEquals(false,rpr.read(ctx,t,"property4").getValue());
Assert.assertTrue(rpr.canRead(ctx,t,"property4"));
}
@Test
public void testOptimalReflectivePropertyResolver() throws Exception {
ReflectivePropertyResolver rpr = new ReflectivePropertyResolver();
Tester t = new Tester();
t.setProperty("hello");
EvaluationContext ctx = new StandardEvaluationContext(t);
// Assert.assertTrue(rpr.canRead(ctx, t, "property"));
// Assert.assertEquals("hello",rpr.read(ctx, t, "property").getValue());
// Assert.assertEquals("hello",rpr.read(ctx, t, "property").getValue()); // cached accessor used
PropertyAccessor optA = rpr.createOptimalAccessor(ctx, t, "property");
Assert.assertTrue(optA.canRead(ctx, t, "property"));
Assert.assertFalse(optA.canRead(ctx, t, "property2"));
try {
optA.canWrite(ctx, t, "property");
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
try {
optA.canWrite(ctx, t, "property2");
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
Assert.assertEquals("hello",optA.read(ctx, t, "property").getValue());
Assert.assertEquals("hello",optA.read(ctx, t, "property").getValue()); // cached accessor used
try {
optA.getSpecificTargetClasses();
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
try {
optA.write(ctx,t,"property",null);
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
optA = rpr.createOptimalAccessor(ctx, t, "field");
Assert.assertTrue(optA.canRead(ctx, t, "field"));
Assert.assertFalse(optA.canRead(ctx, t, "field2"));
try {
optA.canWrite(ctx, t, "field");
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
try {
optA.canWrite(ctx, t, "field2");
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
Assert.assertEquals(3,optA.read(ctx, t, "field").getValue());
Assert.assertEquals(3,optA.read(ctx, t, "field").getValue()); // cached accessor used
try {
optA.getSpecificTargetClasses();
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
try {
optA.write(ctx,t,"field",null);
Assert.fail();
} catch (UnsupportedOperationException uoe) {
// success
}
}

View File

@ -37,9 +37,17 @@ public class LiteralExpressionTests {
EvaluationContext ctx = new StandardEvaluationContext();
checkString("somevalue", lEx.getValue(ctx));
checkString("somevalue", lEx.getValue(ctx, String.class));
checkString("somevalue", lEx.getValue(new Rooty()));
checkString("somevalue", lEx.getValue(new Rooty(), String.class));
checkString("somevalue", lEx.getValue(ctx, new Rooty()));
checkString("somevalue", lEx.getValue(ctx, new Rooty(),String.class));
Assert.assertEquals("somevalue", lEx.getExpressionString());
Assert.assertFalse(lEx.isWritable(new StandardEvaluationContext()));
Assert.assertFalse(lEx.isWritable(new Rooty()));
Assert.assertFalse(lEx.isWritable(new StandardEvaluationContext(), new Rooty()));
}
static class Rooty {}
@Test
public void testSetValue() {
@ -52,6 +60,24 @@ public class LiteralExpressionTests {
// success, not allowed - whilst here, check the expression value in the exception
Assert.assertEquals(ee.getExpressionString(), "somevalue");
}
try {
LiteralExpression lEx = new LiteralExpression("somevalue");
lEx.setValue(new Rooty(), "flibble");
Assert.fail("Should have got an exception that the value cannot be set");
}
catch (EvaluationException ee) {
// success, not allowed - whilst here, check the expression value in the exception
Assert.assertEquals(ee.getExpressionString(), "somevalue");
}
try {
LiteralExpression lEx = new LiteralExpression("somevalue");
lEx.setValue(new StandardEvaluationContext(), new Rooty(), "flibble");
Assert.fail("Should have got an exception that the value cannot be set");
}
catch (EvaluationException ee) {
// success, not allowed - whilst here, check the expression value in the exception
Assert.assertEquals(ee.getExpressionString(), "somevalue");
}
}
@Test
@ -59,6 +85,12 @@ public class LiteralExpressionTests {
LiteralExpression lEx = new LiteralExpression("somevalue");
Assert.assertEquals(String.class, lEx.getValueType());
Assert.assertEquals(String.class, lEx.getValueType(new StandardEvaluationContext()));
Assert.assertEquals(String.class, lEx.getValueType(new Rooty()));
Assert.assertEquals(String.class, lEx.getValueType(new StandardEvaluationContext(), new Rooty()));
Assert.assertEquals(String.class, lEx.getValueTypeDescriptor().getType());
Assert.assertEquals(String.class, lEx.getValueTypeDescriptor(new StandardEvaluationContext()).getType());
Assert.assertEquals(String.class, lEx.getValueTypeDescriptor(new Rooty()).getType());
Assert.assertEquals(String.class, lEx.getValueTypeDescriptor(new StandardEvaluationContext(), new Rooty()).getType());
}
private void checkString(String expectedString, Object value) {

View File

@ -20,6 +20,7 @@ import junit.framework.Assert;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
@ -98,6 +99,10 @@ public class TemplateExpressionParsingTests extends ExpressionTestCase {
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
o = expr.getValue();
Assert.assertEquals("abc", o.toString());
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
o = expr.getValue((Object)null);
Assert.assertEquals("abc", o.toString());
}
@Test
@ -106,13 +111,53 @@ public class TemplateExpressionParsingTests extends ExpressionTestCase {
Expression ex = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
checkString("hello world", ex.getValue());
checkString("hello world", ex.getValue(String.class));
checkString("hello world", ex.getValue((Object)null, String.class));
checkString("hello world", ex.getValue(new Rooty()));
checkString("hello world", ex.getValue(new Rooty(), String.class));
EvaluationContext ctx = new StandardEvaluationContext();
checkString("hello world", ex.getValue(ctx));
checkString("hello world", ex.getValue(ctx, String.class));
checkString("hello world", ex.getValue(ctx, null, String.class));
checkString("hello world", ex.getValue(ctx, new Rooty()));
checkString("hello world", ex.getValue(ctx, new Rooty(), String.class));
checkString("hello world", ex.getValue(ctx, new Rooty(), String.class));
Assert.assertEquals("hello ${'world'}", ex.getExpressionString());
Assert.assertFalse(ex.isWritable(new StandardEvaluationContext()));
Assert.assertFalse(ex.isWritable(new Rooty()));
Assert.assertFalse(ex.isWritable(new StandardEvaluationContext(), new Rooty()));
Assert.assertEquals(String.class,ex.getValueType());
Assert.assertEquals(String.class,ex.getValueType(ctx));
Assert.assertEquals(String.class,ex.getValueTypeDescriptor().getType());
Assert.assertEquals(String.class,ex.getValueTypeDescriptor(ctx).getType());
Assert.assertEquals(String.class,ex.getValueType(new Rooty()));
Assert.assertEquals(String.class,ex.getValueType(ctx, new Rooty()));
Assert.assertEquals(String.class,ex.getValueTypeDescriptor(new Rooty()).getType());
Assert.assertEquals(String.class,ex.getValueTypeDescriptor(ctx, new Rooty()).getType());
try {
ex.setValue(ctx, null);
Assert.fail();
} catch (EvaluationException ee) {
// success
}
try {
ex.setValue((Object)null, null);
Assert.fail();
} catch (EvaluationException ee) {
// success
}
try {
ex.setValue(ctx, null, null);
Assert.fail();
} catch (EvaluationException ee) {
// success
}
}
static class Rooty {}
@Test
public void testNestedExpressions() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
@ -204,6 +249,16 @@ public class TemplateExpressionParsingTests extends ExpressionTestCase {
Assert.assertEquals("abc", tpc.getExpressionPrefix());
Assert.assertEquals("def", tpc.getExpressionSuffix());
Assert.assertTrue(tpc.isTemplate());
tpc = new TemplateParserContext();
Assert.assertEquals("#{", tpc.getExpressionPrefix());
Assert.assertEquals("}", tpc.getExpressionSuffix());
Assert.assertTrue(tpc.isTemplate());
ParserContext pc = ParserContext.TEMPLATE_EXPRESSION;
Assert.assertEquals("#{", pc.getExpressionPrefix());
Assert.assertEquals("}", pc.getExpressionSuffix());
Assert.assertTrue(pc.isTemplate());
}
// ---