From 916e9d6efa390faf8511942c0ab40dadf5da8382 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Mon, 5 Mar 2012 15:53:14 -0800 Subject: [PATCH 1/2] Support [] array ref syntax in SpEL T() construct Prior to this change, SpEL would not allow the use of '[]' in expressions like the following: T(foo.Bar[]) This commit updates TypeReference and InternalSpelExpressionParser to support this syntax, avoiding the need for workarounds like: new foo.bar[0].class Issue: SPR-9203 --- .../expression/spel/ast/TypeReference.java | 32 +++++++++++++++++-- .../InternalSpelExpressionParser.java | 14 +++++--- .../expression/spel/SpringEL300Tests.java | 31 +++++++++++++++++- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java index 77724ec3a4..52040c7a0c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.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. @@ -16,6 +16,8 @@ package org.springframework.expression.spel.ast; +import java.lang.reflect.Array; + import org.springframework.expression.EvaluationException; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; @@ -27,8 +29,15 @@ import org.springframework.expression.spel.ExpressionState; */ public class TypeReference extends SpelNodeImpl { + private int dimensions; + public TypeReference(int pos,SpelNodeImpl qualifiedId) { + this(pos,qualifiedId,0); + } + + public TypeReference(int pos,SpelNodeImpl qualifiedId,int dims) { super(pos,qualifiedId); + this.dimensions = dims; } @Override @@ -39,10 +48,24 @@ public class TypeReference extends SpelNodeImpl { TypeCode tc = TypeCode.valueOf(typename.toUpperCase()); if (tc != TypeCode.OBJECT) { // it is a primitive type - return new TypedValue(tc.getType()); + Class clazz = tc.getType(); + clazz = makeArrayIfNecessary(clazz); + return new TypedValue(clazz); } } - return new TypedValue(state.findType(typename)); + Class clazz = state.findType(typename); + clazz = makeArrayIfNecessary(clazz); + return new TypedValue(clazz); + } + + private Class makeArrayIfNecessary(Class clazz) { + if (dimensions!=0) { + for (int i=0;i tokenStream; @@ -497,8 +497,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { eatToken(TokenKind.LPAREN); SpelNodeImpl node = eatPossiblyQualifiedId(); // dotted qualified id + // Are there array dimensions? + int dims = 0; + while (peekToken(TokenKind.LSQUARE,true)) { + eatToken(TokenKind.RSQUARE); + dims++; + } eatToken(TokenKind.RPAREN); - constructedNodes.push(new TypeReference(toPos(typeName),node)); + constructedNodes.push(new TypeReference(toPos(typeName),node,dims)); return true; } return false; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java index 0fa4d1fbd1..bbedf7513f 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.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. @@ -1175,6 +1175,35 @@ public class SpringEL300Tests extends ExpressionTestCase { } } + @Test + public void testArray() { + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + Expression expression = null; + Object result = null; + + expression = parser.parseExpression("new java.lang.Long[0].class"); + result = expression.getValue(context, ""); + assertEquals("Equal assertion failed: ", "class [Ljava.lang.Long;", result.toString()); + + expression = parser.parseExpression("T(java.lang.Long[])"); + result = expression.getValue(context, ""); + assertEquals("Equal assertion failed: ", "class [Ljava.lang.Long;", result.toString()); + + expression = parser.parseExpression("T(java.lang.String[][][])"); + result = expression.getValue(context, ""); + assertEquals("Equal assertion failed: ", "class [[[Ljava.lang.String;", result.toString()); + assertEquals("T(java.lang.String[][][])",((SpelExpression)expression).toStringAST()); + + expression = parser.parseExpression("new int[0].class"); + result = expression.getValue(context, ""); + assertEquals("Equal assertion failed: ", "class [I", result.toString()); + + expression = parser.parseExpression("T(int[][])"); + result = expression.getValue(context, ""); + assertEquals("Equal assertion failed: ", "class [[I", result.toString()); + } + } From de2d8082121d3c2187b5555eb1e3a77f7128ba25 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Thu, 17 May 2012 13:42:37 +0300 Subject: [PATCH 2/2] Eliminate trailing whitespace in SpEL classes --- .../expression/spel/ast/TypeReference.java | 4 +- .../InternalSpelExpressionParser.java | 142 +++++++++--------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java index 52040c7a0c..9f9bb0af59 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java @@ -24,7 +24,7 @@ import org.springframework.expression.spel.ExpressionState; /** * Represents a reference to a type, for example "T(String)" or "T(com.somewhere.Foo)" - * + * * @author Andy Clement */ public class TypeReference extends SpelNodeImpl { @@ -79,5 +79,5 @@ public class TypeReference extends SpelNodeImpl { sb.append(")"); return sb.toString(); } - + } 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 6b3278ef58..ec43b2bfab 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 @@ -70,7 +70,7 @@ import org.springframework.util.Assert; /** * Hand written SpEL parser. Instances are reusable but are not thread safe. - * + * * @author Andy Clement * @since 3.0 */ @@ -81,16 +81,16 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // The token stream constructed from that expression string private List tokenStream; - + // length of a populated token stream private int tokenStreamLength; - + // Current location in the token stream when processing tokens private int tokenStreamPointer; - + // For rules that build nodes, they are stacked here for return private Stack constructedNodes = new Stack(); - + private SpelParserConfiguration configuration; @@ -118,17 +118,17 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken())); } Assert.isTrue(constructedNodes.isEmpty()); - return new SpelExpression(expressionString, ast, configuration); + return new SpelExpression(expressionString, ast, configuration); } catch (InternalParseException ipe) { throw ipe.getCause(); } } - + // expression // : logicalOrExpression - // ( (ASSIGN^ logicalOrExpression) - // | (DEFAULT^ logicalOrExpression) + // ( (ASSIGN^ logicalOrExpression) + // | (DEFAULT^ logicalOrExpression) // | (QMARK^ expression COLON! expression) // | (ELVIS^ expression))?; private SpelNodeImpl eatExpression() { @@ -157,15 +157,15 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1)); } nextToken(); - SpelNodeImpl ifTrueExprValue = eatExpression(); + SpelNodeImpl ifTrueExprValue = eatExpression(); eatToken(TokenKind.COLON); - SpelNodeImpl ifFalseExprValue = eatExpression(); + SpelNodeImpl ifFalseExprValue = eatExpression(); return new Ternary(toPos(t),expr,ifTrueExprValue,ifFalseExprValue); } } return expr; } - + //logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*; private SpelNodeImpl eatLogicalOrExpression() { SpelNodeImpl expr = eatLogicalAndExpression(); @@ -189,7 +189,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } return expr; } - + // relationalExpression : sumExpression (relationalOperator^ sumExpression)?; private SpelNodeImpl eatRelationalExpression() { SpelNodeImpl expr = eatSumExpression(); @@ -227,10 +227,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } return expr; } - + //sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*; private SpelNodeImpl eatSumExpression() { - SpelNodeImpl expr = eatProductExpression(); + SpelNodeImpl expr = eatProductExpression(); while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) { Token t = nextToken();//consume PLUS or MINUS SpelNodeImpl rhExpr = eatProductExpression(); @@ -244,7 +244,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } return expr; } - + // productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ; private SpelNodeImpl eatProductExpression() { SpelNodeImpl expr = eatPowerExpression(); @@ -263,7 +263,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } return expr; } - + // powerExpr : unaryExpression (POWER^ unaryExpression)? ; private SpelNodeImpl eatPowerExpression() { SpelNodeImpl expr = eatUnaryExpression(); @@ -293,7 +293,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return eatPrimaryExpression(); } } - + // primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?); private SpelNodeImpl eatPrimaryExpression() { List nodes = new ArrayList(); @@ -308,7 +308,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return new CompoundExpression(toPos(start.getStartPosition(),nodes.get(nodes.size()-1).getEndPosition()),nodes.toArray(new SpelNodeImpl[nodes.size()])); } } - + // node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+; private boolean maybeEatNode() { SpelNodeImpl expr = null; @@ -324,7 +324,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return true; } } - + // nonDottedNode: indexer; private SpelNodeImpl maybeEatNonDottedNode() { if (peekToken(TokenKind.LSQUARE)) { @@ -334,14 +334,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } return null; } - + //dottedNode // : ((methodOrProperty // | functionOrVar - // | projection - // | selection - // | firstSelection - // | lastSelection + // | projection + // | selection + // | firstSelection + // | lastSelection // )) // ; private SpelNodeImpl eatDottedNode() { @@ -351,20 +351,20 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return pop(); } if (peekToken()==null) { - // unexpectedly ran out of data + // unexpectedly ran out of data raiseInternalException(t.startpos,SpelMessage.OOD); } else { raiseInternalException(t.startpos,SpelMessage.UNEXPECTED_DATA_AFTER_DOT,toString(peekToken())); } return null; } - - // functionOrVar + + // functionOrVar // : (POUND ID LPAREN) => function // | var - // - // function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs); - // var : POUND id=ID -> ^(VARIABLEREF[$id]); + // + // function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs); + // var : POUND id=ID -> ^(VARIABLEREF[$id]); private boolean maybeEatFunctionOrVar() { if (!peekToken(TokenKind.HASH)) { return false; @@ -380,7 +380,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return true; } } - + // methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!; private SpelNodeImpl[] maybeEatMethodArgs() { if (!peekToken(TokenKind.LPAREN)) { @@ -391,7 +391,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { eatToken(TokenKind.RPAREN); return args.toArray(new SpelNodeImpl[args.size()]); } - + private void eatConstructorArgs(List accumulatedArguments) { if (!peekToken(TokenKind.LPAREN)) { throw new InternalParseException(new SpelParseException(expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS)); @@ -399,7 +399,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { consumeArguments(accumulatedArguments); eatToken(TokenKind.RPAREN); } - + /** * Used for consuming arguments for either a method or a constructor call */ @@ -421,25 +421,25 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS); } } - + private int positionOf(Token t) { if (t==null) { - // if null assume the problem is because the right token was + // if null assume the problem is because the right token was // not found at the end of the expression return expressionString.length(); } else { return t.startpos; } } - - //startNode + + //startNode // : parenExpr | literal // | type - // | methodOrProperty + // | methodOrProperty // | functionOrVar - // | projection - // | selection + // | projection + // | selection // | firstSelection // | lastSelection // | indexer @@ -461,7 +461,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return null; } } - + // parse: @beanname @'bean.name' // quoted if dotted private boolean maybeEatBeanReference() { @@ -479,7 +479,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } else { raiseInternalException(beanRefToken.startpos,SpelMessage.INVALID_BEAN_REFERENCE); } - + BeanReference beanReference = new BeanReference(toPos(beanNameToken),beanname); constructedNodes.push(beanReference); return true; @@ -534,7 +534,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr)); return true; } - + // list = LCURLY (element (COMMA element)*) RCURLY private boolean maybeEatInlineList() { Token t = peekToken(); @@ -550,14 +550,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { List listElements = new ArrayList(); do { listElements.add(eatExpression()); - } while (peekToken(TokenKind.COMMA,true)); + } while (peekToken(TokenKind.COMMA,true)); closingCurly = eatToken(TokenKind.RCURLY); expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()])); } constructedNodes.push(expr); return true; } - + private boolean maybeEatIndexer() { Token t = peekToken(); if (!peekToken(TokenKind.LSQUARE,true)) { @@ -568,7 +568,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { constructedNodes.push(new Indexer(toPos(t),expr)); return true; } - + private boolean maybeEatSelection(boolean nullSafeNavigation) { Token t = peekToken(); if (!peekSelectToken()) { @@ -577,7 +577,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { nextToken(); SpelNodeImpl expr = eatExpression(); eatToken(TokenKind.RSQUARE); - if (t.kind==TokenKind.SELECT_FIRST) { + if (t.kind==TokenKind.SELECT_FIRST) { constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr)); } else if (t.kind==TokenKind.SELECT_LAST) { constructedNodes.push(new Selection(nullSafeNavigation,Selection.LAST,toPos(t),expr)); @@ -597,11 +597,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { 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))); + 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()])); } - + // This is complicated due to the support for dollars in identifiers. Dollars are normally separate tokens but // there we want to combine a series of identifiers and dollars into a single identifier private boolean maybeEatMethodOrProperty(boolean nullSafeNavigation) { @@ -622,8 +622,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return false; } - - //constructor + + //constructor //: ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs) private boolean maybeEatConstructorReference() { if (peekIdentifierToken("new")) { @@ -665,12 +665,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private SpelNodeImpl pop() { return constructedNodes.pop(); } - - // literal - // : INTEGER_LITERAL + + // literal + // : INTEGER_LITERAL // | boolLiteral - // | STRING_LITERAL - // | HEXADECIMAL_INTEGER_LITERAL + // | STRING_LITERAL + // | HEXADECIMAL_INTEGER_LITERAL // | REAL_LITERAL // | DQ_STRING_LITERAL // | NULL_LITERAL @@ -691,14 +691,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { push(Literal.getRealLiteral(t.data, toPos(t),false)); } else if (t.kind==TokenKind.LITERAL_REAL_FLOAT) { push(Literal.getRealLiteral(t.data, toPos(t), true)); - } else if (peekIdentifierToken("true")) { + } else if (peekIdentifierToken("true")) { push(new BooleanLiteral(t.data,toPos(t),true)); } else if (peekIdentifierToken("false")) { push(new BooleanLiteral(t.data,toPos(t),false)); } else if (t.kind==TokenKind.LITERAL_STRING) { push(new StringLiteral(t.data,toPos(t),t.data)); } else { - return false; + return false; } nextToken(); return true; @@ -719,11 +719,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // relationalOperator // : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN - // | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES + // | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES private Token maybeEatRelationalOperator() { Token t = peekToken(); if (t==null) { - return null; + return null; } if (t.isNumericRelationalOperator()) { return t; @@ -736,7 +736,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return t.asMatchesToken(); } else if (idString.equalsIgnoreCase("between")) { return t.asBetweenToken(); - } + } } return null; } @@ -746,7 +746,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { if (t==null) { raiseInternalException( expressionString.length(), SpelMessage.OOD); } - if (t.kind!=expectedKind) { + if (t.kind!=expectedKind) { raiseInternalException(t.startpos,SpelMessage.NOT_EXPECTED_TOKEN, expectedKind.toString().toLowerCase(),t.getKind().toString().toLowerCase()); } return t; @@ -798,18 +798,18 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { Token t = peekToken(); return t.kind==TokenKind.IDENTIFIER && t.stringValue().equalsIgnoreCase(identifierString); } - + private boolean peekSelectToken() { if (!moreTokens()) return false; Token t = peekToken(); return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST; } - - + + private boolean moreTokens() { return tokenStreamPointer=tokenStreamLength) { return null; @@ -825,9 +825,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } private void raiseInternalException(int pos, SpelMessage message,Object... inserts) { - throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts)); + throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts)); } - + public String toString(Token t) { if (t.getKind().hasPayload()) { return t.stringValue(); @@ -835,7 +835,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return t.kind.toString().toLowerCase(); } } - + private void checkRightOperand(Token token, SpelNodeImpl operandExpression) { if (operandExpression==null) { raiseInternalException(token.startpos,SpelMessage.RIGHT_OPERAND_PROBLEM); @@ -852,5 +852,5 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private int toPos(int start,int end) { return (start<<16)+end; } - + }