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 | 178 ++++++++---------- .../expression/spel/LiteralTests.java | 129 ++++++------- .../expression/spel/ParsingTests.java | 8 +- 8 files changed, 226 insertions(+), 225 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 c133af0f367..ca5d9c28d01 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 c7e0fd355c3..5af02af1ee4 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. + * + *

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. * - *

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. + *

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 821a46c1bd2..3c9c232c3ee 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 3c0b44d9d9b..170914e456f 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 d4216d2a55c..9d8c0bbaf06 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 72aa836588a..abc80b03700 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,53 +40,17 @@ 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); } - - @SuppressWarnings("serial") - static class TestException extends Exception { - - } - - static class Tester { - - public static int counter; - public int i; - - - public Tester() { - } - - public Tester(int i) throws Exception { - counter++; - if (i == 1) { - throw new IllegalArgumentException("IllegalArgumentException for 1"); - } - if (i == 2) { - throw new RuntimeException("RuntimeException for 2"); - } - if (i == 4) { - throw new TestException(); - } - this.i = i; - } - - public Tester(PlaceOfBirth pob) { - - } - - } - - @Test - void testConstructorThrowingException_SPR6760() { + void constructorThrowingException() { // Test ctor on inventor: // On 1 it will throw an IllegalArgumentException // On 2 it will throw a RuntimeException @@ -98,18 +61,18 @@ class ConstructorInvocationTests extends AbstractExpressionTests { 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); + 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(eContext)).isEqualTo(1); + 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. - eContext.setVariable("bar", new PlaceOfBirth("London")); - o = expr.getValue(eContext); + 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 @@ -117,46 +80,48 @@ class ConstructorInvocationTests extends AbstractExpressionTests { // a retry. // First, switch back to throwException(int) - eContext.setVariable("bar", 3); - o = expr.getValue(eContext); + context.setVariable("bar", 3); + o = expr.getValue(context); assertThat(o).isEqualTo(3); - assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(2); + 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 - eContext.setVariable("bar", 4); + context.setVariable("bar", 4); assertThatException() - .isThrownBy(() -> expr.getValue(eContext)) + .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(eContext)).isEqualTo(3); + assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(3); // 1 will make it throw a RuntimeException - SpEL will let this through - eContext.setVariable("bar", 1); + context.setVariable("bar", 1); assertThatException() - .isThrownBy(() -> expr.getValue(eContext)) + .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(eContext)).isEqualTo(4); + assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(4); } @Test - void testAddingConstructorResolvers() { + 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 = new DummyConstructorResolver(); + ConstructorResolver dummy = (context, typeName, argumentTypes) -> { + throw new UnsupportedOperationException(); + }; ctx.addConstructorResolver(dummy); assertThat(ctx.getConstructorResolvers()).hasSize(2); @@ -169,48 +134,29 @@ class ConstructorInvocationTests extends AbstractExpressionTests { 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); + 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 org.springframework.expression.spel.testresources.Fruit(1,2,3).stringscount()", 3, Integer.class); + evaluate("new Fruit(1,2,3).stringscount()", 3, Integer.class); // needs string conversion - evaluate("new org.springframework.expression.spel.testresources.Fruit(1).stringscount()", 1, Integer.class); + evaluate("new 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); + 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); } /* @@ -218,7 +164,7 @@ class ConstructorInvocationTests extends AbstractExpressionTests { * the argument in order to satisfy a suitable constructor. */ @Test - void testWidening01() { + 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 @@ -226,8 +172,40 @@ class ConstructorInvocationTests extends AbstractExpressionTests { } @Test - void testArgumentConversion01() { + void argumentConversion() { evaluate("new String(3.0d)", "3.0", String.class); } + + @SuppressWarnings("serial") + static class TestException extends Exception { + } + + static class Tester { + + public static int counter; + public int i; + + + public Tester() { + } + + public Tester(int i) throws Exception { + counter++; + if (i == 1) { + throw new IllegalArgumentException("IllegalArgumentException for 1"); + } + if (i == 2) { + throw new RuntimeException("RuntimeException for 2"); + } + if (i == 4) { + throw new TestException(); + } + this.i = i; + } + + public Tester(PlaceOfBirth pob) { + } + } + } 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 729c09aa9dd..abbfd936df0 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); + void stringsContainingQuotes() { + evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class); + evaluate("'Tony\\r''s Pizza'", "Tony\\r's Pizza", String.class); + evaluate("\"Hello World\"", "Hello World", String.class); + evaluate("\"Hello ' World\"", "Hello ' World", String.class); } @Test - void testLiteralString03() { - evaluate("'hello'", "hello", String.class); + void integers() { + evaluate("1", 1, Integer.class); + evaluate("1415", 1415, Integer.class); } @Test - void testLiteralString04() { - evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class); - evaluate("'Tony\\r''s Pizza'", "Tony\\r's Pizza", String.class); + void longs() { + evaluate("1L", 1L, Long.class); + evaluate("1415L", 1415L, Long.class); } @Test - void testLiteralString05() { - evaluate("\"Hello World\"", "Hello World", String.class); + void signedIntegers() { + evaluate("-1", -1, Integer.class); + evaluate("-0xa", -10, Integer.class); } @Test - void testLiteralString06() { - evaluate("\"Hello ' World\"", "Hello ' World", String.class); + void signedLongs() { + evaluate("-1L", -1L, Long.class); + evaluate("-0x20l", -32L, Long.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 hexIntegers() { + evaluate("0x7FFFF", 524287, Integer.class); + evaluate("0X7FFFF", 524287, Integer.class); } @Test - void testLongIntLiteral01() { + 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,18 +99,24 @@ 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); + 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); + evaluate("1.25F", 1.25f, Float.class); + evaluate("2.5F", 2.5f, Float.class); + evaluate("-3.5F", -3.5f, Float.class); } @Test - void testLiteralReal01_CreatingDoubles() { + 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); @@ -125,18 +126,7 @@ class LiteralTests extends AbstractExpressionTests { } @Test - void testLiteralReal02_CreatingFloats() { - // For now, everything becomes a double... - evaluate("1.25f", 1.25f, Float.class); - evaluate("2.5f", 2.5f, Float.class); - evaluate("-3.5f", -3.5f, Float.class); - evaluate("1.25F", 1.25f, Float.class); - evaluate("2.5F", 2.5f, Float.class); - evaluate("-3.5F", -3.5f, Float.class); - } - - @Test - void testLiteralReal03_UsingExponents() { + 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 5bd15b113d1..975e0366768 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() {