Polishing
This commit is contained in:
parent
67958656e4
commit
2b52582dff
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default the mathematical operators {@link Operation} support simple types
|
* By default, the mathematical operators defined in {@link Operation} support simple
|
||||||
* like numbers. By providing an implementation of OperatorOverloader, a user
|
* types like numbers. By providing an implementation of {@code OperatorOverloader},
|
||||||
* of the expression language can support these operations on other types.
|
* a user of the expression language can support these operations on other types.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
|
|
@ -29,21 +29,21 @@ import org.springframework.lang.Nullable;
|
||||||
public interface OperatorOverloader {
|
public interface OperatorOverloader {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the operator overloader supports the specified operation
|
* Return {@code true} if this operator overloader supports the specified
|
||||||
* between the two operands and so should be invoked to handle it.
|
* operation on the two operands and should be invoked to handle it.
|
||||||
* @param operation the operation to be performed
|
* @param operation the operation to be performed
|
||||||
* @param leftOperand the left operand
|
* @param leftOperand the left operand
|
||||||
* @param rightOperand the right operand
|
* @param rightOperand the right operand
|
||||||
* @return true if the OperatorOverloader supports the specified operation
|
* @return true if this {@code OperatorOverloader} supports the specified
|
||||||
* between the two operands
|
* operation between the two operands
|
||||||
* @throws EvaluationException if there is a problem performing the operation
|
* @throws EvaluationException if there is a problem performing the operation
|
||||||
*/
|
*/
|
||||||
boolean overridesOperation(Operation operation, @Nullable Object leftOperand, @Nullable Object rightOperand)
|
boolean overridesOperation(Operation operation, @Nullable Object leftOperand, @Nullable Object rightOperand)
|
||||||
throws EvaluationException;
|
throws EvaluationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the specified operation on two operands, returning a result.
|
* Perform the specified operation on the two operands, returning a result.
|
||||||
* See {@link Operation} for supported operations.
|
* <p>See {@link Operation} for supported operations.
|
||||||
* @param operation the operation to be performed
|
* @param operation the operation to be performed
|
||||||
* @param leftOperand the left operand
|
* @param leftOperand the left operand
|
||||||
* @param rightOperand the right operand
|
* @param rightOperand the right operand
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a template expression broken into pieces. Each piece will be an Expression
|
* Represents a template expression broken into pieces.
|
||||||
* but pure text parts to the template will be represented as LiteralExpression objects.
|
*
|
||||||
* An example of a template expression might be:
|
* <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">
|
* <pre class="code">
|
||||||
* "Hello ${getName()}"
|
* "Hello ${getName()}"
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* which will be represented as a CompositeStringExpression of two parts. The first part
|
* which will be represented as a {@code CompositeStringExpression} of two parts:
|
||||||
* being a LiteralExpression representing 'Hello ' and the second part being a real
|
* the first part being a {@link LiteralExpression} representing 'Hello ' and the
|
||||||
* expression that will call {@code getName()} when invoked.
|
* second part being a real expression that will call {@code getName()} when invoked.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A very simple hardcoded implementation of the Expression interface that represents a
|
* A very simple, hard-coded implementation of the {@link Expression} interface
|
||||||
* string literal. It is used with CompositeStringExpression when representing a template
|
* that represents a string literal.
|
||||||
* expression which is made up of pieces - some being real expressions to be handled by
|
*
|
||||||
|
* <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.
|
* an EL implementation like SpEL, and some being just textual elements.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* result, evaluating all returned expressions and concatenating the results produces
|
||||||
* the complete evaluated string. Unwrapping is only done of the outermost delimiters
|
* 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
|
* 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
|
* functionality are supported without any problem. The parsing is aware of the
|
||||||
* structure of an embedded expression. It assumes that parentheses '(', square
|
* structure of an embedded expression. It assumes that parentheses '(', square
|
||||||
* brackets '[' and curly brackets '}' must be in pairs within the expression unless
|
* 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
|
* they are within a string literal and the string literal starts and terminates with a
|
||||||
* single quote '.
|
* single quote '.
|
||||||
* @param expressionString the expression string
|
* @param expressionString the expression string
|
||||||
* @return the parsed expressions
|
* @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
|
* 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
|
* 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:
|
* because the related end bracket cannot be found. Bracket is used to describe
|
||||||
* square brackets [] round brackets () and curly brackets {}
|
* square brackets [], round brackets (), and curly brackets {}.
|
||||||
*/
|
*/
|
||||||
private static class Bracket {
|
private record Bracket(char bracket, int pos) {
|
||||||
|
|
||||||
char bracket;
|
|
||||||
|
|
||||||
int pos;
|
|
||||||
|
|
||||||
Bracket(char bracket, int pos) {
|
|
||||||
this.bracket = bracket;
|
|
||||||
this.pos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean compatibleWithCloseBracket(char closeBracket) {
|
boolean compatibleWithCloseBracket(char closeBracket) {
|
||||||
if (this.bracket == '{') {
|
return switch (this.bracket) {
|
||||||
return closeBracket == '}';
|
case '{' -> closeBracket == '}';
|
||||||
}
|
case '[' -> closeBracket == ']';
|
||||||
else if (this.bracket == '[') {
|
default -> closeBracket == ')';
|
||||||
return closeBracket == ']';
|
};
|
||||||
}
|
|
||||||
return closeBracket == ')';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char theOpenBracketFor(char closeBracket) {
|
static char theOpenBracketFor(char closeBracket) {
|
||||||
if (closeBracket == '}') {
|
return switch (closeBracket) {
|
||||||
return '{';
|
case '}' -> '{';
|
||||||
}
|
case ']' -> '[';
|
||||||
else if (closeBracket == ']') {
|
default -> '(';
|
||||||
return '[';
|
};
|
||||||
}
|
|
||||||
return '(';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char theCloseBracketFor(char openBracket) {
|
static char theCloseBracketFor(char openBracket) {
|
||||||
if (openBracket == '{') {
|
return switch (openBracket) {
|
||||||
return '}';
|
case '{' -> '}';
|
||||||
}
|
case '[' -> ']';
|
||||||
else if (openBracket == '[') {
|
default -> ')';
|
||||||
return ']';
|
};
|
||||||
}
|
|
||||||
return ')';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.core.convert.TypeDescriptor;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
|
||||||
import org.springframework.expression.Operation;
|
import org.springframework.expression.Operation;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
|
@ -223,7 +222,7 @@ class ExpressionStateTests extends AbstractExpressionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeLocator() throws EvaluationException {
|
void typeLocator() {
|
||||||
assertThat(state.getEvaluationContext().getTypeLocator()).isNotNull();
|
assertThat(state.getEvaluationContext().getTypeLocator()).isNotNull();
|
||||||
assertThat(state.findType("java.lang.Integer")).isEqualTo(Integer.class);
|
assertThat(state.findType("java.lang.Integer")).isEqualTo(Integer.class);
|
||||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||||
|
|
@ -232,7 +231,7 @@ class ExpressionStateTests extends AbstractExpressionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeConversion() throws EvaluationException {
|
void typeConversion() {
|
||||||
String s = (String) state.convertValue(34, TypeDescriptor.valueOf(String.class));
|
String s = (String) state.convertValue(34, TypeDescriptor.valueOf(String.class));
|
||||||
assertThat(s).isEqualTo("34");
|
assertThat(s).isEqualTo("34");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,56 +18,50 @@ package org.springframework.expression.spel;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.expression.EvaluationException;
|
|
||||||
import org.springframework.expression.Operation;
|
import org.springframework.expression.Operation;
|
||||||
import org.springframework.expression.OperatorOverloader;
|
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 Andy Clement
|
||||||
|
* @author Sam Brannen
|
||||||
*/
|
*/
|
||||||
class OperatorOverloaderTests extends AbstractExpressionTests {
|
class OperatorOverloaderTests extends AbstractExpressionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSimpleOperations() {
|
void simpleOperations() {
|
||||||
// no built-in support for this:
|
// default behavior
|
||||||
evaluateAndCheckError("'abc'-true",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
evaluate("'abc' + true", "abctrue", String.class);
|
||||||
|
evaluate("'abc' + null", "abcnull", String.class);
|
||||||
|
|
||||||
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
|
// no built-in support for <string> - <boolean>
|
||||||
eContext.setOperatorOverloader(new StringAndBooleanAddition());
|
evaluateAndCheckError("'abc' - true", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||||
|
|
||||||
SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'+true");
|
super.context.setOperatorOverloader(new StringAndBooleanOperatorOverloader());
|
||||||
assertThat(expr.getValue(eContext)).isEqualTo("abctrue");
|
|
||||||
|
|
||||||
expr = (SpelExpression)parser.parseExpression("'abc'-true");
|
// unaffected
|
||||||
assertThat(expr.getValue(eContext)).isEqualTo("abc");
|
evaluate("'abc' + true", "abctrue", String.class);
|
||||||
|
evaluate("'abc' + null", "abcnull", String.class);
|
||||||
|
|
||||||
expr = (SpelExpression)parser.parseExpression("'abc'+null");
|
// <string> - <boolean> has been overloaded
|
||||||
assertThat(expr.getValue(eContext)).isEqualTo("abcnull");
|
evaluate("'abc' - true", "abcTRUE", String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class StringAndBooleanAddition implements OperatorOverloader {
|
private static class StringAndBooleanOperatorOverloader implements OperatorOverloader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object operate(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException {
|
public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) {
|
||||||
if (operation==Operation.ADD) {
|
return (leftOperand instanceof String && rightOperand instanceof Boolean);
|
||||||
return leftOperand + ((Boolean) rightOperand).toString();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return leftOperand;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException {
|
public Object operate(Operation operation, Object leftOperand, Object rightOperand) {
|
||||||
return leftOperand instanceof String && rightOperand instanceof Boolean;
|
if (operation == Operation.SUBTRACT) {
|
||||||
|
return leftOperand + ((Boolean) rightOperand).toString().toUpperCase();
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException(operation.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue