Skip to content

Commit 99b2f6a

Browse files
committed
Merge pull request #163 from philwebb/SPR-9862
# By Phillip Webb * SPR-9862: Allow SpEL reserved words in type package names
2 parents 7c399d7 + edce2e7 commit 99b2f6a

File tree

4 files changed

+104
-23
lines changed

4 files changed

+104
-23
lines changed

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

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package org.springframework.expression.spel.standard;
1818

1919
import java.util.ArrayList;
20+
import java.util.LinkedList;
2021
import java.util.List;
2122
import java.util.Stack;
23+
import java.util.regex.Pattern;
2224

2325
import org.springframework.expression.ParseException;
2426
import org.springframework.expression.ParserContext;
@@ -29,6 +31,7 @@
2931
import org.springframework.expression.spel.SpelParserConfiguration;
3032
import org.springframework.expression.spel.ast.*;
3133
import org.springframework.util.Assert;
34+
import org.springframework.util.StringUtils;
3235

3336
/**
3437
* Hand written SpEL parser. Instances are reusable but are not thread safe.
@@ -38,6 +41,8 @@
3841
*/
3942
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
4043

44+
private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+");
45+
4146
// The expression being parsed
4247
private String expressionString;
4348

@@ -567,14 +572,35 @@ private boolean maybeEatSelection(boolean nullSafeNavigation) {
567572
* TODO AndyC Could create complete identifiers (a.b.c) here rather than a sequence of them? (a, b, c)
568573
*/
569574
private SpelNodeImpl eatPossiblyQualifiedId() {
570-
List<SpelNodeImpl> qualifiedIdPieces = new ArrayList<SpelNodeImpl>();
571-
Token startnode = eatToken(TokenKind.IDENTIFIER);
572-
qualifiedIdPieces.add(new Identifier(startnode.stringValue(),toPos(startnode)));
573-
while (peekToken(TokenKind.DOT,true)) {
574-
Token node = eatToken(TokenKind.IDENTIFIER);
575-
qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node)));
576-
}
577-
return new QualifiedIdentifier(toPos(startnode.startpos,qualifiedIdPieces.get(qualifiedIdPieces.size()-1).getEndPosition()),qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()]));
575+
LinkedList<SpelNodeImpl> qualifiedIdPieces = new LinkedList<SpelNodeImpl>();
576+
Token node = peekToken();
577+
while (isValidQualifiedId(node)) {
578+
nextToken();
579+
if(node.kind != TokenKind.DOT) {
580+
qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node)));
581+
}
582+
node = peekToken();
583+
}
584+
if(qualifiedIdPieces.isEmpty()) {
585+
if(node == null) {
586+
raiseInternalException( expressionString.length(), SpelMessage.OOD);
587+
}
588+
raiseInternalException(node.startpos, SpelMessage.NOT_EXPECTED_TOKEN,
589+
"qualified ID", node.getKind().toString().toLowerCase());
590+
}
591+
int pos = toPos(qualifiedIdPieces.getFirst().getStartPosition(), qualifiedIdPieces.getLast().getEndPosition());
592+
return new QualifiedIdentifier(pos, qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()]));
593+
}
594+
595+
private boolean isValidQualifiedId(Token node) {
596+
if(node == null || node.kind == TokenKind.LITERAL_STRING) {
597+
return false;
598+
}
599+
if(node.kind == TokenKind.DOT || node.kind == TokenKind.IDENTIFIER) {
600+
return true;
601+
}
602+
String value = node.stringValue();
603+
return StringUtils.hasLength(value) && VALID_QUALIFIED_ID_PATTERN.matcher(value).matches();
578604
}
579605

580606
// This is complicated due to the support for dollars in identifiers. Dollars are normally separate tokens but

spring-expression/src/test/java/org/springframework/expression/spel/ParserErrorMessagesTests.java

Lines changed: 3 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.
@@ -20,7 +20,7 @@
2020

2121
/**
2222
* Tests the messages and exceptions that come out for badly formed expressions
23-
*
23+
*
2424
* @author Andy Clement
2525
*/
2626
public class ParserErrorMessagesTests extends ExpressionTestCase {
@@ -56,7 +56,7 @@ public void testBrokenExpression07() {
5656
// T() can only take an identifier (possibly qualified), not a literal
5757
// message ought to say identifier rather than ID
5858
parseAndCheckError("null instanceof T('a')", SpelMessage.NOT_EXPECTED_TOKEN, 18,
59-
"identifier","literal_string");
59+
"qualified ID","literal_string");
6060
}
6161

