diff --git a/spring-expression/src/main/java/org/springframework/expression/Expression.java b/spring-expression/src/main/java/org/springframework/expression/Expression.java index 98982967bc..2be2d0319f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/Expression.java +++ b/spring-expression/src/main/java/org/springframework/expression/Expression.java @@ -20,18 +20,19 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; /** - * An expression capable of evaluating itself against context objects. Encapsulates the - * details of a previously parsed expression string. Provides a common abstraction for - * expression evaluation independent of any language like OGNL or the Unified EL. + * An expression capable of evaluating itself against context objects. + * Encapsulates the details of a previously parsed expression string. + * Provides a common abstraction for expression evaluation. * * @author Keith Donald * @author Andy Clement + * @author Juergen Hoeller * @since 3.0 */ public interface Expression { /** - * Return the original string used to create this expression, unmodified. + * Return the original string used to create this expression (unmodified). * @return the original expression string */ String getExpressionString(); @@ -45,8 +46,9 @@ public interface Expression { Object getValue() 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. + * 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 * @return the evaluation result * @throws EvaluationException if there is a problem during evaluation @@ -55,8 +57,8 @@ public interface Expression { T getValue(@Nullable Class desiredResultType) throws EvaluationException; /** - * Evaluate this expression against the specified root object - * @param rootObject the root object against which properties/etc will be resolved + * Evaluate this expression against the specified root object. + * @param rootObject the root object against which to evaluate the expression * @return the evaluation result * @throws EvaluationException if there is a problem during evaluation */ @@ -64,10 +66,10 @@ public interface Expression { Object getValue(Object rootObject) 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 + * 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 to evaluate the expression * @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 @@ -76,7 +78,8 @@ public interface Expression { T getValue(Object rootObject, @Nullable Class 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. * @param context the context in which to evaluate the expression * @return the evaluation result * @throws EvaluationException if there is a problem during evaluation @@ -85,10 +88,11 @@ public interface Expression { 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. + * 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 + * @param rootObject the root object against which to evaluate the expression * @return the evaluation result * @throws EvaluationException if there is a problem during evaluation */ @@ -96,8 +100,9 @@ public interface Expression { 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 + * 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. * @param context the context in which to evaluate the expression * @param desiredResultType the class the caller would like the result to be @@ -108,12 +113,13 @@ public interface Expression { T getValue(EvaluationContext context, @Nullable Class 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. + * 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 rootObject the root object against which to evaluate the expression * @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 @@ -123,7 +129,7 @@ public interface Expression { throws EvaluationException; /** - * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} + * Return the most general type that can be passed to a {@link #setValue} * method using the default context. * @return the most general type of value that can be set on this context * @throws EvaluationException if there is a problem determining the type @@ -132,8 +138,8 @@ public interface Expression { Class getValueType() throws EvaluationException; /** - * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} - * method using the default context. + * Return the most general type that can be passed to the + * {@link #setValue(Object, 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 @@ -142,8 +148,8 @@ public interface Expression { 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. + * Return the most general type that can be passed to the + * {@link #setValue(EvaluationContext, Object)} method for the given context. * @param context the context in 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 @@ -152,8 +158,9 @@ public interface Expression { 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. + * Return the most general type that can be passed to the + * {@link #setValue(EvaluationContext, Object, 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 @@ -163,40 +170,41 @@ public interface Expression { Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException; /** - * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} + * Return the most general type that can be passed to a {@link #setValue} * method using the default context. - * @return a type descriptor for the most general type of value that can be set on this context + * @return a type descriptor for values that can be set on this context * @throws EvaluationException if there is a problem determining the type */ @Nullable TypeDescriptor getValueTypeDescriptor() throws EvaluationException; /** - * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} - * method using the default context. + * Return the most general type that can be passed to the + * {@link #setValue(Object, 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 + * @return a type descriptor for values that can be set on this context * @throws EvaluationException if there is a problem determining the type */ @Nullable TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException; /** - * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} - * method for the given context. + * Return the most general type that can be passed to the + * {@link #setValue(EvaluationContext, Object)} method for the given context. * @param context the context in which to evaluate the expression - * @return a type descriptor for the most general type of value that can be set on this context + * @return a type descriptor for values that can be set on this context * @throws EvaluationException if there is a problem determining the type */ @Nullable 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. + * Return the most general type that can be passed to the + * {@link #setValue(EvaluationContext, Object, 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 + * @return a type descriptor for values that can be set on this context * @throws EvaluationException if there is a problem determining the type */ @Nullable @@ -205,7 +213,7 @@ public interface Expression { /** * 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 + * @return {@code true} if the expression is writable; {@code false} otherwise * @throws EvaluationException if there is a problem determining if it is writable */ boolean isWritable(Object rootObject) throws EvaluationException; @@ -213,7 +221,7 @@ public interface Expression { /** * Determine if an expression can be written to, i.e. setValue() can be called. * @param context the context in which the expression should be checked - * @return true if the expression is writable + * @return {@code true} if the expression is writable; {@code false} otherwise * @throws EvaluationException if there is a problem determining if it is writable */ boolean isWritable(EvaluationContext context) throws EvaluationException; @@ -223,7 +231,7 @@ public interface Expression { * 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 + * @return {@code true} if the expression is writable; {@code false} otherwise * @throws EvaluationException if there is a problem determining if it is writable */ boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index 181ab03cee..ae895ce288 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java @@ -115,7 +115,6 @@ public class SpelExpression implements Expression { @Override public Object getValue() throws EvaluationException { - Object result; if (this.compiledAst != null) { try { TypedValue contextRoot = @@ -135,8 +134,9 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); - result = this.ast.getValue(expressionState); + Object result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } @@ -170,6 +170,7 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); @@ -179,7 +180,6 @@ public class SpelExpression implements Expression { @Override public Object getValue(Object rootObject) throws EvaluationException { - Object result; if (this.compiledAst != null) { try { return this.compiledAst.getValue(rootObject, evaluationContext); @@ -196,9 +196,10 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); - result = this.ast.getValue(expressionState); + Object result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } @@ -229,6 +230,7 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); @@ -240,7 +242,8 @@ public class SpelExpression implements Expression { @Override public Object getValue(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); - if (compiledAst!= null) { + + if (this.compiledAst != null) { try { TypedValue contextRoot = context.getRootObject(); return this.compiledAst.getValue(contextRoot.getValue(), context); @@ -257,6 +260,7 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(context, this.configuration); Object result = this.ast.getValue(expressionState); checkCompile(expressionState); @@ -266,6 +270,8 @@ public class SpelExpression implements Expression { @SuppressWarnings("unchecked") @Override public T getValue(EvaluationContext context, @Nullable Class expectedResultType) throws EvaluationException { + Assert.notNull(context, "EvaluationContext is required"); + if (this.compiledAst != null) { try { TypedValue contextRoot = context.getRootObject(); @@ -289,6 +295,7 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(context, this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); @@ -298,6 +305,7 @@ public class SpelExpression implements Expression { @Override public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); + if (this.compiledAst != null) { try { return this.compiledAst.getValue(rootObject,context); @@ -314,6 +322,7 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); Object result = this.ast.getValue(expressionState); checkCompile(expressionState); @@ -325,9 +334,11 @@ public class SpelExpression implements Expression { public T getValue(EvaluationContext context, Object rootObject, @Nullable Class expectedResultType) throws EvaluationException { + Assert.notNull(context, "EvaluationContext is required"); + if (this.compiledAst != null) { try { - Object result = this.compiledAst.getValue(rootObject,context); + Object result = this.compiledAst.getValue(rootObject, context); if (expectedResultType != null) { return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); } @@ -347,6 +358,7 @@ public class SpelExpression implements Expression { } } } + ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index 162e078d2d..29c1b6cc5b 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java @@ -335,14 +335,14 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { // See SpelCompiler.loadClass() Field f = SpelExpression.class.getDeclaredField("compiledAst"); Set classloadersUsed = new HashSet<>(); - for (int i=0;i<1500;i++) { // 1500 is greater than SpelCompiler.CLASSES_DEFINED_LIMIT + for (int i =0; i < 1500; i++) { // 1500 is greater than SpelCompiler.CLASSES_DEFINED_LIMIT expression = parser.parseExpression("4 + 5"); - assertEquals(9, (int)expression.getValue(Integer.class)); + assertEquals(9, (int) expression.getValue(Integer.class)); assertCanCompile(expression); f.setAccessible(true); CompiledExpression cEx = (CompiledExpression)f.get(expression); classloadersUsed.add(cEx.getClass().getClassLoader()); - assertEquals(9, (int)expression.getValue(Integer.class)); + assertEquals(9, (int) expression.getValue(Integer.class)); } assertTrue(classloadersUsed.size() > 1); } @@ -474,7 +474,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression = parser.parseExpression("T(Integer).valueOf(42)"); expression.getValue(Integer.class); assertCanCompile(expression); - assertEquals(new Integer(42), expression.getValue(new StandardEvaluationContext(), Integer.class)); + assertEquals(new Integer(42), expression.getValue(Integer.class)); // Code gen is different for -1 .. 6 because there are bytecode instructions specifically for those // values @@ -853,9 +853,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("4.0", expression.getValue(ctx).toString()); } - // Confirms visibility of what is being called. @Test public void functionReferenceVisibility_SPR12359() throws Exception { + // Confirms visibility of what is being called. StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {"1"}); context.registerFunction("doCompare", SomeCompareMethod.class.getDeclaredMethod( "compare", Object.class, Object.class)); @@ -866,7 +866,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertCantCompile(expression); // type not public but method is - context = new StandardEvaluationContext(new Object[] {"1"}); + context = new StandardEvaluationContext(new Object[] {"1"}); context.registerFunction("doCompare", SomeCompareMethod.class.getDeclaredMethod( "compare2", Object.class, Object.class)); context.setVariable("arg", "2"); @@ -877,7 +877,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test public void functionReferenceNonCompilableArguments_SPR12359() throws Exception { - StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {"1"}); + StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {"1"}); context.registerFunction("negate", SomeCompareMethod2.class.getDeclaredMethod( "negate", Integer.TYPE)); context.setVariable("arg", "2"); @@ -3093,7 +3093,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, getClass().getClassLoader())); Person3 person = new Person3("foo", 1); SpelExpression expression = parser.parseRaw("#it?.age?.equals([0])"); - StandardEvaluationContext context = new StandardEvaluationContext(new Object[] { 1 }); + StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {1}); context.setVariable("it", person); expression.setEvaluationContext(context); assertTrue(expression.getValue(Boolean.class)); @@ -3120,8 +3120,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { // code generation for ENGLISH should notice there is something on the stack that // is not required and pop it off. expression = parser.parseExpression("#userId.toString().toLowerCase(T(java.util.Locale).ENGLISH)"); - StandardEvaluationContext context = - new StandardEvaluationContext(); + StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("userId", "RoDnEy"); assertEquals("rodney", expression.getValue(context)); assertCanCompile(expression); @@ -3270,8 +3269,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression = parser.parseExpression("#it?.age.equals([0])"); Person person = new Person(1); - StandardEvaluationContext context = - new StandardEvaluationContext(new Object[] { person.getAge() }); + StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {person.getAge()}); context.setVariable("it", person); assertTrue(expression.getValue(context, Boolean.class)); assertCanCompile(expression); @@ -3279,29 +3277,26 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { // Variant of above more like what was in the bug report: SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, - getClass().getClassLoader())); + new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, getClass().getClassLoader())); SpelExpression ex = parser.parseRaw("#it?.age.equals([0])"); - context = new StandardEvaluationContext(new Object[] { person.getAge() }); + context = new StandardEvaluationContext(new Object[] {person.getAge()}); context.setVariable("it", person); assertTrue(ex.getValue(context, Boolean.class)); assertTrue(ex.getValue(context, Boolean.class)); PersonInOtherPackage person2 = new PersonInOtherPackage(1); ex = parser.parseRaw("#it?.age.equals([0])"); - context = - new StandardEvaluationContext(new Object[] { person2.getAge() }); + context = new StandardEvaluationContext(new Object[] {person2.getAge()}); context.setVariable("it", person2); assertTrue(ex.getValue(context, Boolean.class)); assertTrue(ex.getValue(context, Boolean.class)); ex = parser.parseRaw("#it?.age.equals([0])"); - context = - new StandardEvaluationContext(new Object[] { person2.getAge() }); + context = new StandardEvaluationContext(new Object[] {person2.getAge()}); context.setVariable("it", person2); - assertTrue((Boolean)ex.getValue(context)); - assertTrue((Boolean)ex.getValue(context)); + assertTrue((Boolean) ex.getValue(context)); + assertTrue((Boolean) ex.getValue(context)); } @Test