Skip to content

Commit

Permalink
Document the between operator in SpEL
Browse files Browse the repository at this point in the history
Closes gh-32140
  • Loading branch information
sbrannen committed Jan 29, 2024
1 parent e97fc7b commit 84cce60
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,64 +66,118 @@ 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:
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:

* `lt` (`<`)
* `gt` (`>`)
* `le` (`\<=`)
* `ge` (`>=`)
* `eq` (`==`)
* `ne` (`!=`)
* `not` (`!`)

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
boolean falseValue = parser.parseExpression(
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
boolean trueValue = parser.parseExpression(
result = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression(
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
val falseValue = parser.parseExpression(
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
val trueValue = parser.parseExpression(
result = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
// evaluates to false
val falseValue = parser.parseExpression(
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`.

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:

* `lt` (`<`)
* `gt` (`>`)
* `le` (`\<=`)
* `ge` (`>=`)
* `eq` (`==`)
* `ne` (`!=`)
* `not` (`!`)

All of the textual operators are case-insensitive.


[[expressions-operators-logical]]
== Logical Operators
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,12 +26,18 @@
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.
*
* <p>The left operand must be a single value, and the right operand must be a
* 2-element list which defines the range.
*
* <p>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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ void methodInvocation2() {
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();
Expand All @@ -232,17 +233,47 @@ void relationalOperators() {

@Test
void otherOperators() {
boolean result;

// evaluates to true
result = parser.parseExpression(
"1 between {1, 5}").getValue(Boolean.class);
assertThat(result).isTrue();

// evaluates to false
boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
assertThat(falseValue).isFalse();
result = parser.parseExpression(
"1 between {10, 15}").getValue(Boolean.class);
assertThat(result).isFalse();

// evaluates to true
boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
assertThat(trueValue).isTrue();
result = parser.parseExpression(
"'elephant' between {'aardvark', 'zebra'}").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(
"'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
Expand Down

0 comments on commit 84cce60

Please sign in to comment.