diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index dde96b33aaa..be8e988157b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -17,8 +17,10 @@ package org.springframework.expression.spel.standard; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Stack; +import java.util.regex.Pattern; import org.springframework.expression.ParseException; import org.springframework.expression.ParserContext; @@ -29,6 +31,7 @@ import org.springframework.expression.spel.SpelParseException; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.ast.*; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Hand written SpEL parser. Instances are reusable but are not thread safe. @@ -38,6 +41,8 @@ import org.springframework.util.Assert; */ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { + private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+"); + // The expression being parsed private String expressionString; @@ -567,14 +572,35 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { * TODO AndyC Could create complete identifiers (a.b.c) here rather than a sequence of them? (a, b, c) */ private SpelNodeImpl eatPossiblyQualifiedId() { - List qualifiedIdPieces = new ArrayList(); - Token startnode = eatToken(TokenKind.IDENTIFIER); - qualifiedIdPieces.add(new Identifier(startnode.stringValue(),toPos(startnode))); - while (peekToken(TokenKind.DOT,true)) { - Token node = eatToken(TokenKind.IDENTIFIER); - qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node))); - } - return new QualifiedIdentifier(toPos(startnode.startpos,qualifiedIdPieces.get(qualifiedIdPieces.size()-1).getEndPosition()),qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()])); + LinkedList qualifiedIdPieces = new LinkedList(); + Token node = peekToken(); + while (isValidQualifiedId(node)) { + nextToken(); + if(node.kind != TokenKind.DOT) { + qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node))); + } + node = peekToken(); + } + if(qualifiedIdPieces.isEmpty()) { + if(node == null) { + raiseInternalException( expressionString.length(), SpelMessage.OOD); + } + raiseInternalException(node.startpos, SpelMessage.NOT_EXPECTED_TOKEN, + "qualified ID", node.getKind().toString().toLowerCase()); + } + int pos = toPos(qualifiedIdPieces.getFirst().getStartPosition(), qualifiedIdPieces.getLast().getEndPosition()); + return new QualifiedIdentifier(pos, qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()])); + } + + private boolean isValidQualifiedId(Token node) { + if(node == null || node.kind == TokenKind.LITERAL_STRING) { + return false; + } + if(node.kind == TokenKind.DOT || node.kind == TokenKind.IDENTIFIER) { + return true; + } + String value = node.stringValue(); + return StringUtils.hasLength(value) && VALID_QUALIFIED_ID_PATTERN.matcher(value).matches(); } // This is complicated due to the support for dollars in identifiers. Dollars are normally separate tokens but diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ParserErrorMessagesTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ParserErrorMessagesTests.java index 6ac86ba2578..d574d96b5ac 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ParserErrorMessagesTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ParserErrorMessagesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -20,7 +20,7 @@ import org.junit.Test; /** * Tests the messages and exceptions that come out for badly formed expressions - * + * * @author Andy Clement */ public class ParserErrorMessagesTests extends ExpressionTestCase { @@ -56,7 +56,7 @@ public class ParserErrorMessagesTests extends ExpressionTestCase { // T() can only take an identifier (possibly qualified), not a literal // message ought to say identifier rather than ID parseAndCheckError("null instanceof T('a')", SpelMessage.NOT_EXPECTED_TOKEN, 18, - "identifier","literal_string"); + "qualified ID","literal_string"); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java similarity index 97% rename from spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java rename to spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index 9ccdd0eee7d..b38c7198960 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -16,32 +16,49 @@ package org.springframework.expression.spel; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + import junit.framework.Assert; + import org.junit.Ignore; import org.junit.Test; + import org.springframework.core.convert.TypeDescriptor; -import org.springframework.expression.*; +import org.springframework.expression.AccessException; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.MethodExecutor; +import org.springframework.expression.MethodResolver; +import org.springframework.expression.ParserContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.ReflectiveMethodResolver; import org.springframework.expression.spel.support.ReflectivePropertyAccessor; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.*; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** - * Tests based on Jiras up to the release of Spring 3.0.0 + * Reproduction tests cornering various SpEL JIRA issues. * * @author Andy Clement * @author Clark Duplichien */ -public class SpringEL300Tests extends ExpressionTestCase { +public class SpelReproTests extends ExpressionTestCase { @Test public void testNPE_SPR5661() { @@ -147,12 +164,12 @@ public class SpringEL300Tests extends ExpressionTestCase { Expression expr = new SpelExpressionParser().parseRaw("T(java.util.Map$Entry)"); Assert.assertEquals(Map.Entry.class,expr.getValue(eContext)); - expr = new SpelExpressionParser().parseRaw("T(org.springframework.expression.spel.SpringEL300Tests$Outer$Inner).run()"); + expr = new SpelExpressionParser().parseRaw("T(org.springframework.expression.spel.SpelReproTests$Outer$Inner).run()"); Assert.assertEquals(12,expr.getValue(eContext)); - expr = new SpelExpressionParser().parseRaw("new org.springframework.expression.spel.SpringEL300Tests$Outer$Inner().run2()"); + expr = new SpelExpressionParser().parseRaw("new org.springframework.expression.spel.SpelReproTests$Outer$Inner().run2()"); Assert.assertEquals(13,expr.getValue(eContext)); -} + } static class Outer { static class Inner { @@ -1034,6 +1051,15 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals("abc",exp.getValue(ctx)); } + @Test + public void testReservedWordProperties_9862() throws Exception { + StandardEvaluationContext ctx = new StandardEvaluationContext(); + SpelExpressionParser parser = new SpelExpressionParser(); + SpelExpression expression = parser.parseRaw("T(org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver).CONST"); + Object value = expression.getValue(ctx); + assertEquals(value, Reserver.CONST); + } + /** * We add property accessors in the order: * First, Second, Third, Fourth. diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/testresources/le/div/mod/reserved/Reserver.java b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/le/div/mod/reserved/Reserver.java new file mode 100644 index 00000000000..62b143e1272 --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/le/div/mod/reserved/Reserver.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.testresources.le.div.mod.reserved; + +/** + * For use when testing that the SpEL expression parser can accommodate SpEL's own + * reserved words being used in package names. + * + * @author Phillip Webb + */ +public class Reserver { + + public static final String CONST = "Const"; + +}