From 84cce6018c0dfa77784f548b7877aa1a2e7fb56c Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:34:12 +0100 Subject: [PATCH] Document the between operator in SpEL Closes gh-32140 --- .../expressions/language-ref/operators.adoc | 142 ++++++++++++------ .../expression/spel/ast/OperatorBetween.java | 16 +- .../spel/SpelDocumentationTests.java | 47 +++++- 3 files changed, 148 insertions(+), 57 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc index 3325171668..0b9e422945 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc @@ -66,50 +66,6 @@ If you prefer numeric comparisons instead, avoid number-based `null` comparisons in favor of comparisons against zero (for example, `X > 0` or `X < 0`). ==== -In addition to the standard relational operators, SpEL supports the `instanceof` and regular -expression-based `matches` operators. The following listing shows examples of both: - -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - // evaluates to false - boolean falseValue = parser.parseExpression( - "'xyz' instanceof T(Integer)").getValue(Boolean.class); - - // evaluates to true - boolean trueValue = parser.parseExpression( - "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); - - // evaluates to false - boolean falseValue = parser.parseExpression( - "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ----- - // evaluates to false - val falseValue = parser.parseExpression( - "'xyz' instanceof T(Integer)").getValue(Boolean::class.java) - - // evaluates to true - val trueValue = parser.parseExpression( - "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java) - - // evaluates to false - val falseValue = parser.parseExpression( - "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java) ----- -====== - -CAUTION: Be careful with primitive types, as they are immediately boxed up to their -wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while -`1 instanceof T(Integer)` evaluates to `true`. - Each symbolic operator can also be specified as a purely textual equivalent. This avoids problems where the symbols used have special meaning for the document type in which the expression is embedded (such as in an XML document). The textual equivalents are: @@ -124,6 +80,104 @@ expression is embedded (such as in an XML document). The textual equivalents are All of the textual operators are case-insensitive. +In addition to the standard relational operators, SpEL supports the `between`, +`instanceof`, and regular expression-based `matches` operators. The following listing +shows examples of all three: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + boolean result; + + // evaluates to true + result = parser.parseExpression( + "1 between {1, 5}").getValue(Boolean.class); + + // evaluates to false + result = parser.parseExpression( + "1 between {10, 15}").getValue(Boolean.class); + + // evaluates to true + result = parser.parseExpression( + "'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class); + + // evaluates to false + result = parser.parseExpression( + "'elephant' between {'aardvark', 'cobra'}").getValue(Boolean.class); + + // evaluates to true + result = parser.parseExpression( + "123 instanceof T(Integer)").getValue(Boolean.class); + + // evaluates to false + result = parser.parseExpression( + "'xyz' instanceof T(Integer)").getValue(Boolean.class); + + // evaluates to true + result = parser.parseExpression( + "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + + // evaluates to false + result = parser.parseExpression( + "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + // evaluates to true + var result = parser.parseExpression( + "1 between {1, 5}").getValue(Boolean::class.java) + + // evaluates to false + result = parser.parseExpression( + "1 between {10, 15}").getValue(Boolean::class.java) + + // evaluates to true + result = parser.parseExpression( + "'elephant' between {'aardvark', 'zebra'}").getValue(Boolean::class.java) + + // evaluates to false + result = parser.parseExpression( + "'elephant' between {'aardvark', 'cobra'}").getValue(Boolean::class.java) + + // evaluates to true + result = parser.parseExpression( + "123 instanceof T(Integer)").getValue(Boolean::class.java) + + // evaluates to false + result = parser.parseExpression( + "'xyz' instanceof T(Integer)").getValue(Boolean::class.java) + + // evaluates to true + result = parser.parseExpression( + "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java) + + // evaluates to false + result = parser.parseExpression( + "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java) +---- +====== + +[NOTE] +==== +The left operand to the `between` operator must be a single value, and the right operand +must be a 2-element list which defines the range. + +The `between` operator returns `true` if the left operand is _between_ the two elements +in the range, inclusive (using a comparison algorithm based on `java.lang.Comparable`). +In addition, the first element in the range must be less than or equal to the second +element in the range. +==== + +CAUTION: Be careful with primitive types, as they are immediately boxed up to their +wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while +`1 instanceof T(Integer)` evaluates to `true`. + [[expressions-operators-logical]] == Logical Operators diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java index 12733435a9..dc7cb7a307 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -26,12 +26,18 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.BooleanTypedValue; /** - * Represents the between operator. The left operand to between must be a single value and - * the right operand must be a list - this operator returns true if the left operand is - * between (using the registered comparator) the two elements in the list. The definition - * of between being inclusive follows the SQL BETWEEN definition. + * Represents the {@code between} operator. + * + *

The left operand must be a single value, and the right operand must be a + * 2-element list which defines the range. + * + *

This operator returns {@code true} if the left operand is between the two + * elements in the range, inclusive (using the registered {@link TypeComparator} + * for comparison). In addition, the first element in the range must be less than + * or equal to the second element in the range. * * @author Andy Clement + * @author Sam Brannen * @since 3.0 */ public class OperatorBetween extends Operator { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index 679572ce61..6fd7d5f3e0 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -221,6 +221,7 @@ class SpelDocumentationTests extends AbstractExpressionTests { void relationalOperators() { boolean result = parser.parseExpression("2 == 2").getValue(Boolean.class); assertThat(result).isTrue(); + // evaluates to false result = parser.parseExpression("2 < -5.0").getValue(Boolean.class); assertThat(result).isFalse(); @@ -232,17 +233,47 @@ class SpelDocumentationTests extends AbstractExpressionTests { @Test void otherOperators() { - // evaluates to false - boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); - assertThat(falseValue).isFalse(); + boolean result; // evaluates to true - boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); - assertThat(trueValue).isTrue(); + result = parser.parseExpression( + "1 between {1, 5}").getValue(Boolean.class); + assertThat(result).isTrue(); - //evaluates to false - falseValue = parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); - assertThat(falseValue).isFalse(); + // evaluates to false + result = parser.parseExpression( + "1 between {10, 15}").getValue(Boolean.class); + assertThat(result).isFalse(); + + // evaluates to true + result = parser.parseExpression( + "'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class); + assertThat(result).isTrue(); + + // evaluates to false + result = parser.parseExpression( + "'elephant' between {'aardvark', 'cobra'}").getValue(Boolean.class); + assertThat(result).isFalse(); + + // evaluates to true + result = parser.parseExpression( + "123 instanceof T(Integer)").getValue(Boolean.class); + assertThat(result).isTrue(); + + // evaluates to false + result = parser.parseExpression( + "'xyz' instanceof T(Integer)").getValue(Boolean.class); + assertThat(result).isFalse(); + + // evaluates to true + result = parser.parseExpression( + "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + assertThat(result).isTrue(); + + // evaluates to false + result = parser.parseExpression( + "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + assertThat(result).isFalse(); } @Test