Skip to content

Commit e4a926e

Browse files
aclementsbrannen
authored andcommitted
Modify SpEL Tokenizer to support methods on numbers
When attempting to parse an Integer literal expression such as 42.toString(), SpEL currently throws a SpelParseException with a message similar to: "EL1041E:(pos 3): After parsing a valid expression, there is still more data in the expression: 'toString'". The problem here is that '3.' is currently considered a valid number (including the dot). However, SpEL succeeds at parsing an equivalent expression for a Double literal such as 3.14.isInfinite(). To address this issue, the SpEL Tokenizer no longer consumes the trailing '.' on an integer as part of the integer. So '3.foo()' will now be parsed as '3' '.' 'foo()' and not '3.' 'foo()' -- which was what prevented parsing of method invocations on integers. To keep the change simple, the parser will no longer handle real numbers of the form '3.e4'. From now on they must include the extra 0 (i.e., '3.0e4'). Issue: SPR-9612
1 parent 015086c commit e4a926e

File tree

4 files changed

+197
-174
lines changed

4 files changed

+197
-174
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,9 +30,10 @@
3030
import org.springframework.util.Assert;
3131

3232
/**
33-
* A SpelExpressions represents a parsed (valid) expression that is ready to be evaluated in a specified context. An
34-
* expression can be evaluated standalone or in a specified context. During expression evaluation the context may be
35-
* asked to resolve references to types, beans, properties, methods.
33+
* A {@code SpelExpression} represents a parsed (valid) expression that is ready
34+
* to be evaluated in a specified context. An expression can be evaluated
35+
* standalone or in a specified context. During expression evaluation the context
36+
* may be asked to resolve references to types, beans, properties, and methods.
3637
*
3738
* @author Andy Clement
3839
* @since 3.0
@@ -103,22 +104,22 @@ public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> exp
103104
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
104105
}
105106

106-
public Class getValueType() throws EvaluationException {
107+
public Class<?> getValueType() throws EvaluationException {
107108
return getValueType(getEvaluationContext());
108109
}
109110

110-
public Class getValueType(Object rootObject) throws EvaluationException {
111+
public Class<?> getValueType(Object rootObject) throws EvaluationException {
111112
return getValueType(getEvaluationContext(), rootObject);
112113
}
113114

114-
public Class getValueType(EvaluationContext context) throws EvaluationException {
115+
public Class<?> getValueType(EvaluationContext context) throws EvaluationException {
115116
Assert.notNull(context, "The EvaluationContext is required");
116117
ExpressionState eState = new ExpressionState(context, configuration);
117118
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
118119
return typeDescriptor != null ? typeDescriptor.getType() : null;
119120
}
120121

121-
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
122+
public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
122123
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
123124
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
124125
return typeDescriptor != null ? typeDescriptor.getType() : null;

spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -289,10 +289,19 @@ private void lexNumericLiteral(boolean firstCharIsZero) {
289289
ch = toProcess[pos];
290290
if (ch=='.') {
291291
isReal = true;
292+
int dotpos = pos;
292293
// carry on consuming digits
293294
do {
294295
pos++;
295296
} while (isDigit(toProcess[pos]));
297+
if (pos == dotpos + 1) {
298+
// the number is something like '3.'. It is really an int but may be
299+
// part of something like '3.toString()'. In this case process it as
300+
// an int and leave the dot as a separate token.
301+
pos = dotpos;
302+
pushIntToken(subarray(start, pos), false, start, pos);
303+
return;
304+
}
296305
}
297306

298307
int endOfNumber = pos;
@@ -307,7 +316,7 @@ private void lexNumericLiteral(boolean firstCharIsZero) {
307316
pushIntToken(subarray(start, endOfNumber), true, start, endOfNumber);
308317
pos++;
309318
} else if (isExponentChar(toProcess[pos])) {
310-
isReal = true; // if it wasnt before, it is now
319+
isReal = true; // if it wasn't before, it is now
311320
pos++;
312321
char possibleSign = toProcess[pos];
313322
if (isSign(possibleSign)) {
@@ -502,6 +511,5 @@ private boolean isHexadecimalDigit(char ch) {
502511
flags[ch]|= IS_ALPHA;
503512
}
504513
}
505-
506514

507515
}

0 commit comments

Comments
 (0)