From c382b6f05940969ac9161ed0368e12e69df3cd58 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Mon, 30 Mar 2015 16:15:12 -0700 Subject: [PATCH] Allow NEW and T to be used as unquoted map keys in SpEL This change provides support for map[NEW], map[new], map[T] and map[t]. Prior to this change the 'new' and 't' had to be quoted because they were keywords in SpEL for a constructor reference and type reference respectively. Issue: SPR-11783 --- .../InternalSpelExpressionParser.java | 16 ++++- .../expression/spel/MapTests.java | 66 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) 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 b7ef07ed77..0f8b49ff7a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -556,7 +556,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { if (!typeName.stringValue().equals("T")) { return false; } - nextToken(); + // It looks like a type reference but is T being used as a map key? + Token t = nextToken(); + if (peekToken(TokenKind.RSQUARE)) { + // looks like 'T]' (T is map key) + push(new PropertyOrFieldReference(false,t.data,toPos(t))); + return true; + } eatToken(TokenKind.LPAREN); SpelNodeImpl node = eatPossiblyQualifiedId(); // dotted qualified id @@ -754,6 +760,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private boolean maybeEatConstructorReference() { if (peekIdentifierToken("new")) { Token newToken = nextToken(); + // It looks like a constructor reference but is NEW being used as a map key? + if (peekToken(TokenKind.RSQUARE)) { + // looks like 'NEW]' (so NEW used as map key) + push(new PropertyOrFieldReference(false,newToken.data,toPos(newToken))); + return true; + } SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId(); List nodes = new ArrayList(); nodes.add(possiblyQualifiedConstructorName); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MapTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MapTests.java index c0e023c950..73e4b14f95 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MapTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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,9 +20,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Map; import org.junit.Test; - import org.springframework.expression.spel.ast.InlineMap; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -136,4 +136,66 @@ public class MapTests extends AbstractExpressionTests { // list should be unmodifiable evaluate("{a:1, b:2, c:3, d:4, e:5}[a]=6", "[a:1,b: 2,c: 3,d: 4,e: 5]", unmodifiableClass); } + + @Test + public void testMapKeysThatAreAlsoSpELKeywords() { + SpelExpressionParser parser = new SpelExpressionParser(); + SpelExpression expression = null; + Object o = null; + + // expression = (SpelExpression) parser.parseExpression("foo['NEW']"); + // o = expression.getValue(new MapHolder()); + // assertEquals("VALUE",o); + + expression = (SpelExpression) parser.parseExpression("foo[T]"); + o = expression.getValue(new MapHolder()); + assertEquals("TV", o); + + expression = (SpelExpression) parser.parseExpression("foo[t]"); + o = expression.getValue(new MapHolder()); + assertEquals("tv", o); + + expression = (SpelExpression) parser.parseExpression("foo[NEW]"); + o = expression.getValue(new MapHolder()); + assertEquals("VALUE", o); + + expression = (SpelExpression) parser.parseExpression("foo[new]"); + o = expression.getValue(new MapHolder()); + assertEquals("value", o); + + expression = (SpelExpression) parser.parseExpression("foo['abc.def']"); + o = expression.getValue(new MapHolder()); + assertEquals("value", o); + + expression = (SpelExpression)parser.parseExpression("foo[foo[NEW]]"); + o = expression.getValue(new MapHolder()); + assertEquals("37",o); + + expression = (SpelExpression)parser.parseExpression("foo[foo[new]]"); + o = expression.getValue(new MapHolder()); + assertEquals("38",o); + + expression = (SpelExpression)parser.parseExpression("foo[foo[foo[T]]]"); + o = expression.getValue(new MapHolder()); + assertEquals("value",o); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static class MapHolder { + + public Map foo; + + public MapHolder() { + foo = new HashMap(); + foo.put("NEW", "VALUE"); + foo.put("new", "value"); + foo.put("T", "TV"); + foo.put("t", "tv"); + foo.put("abc.def", "value"); + foo.put("VALUE","37"); + foo.put("value","38"); + foo.put("TV","new"); + } + } + }