Tamas Szekeres opened SPR-16232 and commented
The Tokenizer
cuts the integer literal -2147483648
(whose value is equal to Integer.MIN_VALUE
) into a MINUS
token and a LITERAL_INT
. The integer token, in itself, falls outside of the integer range, so from that point of view it is understandable that the parsing later fails, but from a usability one, it should work.
public void testParseWhenMinIntegerSetShouldNotBeThrowingExceptionButItDoes() {
final ExpressionParser parser = new SpelExpressionParser();
final Expression expression = parser.parseExpression(String.valueOf(Integer.MIN_VALUE));
expression.getValue();
}
FAILED: testParseWhenMinIntegerSetShouldNotBeThrowingExceptionButItDoes
org.springframework.expression.spel.SpelParseException: EL1035E: The value '2147483648' cannot be parsed as an int
at org.springframework.expression.spel.ast.Literal.getIntLiteral(Literal.java:80)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.maybeEatLiteral(InternalSpelExpressionParser.java:840)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatStartNode(InternalSpelExpressionParser.java:500)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatPrimaryExpression(InternalSpelExpressionParser.java:343)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatUnaryExpression(InternalSpelExpressionParser.java:337)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatUnaryExpression(InternalSpelExpressionParser.java:316)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatPowerIncDecExpression(InternalSpelExpressionParser.java:293)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatProductExpression(InternalSpelExpressionParser.java:272)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatSumExpression(InternalSpelExpressionParser.java:255)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatRelationalExpression(InternalSpelExpressionParser.java:210)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatLogicalAndExpression(InternalSpelExpressionParser.java:198)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatLogicalOrExpression(InternalSpelExpressionParser.java:186)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatExpression(InternalSpelExpressionParser.java:146)
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:127)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:60)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:32)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:73)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:60)
...
Caused by: java.lang.NumberFormatException: For input string: "2147483648"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:499)
at org.springframework.expression.spel.ast.Literal.getIntLiteral(Literal.java:76)
... 44 more
Affects: 4.3.11, 4.3.12
Comment From: sbrannen
First and foremost, thank you for the detailed report and apologies for taking so long to process this issue.
Please note that, depending on your exact use case, you may be able to work around this limitation if you're using the StandardEvaluationContext
(and not the SimpleEvaluationContext
) by making use of the type operator T()
and Integer.valueOf(String)
as demonstrated in the following test which passes.
@Test
void parsingIntegerMinValueFromStringLiteral() {
EvaluationContext context = new StandardEvaluationContext();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("T(Integer).valueOf('-2147483648')");
assertThat(expression.getValue(context)).isEqualTo(Integer.MIN_VALUE);
}
Comment From: sbrannen
An alternative would be to represent the Integer.MIN_VALUE
literal value as a Long
instead of an Integer
as follows, in case that works for your use case.
@Test
void parsingIntegerMinValueFromLongLiteral() {
EvaluationContext context = new StandardEvaluationContext();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("-2147483648L");
assertThat(expression.getValue(context, Integer.class)).isEqualTo(Integer.MIN_VALUE);
}
Note, however, that the same limitation applies to Long.MIN_VALUE
when used as a long literal within a SpEL expression. The following test fails accordingly.
@Test
void parsingLongMinValueFromLongLiteral() {
EvaluationContext context = new StandardEvaluationContext();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("-9223372036854775808L");
assertThat(expression.getValue(context, Long.class)).isEqualTo(Long.MIN_VALUE);
}
The above test fails with:
org.springframework.expression.spel.SpelParseException: EL1036E: The value '9223372036854775808' cannot be parsed as a long
Comment From: sbrannen
After further investigation, I have determined that the following expressions can be used as workarounds, depending on your exact use case.
Integer.MIN_VALUE (-2147483648)
-2147483647 - 1
-- always works (equal toInteger.MIN_VALUE
+ 1 - 1)-2147483648L
-- always works (storesInteger.MIN_VALUE
in aLong
)0.MIN_VALUE
-- always works, even withSimpleEvaluationContext
;0
can be any integer literalT(Integer).MIN_VALUE
-- requiresStandardEvaluationContext
T(Integer).valueOf('-2147483648')
-- requiresStandardEvaluationContext
Long.MIN_VALUE (-9223372036854775808)
-9223372036854775807 - 1
-- always works (equal toLong.MIN_VALUE
+ 1 - 1)0L.MIN_VALUE
-- always works, even withSimpleEvaluationContext
;0
can be any long literalT(Long).MIN_VALUE
-- requiresStandardEvaluationContext
T(Long).valueOf('-9223372036854775808')
-- requiresStandardEvaluationContext
Comment From: sbrannen
After considerable analysis, I have determined that supporting Integer.MIN_VALUE
and Long.MIN_VALUE
as integer and long literals is out of scope for the Spring Expression Language (SpEL).
The reason is that the internals of the SpEL tokenizer, parser, and AST node implementations rely heavily on the fact that literals are always positive values. For example, when you specify an integer literal such as -2
in an expression, the value is never stored or represented as -2
. Instead, the value is stored as 2
in an IntLiteral
which is supplied as the sole operand to the OpMinus
AST node. So -2
is effectively represented internally as OpMinus(IntLiteral(2))
and calculated on-the-fly as (0 - 2)
.
Changing the semantics to actually store -2
in an IntLiteral
or LongLiteral
would break various constructs in SpEL.
In light of that and the aforementioned workarounds, I am repurposing this as a documentation issue to point out this limitation.
Comment From: sbrannen
@firefoxpdm, are you perhaps the same firefoxpdm
that created this issue on JIRA?
Comment From: sbrannen
While working on #32187, I realized that the easiest way to express Integer.MIN_VALUE
within an expression (when not evaluated in a StandardEvaluationContext
) is to simply calculate the valuate using SpEL's exponential power operator (-2^31
).