SPR-6230: SpEL improvements
This commit is contained in:
parent
06286b19ce
commit
c9057fd1da
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
// ---
|
||||
|
|
|
|||
Loading…
Reference in New Issue