Polish SpEL documentation and tests
This commit is contained in:
parent
9df94357de
commit
00b07659d9
|
@ -9,7 +9,7 @@ SpEL supports the following types of literal expressions.
|
|||
- boolean values: `true` or `false`
|
||||
- null
|
||||
|
||||
Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To
|
||||
Strings can be delimited by single quotation marks (`'`) or double quotation marks (`"`). To
|
||||
include a single quotation mark within a string literal enclosed in single quotation
|
||||
marks, use two adjacent single quotation mark characters. Similarly, to include a double
|
||||
quotation mark within a string literal enclosed in double quotation marks, use two
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,32 +16,37 @@
|
|||
|
||||
package org.springframework.expression;
|
||||
|
||||
|
||||
// TODO Is the resolver/executor model too pervasive in this package?
|
||||
/**
|
||||
* Executors are built by resolvers and can be cached by the infrastructure to repeat an
|
||||
* operation quickly without going back to the resolvers. For example, the particular
|
||||
* constructor to run on a class may be discovered by the reflection constructor resolver
|
||||
* - it will then build a ConstructorExecutor that executes that constructor and the
|
||||
* ConstructorExecutor can be reused without needing to go back to the resolver to
|
||||
* discover the constructor again.
|
||||
* A {@code ConstructorExecutor} is built by a {@link ConstructorResolver} and
|
||||
* can be cached by the infrastructure to repeat an operation quickly without
|
||||
* going back to the resolvers.
|
||||
*
|
||||
* <p>They can become stale, and in that case should throw an AccessException - this will
|
||||
* cause the infrastructure to go back to the resolvers to ask for a new one.
|
||||
* <p>For example, the particular constructor to execute on a class may be discovered
|
||||
* by a {@code ConstructorResolver} which then builds a {@code ConstructorExecutor}
|
||||
* that executes that constructor, and the resolved {@code ConstructorExecutor}
|
||||
* can be reused without needing to go back to the resolvers to discover the
|
||||
* constructor again.
|
||||
*
|
||||
* <p>If a {@code ConstructorExecutor} becomes stale, it should throw an
|
||||
* {@link AccessException} which signals to the infrastructure to go back to the
|
||||
* resolvers to ask for a new one.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see ConstructorResolver
|
||||
* @see MethodExecutor
|
||||
*/
|
||||
public interface ConstructorExecutor {
|
||||
|
||||
/**
|
||||
* Execute a constructor in the specified context using the specified arguments.
|
||||
* @param context the evaluation context in which the command is being executed
|
||||
* @param arguments the arguments to the constructor call, should match (in terms
|
||||
* of number and type) whatever the command will need to run
|
||||
* @param context the evaluation context in which the constructor is being executed
|
||||
* @param arguments the arguments to the constructor; should match (in terms
|
||||
* of number and type) whatever the constructor will need to run
|
||||
* @return the new object
|
||||
* @throws AccessException if there is a problem executing the command or the
|
||||
* CommandExecutor is no longer valid
|
||||
* @throws AccessException if there is a problem executing the constructor or
|
||||
* if this {@code ConstructorExecutor} has become stale
|
||||
*/
|
||||
TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -22,24 +22,33 @@ import org.springframework.core.convert.TypeDescriptor;
|
|||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A constructor resolver attempts to locate a constructor and returns a ConstructorExecutor
|
||||
* that can be used to invoke that constructor. The ConstructorExecutor will be cached but
|
||||
* if it 'goes stale' the resolvers will be called again.
|
||||
* A constructor resolver attempts to locate a constructor and returns a
|
||||
* {@link ConstructorExecutor} that can be used to invoke that constructor.
|
||||
*
|
||||
* <p>The {@code ConstructorExecutor} will be cached, but if it becomes stale the
|
||||
* resolvers will be called again.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see ConstructorExecutor
|
||||
* @see MethodResolver
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ConstructorResolver {
|
||||
|
||||
/**
|
||||
* Within the supplied context determine a suitable constructor on the supplied type
|
||||
* that can handle the specified arguments. Return a ConstructorExecutor that can be
|
||||
* used to invoke that constructor (or {@code null} if no constructor could be found).
|
||||
* Within the supplied context, resolve a suitable constructor on the
|
||||
* supplied type that can handle the specified arguments.
|
||||
* <p>Returns a {@link ConstructorExecutor} that can be used to invoke that
|
||||
* constructor (or {@code null} if no constructor could be found).
|
||||
* @param context the current evaluation context
|
||||
* @param typeName the type upon which to look for the constructor
|
||||
* @param argumentTypes the arguments that the constructor must be able to handle
|
||||
* @return a ConstructorExecutor that can invoke the constructor, or null if non found
|
||||
* @param typeName the fully-qualified name of the type upon which to look
|
||||
* for the constructor
|
||||
* @param argumentTypes the types of arguments that the constructor must be
|
||||
* able to handle
|
||||
* @return a {@code ConstructorExecutor} that can invoke the constructor,
|
||||
* or {@code null} if the constructor cannot be found
|
||||
*/
|
||||
@Nullable
|
||||
ConstructorExecutor resolve(EvaluationContext context, String typeName, List<TypeDescriptor> argumentTypes)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,30 +17,37 @@
|
|||
package org.springframework.expression;
|
||||
|
||||
/**
|
||||
* MethodExecutors are built by the resolvers and can be cached by the infrastructure to
|
||||
* repeat an operation quickly without going back to the resolvers. For example, the
|
||||
* particular method to run on an object may be discovered by the reflection method
|
||||
* resolver - it will then build a MethodExecutor that executes that method and the
|
||||
* MethodExecutor can be reused without needing to go back to the resolver to discover
|
||||
* the method again.
|
||||
* A {@code MethodExecutor} is built by a {@link MethodResolver} and can be cached
|
||||
* by the infrastructure to repeat an operation quickly without going back to the
|
||||
* resolvers.
|
||||
*
|
||||
* <p>They can become stale, and in that case should throw an AccessException:
|
||||
* This will cause the infrastructure to go back to the resolvers to ask for a new one.
|
||||
* <p>For example, the particular method to execute on an object may be discovered
|
||||
* by a {@code MethodResolver} which then builds a {@code MethodExecutor} that
|
||||
* executes that method, and the resolved {@code MethodExecutor} can be reused
|
||||
* without needing to go back to the resolvers to discover the method again.
|
||||
*
|
||||
* <p>If a {@code MethodExecutor} becomes stale, it should throw an
|
||||
* {@link AccessException} which signals to the infrastructure to go back to the
|
||||
* resolvers to ask for a new one.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see MethodResolver
|
||||
* @see ConstructorExecutor
|
||||
*/
|
||||
public interface MethodExecutor {
|
||||
|
||||
/**
|
||||
* Execute a command using the specified arguments, and using the specified expression state.
|
||||
* @param context the evaluation context in which the command is being executed
|
||||
* @param target the target object of the call - null for static methods
|
||||
* @param arguments the arguments to the executor, should match (in terms of number
|
||||
* and type) whatever the command will need to run
|
||||
* @return the value returned from execution
|
||||
* @throws AccessException if there is a problem executing the command or the
|
||||
* MethodExecutor is no longer valid
|
||||
* Execute a method in the specified context using the specified arguments.
|
||||
* @param context the evaluation context in which the method is being executed
|
||||
* @param target the target of the method invocation; may be {@code null} for
|
||||
* {@code static} methods
|
||||
* @param arguments the arguments to the method; should match (in terms of
|
||||
* number and type) whatever the method will need to run
|
||||
* @return the value returned from the method
|
||||
* @throws AccessException if there is a problem executing the method or
|
||||
* if this {@code MethodExecutor} has become stale
|
||||
*/
|
||||
TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -22,23 +22,32 @@ import org.springframework.core.convert.TypeDescriptor;
|
|||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A method resolver attempts to locate a method and returns a command executor that can be
|
||||
* used to invoke that method. The command executor will be cached, but if it 'goes stale'
|
||||
* the resolvers will be called again.
|
||||
* A method resolver attempts to locate a method and returns a
|
||||
* {@link MethodExecutor} that can be used to invoke that method.
|
||||
*
|
||||
* <p>The {@code MethodExecutor} will be cached, but if it becomes stale the
|
||||
* resolvers will be called again.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see MethodExecutor
|
||||
* @see ConstructorResolver
|
||||
*/
|
||||
public interface MethodResolver {
|
||||
|
||||
/**
|
||||
* Within the supplied context determine a suitable method on the supplied object that
|
||||
* can handle the specified arguments. Return a {@link MethodExecutor} that can be used
|
||||
* to invoke that method, or {@code null} if no method could be found.
|
||||
* Within the supplied context, resolve a suitable method on the supplied
|
||||
* object that can handle the specified arguments.
|
||||
* <p>Returns a {@link MethodExecutor} that can be used to invoke that method,
|
||||
* or {@code null} if no method could be found.
|
||||
* @param context the current evaluation context
|
||||
* @param targetObject the object upon which the method is being called
|
||||
* @param argumentTypes the arguments that the constructor must be able to handle
|
||||
* @return a MethodExecutor that can invoke the method, or null if the method cannot be found
|
||||
* @param name the name of the method
|
||||
* @param argumentTypes the types of arguments that the method must be able
|
||||
* to handle
|
||||
* @return a {@code MethodExecutor} that can invoke the method, or {@code null}
|
||||
* if the method cannot be found
|
||||
*/
|
||||
@Nullable
|
||||
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
|
||||
|
|
|
@ -21,13 +21,12 @@ import java.util.List;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.ConstructorExecutor;
|
||||
import org.springframework.expression.ConstructorResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeLocator;
|
||||
import org.springframework.expression.spel.testresources.Fruit;
|
||||
import org.springframework.expression.spel.testresources.PlaceOfBirth;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -41,19 +40,145 @@ import static org.assertj.core.api.Assertions.assertThatException;
|
|||
class ConstructorInvocationTests extends AbstractExpressionTests {
|
||||
|
||||
@Test
|
||||
void testTypeConstructors() {
|
||||
void constructorWithArgument() {
|
||||
evaluate("new String('hello world')", "hello world", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonExistentType() {
|
||||
void nonExistentType() {
|
||||
evaluateAndCheckError("new FooBar()", SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorThrowingException() {
|
||||
// Test ctor on inventor:
|
||||
// On 1 it will throw an IllegalArgumentException
|
||||
// On 2 it will throw a RuntimeException
|
||||
// On 3 it will exit normally
|
||||
// In each case it increments the Tester field 'counter' when invoked
|
||||
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
Expression expr = parser.parseExpression("new org.springframework.expression.spel.ConstructorInvocationTests$Tester(#bar).i");
|
||||
|
||||
// Normal exit
|
||||
StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
|
||||
context.setRootObject(new Tester());
|
||||
context.setVariable("bar", 3);
|
||||
Object o = expr.getValue(context);
|
||||
assertThat(o).isEqualTo(3);
|
||||
assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(1);
|
||||
|
||||
// Now the expression has cached that throwException(int) is the right thing to
|
||||
// call. Let's change 'bar' to be a PlaceOfBirth which indicates the cached
|
||||
// reference is out of date.
|
||||
context.setVariable("bar", new PlaceOfBirth("London"));
|
||||
o = expr.getValue(context);
|
||||
assertThat(o).isEqualTo(0);
|
||||
// That confirms the logic to mark the cached reference stale and retry is working
|
||||
|
||||
// Now let's cause the method to exit via exception and ensure it doesn't cause
|
||||
// a retry.
|
||||
|
||||
// First, switch back to throwException(int)
|
||||
context.setVariable("bar", 3);
|
||||
o = expr.getValue(context);
|
||||
assertThat(o).isEqualTo(3);
|
||||
assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(2);
|
||||
|
||||
// 4 will make it throw a checked exception - this will be wrapped by spel on the
|
||||
// way out
|
||||
context.setVariable("bar", 4);
|
||||
assertThatException()
|
||||
.isThrownBy(() -> expr.getValue(context))
|
||||
.withMessageContaining("Tester");
|
||||
// A problem occurred whilst attempting to construct an object of type
|
||||
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
|
||||
// using arguments '(java.lang.Integer)'
|
||||
|
||||
// If counter is 4 then the method got called twice!
|
||||
assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(3);
|
||||
|
||||
// 1 will make it throw a RuntimeException - SpEL will let this through
|
||||
context.setVariable("bar", 1);
|
||||
assertThatException()
|
||||
.isThrownBy(() -> expr.getValue(context))
|
||||
.isNotInstanceOf(SpelEvaluationException.class);
|
||||
// A problem occurred whilst attempting to construct an object of type
|
||||
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
|
||||
// using arguments '(java.lang.Integer)'
|
||||
|
||||
// If counter is 5 then the method got called twice!
|
||||
assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorResolvers() {
|
||||
StandardEvaluationContext ctx = new StandardEvaluationContext();
|
||||
|
||||
// reflective constructor accessor is the only one by default
|
||||
List<ConstructorResolver> constructorResolvers = ctx.getConstructorResolvers();
|
||||
assertThat(constructorResolvers).hasSize(1);
|
||||
|
||||
ConstructorResolver dummy = (context, typeName, argumentTypes) -> {
|
||||
throw new UnsupportedOperationException();
|
||||
};
|
||||
ctx.addConstructorResolver(dummy);
|
||||
assertThat(ctx.getConstructorResolvers()).hasSize(2);
|
||||
|
||||
List<ConstructorResolver> copy = new ArrayList<>(ctx.getConstructorResolvers());
|
||||
assertThat(ctx.removeConstructorResolver(dummy)).isTrue();
|
||||
assertThat(ctx.removeConstructorResolver(dummy)).isFalse();
|
||||
assertThat(ctx.getConstructorResolvers()).hasSize(1);
|
||||
|
||||
ctx.setConstructorResolvers(copy);
|
||||
assertThat(ctx.getConstructorResolvers()).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void varargsConstructors() {
|
||||
((StandardTypeLocator) super.context.getTypeLocator()).registerImport(Fruit.class.getPackageName());
|
||||
|
||||
// Calling 'Fruit(String... strings)' - returns length_of_strings
|
||||
evaluate("new Fruit('a','b','c').stringscount()", 3, Integer.class);
|
||||
evaluate("new Fruit('a').stringscount()", 1, Integer.class);
|
||||
evaluate("new Fruit().stringscount()", 0, Integer.class);
|
||||
// all need converting to strings
|
||||
evaluate("new Fruit(1,2,3).stringscount()", 3, Integer.class);
|
||||
// needs string conversion
|
||||
evaluate("new Fruit(1).stringscount()", 1, Integer.class);
|
||||
// first and last need conversion
|
||||
evaluate("new Fruit(1,'a',3.0d).stringscount()", 3, Integer.class);
|
||||
|
||||
// Calling 'Fruit(int i, String... strings)' - returns int + length_of_strings
|
||||
evaluate("new Fruit(5,'a','b','c').stringscount()", 8, Integer.class);
|
||||
evaluate("new Fruit(2,'a').stringscount()", 3, Integer.class);
|
||||
evaluate("new Fruit(4).stringscount()", 4, Integer.class);
|
||||
evaluate("new Fruit(8,2,3).stringscount()", 10, Integer.class);
|
||||
evaluate("new Fruit(9).stringscount()", 9, Integer.class);
|
||||
evaluate("new Fruit(2,'a',3.0d).stringscount()", 4, Integer.class);
|
||||
evaluate("new Fruit(8,stringArrayOfThreeItems).stringscount()", 11, Integer.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* These tests are attempting to call constructors where we need to widen or convert
|
||||
* the argument in order to satisfy a suitable constructor.
|
||||
*/
|
||||
@Test
|
||||
void widening() {
|
||||
// widening of int 3 to double 3 is OK
|
||||
evaluate("new Double(3)", 3.0d, Double.class);
|
||||
// widening of int 3 to long 3 is OK
|
||||
evaluate("new Long(3)", 3L, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void argumentConversion() {
|
||||
evaluate("new String(3.0d)", "3.0", String.class);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class TestException extends Exception {
|
||||
|
||||
}
|
||||
|
||||
static class Tester {
|
||||
|
@ -80,154 +205,7 @@ class ConstructorInvocationTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
public Tester(PlaceOfBirth pob) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConstructorThrowingException_SPR6760() {
|
||||
// Test ctor on inventor:
|
||||
// On 1 it will throw an IllegalArgumentException
|
||||
// On 2 it will throw a RuntimeException
|
||||
// On 3 it will exit normally
|
||||
// In each case it increments the Tester field 'counter' when invoked
|
||||
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
Expression expr = parser.parseExpression("new org.springframework.expression.spel.ConstructorInvocationTests$Tester(#bar).i");
|
||||
|
||||
// Normal exit
|
||||
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
|
||||
eContext.setRootObject(new Tester());
|
||||
eContext.setVariable("bar", 3);
|
||||
Object o = expr.getValue(eContext);
|
||||
assertThat(o).isEqualTo(3);
|
||||
assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(1);
|
||||
|
||||
// Now the expression has cached that throwException(int) is the right thing to
|
||||
// call. Let's change 'bar' to be a PlaceOfBirth which indicates the cached
|
||||
// reference is out of date.
|
||||
eContext.setVariable("bar", new PlaceOfBirth("London"));
|
||||
o = expr.getValue(eContext);
|
||||
assertThat(o).isEqualTo(0);
|
||||
// That confirms the logic to mark the cached reference stale and retry is working
|
||||
|
||||
// Now let's cause the method to exit via exception and ensure it doesn't cause
|
||||
// a retry.
|
||||
|
||||
// First, switch back to throwException(int)
|
||||
eContext.setVariable("bar", 3);
|
||||
o = expr.getValue(eContext);
|
||||
assertThat(o).isEqualTo(3);
|
||||
assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(2);
|
||||
|
||||
// 4 will make it throw a checked exception - this will be wrapped by spel on the
|
||||
// way out
|
||||
eContext.setVariable("bar", 4);
|
||||
assertThatException()
|
||||
.isThrownBy(() -> expr.getValue(eContext))
|
||||
.withMessageContaining("Tester");
|
||||
// A problem occurred whilst attempting to construct an object of type
|
||||
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
|
||||
// using arguments '(java.lang.Integer)'
|
||||
|
||||
// If counter is 4 then the method got called twice!
|
||||
assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(3);
|
||||
|
||||
// 1 will make it throw a RuntimeException - SpEL will let this through
|
||||
eContext.setVariable("bar", 1);
|
||||
assertThatException()
|
||||
.isThrownBy(() -> expr.getValue(eContext))
|
||||
.isNotInstanceOf(SpelEvaluationException.class);
|
||||
// A problem occurred whilst attempting to construct an object of type
|
||||
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
|
||||
// using arguments '(java.lang.Integer)'
|
||||
|
||||
// If counter is 5 then the method got called twice!
|
||||
assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddingConstructorResolvers() {
|
||||
StandardEvaluationContext ctx = new StandardEvaluationContext();
|
||||
|
||||
// reflective constructor accessor is the only one by default
|
||||
List<ConstructorResolver> constructorResolvers = ctx.getConstructorResolvers();
|
||||
assertThat(constructorResolvers).hasSize(1);
|
||||
|
||||
ConstructorResolver dummy = new DummyConstructorResolver();
|
||||
ctx.addConstructorResolver(dummy);
|
||||
assertThat(ctx.getConstructorResolvers()).hasSize(2);
|
||||
|
||||
List<ConstructorResolver> copy = new ArrayList<>(ctx.getConstructorResolvers());
|
||||
assertThat(ctx.removeConstructorResolver(dummy)).isTrue();
|
||||
assertThat(ctx.removeConstructorResolver(dummy)).isFalse();
|
||||
assertThat(ctx.getConstructorResolvers()).hasSize(1);
|
||||
|
||||
ctx.setConstructorResolvers(copy);
|
||||
assertThat(ctx.getConstructorResolvers()).hasSize(2);
|
||||
}
|
||||
|
||||
|
||||
static class DummyConstructorResolver implements ConstructorResolver {
|
||||
|
||||
@Override
|
||||
public ConstructorExecutor resolve(EvaluationContext context, String typeName,
|
||||
List<TypeDescriptor> argumentTypes) {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testVarargsInvocation01() {
|
||||
// Calling 'Fruit(String... strings)'
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit('a','b','c').stringscount()", 3,
|
||||
Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit('a').stringscount()", 1, Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit().stringscount()", 0, Integer.class);
|
||||
// all need converting to strings
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(1,2,3).stringscount()", 3, Integer.class);
|
||||
// needs string conversion
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(1).stringscount()", 1, Integer.class);
|
||||
// first and last need conversion
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(1,'a',3.0d).stringscount()", 3,
|
||||
Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVarargsInvocation02() {
|
||||
// Calling 'Fruit(int i, String... strings)' - returns int+length_of_strings
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(5,'a','b','c').stringscount()", 8,
|
||||
Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(2,'a').stringscount()", 3, Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(4).stringscount()", 4, Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(8,2,3).stringscount()", 10, Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(9).stringscount()", 9, Integer.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.Fruit(2,'a',3.0d).stringscount()", 4,
|
||||
Integer.class);
|
||||
evaluate(
|
||||
"new org.springframework.expression.spel.testresources.Fruit(8,stringArrayOfThreeItems).stringscount()",
|
||||
11, Integer.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* These tests are attempting to call constructors where we need to widen or convert
|
||||
* the argument in order to satisfy a suitable constructor.
|
||||
*/
|
||||
@Test
|
||||
void testWidening01() {
|
||||
// widening of int 3 to double 3 is OK
|
||||
evaluate("new Double(3)", 3.0d, Double.class);
|
||||
// widening of int 3 to long 3 is OK
|
||||
evaluate("new Long(3)", 3L, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testArgumentConversion01() {
|
||||
evaluate("new String(3.0d)", "3.0", String.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,83 +19,78 @@ package org.springframework.expression.spel;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests the evaluation of basic literals: boolean, integer, hex integer, long, real, null, date
|
||||
* Tests the evaluation of basic literals: boolean, string, integer, long,
|
||||
* hex integer, hex long, float, double, null.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class LiteralTests extends AbstractExpressionTests {
|
||||
|
||||
@Test
|
||||
void testLiteralBoolean01() {
|
||||
evaluate("false", "false", Boolean.class);
|
||||
void booleans() {
|
||||
evaluate("false", false, Boolean.class);
|
||||
evaluate("true", true, Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralBoolean02() {
|
||||
evaluate("true", "true", Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralInteger01() {
|
||||
evaluate("1", "1", Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralInteger02() {
|
||||
evaluate("1415", "1415", Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralString01() {
|
||||
void strings() {
|
||||
evaluate("'hello'", "hello", String.class);
|
||||
evaluate("'joe bloggs'", "joe bloggs", String.class);
|
||||
evaluate("'Hello World'", "Hello World", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralString02() {
|
||||
evaluate("'joe bloggs'", "joe bloggs", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralString03() {
|
||||
evaluate("'hello'", "hello", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralString04() {
|
||||
void stringsContainingQuotes() {
|
||||
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
|
||||
evaluate("'Tony\\r''s Pizza'", "Tony\\r's Pizza", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralString05() {
|
||||
evaluate("\"Hello World\"", "Hello World", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralString06() {
|
||||
evaluate("\"Hello ' World\"", "Hello ' World", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHexIntLiteral01() {
|
||||
evaluate("0x7FFFF", "524287", Integer.class);
|
||||
evaluate("0x7FFFFL", 524287L, Long.class);
|
||||
evaluate("0X7FFFF", "524287", Integer.class);
|
||||
evaluate("0X7FFFFl", 524287L, Long.class);
|
||||
void integers() {
|
||||
evaluate("1", 1, Integer.class);
|
||||
evaluate("1415", 1415, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLongIntLiteral01() {
|
||||
void longs() {
|
||||
evaluate("1L", 1L, Long.class);
|
||||
evaluate("1415L", 1415L, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void signedIntegers() {
|
||||
evaluate("-1", -1, Integer.class);
|
||||
evaluate("-0xa", -10, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void signedLongs() {
|
||||
evaluate("-1L", -1L, Long.class);
|
||||
evaluate("-0x20l", -32L, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hexIntegers() {
|
||||
evaluate("0x7FFFF", 524287, Integer.class);
|
||||
evaluate("0X7FFFF", 524287, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hexLongs() {
|
||||
evaluate("0X7FFFFl", 524287L, Long.class);
|
||||
evaluate("0x7FFFFL", 524287L, Long.class);
|
||||
evaluate("0xCAFEBABEL", 3405691582L, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLongIntInteractions01() {
|
||||
void hexLongAndIntInteractions() {
|
||||
evaluate("0x20 * 2L", 64L, Long.class);
|
||||
// ask for the result to be made into an Integer
|
||||
evaluateAndAskForReturnType("0x20 * 2L", 64, Integer.class);
|
||||
|
@ -104,29 +99,8 @@ class LiteralTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSignedIntLiterals() {
|
||||
evaluate("-1", -1, Integer.class);
|
||||
evaluate("-0xa", -10, Integer.class);
|
||||
evaluate("-1L", -1L, Long.class);
|
||||
evaluate("-0x20l", -32L, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralReal01_CreatingDoubles() {
|
||||
evaluate("1.25", 1.25d, Double.class);
|
||||
evaluate("2.99", 2.99d, Double.class);
|
||||
evaluate("-3.141", -3.141d, Double.class);
|
||||
evaluate("1.25d", 1.25d, Double.class);
|
||||
evaluate("2.99d", 2.99d, Double.class);
|
||||
evaluate("-3.141d", -3.141d, Double.class);
|
||||
evaluate("1.25D", 1.25d, Double.class);
|
||||
evaluate("2.99D", 2.99d, Double.class);
|
||||
evaluate("-3.141D", -3.141d, Double.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralReal02_CreatingFloats() {
|
||||
// For now, everything becomes a double...
|
||||
void floats() {
|
||||
// "f" or "F" must be explicitly specified.
|
||||
evaluate("1.25f", 1.25f, Float.class);
|
||||
evaluate("2.5f", 2.5f, Float.class);
|
||||
evaluate("-3.5f", -3.5f, Float.class);
|
||||
|
@ -136,7 +110,23 @@ class LiteralTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testLiteralReal03_UsingExponents() {
|
||||
void doubles() {
|
||||
// Real numbers are Doubles by default
|
||||
evaluate("1.25", 1.25d, Double.class);
|
||||
evaluate("2.99", 2.99d, Double.class);
|
||||
evaluate("-3.141", -3.141d, Double.class);
|
||||
|
||||
// But "d" or "D" can also be explicitly specified.
|
||||
evaluate("1.25d", 1.25d, Double.class);
|
||||
evaluate("2.99d", 2.99d, Double.class);
|
||||
evaluate("-3.141d", -3.141d, Double.class);
|
||||
evaluate("1.25D", 1.25d, Double.class);
|
||||
evaluate("2.99D", 2.99d, Double.class);
|
||||
evaluate("-3.141D", -3.141d, Double.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doublesUsingExponents() {
|
||||
evaluate("6.0221415E+23", "6.0221415E23", Double.class);
|
||||
evaluate("6.0221415e+23", "6.0221415E23", Double.class);
|
||||
evaluate("6.0221415E+23d", "6.0221415E23", Double.class);
|
||||
|
@ -145,30 +135,33 @@ class LiteralTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testLiteralReal04_BadExpressions() {
|
||||
void doublesUsingExponentsWithInvalidInput() {
|
||||
parseAndCheckError("6.1e23e22", SpelMessage.MORE_INPUT, 6, "e22");
|
||||
parseAndCheckError("6.1f23e22", SpelMessage.MORE_INPUT, 4, "23e22");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLiteralNull01() {
|
||||
void nullLiteral() {
|
||||
evaluate("null", null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConversions() {
|
||||
void conversions() {
|
||||
// getting the expression type to be what we want - either:
|
||||
evaluate("new Integer(37).byteValue()", (byte) 37, Byte.class); // calling byteValue() on Integer.class
|
||||
evaluateAndAskForReturnType("new Integer(37)", (byte) 37, Byte.class); // relying on registered type converters
|
||||
evaluate("37.byteValue", (byte) 37, Byte.class); // calling byteValue() on Integer.class
|
||||
evaluateAndAskForReturnType("37", (byte) 37, Byte.class); // relying on registered type converters
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotWritable() {
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("37");
|
||||
assertThat(expr.isWritable(new StandardEvaluationContext())).isFalse();
|
||||
expr = (SpelExpression)parser.parseExpression("37L");
|
||||
assertThat(expr.isWritable(new StandardEvaluationContext())).isFalse();
|
||||
expr = (SpelExpression)parser.parseExpression("true");
|
||||
assertThat(expr.isWritable(new StandardEvaluationContext())).isFalse();
|
||||
void notWritable() {
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("37");
|
||||
assertThat(expr.isWritable(context)).isFalse();
|
||||
|
||||
expr = (SpelExpression) parser.parseExpression("37L");
|
||||
assertThat(expr.isWritable(context)).isFalse();
|
||||
|
||||
expr = (SpelExpression) parser.parseExpression("true");
|
||||
assertThat(expr.isWritable(context)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ class ParsingTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void literalNull() {
|
||||
void nullLiteral() {
|
||||
parseCheck("null");
|
||||
}
|
||||
|
||||
|
@ -399,12 +399,12 @@ class ParsingTests {
|
|||
|
||||
@Test
|
||||
void mathOperatorsAddStrings() {
|
||||
parseCheck("'a'+'b'", "('a' + 'b')");
|
||||
parseCheck("'a' + 'b'", "('a' + 'b')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mathOperatorsAddMultipleStrings() {
|
||||
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
|
||||
parseCheck("'hello' + ' ' + 'world'", "(('hello' + ' ') + 'world')");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -429,7 +429,7 @@ class ParsingTests {
|
|||
}
|
||||
|
||||
@Nested
|
||||
class References {
|
||||
class BeanReferences {
|
||||
|
||||
@Test
|
||||
void references() {
|
||||
|
|
Loading…
Reference in New Issue