To improve consistency and avoid confusion regarding primitive types
and their wrapper types, this commit ensures that we always use class
literals for primitive types.
For example, instead of using the `Void.TYPE` constant, we now
consistently use `void.class`.
Prior to this commit, SpEL's Indexer incorrectly requested conversion
to wrappers instead of primitives when setting an element in a
primitive array.
This commit addresses this by requesting primitive conversion -- for
example, conversion to `int.class` instead of `Integer.class` when
setting a value in an `int[]` array.
For greater clarity, this commit also switches from using `TYPE`
constants in wrapper classes to primitive class literals -- for
example, from `Integer.TYPE` to `int.class`.
Closes gh-32147
Since SpEL is no longer "in progress", this commit removes the obsolete
InProgressTests class and moves all non-duplicated test cases to other
test classes.
Prior to this commit, only the MethodFilter and ConstructorResolver
functional SPIs in the org.springframework.expression package were
annotated with @FunctionalInterface.
For consistency, this commit designates each of the following
functional SPIs in that package as a @FunctionalInterface as well.
- BeanResolver
- ConstructorExecutor
- MethodExecutor
- MethodResolver
Closes gh-32135
This commit introduces support for a Spring property named
`spring.context.expression.maxLength`. When set, the value of that
property is used internally in StandardBeanExpressionResolver to
configure the SpelParserConfiguration used when evaluating String
values in bean definitions, @Value, etc.
Closes gh-31952
Search for : assertThat\((.+).isEmpty\(\)\).isTrue\(\)
Replace with : assertThat($1).isEmpty()
Search for : assertThat\((.+).isEmpty\(\)\).isFalse\(\)
Replace with : assertThat($1).isNotEmpty()
Closes gh-31758
Search for : assertThat\((.+)\.equals\((\w+)\)\)\.isTrue\(\)
Replace with : assertThat($1).isEqualTo($2)
Search for : assertThat\((.+)\.equals\((\w+)\)\)\.isFalse\(\)
Replace with : assertThat($1).isNotEqualTo($2)
Closes gh-31763
This commit makes sure that the per-operation execution context for
caching and event listening does not recreate the default internal
delegates, but rather get initialized with a shared state.
This reduces the number of instances created per operation execution,
reducing the GC pressure as a result. This also makes sure that any
cache, such as the one in StandardTypeLocator, is reused.
Closes gh-31617
Due to the changes in gh-31341, if the repeat count in a SpEL
expression (using the repeat operator '*') is negative, we throw a
SpelEvaluationException with the MAX_REPEATED_TEXT_SIZE_EXCEEDED
message which is incorrect and misleading.
Prior to gh-31341, a negative repeat count resulted in an
IllegalArgumentException being thrown by String#repeat(), which was
acceptable in terms of diagnostics, but that did not make it
immediately clear to the user what the underlying cause was.
In light of the above, this commit improves diagnostics for a negative
repeated text count in SpEL expressions by throwing a
SpelEvaluationException with a new NEGATIVE_REPEATED_TEXT_COUNT error
message.
Closes gh-31342
If the resulting size of repeated text in a SpEL expression (using the
repeat operator '*') would exceed MAX_REPEATED_TEXT_SIZE, we currently
throw a SpelEvaluationException with the
MAX_REPEATED_TEXT_SIZE_EXCEEDED message.
However, if the calculation of the repeated text size results in
integer overflow, our max size check fails to detect that, and
String#repeat(int) throws a preemptive OutOfMemoryError from which the
application immediately recovers.
To improve diagnostics for users, this commit ensures that we
consistently throw a SpelEvaluationException with the
MAX_REPEATED_TEXT_SIZE_EXCEEDED message when integer overflow occurs.
Closes gh-31341
Prior to this commit the Spring Expression Language (SpEL) was able to
properly parse an expression that uses the safe navigation operator
(?.) with a method that has a `void` return type (for example,
"myObject?.doSomething()"); however, SpEL was not able to evaluate or
compile such expressions.
This commit addresses the evaluation issue by selectively not boxing
the exit type descriptor (for inclusion in the generated bytecode) when
the method's return type is `void`.
This commit addresses the compilation issue by pushing a null object
reference onto the stack in the generated byte code when the method's
return type is `void`.
Closes gh-27421
Prior to this commit, if a Spring Expression Language (SpEL) expression
contained property, field, or method references using the null-safe
navigation operator (?.), the generated AST String representation
incorrectly omitted the '?' characters.
For example, 'myProperty?.myMethod()' had a generated AST string
representation of 'myProperty.myMethod()'.
This commit addresses this by introducing isNullSafe() in
MethodReference and reworking the logic in
CompoundExpression.toStringAST().
Closes gh-31326
This commit revises the contribution for gh-25921 in the following ways.
- Use instanceof pattern matching
- Use List.of() and Map.of()
- Add missing @since tags
- Polish Javadoc
- Rename isNegativeNumber() to isNegativeNumberLiteral()
- Restructure InlineCollectionTests using @Nested, etc.
- Fix testListWithVariableNotCached() test: it previously set a SpEL
"variable" but tested a "property" in the root context object, which
effectively did not test anything.
- Introduce additional tests: listWithPropertyAccessIsNotCached(),
mapWithVariableIsNotCached(), and mapWithPropertyAccessIsNotCached().
Prior to this commit, it was unclear to users and third parties that it
is necessary to manually configure a StandardTypeLocator with a
specific ClassLoader to ensure that the SpEL expression parser is able
to reliably locate user types.
For example, the StandardBeanExpressionResolver in the spring-context
module configures a StandardTypeLocator using the bean ClassLoader of
the corresponding BeanFactory.
This commit improves the documentation to raise awareness of this fact.
Closes gh-26253
This commit adds support for inlined Kotlin value
class properties in SpEL by leveraging Kotlin
reflection instead of Java reflection broken
by the mangled method name.
Closes gh-30468
Java 12 introduced java.lang.Class#componentType() as a shortcut for
getComponentType().
Since we started using arrayType() in fe5560400c, this commit switches
to componentType() for consistent API usage style.
Prior to this commit, if a Spring Expression Language (SpEL) expression
contained indexed access to an object, the generated AST String
representation incorrectly included a dot ('.') before the index access.
For example, 'property[0]' had a generated AST string representation of
'property.[0]'.
This commit addresses this by reworking the logic in
CompoundExpression.toStringAST().
Closes gh-30610
Prior to this commit, only the letters 'A' - 'Z' (ignoring case) were
supported in identifiers (i.e., property, field, and variable names).
This known (yet undocumented) limitation prevented the use of characters
such as 'ü', 'ñ', 'é' as well as letters from other character sets such
as Chinese, Japanese, Cyrillic, etc.
This commit lifts that restriction by delegating to Character.isLetter()
to determine if a character in a SpEL expression is a letter.
Closes gh-30580
Prior to this commit, when an unsupported character such as "ü" was
encountered in a SpEL expression, the error message was:
Cannot handle (252) 'ü'
With this commit, the error message is now similar to:
Unsupported character 'ü' (252) encountered at position 5 in expression.
See gh-30580
Closes gh-30602
This commit adds support for MethodHandles in SpEL, using the same
syntax as user-defined functions (which also covers reflective Methods).
The most benefit is expected with handles that capture a static method
with no arguments, or with fully bound handles (where all the arguments
have been bound, including a target instance as first bound argument
if necessary). Partially bound MethodHandle should also be supported.
A best effort approach is taken to detect varargs as there is no API
support to determine if an argument is a vararg or an explicit array,
unlike with Method. Argument conversions are also applied. Finally,
array repacking is not always necessary with varargs so it is only
performed when the vararg is the sole argument to the invoked method.
See gh-27099
Closes gh-30045
This commit improves both the javadoc and the reference guide section on
the Elvis SpEL operator to clarify that in addition to `null` objects,
empty Strings also lead the operator to evaluate to its second operand.
The reference guide's advanced snippet is modified to use such an empty
String instead of `null` to make that behavior prominent with some code.
See gh-30318
Closes gh-30352
Prior to gh-30325, supplying a null reference for a SpEL expression was
effectively equivalent to supplying the String "null" as the
expression. Consequently, evaluation of a null reference expression
always evaluated to a null reference. However, that was accidental
rather than by design.
Due to the introduction of the checkExpressionLength(String) method in
InternalSpelExpressionParser (in conjunction with gh-30325), an attempt
to evaluate a null reference as a SpEL expression now results in a
NullPointerException.
To address both of these issues,
TemplateAwareExpressionParser.parseExpression() and
SpelExpressionParser.parseRaw() now reject null and empty SpEL
expressions.
Closes gh-30371
This commit introduces infrastructure to differentiate between
programmatic setting of a variable in an EvaluationContext versus the
assignment of a variable within a SpEL expression using the assignment
operator (=). In addition, this commit disables variable assignment
within expressions when using the SimpleEvaluationContext.
Closes gh-30326
This commit introduces support for limiting the maximum length of a
string resulting from the concatenation operator (+) in SpEL
expressions.
Closes gh-30324
This commit changes the max regex length in SpEL expressions from 1024
to 1000 in order to consistently use "round" numbers for recently
introduced limits.
See gh-30265
This commit increases the max regex length in SpEL expressions from 256
to 1024 in order to support use cases where a regex may be rather long
without necessarily increasing the complexity of the regex.
Closes gh-30265
This commit refactors some AssertJ assertions into more idiomatic and
readable ones. Using the dedicated assertion instead of a generic one
will produce more meaningful error messages.
For instance, consider collection size:
```
// expected: 5 but was: 2
assertThat(collection.size()).equals(5);
// Expected size: 5 but was: 2 in: [1, 2]
assertThat(collection).hasSize(5);
```
Closes gh-30104
Supplying a large regular expression to the `matches` operator in a
SpEL expression can result in errors that are not very helpful to the
user.
This commit improves the diagnostics in SpEL for the `matches` operator
by throwing a SpelEvaluationException with a meaningful error message
to better assist the user.
Closes gh-30144
Attempting to create repeated text in a SpEL expression using the
repeat operator can result in errors that are not very helpful to the
user.
This commit improves the diagnostics in SpEL for the repeat operator by
throwing a SpelEvaluationException with a meaningful error message in
order to better assist the user.
Closes gh-30142
Prior to this commit, the pattern cache for the SpEL `matches` operator
only applied to expressions such as the following where the same
`matches` operator is invoked multiple times with different input:
"map.keySet().?[#this matches '.+xyz']"
The pattern cache did not apply to expressions such as the following
where the same pattern ('.+xyz') is used in multiple `matches`
operations:
"foo matches '.+xyz' AND bar matches '.+xyz'"
This commit addresses this by moving the instance of the pattern cache
map from OperatorMatches to InternalSpelExpressionParser so that the
cache can be reused for all `matches` operations for the given parser.
Closes gh-30140
This commit ensures that methods declared in java.lang.Object (such as
toString() can be invoked on a JDK proxy instance in a SpEL expression.
Closes gh-25316
Prior to this commit, SpEL's ConstructorReference did not provide
support for arrays when generating a string representation of the
internal AST. For example, 'new String[3]' was represented as 'new
String()' instead of 'new String[3]'.
This commit introduces support for standard array construction and array
construction with initializers in ConstructorReference's toStringAST()
implementation.
Closes gh-29665
Prior to this commit, there were two bugs in the support for quotes
within String literals in SpEL expressions.
- Two double quotes ("") or two single quotes ('') were always replaced
with one double quote or one single quote, respectively, regardless
of which quote character was used to enclose the original String
literal. This resulted in the loss of one of the double quotes when
the String literal was enclosed in single quotes, and vice versa. For
example, 'x "" y' became 'x " y'.
- A single quote which was properly escaped in a String literal
enclosed within single quotes was not escaped in the AST string
representation of the expression. For example, 'x '' y' became 'x ' y'.
This commit fixes both of these related issues in StringLiteral and
overhauls the structure of ParsingTests.
Closes gh-29604, gh-28356
Prior to this commit, ternary and Elvis expressions enclosed in
parentheses (to account for operator precedence) were properly parsed
and evaluated; however, the corresponding toStringAST() implementations
did not enclose the results in parentheses. Consequently, the string
representation of the ASTs did not reflect the original semantics of
such expressions.
For example, given "(4 % 2 == 0 ? 1 : 0) * 10" as the expression to
parse and evaluate, the result of toStringAST() was previously
"(((4 % 2) == 0) ? 1 : 0 * 10)" instead of
"((((4 % 2) == 0) ? 1 : 0) * 10)", implying that 0 should be multiplied
by 10 instead of multiplying the result of the ternary expression by 10.
This commit addresses this by ensuring that SpEL ternary and Elvis
expressions are enclosed in parentheses in toStringAST().
Closes gh-29463
The antlr-based SpEL expression grammar file (SpringExpressions.g) was
introduced during initial development and prototyping of the Spring
Expression language; however, it was quickly abandoned in favor of a
handcrafted implementation. Consequently, it has become obsolete over
time and has never been actively maintained.
This commit therefore removes the obsolete SpEL expression grammar file.
Closes gh-28425
Attempting to create a large array in a SpEL expression can result in
an OutOfMemoryError. Although the JVM recovers from that, the error
message is not very helpful to the user.
This commit improves the diagnostics in SpEL for large array creation
by throwing a SpelEvaluationException with a meaningful error message
in order to improve diagnostics for the user.
Closes gh-28145
Prior to this commit, SpEL was able to recover from an error that
occurred while running a CompiledExpression; however, SpEL was not able
to recover from an error that occurred while compiling the expression
(such as a java.lang.VerifyError). The latter can occur when multiple
threads concurrently change types involved in the expression, such as
the concrete type of a custom variable registered via
EvaluationContext.setVariable(...), which can result in SpEL generating
invalid bytecode.
This commit addresses this issue by catching exceptions thrown while
compiling an expression and updating the `failedAttempts` and
`interpretedCount` counters accordingly. If an exception is caught
while operating in SpelCompilerMode.IMMEDIATE mode, the exception will
be propagated via a SpelEvaluationException with a new
SpelMessage.EXCEPTION_COMPILING_EXPRESSION error category.
Closes gh-28043
The test case is intended to be for the method 'withRootObject()', but
actually it's copied from the previous test method that does not use
'withRootObject()'.
This commit fixes the propertyReadWriteWithRootObject() test method in
PropertyAccessTests.
Closes gh-27905
This commit replaces the use of Collections.unmodifiableList/Set/Map
with the corresponding 'of(...)' factory methods introduced in Java 9.
Closes gh-27824
Prior to this commit, if a single null value was passed to a method with
a varargs array of type java.util.Optional, that null value was passed
unmodified. On the contrary, a null passed with additional values to
such a method resulted in the null being converted to Optional.empty().
This commit ensures that a single null value is also converted to
Optional.empty() for such SpEL expressions.
Closes gh-27795
A regression was introduced in gh-27582. Specifically, when null is
supplied as the single argument for a varargs parameter in a method or
function in a SpEL expression, ReflectionHelper currently throws a
NullPointerException instead of leaving the null value unchanged.
This commit fixes this regression.
Closes gh-27719
Prior to this commit, the SpEL relational operators for comparison
would have the following problem:
* their implementation would claim that incompatible types
could be compared and later fail during comparison
* not delegate the comparison to the actual `TypeComparator`
implementation but rely on operator specifics
This commit ensures that the `TypeComparator` implementation is used for
both `canCompare` and `compare` calls in the operators.
See gh-1581
Prior to this commit, if a SpEL expression invoked a method or
registered function that declares a String varargs argument, there were
sometimes issues with converting the input arguments into the varargs
array argument. Specifically, if the expression supplied a single
String argument containing a comma for the varargs (such as "a,b"),
SpEL's ReflectionHelper.convertArguments() method incorrectly converted
that single String to an array via the ConversionService, which
indirectly converted that String using the StringToArrayConverter,
which converts a comma-delimited String to an array. Thus, "a,b"
effectively got converted to a two-dimensional array ["a", "b"] instead
of simply ["a,b"].
This commit fixes this bug by avoiding use of the TypeConverter and
ConversionService for single arguments supplied as varargs when the
single argument's type matches the varargs array component type.
Closes gh-27582
This commit also applies additional clean-up tasks such as the following.
- final fields
This commit also makes use of java.lang.String.repeat(int) in OptMultiply.
This has only been applied to `src/main/java`.
In order to be able to use text blocks and other new Java language
features, we are upgrading to a recent version of Checkstyle.
The latest version of spring-javaformat-checkstyle (0.0.28) is built
against Checkstyle 8.32 which does not include support for language
features such as text blocks. Support for text blocks was added in
Checkstyle 8.36.
In addition, there is a binary compatibility issue between
spring-javaformat-checkstyle 0.0.28 and Checkstyle 8.42. Thus we cannot
use Checkstyle 8.42 or higher.
In this commit, we therefore upgrade to spring-javaformat-checkstyle
0.0.28 and downgrade to Checkstyle 8.41.
This change is being applied to `5.3.x` as well as `main` in order to
benefit from the enhanced checking provided in more recent versions of
Checkstyle.
Closes gh-27481
To slightly improve performance, this commit switches to
StringBuilder.append(char) instead of StringBuilder.append(String)
whenever we append a single character to a StringBuilder.
Closes gh-27098
This commit allows the spring-expression module to be more easily repackaged
for embedding in third-party JARs -- for example, via the Shadow Gradle plugin.
Closes gh-26779