From 00b07659d92a243807571882b69bb16dd992b17e Mon Sep 17 00:00:00 2001
From: Sam Brannen <104798+sbrannen@users.noreply.github.com>
Date: Fri, 26 Jan 2024 12:37:44 +0100
Subject: [PATCH] Polish SpEL documentation and tests
---
.../expressions/language-ref/literal.adoc | 2 +-
.../expression/ConstructorExecutor.java | 37 ++-
.../expression/ConstructorResolver.java | 29 +-
.../expression/MethodExecutor.java | 41 +--
.../expression/MethodResolver.java | 27 +-
.../spel/ConstructorInvocationTests.java | 284 ++++++++----------
.../expression/spel/LiteralTests.java | 157 +++++-----
.../expression/spel/ParsingTests.java | 8 +-
8 files changed, 293 insertions(+), 292 deletions(-)
diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc
index c133af0f36..ca5d9c28d0 100644
--- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc
+++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc
@@ -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
diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java
index c7e0fd355c..5af02af1ee 100644
--- a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java
+++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java
@@ -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.
*
- *
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.
+ *
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.
+ *
+ *
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;
diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java
index 821a46c1bd..3c9c232c3e 100644
--- a/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java
+++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java
@@ -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.
+ *
+ *
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.
+ *
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 argumentTypes)
diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java
index 3c0b44d9d9..170914e456 100644
--- a/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java
+++ b/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java
@@ -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.
*
- * 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.
+ *
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.
+ *
+ *
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;
diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java
index d4216d2a55..9d8c0bbaf0 100644
--- a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java
+++ b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java
@@ -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.
+ *
+ *
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.
+ *
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,
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java
index 72aa836588..abc80b0370 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java
@@ -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 constructorResolvers = ctx.getConstructorResolvers();
+ assertThat(constructorResolvers).hasSize(1);
+
+ ConstructorResolver dummy = (context, typeName, argumentTypes) -> {
+ throw new UnsupportedOperationException();
+ };
+ ctx.addConstructorResolver(dummy);
+ assertThat(ctx.getConstructorResolvers()).hasSize(2);
+
+ List 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 constructorResolvers = ctx.getConstructorResolvers();
- assertThat(constructorResolvers).hasSize(1);
-
- ConstructorResolver dummy = new DummyConstructorResolver();
- ctx.addConstructorResolver(dummy);
- assertThat(ctx.getConstructorResolvers()).hasSize(2);
-
- List 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 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);
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/LiteralTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/LiteralTests.java
index 729c09aa9d..abbfd936df 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/LiteralTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/LiteralTests.java
@@ -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();
}
+
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java
index 5bd15b113d..975e036676 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java
@@ -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() {