@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
/ *
* Copyright 2002 - 2019 the original author or authors .
* Copyright 2002 - 2023 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 .
@ -24,6 +24,7 @@ import java.util.Map;
@@ -24,6 +24,7 @@ import java.util.Map;
import org.junit.jupiter.api.Test ;
import org.springframework.expression.Expression ;
import org.springframework.expression.spel.ast.InlineMap ;
import org.springframework.expression.spel.standard.SpelExpression ;
import org.springframework.expression.spel.standard.SpelExpressionParser ;
@ -35,33 +36,26 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -35,33 +36,26 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* Test usage of inline maps .
*
* @author Andy Clement
* @author Sam Brannen
* @since 4 . 1
* /
public class MapTests extends AbstractExpressionTests {
class MapTests extends AbstractExpressionTests {
// if the list is full of literals then it will be of the type unmodifiableClass
// if the list is full of literals then it will be of the type unmodifiableMap Class
// rather than HashMap (or similar)
Class < ? > unmodifiableClass = Collections . unmodifiableMap ( new LinkedHashMap < > ( ) ) . getClass ( ) ;
private static final Class < ? > unmodifiableMap Class = Collections . unmodifiableMap ( Map . of ( ) ) . 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 ) ;
void inlineMapCreationForLiterals ( ) {
evaluate ( "{'a':1, 'b':2, 'c':3, 'd':4, 'e':5}" , "{a=1, b=2, c=3, d=4, e=5}" , unmodifiableMapClass ) ;
evaluate ( "{'a':1}" , "{a=1}" , unmodifiableMapClass ) ;
evaluate ( "{'abc':'def', 'uvw':'xyz'}" , "{abc=def, uvw=xyz}" , unmodifiableMapClass ) ;
evaluate ( "{:}" , "{}" , unmodifiableMapClass ) ;
}
@Test
public void testInlineMapCreation02 ( ) {
evaluate ( "{'abc':'def', 'uvw':'xyz'}" , "{abc=def, uvw=xyz}" , unmodifiableClass ) ;
}
@Test
public void testInlineMapCreation03 ( ) {
evaluate ( "{:}" , "{}" , unmodifiableClass ) ;
}
@Test
public void testInlineMapCreation04 ( ) {
void inlineMapCreationForNonLiterals ( ) {
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 ) ;
@ -70,43 +64,48 @@ public class MapTests extends AbstractExpressionTests {
@@ -70,43 +64,48 @@ public class MapTests extends AbstractExpressionTests {
}
@Test
public void testI nlineMapAndNesting( ) {
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 ) ;
void i nlineMapAndNesting( ) {
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}}" , unmodifiableMap Class ) ;
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}}" , unmodifiableMap Class ) ;
evaluate ( "{a:{1,2,3},b:{4,5,6}}" , "{a=[1, 2, 3], b=[4, 5, 6]}" , unmodifiableMap Class ) ;
}
@Test
public void testI nlineMapWithFunkyKeys( ) {
evaluate ( "{#root.name:true}" , "{Nikola Tesla=true}" , LinkedHashMap . class ) ;
void i nlineMapWithFunkyKeys( ) {
evaluate ( "{#root.name:true}" , "{Nikola Tesla=true}" , LinkedHashMap . class ) ;
}
@Test
public void testInlineMap Error( ) {
void inlineMapSyntax Error( ) {
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 ) ;
void inelineMapIsInstanceOfMap ( ) {
evaluate ( "{a:1, b:2} 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);
void inlineMapProjection ( ) {
evaluate ( "{a:1,b:2,c:3,d:4}.![value > 2]" , "[false, false, true, true]" , ArrayList . class ) ;
evaluate ( "{a:1,b:2,c:3,d:4}.![value % 2 == 0]" , "[false, true, false, true]" , ArrayList . class ) ;
evaluate ( "{a:1,b:2,c:3,d:4}.![#isEven(value) == 'y']" , "[false, true, false, true]" , ArrayList . class ) ;
}
@Test
public void testSetConstruction01 ( ) {
evaluate ( "new java.util.HashMap().putAll({a:'a',b:'b',c:'c'})" , null , Object . class ) ;
void inlineMapSelection ( ) {
evaluate ( "{a:1,b:2,c:3,d:4}.?[value > 2]" , "{c=3, d=4}" , HashMap . class ) ;
evaluate ( "{a:1,b:2,c:3,d:4,e:5,f:6}.?[value % 2 == 0]" , "{b=2, d=4, f=6}" , HashMap . class ) ;
evaluate ( "{a:1,b:2,c:3,d:4,e:5,f:6}.?[#isEven(value) == 'y']" , "{b=2, d=4, f=6}" , HashMap . class ) ;
}
@Test
public void testConstantRepresentation1 ( ) {
void mapConstruction ( ) {
evaluate ( "new java.util.HashMap().putAll({a:'a',b:'b'})" , null , Object . class ) ;
}
@Test
void constantMaps ( ) {
checkConstantMap ( "{f:{'a','b','c'}}" , true ) ;
checkConstantMap ( "{'a':1,'b':2,'c':3,'d':4,'e':5}" , true ) ;
checkConstantMap ( "{aaa:'abc'}" , true ) ;
@ -117,8 +116,7 @@ public class MapTests extends AbstractExpressionTests {
@@ -117,8 +116,7 @@ public class MapTests extends AbstractExpressionTests {
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 ) ;
// for nested InlineMap
checkConstantMap ( "{a:{k:#d}}" , false ) ;
checkConstantMap ( "{a:{k:#d}}" , false ) ; // nested InlineMap
checkConstantMap ( "{@bean:@bean}" , false ) ;
}
@ -126,34 +124,29 @@ public class MapTests extends AbstractExpressionTests {
@@ -126,34 +124,29 @@ public class MapTests extends AbstractExpressionTests {
SpelExpressionParser parser = new SpelExpressionParser ( ) ;
SpelExpression expression = ( SpelExpression ) parser . parseExpression ( expressionText ) ;
SpelNode node = expression . getAST ( ) ;
boolean condition = node instanceof InlineMap ;
assertThat ( condition ) . isTrue ( ) ;
InlineMap inlineMap = ( InlineMap ) node ;
if ( expectedToBeConstant ) {
assertThat ( inlineMap . isConstant ( ) ) . isTrue ( ) ;
}
else {
assertThat ( inlineMap . isConstant ( ) ) . isFalse ( ) ;
}
assertThat ( node ) . isInstanceOfSatisfying ( InlineMap . class , inlineMap - > {
if ( expectedToBeConstant ) {
assertThat ( inlineMap . isConstant ( ) ) . isTrue ( ) ;
}
else {
assertThat ( inlineMap . isConstant ( ) ) . isFalse ( ) ;
}
} ) ;
}
@Test
public void testInlineMapWriting ( ) {
// list should be unmodifiable
assertThatExceptionOfType ( UnsupportedOperationException . class ) . isThrownBy ( ( ) - >
evaluate ( "{a:1, b:2, c:3, d:4, e:5}[a]=6" , "[a:1,b: 2,c: 3,d: 4,e: 5]" , unmodifiableClass ) ) ;
void inlineMapIsUnmodifiable ( ) {
Expression expr = parser . parseExpression ( "{a:1}[a] = 6" ) ;
assertThatExceptionOfType ( UnsupportedOperationException . class )
. isThrownBy ( ( ) - > expr . getValue ( context ) ) ;
}
@Test
public void testM apKeysThatAreAlsoSpELKeywords( ) {
void m apKeysThatAreAlsoSpELKeywords( ) {
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 ( ) ) ;
assertThat ( o ) . isEqualTo ( "TV" ) ;
@ -162,6 +155,10 @@ public class MapTests extends AbstractExpressionTests {
@@ -162,6 +155,10 @@ public class MapTests extends AbstractExpressionTests {
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "tv" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo['NEW']" ) ;
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "VALUE" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[NEW]" ) ;
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "VALUE" ) ;
@ -174,35 +171,32 @@ public class MapTests extends AbstractExpressionTests {
@@ -174,35 +171,32 @@ public class MapTests extends AbstractExpressionTests {
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "value" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[foo[NEW]]" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[foo[NEW]]" ) ;
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "37" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[foo[new]]" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[foo[new]]" ) ;
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "38" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[foo[foo[T]]]" ) ;
expression = ( SpelExpression ) parser . parseExpression ( "foo[foo[foo[T]]]" ) ;
o = expression . getValue ( new MapHolder ( ) ) ;
assertThat ( o ) . isEqualTo ( "value" ) ;
}
@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" ) ;
}
static class MapHolder {
public Map foo = Map . of (
"NEW" , "VALUE" ,
"new" , "value" ,
"T" , "TV" ,
"t" , "tv" ,
"abc.def" , "value" ,
"VALUE" , "37" ,
"value" , "38" ,
"TV" , "new"
) ;
}
}