Polish SpEL documentation and tests

This commit is contained in:
Sam Brannen 2024-01-26 12:37:44 +01:00
parent 9df94357de
commit 00b07659d9
8 changed files with 293 additions and 292 deletions

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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() {