diff --git a/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java b/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java index 2e6d431ed85..8bece1f0fef 100644 --- a/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java +++ b/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -19,9 +19,9 @@ package org.springframework.expression; import org.springframework.lang.Nullable; /** - * By default the mathematical operators {@link Operation} support simple types - * like numbers. By providing an implementation of OperatorOverloader, a user - * of the expression language can support these operations on other types. + * By default, the mathematical operators defined in {@link Operation} support simple + * types like numbers. By providing an implementation of {@code OperatorOverloader}, + * a user of the expression language can support these operations on other types. * * @author Andy Clement * @since 3.0 @@ -29,21 +29,21 @@ import org.springframework.lang.Nullable; public interface OperatorOverloader { /** - * Return true if the operator overloader supports the specified operation - * between the two operands and so should be invoked to handle it. + * Return {@code true} if this operator overloader supports the specified + * operation on the two operands and should be invoked to handle it. * @param operation the operation to be performed * @param leftOperand the left operand * @param rightOperand the right operand - * @return true if the OperatorOverloader supports the specified operation - * between the two operands + * @return true if this {@code OperatorOverloader} supports the specified + * operation between the two operands * @throws EvaluationException if there is a problem performing the operation */ boolean overridesOperation(Operation operation, @Nullable Object leftOperand, @Nullable Object rightOperand) throws EvaluationException; /** - * Execute the specified operation on two operands, returning a result. - * See {@link Operation} for supported operations. + * Perform the specified operation on the two operands, returning a result. + *

See {@link Operation} for supported operations. * @param operation the operation to be performed * @param leftOperand the left operand * @param rightOperand the right operand diff --git a/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java b/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java index a37cb2162eb..c98a5434015 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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. @@ -24,17 +24,19 @@ import org.springframework.expression.TypedValue; import org.springframework.lang.Nullable; /** - * Represents a template expression broken into pieces. Each piece will be an Expression - * but pure text parts to the template will be represented as LiteralExpression objects. - * An example of a template expression might be: + * Represents a template expression broken into pieces. + * + *

Each piece will be an {@link Expression}, but pure text parts of the + * template will be represented as {@link LiteralExpression} objects. An example + * of a template expression might be: * *

  * "Hello ${getName()}"
  * 
* - * which will be represented as a CompositeStringExpression of two parts. The first part - * being a LiteralExpression representing 'Hello ' and the second part being a real - * expression that will call {@code getName()} when invoked. + * which will be represented as a {@code CompositeStringExpression} of two parts: + * the first part being a {@link LiteralExpression} representing 'Hello ' and the + * second part being a real expression that will call {@code getName()} when invoked. * * @author Andy Clement * @author Juergen Hoeller diff --git a/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java b/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java index 07c64bd9f61..dbe37534215 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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. @@ -24,9 +24,11 @@ import org.springframework.expression.TypedValue; import org.springframework.lang.Nullable; /** - * A very simple hardcoded implementation of the Expression interface that represents a - * string literal. It is used with CompositeStringExpression when representing a template - * expression which is made up of pieces - some being real expressions to be handled by + * A very simple, hard-coded implementation of the {@link Expression} interface + * that represents a string literal. + * + *

It is used with {@link CompositeStringExpression} when representing a template + * expression which is made up of pieces, some being real expressions to be handled by * an EL implementation like SpEL, and some being just textual elements. * * @author Andy Clement diff --git a/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java index e0fb6bc0b41..f979a258fb4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -80,11 +80,11 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser * result, evaluating all returned expressions and concatenating the results produces * the complete evaluated string. Unwrapping is only done of the outermost delimiters * found, so the string 'hello ${foo${abc}}' would break into the pieces 'hello ' and - * 'foo${abc}'. This means that expression languages that used ${..} as part of their + * 'foo${abc}'. This means that expression languages that use ${..} as part of their * functionality are supported without any problem. The parsing is aware of the * structure of an embedded expression. It assumes that parentheses '(', square - * brackets '[' and curly brackets '}' must be in pairs within the expression unless - * they are within a string literal and a string literal starts and terminates with a + * brackets '[', and curly brackets '}' must be in pairs within the expression unless + * they are within a string literal and the string literal starts and terminates with a * single quote '. * @param expressionString the expression string * @return the parsed expressions @@ -238,48 +238,33 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser /** * This captures a type of bracket and the position in which it occurs in the * expression. The positional information is used if an error has to be reported - * because the related end bracket cannot be found. Bracket is used to describe: - * square brackets [] round brackets () and curly brackets {} + * because the related end bracket cannot be found. Bracket is used to describe + * square brackets [], round brackets (), and curly brackets {}. */ - private static class Bracket { - - char bracket; - - int pos; - - Bracket(char bracket, int pos) { - this.bracket = bracket; - this.pos = pos; - } + private record Bracket(char bracket, int pos) { boolean compatibleWithCloseBracket(char closeBracket) { - if (this.bracket == '{') { - return closeBracket == '}'; - } - else if (this.bracket == '[') { - return closeBracket == ']'; - } - return closeBracket == ')'; + return switch (this.bracket) { + case '{' -> closeBracket == '}'; + case '[' -> closeBracket == ']'; + default -> closeBracket == ')'; + }; } static char theOpenBracketFor(char closeBracket) { - if (closeBracket == '}') { - return '{'; - } - else if (closeBracket == ']') { - return '['; - } - return '('; + return switch (closeBracket) { + case '}' -> '{'; + case ']' -> '['; + default -> '('; + }; } static char theCloseBracketFor(char openBracket) { - if (openBracket == '{') { - return '}'; - } - else if (openBracket == '[') { - return ']'; - } - return ')'; + return switch (openBracket) { + case '{' -> '}'; + case '[' -> ']'; + default -> ')'; + }; } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java index 999c1c537ad..c81ca08446d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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,7 +22,6 @@ import org.junit.jupiter.api.Test; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; -import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -223,7 +222,7 @@ class ExpressionStateTests extends AbstractExpressionTests { } @Test - void typeLocator() throws EvaluationException { + void typeLocator() { assertThat(state.getEvaluationContext().getTypeLocator()).isNotNull(); assertThat(state.findType("java.lang.Integer")).isEqualTo(Integer.class); assertThatExceptionOfType(SpelEvaluationException.class) @@ -232,7 +231,7 @@ class ExpressionStateTests extends AbstractExpressionTests { } @Test - void typeConversion() throws EvaluationException { + void typeConversion() { String s = (String) state.convertValue(34, TypeDescriptor.valueOf(String.class)); assertThat(s).isEqualTo("34"); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java index 0dee9d5d8a3..bfac9bb1a3f 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java @@ -18,56 +18,50 @@ package org.springframework.expression.spel; import org.junit.jupiter.api.Test; -import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; import org.springframework.expression.OperatorOverloader; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; - -import static org.assertj.core.api.Assertions.assertThat; /** - * Test providing operator support + * Tests for custom {@link OperatorOverloader} support. * * @author Andy Clement + * @author Sam Brannen */ class OperatorOverloaderTests extends AbstractExpressionTests { @Test - void testSimpleOperations() { - // no built-in support for this: - evaluateAndCheckError("'abc'-true",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES); + void simpleOperations() { + // default behavior + evaluate("'abc' + true", "abctrue", String.class); + evaluate("'abc' + null", "abcnull", String.class); - StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); - eContext.setOperatorOverloader(new StringAndBooleanAddition()); + // no built-in support for - + evaluateAndCheckError("'abc' - true", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES); - SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'+true"); - assertThat(expr.getValue(eContext)).isEqualTo("abctrue"); + super.context.setOperatorOverloader(new StringAndBooleanOperatorOverloader()); - expr = (SpelExpression)parser.parseExpression("'abc'-true"); - assertThat(expr.getValue(eContext)).isEqualTo("abc"); + // unaffected + evaluate("'abc' + true", "abctrue", String.class); + evaluate("'abc' + null", "abcnull", String.class); - expr = (SpelExpression)parser.parseExpression("'abc'+null"); - assertThat(expr.getValue(eContext)).isEqualTo("abcnull"); + // - has been overloaded + evaluate("'abc' - true", "abcTRUE", String.class); } - static class StringAndBooleanAddition implements OperatorOverloader { + private static class StringAndBooleanOperatorOverloader implements OperatorOverloader { @Override - public Object operate(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException { - if (operation==Operation.ADD) { - return leftOperand + ((Boolean) rightOperand).toString(); - } - else { - return leftOperand; - } + public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) { + return (leftOperand instanceof String && rightOperand instanceof Boolean); } @Override - public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException { - return leftOperand instanceof String && rightOperand instanceof Boolean; - + public Object operate(Operation operation, Object leftOperand, Object rightOperand) { + if (operation == Operation.SUBTRACT) { + return leftOperand + ((Boolean) rightOperand).toString().toUpperCase(); + } + throw new UnsupportedOperationException(operation.name()); } }