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:
parent
cd37a26586
commit
fd15a9a822
|
|
@ -33,7 +33,7 @@ import java.util.List;
|
||||||
public interface EvaluationContext {
|
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();
|
TypedValue getRootObject();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,16 @@ public interface Expression {
|
||||||
public Object getValue() throws EvaluationException;
|
public Object getValue() throws EvaluationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate the expression in the default standard context. If the result of the evaluation does not match (and
|
* 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 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.
|
* 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
|
* @param desiredResultType the class the caller would like the result to be
|
||||||
|
|
@ -46,6 +55,18 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public <T> T getValue(Class<T> desiredResultType) throws EvaluationException;
|
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.
|
* 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;
|
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 -
|
* 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
|
* 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;
|
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
|
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
|
||||||
* the default context.
|
* the default context.
|
||||||
|
|
@ -76,6 +122,16 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public Class getValueType() throws EvaluationException;
|
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
|
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
|
||||||
* the given context.
|
* the given context.
|
||||||
|
|
@ -86,6 +142,17 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public Class getValueType(EvaluationContext context) throws EvaluationException;
|
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
|
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
|
||||||
* the default context.
|
* the default context.
|
||||||
|
|
@ -95,6 +162,16 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
|
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
|
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
|
||||||
* the given context.
|
* the given context.
|
||||||
|
|
@ -105,6 +182,17 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
|
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.
|
* Determine if an expression can be written to, i.e. setValue() can be called.
|
||||||
*
|
*
|
||||||
|
|
@ -114,6 +202,26 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public boolean isWritable(EvaluationContext context) throws EvaluationException;
|
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.
|
* Set this expression in the provided context to the value provided.
|
||||||
*
|
*
|
||||||
|
|
@ -123,6 +231,26 @@ public interface Expression {
|
||||||
*/
|
*/
|
||||||
public void setValue(EvaluationContext context, Object value) throws EvaluationException;
|
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.
|
* Returns the original string used to create this expression, unmodified.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ package org.springframework.expression;
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class ExpressionException extends Exception {
|
public class ExpressionException extends RuntimeException {
|
||||||
|
|
||||||
protected String expressionString;
|
protected String expressionString;
|
||||||
protected int position; // -1 if not known - but should be known in all reasonable cases
|
protected int position; // -1 if not known - but should be known in all reasonable cases
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,6 @@ package org.springframework.expression;
|
||||||
* turn. The only rule that affects the call order is that any naming the target class directly 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.
|
* 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
|
* @author Andy Clement
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public class TypedValue {
|
||||||
|
|
||||||
private final Object value;
|
private final Object value;
|
||||||
|
|
||||||
private final TypeDescriptor typeDescriptor;
|
private TypeDescriptor typeDescriptor;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,7 +43,7 @@ public class TypedValue {
|
||||||
*/
|
*/
|
||||||
public TypedValue(Object value) {
|
public TypedValue(Object value) {
|
||||||
this.value = 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() {
|
public TypeDescriptor getTypeDescriptor() {
|
||||||
|
if (this.typeDescriptor==null) {
|
||||||
|
this.typeDescriptor = TypeDescriptor.forObject(value);
|
||||||
|
}
|
||||||
return this.typeDescriptor;
|
return this.typeDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,15 @@ public class CompositeStringExpression implements Expression {
|
||||||
return sb.toString();
|
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) {
|
public Class getValueType(EvaluationContext context) {
|
||||||
return String.class;
|
return String.class;
|
||||||
}
|
}
|
||||||
|
|
@ -109,4 +118,57 @@ public class CompositeStringExpression implements Expression {
|
||||||
return expressions;
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,10 @@ public class LiteralExpression implements Expression {
|
||||||
return this.literalValue;
|
return this.literalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getValue(Object rootObject) {
|
||||||
|
return this.literalValue;
|
||||||
|
}
|
||||||
|
|
||||||
public Class getValueType(EvaluationContext context) {
|
public Class getValueType(EvaluationContext context) {
|
||||||
return String.class;
|
return String.class;
|
||||||
}
|
}
|
||||||
|
|
@ -86,4 +90,61 @@ public class LiteralExpression implements Expression {
|
||||||
return String.class;
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,60 +46,71 @@ public class ExpressionState {
|
||||||
|
|
||||||
private final EvaluationContext relatedContext;
|
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;
|
private int configuration = 0;
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context) {
|
public ExpressionState(EvaluationContext context) {
|
||||||
this.relatedContext = context;
|
this.relatedContext = context;
|
||||||
createVariableScope();
|
this.rootObject = context.getRootObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context, int configuration) {
|
public ExpressionState(EvaluationContext context, int configuration) {
|
||||||
this.relatedContext = context;
|
this.relatedContext = context;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
createVariableScope();
|
this.rootObject = context.getRootObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an empty top level VariableScope
|
public ExpressionState(EvaluationContext context, TypedValue rootObject) {
|
||||||
private void createVariableScope() {
|
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());
|
this.variableScopes.add(new VariableScope());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The active context object is what unqualified references to properties/etc are resolved against.
|
* The active context object is what unqualified references to properties/etc are resolved against.
|
||||||
*/
|
*/
|
||||||
public TypedValue getActiveContextObject() {
|
public TypedValue getActiveContextObject() {
|
||||||
if (this.contextObjects.isEmpty()) {
|
if (this.contextObjects==null || this.contextObjects.isEmpty()) {
|
||||||
TypedValue rootObject = this.relatedContext.getRootObject();
|
return this.rootObject;
|
||||||
if (rootObject == null) {
|
|
||||||
return TypedValue.NULL_TYPED_VALUE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return rootObject;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.contextObjects.peek();
|
return this.contextObjects.peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushActiveContextObject(TypedValue obj) {
|
public void pushActiveContextObject(TypedValue obj) {
|
||||||
|
if (this.contextObjects==null) {
|
||||||
|
this.contextObjects = new Stack<TypedValue>();
|
||||||
|
}
|
||||||
this.contextObjects.push(obj);
|
this.contextObjects.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void popActiveContextObject() {
|
public void popActiveContextObject() {
|
||||||
|
if (this.contextObjects==null) {
|
||||||
|
this.contextObjects = new Stack<TypedValue>();
|
||||||
|
}
|
||||||
this.contextObjects.pop();
|
this.contextObjects.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypedValue getRootContextObject() {
|
public TypedValue getRootContextObject() {
|
||||||
TypedValue root = this.relatedContext.getRootObject();
|
return this.rootObject;
|
||||||
if (root == null) {
|
|
||||||
return TypedValue.NULL_TYPED_VALUE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVariable(String name, Object value) {
|
public void setVariable(String name, Object value) {
|
||||||
|
|
@ -137,22 +148,27 @@ public class ExpressionState {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public void enterScope(Map<String, Object> argMap) {
|
public void enterScope(Map<String, Object> argMap) {
|
||||||
|
ensureVariableScopesInitialized();
|
||||||
this.variableScopes.push(new VariableScope(argMap));
|
this.variableScopes.push(new VariableScope(argMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enterScope(String name, Object value) {
|
public void enterScope(String name, Object value) {
|
||||||
|
ensureVariableScopesInitialized();
|
||||||
this.variableScopes.push(new VariableScope(name, value));
|
this.variableScopes.push(new VariableScope(name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void exitScope() {
|
public void exitScope() {
|
||||||
|
ensureVariableScopesInitialized();
|
||||||
this.variableScopes.pop();
|
this.variableScopes.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocalVariable(String name, Object value) {
|
public void setLocalVariable(String name, Object value) {
|
||||||
|
ensureVariableScopesInitialized();
|
||||||
this.variableScopes.peek().setVariable(name, value);
|
this.variableScopes.peek().setVariable(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object lookupLocalVariable(String name) {
|
public Object lookupLocalVariable(String name) {
|
||||||
|
ensureVariableScopesInitialized();
|
||||||
int scopeNumber = this.variableScopes.size() - 1;
|
int scopeNumber = this.variableScopes.size() - 1;
|
||||||
for (int i = scopeNumber; i >= 0; i--) {
|
for (int i = scopeNumber; i >= 0; i--) {
|
||||||
if (this.variableScopes.get(i).definesVariable(name)) {
|
if (this.variableScopes.get(i).definesVariable(name)) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import org.springframework.core.convert.TypeDescriptor;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.TypedValue;
|
||||||
import org.springframework.expression.common.ExpressionUtils;
|
import org.springframework.expression.common.ExpressionUtils;
|
||||||
import org.springframework.expression.spel.ast.SpelNodeImpl;
|
import org.springframework.expression.spel.ast.SpelNodeImpl;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
|
@ -37,6 +38,9 @@ public class SpelExpression implements Expression {
|
||||||
|
|
||||||
private final String 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 SpelNodeImpl ast;
|
||||||
|
|
||||||
public final int configuration;
|
public final int configuration;
|
||||||
|
|
@ -53,12 +57,23 @@ public class SpelExpression implements Expression {
|
||||||
// implementing Expression
|
// implementing Expression
|
||||||
|
|
||||||
public Object getValue() throws EvaluationException {
|
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);
|
return ast.getValue(expressionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
|
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);
|
Object result = ast.getValue(expressionState);
|
||||||
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
|
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +83,11 @@ public class SpelExpression implements Expression {
|
||||||
return ast.getValue(new ExpressionState(context, configuration));
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
||||||
Object result = ast.getValue(new ExpressionState(context, configuration));
|
Object result = ast.getValue(new ExpressionState(context, configuration));
|
||||||
|
|
@ -81,8 +101,22 @@ public class SpelExpression implements Expression {
|
||||||
return (T) result;
|
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 {
|
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 {
|
public Class getValueType(EvaluationContext context) throws EvaluationException {
|
||||||
|
|
@ -92,8 +126,23 @@ public class SpelExpression implements Expression {
|
||||||
return typeDescriptor.getType();
|
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 {
|
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 {
|
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
|
||||||
|
|
@ -102,6 +151,12 @@ public class SpelExpression implements Expression {
|
||||||
return ast.getValueInternal(eState).getTypeDescriptor();
|
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() {
|
public String getExpressionString() {
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
@ -111,11 +166,30 @@ public class SpelExpression implements Expression {
|
||||||
return ast.isWritable(new ExpressionState(context, configuration));
|
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 {
|
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
|
||||||
Assert.notNull(context, "The EvaluationContext is required");
|
Assert.notNull(context, "The EvaluationContext is required");
|
||||||
ast.setValue(new ExpressionState(context, configuration), value);
|
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
|
// impl only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -136,4 +210,32 @@ public class SpelExpression implements Expression {
|
||||||
return ast.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,6 @@ public class Indexer extends SpelNodeImpl {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue, Class clazz) throws EvaluationException {
|
private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue, Class clazz) throws EvaluationException {
|
||||||
Class<?> arrayComponentType = clazz;
|
Class<?> arrayComponentType = clazz;
|
||||||
if (arrayComponentType == Integer.TYPE) {
|
if (arrayComponentType == Integer.TYPE) {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
TypedValue currentContext = state.getActiveContextObject();
|
TypedValue currentContext = state.getActiveContextObject();
|
||||||
Object[] arguments = new Object[getChildCount()];
|
Object[] arguments = new Object[getChildCount()];
|
||||||
for (int i = 0; i < arguments.length; i++) {
|
for (int i = 0; i < arguments.length; i++) {
|
||||||
// System.out.println(i);
|
|
||||||
arguments[i] = children[i].getValueInternal(state).getValue();
|
arguments[i] = children[i].getValueInternal(state).getValue();
|
||||||
}
|
}
|
||||||
if (currentContext.getValue() == null) {
|
if (currentContext.getValue() == null) {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ public class Projection extends SpelNodeImpl {
|
||||||
this.nullSafe = nullSafe;
|
this.nullSafe = nullSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
TypedValue op = state.getActiveContextObject();
|
TypedValue op = state.getActiveContextObject();
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import org.springframework.expression.TypedValue;
|
||||||
import org.springframework.expression.spel.ExpressionState;
|
import org.springframework.expression.spel.ExpressionState;
|
||||||
import org.springframework.expression.spel.SpelEvaluationException;
|
import org.springframework.expression.spel.SpelEvaluationException;
|
||||||
import org.springframework.expression.spel.SpelMessage;
|
import org.springframework.expression.spel.SpelMessage;
|
||||||
|
import org.springframework.expression.spel.support.ReflectivePropertyResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a simple property or field reference.
|
* Represents a simple property or field reference.
|
||||||
|
|
@ -57,9 +58,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
TypedValue result = readProperty(state, this.name);
|
TypedValue result = readProperty(state, this.name);
|
||||||
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
|
|
||||||
// Dynamically create the objects if the user has requested that optional behaviour
|
// Dynamically create the objects if the user has requested that optional behaviour
|
||||||
if (result.getValue()==null && state.configuredToDynamicallyCreateNullObjects() && nextChildIs(Indexer.class,PropertyOrFieldReference.class)) {
|
if (result.getValue()==null && state.configuredToDynamicallyCreateNullObjects() && nextChildIs(Indexer.class,PropertyOrFieldReference.class)) {
|
||||||
|
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
|
||||||
// Creating lists and maps
|
// Creating lists and maps
|
||||||
if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
|
if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
|
||||||
// Create a new collection or map ready for the indexer
|
// 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 {
|
private TypedValue readProperty(ExpressionState state, String name) throws EvaluationException {
|
||||||
TypedValue contextObject = state.getActiveContextObject();
|
TypedValue contextObject = state.getActiveContextObject();
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
|
||||||
Object targetObject = contextObject.getValue();
|
Object targetObject = contextObject.getValue();
|
||||||
|
|
||||||
if (targetObject == null && nullSafe) {
|
if (targetObject == null && nullSafe) {
|
||||||
|
|
@ -151,6 +152,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
||||||
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
|
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
|
// 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
|
// 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 {
|
try {
|
||||||
for (PropertyAccessor accessor : accessorsToTry) {
|
for (PropertyAccessor accessor : accessorsToTry) {
|
||||||
if (accessor.canRead(eContext, contextObject.getValue(), name)) {
|
if (accessor.canRead(eContext, contextObject.getValue(), name)) {
|
||||||
|
if (accessor instanceof ReflectivePropertyResolver) {
|
||||||
|
accessor = ((ReflectivePropertyResolver)accessor).createOptimalAccessor(eContext, contextObject.getValue(), name);
|
||||||
|
}
|
||||||
this.cachedReadAccessor = accessor;
|
this.cachedReadAccessor = accessor;
|
||||||
return accessor.read(eContext, contextObject.getValue(), name);
|
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 {
|
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
|
||||||
Object contextObject = state.getActiveContextObject().getValue();
|
Object contextObject = state.getActiveContextObject().getValue();
|
||||||
TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
EvaluationContext eContext = state.getEvaluationContext();
|
||||||
|
|
||||||
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
|
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,11 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
public class ReflectivePropertyResolver implements PropertyAccessor {
|
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
|
* @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"))) {
|
if ((type.isArray() && name.equals("length"))) {
|
||||||
return true;
|
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);
|
CacheKey cacheKey = new CacheKey(type, name);
|
||||||
if (this.readerCache.containsKey(cacheKey)) {
|
if (this.readerCache.containsKey(cacheKey)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Method method = findGetterForProperty(name, type, target instanceof Class);
|
Method method = findGetterForProperty(name, type, target instanceof Class);
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
this.readerCache.put(cacheKey, method);
|
TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method,-1));
|
||||||
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,-1)));
|
this.readerCache.put(cacheKey, new InvokerPair(method,typeDescriptor));
|
||||||
|
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Field field = findField(name, type, target instanceof Class);
|
Field field = findField(name, type, target instanceof Class);
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
this.readerCache.put(cacheKey, field);
|
TypeDescriptor typeDescriptor = new TypeDescriptor(field);
|
||||||
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
|
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
|
||||||
|
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,23 +107,25 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
|
||||||
return new TypedValue(Array.getLength(target),TypeDescriptor.valueOf(Integer.TYPE));
|
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);
|
CacheKey cacheKey = new CacheKey(type, name);
|
||||||
Member cachedMember = this.readerCache.get(cacheKey);
|
InvokerPair invoker = this.readerCache.get(cacheKey);
|
||||||
|
|
||||||
if (cachedMember == null || cachedMember instanceof Method) {
|
if (invoker == null || invoker.member instanceof Method) {
|
||||||
Method method = (Method) cachedMember;
|
Method method = (Method) (invoker==null?null:invoker.member);
|
||||||
if (method == null) {
|
if (method == null) {
|
||||||
method = findGetterForProperty(name, type, target instanceof Class);
|
method = findGetterForProperty(name, type, target instanceof Class);
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
cachedMember = method;
|
invoker = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
|
||||||
this.readerCache.put(cacheKey, cachedMember);
|
this.readerCache.put(cacheKey, invoker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
try {
|
try {
|
||||||
ReflectionUtils.makeAccessible(method);
|
ReflectionUtils.makeAccessible(method);
|
||||||
TypeDescriptor resultTypeDescriptor = new TypeDescriptor(new MethodParameter(method,-1));
|
return new TypedValue(method.invoke(target),invoker.typeDescriptor);
|
||||||
return new TypedValue(method.invoke(target),resultTypeDescriptor);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new AccessException("Unable to access property '" + name + "' through getter", 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) {
|
if (invoker == null || invoker.member instanceof Field) {
|
||||||
Field field = (Field) cachedMember;
|
Field field = (Field) (invoker==null?null:invoker.member);
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
field = findField(name, type, target instanceof Class);
|
field = findField(name, type, target instanceof Class);
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
cachedMember = field;
|
invoker = new InvokerPair(field, new TypeDescriptor(field));
|
||||||
this.readerCache.put(cacheKey, cachedMember);
|
this.readerCache.put(cacheKey, invoker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
try {
|
try {
|
||||||
ReflectionUtils.makeAccessible(field);
|
ReflectionUtils.makeAccessible(field);
|
||||||
return new TypedValue(field.get(target),new TypeDescriptor(field));
|
return new TypedValue(field.get(target),invoker.typeDescriptor);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new AccessException("Unable to access field: " + name, ex);
|
throw new AccessException("Unable to access field: " + name, ex);
|
||||||
|
|
@ -151,6 +161,12 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
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);
|
CacheKey cacheKey = new CacheKey(type, name);
|
||||||
if (this.writerCache.containsKey(cacheKey)) {
|
if (this.writerCache.containsKey(cacheKey)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -187,7 +203,9 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
|
||||||
throw new AccessException("Type conversion failure",evaluationException);
|
throw new AccessException("Type conversion failure",evaluationException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.writerCache == null) {
|
||||||
|
this.writerCache = new ConcurrentHashMap<CacheKey, Member>();
|
||||||
|
}
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name);
|
||||||
Member cachedMember = this.writerCache.get(cacheKey);
|
Member cachedMember = this.writerCache.get(cacheKey);
|
||||||
|
|
||||||
|
|
@ -218,7 +236,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
|
||||||
field = findField(name, type, target instanceof Class);
|
field = findField(name, type, target instanceof Class);
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
cachedMember = field;
|
cachedMember = field;
|
||||||
this.readerCache.put(cacheKey, cachedMember);
|
this.writerCache.put(cacheKey, cachedMember);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
|
|
@ -316,6 +334,22 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
|
||||||
return null;
|
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 {
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,15 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||||
|
|
||||||
private TypedValue rootObject;
|
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();
|
private TypeComparator typeComparator = new StandardTypeComparator();
|
||||||
|
|
||||||
|
|
@ -65,9 +65,7 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||||
|
|
||||||
|
|
||||||
public StandardEvaluationContext() {
|
public StandardEvaluationContext() {
|
||||||
this.methodResolvers.add(new ReflectiveMethodResolver());
|
setRootObject(null);
|
||||||
this.constructorResolvers.add(new ReflectiveConstructorResolver());
|
|
||||||
this.propertyAccessors.add(new ReflectivePropertyResolver());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StandardEvaluationContext(Object rootObject) {
|
public StandardEvaluationContext(Object rootObject) {
|
||||||
|
|
@ -77,7 +75,11 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||||
|
|
||||||
|
|
||||||
public void setRootObject(Object rootObject) {
|
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) {
|
public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor) {
|
||||||
|
|
@ -89,35 +91,65 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addConstructorResolver(ConstructorResolver resolver) {
|
public void addConstructorResolver(ConstructorResolver resolver) {
|
||||||
|
ensureConstructorResolversInitialized();
|
||||||
this.constructorResolvers.add(this.constructorResolvers.size() - 1, resolver);
|
this.constructorResolvers.add(this.constructorResolvers.size() - 1, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ConstructorResolver> getConstructorResolvers() {
|
public List<ConstructorResolver> getConstructorResolvers() {
|
||||||
|
ensureConstructorResolversInitialized();
|
||||||
return this.constructorResolvers;
|
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) {
|
public void addMethodResolver(MethodResolver resolver) {
|
||||||
|
ensureMethodResolversInitialized();
|
||||||
this.methodResolvers.add(this.methodResolvers.size() - 1, resolver);
|
this.methodResolvers.add(this.methodResolvers.size() - 1, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MethodResolver> getMethodResolvers() {
|
public List<MethodResolver> getMethodResolvers() {
|
||||||
|
ensureMethodResolversInitialized();
|
||||||
return this.methodResolvers;
|
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) {
|
public void addPropertyAccessor(PropertyAccessor accessor) {
|
||||||
|
ensurePropertyAccessorsInitialized();
|
||||||
this.propertyAccessors.add(this.propertyAccessors.size() - 1, accessor);
|
this.propertyAccessors.add(this.propertyAccessors.size() - 1, accessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PropertyAccessor> getPropertyAccessors() {
|
public List<PropertyAccessor> getPropertyAccessors() {
|
||||||
|
ensurePropertyAccessorsInitialized();
|
||||||
return this.propertyAccessors;
|
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) {
|
public void setTypeLocator(TypeLocator typeLocator) {
|
||||||
Assert.notNull(typeLocator, "TypeLocator must not be null");
|
Assert.notNull(typeLocator, "TypeLocator must not be null");
|
||||||
this.typeLocator = typeLocator;
|
this.typeLocator = typeLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeLocator getTypeLocator() {
|
public TypeLocator getTypeLocator() {
|
||||||
|
if (this.typeLocator == null) {
|
||||||
|
this.typeLocator = new StandardTypeLocator();
|
||||||
|
}
|
||||||
return this.typeLocator;
|
return this.typeLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,6 +159,9 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeConverter getTypeConverter() {
|
public TypeConverter getTypeConverter() {
|
||||||
|
if (this.typeConverter == null) {
|
||||||
|
this.typeConverter = new StandardTypeConverter();
|
||||||
|
}
|
||||||
return this.typeConverter;
|
return this.typeConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel;
|
package org.springframework.expression.spel;
|
||||||
|
|
||||||
|
import java.util.EmptyStackException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
@ -117,8 +118,10 @@ public class ExpressionStateTests extends ExpressionTestCase {
|
||||||
ExpressionState state = getState();
|
ExpressionState state = getState();
|
||||||
Assert.assertEquals(Inventor.class,state.getRootContextObject().getValue().getClass());
|
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);
|
((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());
|
state = new ExpressionState(new StandardEvaluationContext());
|
||||||
Assert.assertEquals(TypedValue.NULL_TYPED_VALUE,state.getRootContextObject());
|
Assert.assertEquals(TypedValue.NULL_TYPED_VALUE,state.getRootContextObject());
|
||||||
|
|
@ -133,6 +136,13 @@ public class ExpressionStateTests extends ExpressionTestCase {
|
||||||
ExpressionState state = getState();
|
ExpressionState state = getState();
|
||||||
Assert.assertEquals(state.getRootContextObject().getValue(),state.getActiveContextObject().getValue());
|
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));
|
state.pushActiveContextObject(new TypedValue(34));
|
||||||
Assert.assertEquals(34,state.getActiveContextObject().getValue());
|
Assert.assertEquals(34,state.getActiveContextObject().getValue());
|
||||||
|
|
||||||
|
|
@ -168,6 +178,17 @@ public class ExpressionStateTests extends ExpressionTestCase {
|
||||||
Assert.assertNull(state.lookupLocalVariable("goo"));
|
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
|
@Test
|
||||||
public void testPopulatedNestedScopesMap() {
|
public void testPopulatedNestedScopesMap() {
|
||||||
ExpressionState state = getState();
|
ExpressionState state = getState();
|
||||||
|
|
@ -258,4 +279,8 @@ public class ExpressionStateTests extends ExpressionTestCase {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EvaluationContext getContext() {
|
||||||
|
return TestScenarioCreator.getTestEvaluationContext();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import org.junit.Test;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.ParseException;
|
import org.springframework.expression.ParseException;
|
||||||
|
import org.springframework.expression.PropertyAccessor;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
import org.springframework.expression.spel.ast.FormatHelper;
|
import org.springframework.expression.spel.ast.FormatHelper;
|
||||||
import org.springframework.expression.spel.support.ReflectionHelper;
|
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(0,rpr.read(ctx,t,"field3").getValue());
|
||||||
Assert.assertEquals(false,rpr.read(ctx,t,"property4").getValue());
|
Assert.assertEquals(false,rpr.read(ctx,t,"property4").getValue());
|
||||||
Assert.assertTrue(rpr.canRead(ctx,t,"property4"));
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,18 @@ public class LiteralExpressionTests {
|
||||||
EvaluationContext ctx = new StandardEvaluationContext();
|
EvaluationContext ctx = new StandardEvaluationContext();
|
||||||
checkString("somevalue", lEx.getValue(ctx));
|
checkString("somevalue", lEx.getValue(ctx));
|
||||||
checkString("somevalue", lEx.getValue(ctx, String.class));
|
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.assertEquals("somevalue", lEx.getExpressionString());
|
||||||
Assert.assertFalse(lEx.isWritable(new StandardEvaluationContext()));
|
Assert.assertFalse(lEx.isWritable(new StandardEvaluationContext()));
|
||||||
|
Assert.assertFalse(lEx.isWritable(new Rooty()));
|
||||||
|
Assert.assertFalse(lEx.isWritable(new StandardEvaluationContext(), new Rooty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class Rooty {}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetValue() {
|
public void testSetValue() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -52,6 +60,24 @@ public class LiteralExpressionTests {
|
||||||
// success, not allowed - whilst here, check the expression value in the exception
|
// success, not allowed - whilst here, check the expression value in the exception
|
||||||
Assert.assertEquals(ee.getExpressionString(), "somevalue");
|
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
|
@Test
|
||||||
|
|
@ -59,6 +85,12 @@ public class LiteralExpressionTests {
|
||||||
LiteralExpression lEx = new LiteralExpression("somevalue");
|
LiteralExpression lEx = new LiteralExpression("somevalue");
|
||||||
Assert.assertEquals(String.class, lEx.getValueType());
|
Assert.assertEquals(String.class, lEx.getValueType());
|
||||||
Assert.assertEquals(String.class, lEx.getValueType(new StandardEvaluationContext()));
|
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) {
|
private void checkString(String expectedString, Object value) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import junit.framework.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ParseException;
|
import org.springframework.expression.ParseException;
|
||||||
import org.springframework.expression.ParserContext;
|
import org.springframework.expression.ParserContext;
|
||||||
|
|
@ -98,6 +99,10 @@ public class TemplateExpressionParsingTests extends ExpressionTestCase {
|
||||||
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||||
o = expr.getValue();
|
o = expr.getValue();
|
||||||
Assert.assertEquals("abc", o.toString());
|
Assert.assertEquals("abc", o.toString());
|
||||||
|
|
||||||
|
expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||||
|
o = expr.getValue((Object)null);
|
||||||
|
Assert.assertEquals("abc", o.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -106,12 +111,52 @@ public class TemplateExpressionParsingTests extends ExpressionTestCase {
|
||||||
Expression ex = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
Expression ex = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||||
checkString("hello world", ex.getValue());
|
checkString("hello world", ex.getValue());
|
||||||
checkString("hello world", ex.getValue(String.class));
|
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();
|
EvaluationContext ctx = new StandardEvaluationContext();
|
||||||
checkString("hello world", ex.getValue(ctx));
|
checkString("hello world", ex.getValue(ctx));
|
||||||
checkString("hello world", ex.getValue(ctx, String.class));
|
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.assertEquals("hello ${'world'}", ex.getExpressionString());
|
||||||
Assert.assertFalse(ex.isWritable(new StandardEvaluationContext()));
|
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
|
@Test
|
||||||
public void testNestedExpressions() throws Exception {
|
public void testNestedExpressions() throws Exception {
|
||||||
|
|
@ -204,6 +249,16 @@ public class TemplateExpressionParsingTests extends ExpressionTestCase {
|
||||||
Assert.assertEquals("abc", tpc.getExpressionPrefix());
|
Assert.assertEquals("abc", tpc.getExpressionPrefix());
|
||||||
Assert.assertEquals("def", tpc.getExpressionSuffix());
|
Assert.assertEquals("def", tpc.getExpressionSuffix());
|
||||||
Assert.assertTrue(tpc.isTemplate());
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue