Add support for inline maps in SpEL expressions
This commit introduces the ability to specify an inline map in
an expression. The syntax is similar to inline lists and of
the form: "{key:value,key2:value}". The keys can optionally
be quoted. The documentation is also updated with information
on the syntax.
Issue: SPR-9472
This commit is contained in:
@@ -1401,6 +1401,11 @@ public class EvaluationTests extends AbstractExpressionTests {
|
||||
expectFailNotAssignable(parser, ctx, "--({1,2,3})");
|
||||
expectFailSetValueNotSupported(parser, ctx, "({1,2,3})=({1,2,3})");
|
||||
|
||||
// InlineMap
|
||||
expectFailNotAssignable(parser, ctx, "({'a':1,'b':2,'c':3})++");
|
||||
expectFailNotAssignable(parser, ctx, "--({'a':1,'b':2,'c':3})");
|
||||
expectFailSetValueNotSupported(parser, ctx, "({'a':1,'b':2,'c':3})=({'a':1,'b':2,'c':3})");
|
||||
|
||||
// BeanReference
|
||||
ctx.setBeanResolver(new MyBeanResolver());
|
||||
expectFailNotAssignable(parser, ctx, "@foo++");
|
||||
|
||||
@@ -93,7 +93,6 @@ public class MapAccessTests extends AbstractExpressionTests {
|
||||
public void testGetValueFromRootMap() {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("key", "value");
|
||||
EvaluationContext context = new StandardEvaluationContext(map);
|
||||
|
||||
ExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
Expression expr = spelExpressionParser.parseExpression("#root['key']");
|
||||
@@ -168,11 +167,11 @@ public class MapAccessTests extends AbstractExpressionTests {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public Map getProperties() {
|
||||
public Map<String,String> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(Map properties) {
|
||||
public void setProperties(Map<String,String> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +197,7 @@ public class MapAccessTests extends AbstractExpressionTests {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
|
||||
((Map) target).put(name, newValue);
|
||||
((Map<Object,Object>) target).put(name, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2014 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
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;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test usage of inline maps.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 4.1
|
||||
*/
|
||||
public class MapTests extends AbstractExpressionTests {
|
||||
|
||||
// if the list is full of literals then it will be of the type unmodifiableClass
|
||||
// rather than HashMap (or similar)
|
||||
Class<?> unmodifiableClass = Collections.unmodifiableMap(new LinkedHashMap<Object,Object>()).getClass();
|
||||
|
||||
|
||||
@Test
|
||||
public void testInlineMapCreation01() {
|
||||
evaluate("{'a':1, 'b':2, 'c':3, 'd':4, 'e':5}", "{a=1, b=2, c=3, d=4, e=5}", unmodifiableClass);
|
||||
evaluate("{'a':1}", "{a=1}", unmodifiableClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapCreation02() {
|
||||
evaluate("{'abc':'def', 'uvw':'xyz'}", "{abc=def, uvw=xyz}", unmodifiableClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapCreation03() {
|
||||
evaluate("{:}", "{}", unmodifiableClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapCreation04() {
|
||||
evaluate("{'key':'abc'=='xyz'}", "{key=false}", LinkedHashMap.class);
|
||||
evaluate("{key:'abc'=='xyz'}", "{key=false}", LinkedHashMap.class);
|
||||
evaluate("{key:'abc'=='xyz',key2:true}[key]", "false", Boolean.class);
|
||||
evaluate("{key:'abc'=='xyz',key2:true}.get('key2')", "true", Boolean.class);
|
||||
evaluate("{key:'abc'=='xyz',key2:true}['key2']", "true", Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapAndNesting() {
|
||||
evaluate("{a:{a:1,b:2,c:3},b:{d:4,e:5,f:6}}", "{a={a=1, b=2, c=3}, b={d=4, e=5, f=6}}", unmodifiableClass);
|
||||
evaluate("{a:{x:1,y:'2',z:3},b:{u:4,v:{'a','b'},w:5,x:6}}", "{a={x=1, y=2, z=3}, b={u=4, v=[a, b], w=5, x=6}}", unmodifiableClass);
|
||||
evaluate("{a:{1,2,3},b:{4,5,6}}", "{a=[1, 2, 3], b=[4, 5, 6]}", unmodifiableClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapWithFunkyKeys() {
|
||||
evaluate("{#root.name:true}","{Nikola Tesla=true}",LinkedHashMap.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapError() {
|
||||
parseAndCheckError("{key:'abc'", SpelMessage.OOD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsIs02() {
|
||||
evaluate("{a:1, b:2, c:3, d:4, e:5} instanceof T(java.util.Map)", "true", Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapAndProjectionSelection() {
|
||||
evaluate("{a:1,b:2,c:3,d:4,e:5,f:6}.![value>3]", "[false, false, false, true, true, true]", ArrayList.class);
|
||||
evaluate("{a:1,b:2,c:3,d:4,e:5,f:6}.?[value>3]", "{d=4, e=5, f=6}", HashMap.class);
|
||||
evaluate("{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10}.?[value%2==0]", "{b=2, d=4, f=6, h=8, j=10}", HashMap.class);
|
||||
// TODO this looks like a serious issue (but not a new one): the context object against which arguments are evaluated seems wrong:
|
||||
// evaluate("{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10}.?[isEven(value) == 'y']", "[2, 4, 6, 8, 10]", ArrayList.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetConstruction01() {
|
||||
evaluate("new java.util.HashMap().putAll({a:'a',b:'b',c:'c'})", null, Object.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstantRepresentation1() {
|
||||
checkConstantMap("{f:{'a','b','c'}}", true);
|
||||
checkConstantMap("{'a':1,'b':2,'c':3,'d':4,'e':5}", true);
|
||||
checkConstantMap("{aaa:'abc'}", true);
|
||||
checkConstantMap("{:}", true);
|
||||
checkConstantMap("{a:#a,b:2,c:3}", false);
|
||||
checkConstantMap("{a:1,b:2,c:Integer.valueOf(4)}", false);
|
||||
checkConstantMap("{a:1,b:2,c:{#a}}", false);
|
||||
checkConstantMap("{#root.name:true}",false);
|
||||
checkConstantMap("{a:1,b:2,c:{d:true,e:false}}", true);
|
||||
checkConstantMap("{a:1,b:2,c:{d:{1,2,3},e:{4,5,6},f:{'a','b','c'}}}", true);
|
||||
}
|
||||
|
||||
private void checkConstantMap(String expressionText, boolean expectedToBeConstant) {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expression = (SpelExpression) parser.parseExpression(expressionText);
|
||||
SpelNode node = expression.getAST();
|
||||
assertTrue(node instanceof InlineMap);
|
||||
InlineMap inlineMap = (InlineMap) node;
|
||||
if (expectedToBeConstant) {
|
||||
assertTrue(inlineMap.isConstant());
|
||||
}
|
||||
else {
|
||||
assertFalse(inlineMap.isConstant());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testInlineMapWriting() {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -298,30 +298,26 @@ public class ParsingTests {
|
||||
}
|
||||
|
||||
// inline list creation
|
||||
// public void testInlineListCreation01() {
|
||||
// parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
|
||||
// }
|
||||
//
|
||||
// public void testInlineListCreation02() {
|
||||
// parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
|
||||
// }
|
||||
@Test
|
||||
public void testInlineListCreation01() {
|
||||
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
|
||||
}
|
||||
|
||||
// // inline map creation
|
||||
// public void testInlineMapCreation01() {
|
||||
// parseCheck("#{'key1':'Value 1', 'today':DateTime.Today}");
|
||||
// }
|
||||
//
|
||||
// public void testInlineMapCreation02() {
|
||||
// parseCheck("#{1:'January', 2:'February', 3:'March'}");
|
||||
// }
|
||||
//
|
||||
// public void testInlineMapCreation03() {
|
||||
// parseCheck("#{'key1':'Value 1', 'today':'Monday'}['key1']");
|
||||
// }
|
||||
//
|
||||
// public void testInlineMapCreation04() {
|
||||
// parseCheck("#{1:'January', 2:'February', 3:'March'}[3]");
|
||||
// }
|
||||
@Test
|
||||
public void testInlineListCreation02() {
|
||||
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
|
||||
}
|
||||
|
||||
// inline map creation
|
||||
@Test
|
||||
public void testInlineMapCreation01() {
|
||||
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapCreation02() {
|
||||
parseCheck("{1:'January',2:'February',3:'March'}");
|
||||
}
|
||||
|
||||
// methods
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user