6262
}

spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java renamed to spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,49 @@
1616

1717
package org.springframework.expression.spel;
1818

19+
import java.lang.reflect.Field;
20+
import java.lang.reflect.Method;
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.LinkedHashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Properties;
27+
1928
import junit.framework.Assert;
29+
2030
import org.junit.Ignore;
2131
import org.junit.Test;
32+
2233
import org.springframework.core.convert.TypeDescriptor;
23-
import org.springframework.expression.*;
34+
import org.springframework.expression.AccessException;
35+
import org.springframework.expression.BeanResolver;
36+
import org.springframework.expression.EvaluationContext;
37+
import org.springframework.expression.EvaluationException;
38+
import org.springframework.expression.Expression;
39+
import org.springframework.expression.ExpressionParser;
40+
import org.springframework.expression.MethodExecutor;
41+
import org.springframework.expression.MethodResolver;
42+
import org.springframework.expression.ParserContext;
43+
import org.springframework.expression.PropertyAccessor;
44+
import org.springframework.expression.TypedValue;
2445
import org.springframework.expression.spel.standard.SpelExpression;
2546
import org.springframework.expression.spel.standard.SpelExpressionParser;
2647
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
2748
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
2849
import org.springframework.expression.spel.support.StandardEvaluationContext;
2950
import org.springframework.expression.spel.support.StandardTypeLocator;
51+
import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver;
3052

31-
import java.lang.reflect.Field;
32-
import java.lang.reflect.Method;
33-
import java.util.*;
34-
35-
import static org.junit.Assert.assertEquals;
36-
import static org.junit.Assert.fail;
53+
import static org.junit.Assert.*;
3754

3855
/**
39-
* Tests based on Jiras up to the release of Spring 3.0.0
56+
* Reproduction tests cornering various SpEL JIRA issues.
4057
*
4158
* @author Andy Clement
4259
* @author Clark Duplichien
4360
*/
44-
public class SpringEL300Tests extends ExpressionTestCase {
61+
public class SpelReproTests extends ExpressionTestCase {
4562

4663
@Test
4764
public void testNPE_SPR5661() {
@@ -147,12 +164,12 @@ public void testSPR5905_InnerTypeReferences() throws Exception {
147164
Expression expr = new SpelExpressionParser().parseRaw("T(java.util.Map$Entry)");
148165
Assert.assertEquals(Map.Entry.class,expr.getValue(eContext));
149166

150-
expr = new SpelExpressionParser().parseRaw("T(org.springframework.expression.spel.SpringEL300Tests$Outer$Inner).run()");
167+
expr = new SpelExpressionParser().parseRaw("T(org.springframework.expression.spel.SpelReproTests$Outer$Inner).run()");
151168
Assert.assertEquals(12,expr.getValue(eContext));
152169

153-
expr = new SpelExpressionParser().parseRaw("new org.springframework.expression.spel.SpringEL300Tests$Outer$Inner().run2()");
170+
expr = new SpelExpressionParser().parseRaw("new org.springframework.expression.spel.SpelReproTests$Outer$Inner().run2()");
154171
Assert.assertEquals(13,expr.getValue(eContext));
155-
}
172+
}
156173

157174
static class Outer {
158175
static class Inner {
@@ -1034,6 +1051,15 @@ public Reserver getReserver() {
10341051
Assert.assertEquals("abc",exp.getValue(ctx));
10351052
}
10361053

1054+
@Test
1055+
public void testReservedWordProperties_9862() throws Exception {
1056+
StandardEvaluationContext ctx = new StandardEvaluationContext();
1057+
SpelExpressionParser parser = new SpelExpressionParser();
1058+
SpelExpression expression = parser.parseRaw("T(org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver).CONST");
1059+
Object value = expression.getValue(ctx);
1060+
assertEquals(value, Reserver.CONST);
1061+
}
1062+
10371063
/**
10381064
* We add property accessors in the order:
10391065
* First, Second, Third, Fourth.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.expression.spel.testresources.le.div.mod.reserved;
18+
19+
/**
20+
* For use when testing that the SpEL expression parser can accommodate SpEL's own
21+
* reserved words being used in package names.
22+
*
23+
* @author Phillip Webb
24+
*/
25+
public class Reserver {
26+
27+
public static final String CONST = "Const";
28+
29+
}

0 commit comments

Comments
 (0)