Polishing

This commit is contained in:
Sam Brannen 2024-01-31 16:41:15 +01:00
parent 67958656e4
commit 2b52582dff
6 changed files with 72 additions and 90 deletions

View File

@ -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.
* <p>See {@link Operation} for supported operations.
* @param operation the operation to be performed
* @param leftOperand the left operand
* @param rightOperand the right operand

View File

@ -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.
*
* <p>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:
*
* <pre class="code">
* &quot;Hello ${getName()}&quot;
* </pre>
*
* 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

View File

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

View File

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

View File

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

View File

@ -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 <string> - <boolean>
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");
// <string> - <boolean> 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());
}
}