19
19
import java .io .IOException ;
20
20
import java .lang .reflect .Field ;
21
21
import java .lang .reflect .Method ;
22
+ import java .lang .reflect .Modifier ;
22
23
import java .util .ArrayList ;
23
24
import java .util .Arrays ;
24
25
import java .util .Collection ;
52
53
53
54
import static java .util .stream .Collectors .joining ;
54
55
import static org .assertj .core .api .Assertions .assertThat ;
55
- import static org .assertj .core .api .Assertions .assertThatException ;
56
56
import static org .assertj .core .api .Assertions .assertThatExceptionOfType ;
57
57
import static org .assertj .core .api .Assertions .within ;
58
58
import static org .assertj .core .api .InstanceOfAssertFactories .BOOLEAN ;
@@ -124,7 +124,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
124
124
* Further TODOs for compilation:
125
125
*
126
126
* - OpMinus with a single literal operand could be treated as a negative literal. Will save a
127
- * pointless loading of 0 and then a subtract instruction in code geneneration .
127
+ * pointless loading of 0 and then a subtract instruction in code generation .
128
128
*
129
129
* - allow other accessors/resolvers to participate in compilation and create their own code.
130
130
*
@@ -143,6 +143,84 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
143
143
private SpelNodeImpl ast ;
144
144
145
145
146
+ @ Nested
147
+ class VariableReferenceTests {
148
+
149
+ @ ParameterizedTest // gh-32356
150
+ @ ValueSource (strings = { "#root" , "#this" })
151
+ void rootVariableWithPublicType (String spel ) {
152
+ String string = "hello" ;
153
+ expression = parser .parseExpression (spel );
154
+ Object result = expression .getValue (string , String .class );
155
+ assertThat (result ).isEqualTo (string );
156
+ assertCanCompile (expression );
157
+ result = expression .getValue (string , String .class );
158
+ assertThat (result ).isEqualTo (string );
159
+
160
+ Integer number = 42 ;
161
+ expression = parser .parseExpression (spel );
162
+ result = expression .getValue (number , Integer .class );
163
+ assertThat (result ).isEqualTo (number );
164
+ assertCanCompile (expression );
165
+ result = expression .getValue (number , Integer .class );
166
+ assertThat (result ).isEqualTo (number );
167
+ }
168
+
169
+ @ ParameterizedTest // gh-32356
170
+ @ ValueSource (strings = {
171
+ "#root.empty ? 0 : #root.size" ,
172
+ "#this.empty ? 0 : #this.size"
173
+ })
174
+ void rootVariableWithNonPublicType (String spel ) {
175
+ Map <String , Integer > map = Map .of ("a" , 13 , "b" , 42 );
176
+
177
+ // Prerequisite: root type must not be public for this use case.
178
+ assertThat (Modifier .isPublic (map .getClass ().getModifiers ())).isFalse ();
179
+
180
+ expression = parser .parseExpression (spel );
181
+ Integer result = expression .getValue (map , Integer .class );
182
+ assertThat (result ).isEqualTo (2 );
183
+ assertCanCompile (expression );
184
+ result = expression .getValue (map , Integer .class );
185
+ assertThat (result ).isEqualTo (2 );
186
+ }
187
+
188
+ @ Test
189
+ void userDefinedVariable () {
190
+ EvaluationContext ctx = new StandardEvaluationContext ();
191
+ ctx .setVariable ("target" , "abc" );
192
+ expression = parser .parseExpression ("#target" );
193
+ assertThat (expression .getValue (ctx )).isEqualTo ("abc" );
194
+ assertCanCompile (expression );
195
+ assertThat (expression .getValue (ctx )).isEqualTo ("abc" );
196
+ ctx .setVariable ("target" , "123" );
197
+ assertThat (expression .getValue (ctx )).isEqualTo ("123" );
198
+
199
+ // Changing the variable type from String to Integer results in a
200
+ // ClassCastException in the compiled code.
201
+ ctx .setVariable ("target" , 42 );
202
+ assertThatExceptionOfType (SpelEvaluationException .class )
203
+ .isThrownBy (() -> expression .getValue (ctx ))
204
+ .withCauseInstanceOf (ClassCastException .class );
205
+
206
+ ctx .setVariable ("target" , "abc" );
207
+ expression = parser .parseExpression ("#target.charAt(0)" );
208
+ assertThat (expression .getValue (ctx )).isEqualTo ('a' );
209
+ assertCanCompile (expression );
210
+ assertThat (expression .getValue (ctx )).isEqualTo ('a' );
211
+ ctx .setVariable ("target" , "1" );
212
+ assertThat (expression .getValue (ctx )).isEqualTo ('1' );
213
+
214
+ // Changing the variable type from String to Integer results in a
215
+ // ClassCastException in the compiled code.
216
+ ctx .setVariable ("target" , 42 );
217
+ assertThatExceptionOfType (SpelEvaluationException .class )
218
+ .isThrownBy (() -> expression .getValue (ctx ))
219
+ .withCauseInstanceOf (ClassCastException .class );
220
+ }
221
+
222
+ }
223
+
146
224
@ Nested
147
225
class IndexingTests {
148
226
@@ -466,10 +544,13 @@ void indexIntoMapOfListOfString() {
466
544
assertThat (getAst ().getExitDescriptor ()).isEqualTo ("Ljava/lang/Object" );
467
545
}
468
546
469
- @ Test
547
+ @ Test // gh-32356
470
548
void indexIntoMapOfPrimitiveIntArray () {
471
549
Map <String , int []> map = Map .of ("foo" , new int [] { 1 , 2 , 3 });
472
550
551
+ // Prerequisite: root type must not be public for this use case.
552
+ assertThat (Modifier .isPublic (map .getClass ().getModifiers ())).isFalse ();
553
+
473
554
// map key access
474
555
expression = parser .parseExpression ("['foo']" );
475
556
@@ -479,22 +560,37 @@ void indexIntoMapOfPrimitiveIntArray() {
479
560
assertThat (stringify (expression .getValue (map ))).isEqualTo ("1 2 3" );
480
561
assertThat (getAst ().getExitDescriptor ()).isEqualTo ("Ljava/lang/Object" );
481
562
482
- // map key access & array index
563
+ // map key access via implicit #root & array index
483
564
expression = parser .parseExpression ("['foo'][1]" );
484
565
485
566
assertThat (expression .getValue (map )).isEqualTo (2 );
486
567
assertCanCompile (expression );
487
568
assertThat (expression .getValue (map )).isEqualTo (2 );
569
+
570
+ // map key access via explicit #root & array index
571
+ expression = parser .parseExpression ("#root['foo'][1]" );
572
+
573
+ assertThat (expression .getValue (map )).isEqualTo (2 );
574
+ assertCanCompile (expression );
575
+ assertThat (expression .getValue (map )).isEqualTo (2 );
576
+
577
+ // map key access via explicit #this & array index
578
+ expression = parser .parseExpression ("#this['foo'][1]" );
579
+
580
+ assertThat (expression .getValue (map )).isEqualTo (2 );
581
+ assertCanCompile (expression );
582
+ assertThat (expression .getValue (map )).isEqualTo (2 );
488
583
}
489
584
490
- @ Test
585
+ @ Test // gh-32356
491
586
void indexIntoMapOfPrimitiveIntArrayWithCompilableMapAccessor () {
492
587
StandardEvaluationContext context = new StandardEvaluationContext ();
493
588
context .addPropertyAccessor (new CompilableMapAccessor ());
494
589
495
- // Map<String, int[]> map = Map.of("foo", new int[] { 1, 2, 3 });
496
- Map <String , int []> map = new HashMap <>();
497
- map .put ("foo" , new int [] { 1 , 2 , 3 });
590
+ Map <String , int []> map = Map .of ("foo" , new int [] { 1 , 2 , 3 });
591
+
592
+ // Prerequisite: root type must not be public for this use case.
593
+ assertThat (Modifier .isPublic (map .getClass ().getModifiers ())).isFalse ();
498
594
499
595
// map key access
500
596
expression = parser .parseExpression ("['foo']" );
@@ -516,12 +612,13 @@ void indexIntoMapOfPrimitiveIntArrayWithCompilableMapAccessor() {
516
612
517
613
assertThat (expression .getValue (context , map )).isEqualTo (2 );
518
614
assertCanCompile (expression );
519
- // TODO If map is created via Map.of(), the following fails with an IllegalAccessError.
520
- //
521
- // IllegalAccessError: failed to access class java.util.ImmutableCollections$Map1 from class
522
- // spel.Ex2774 (java.util.ImmutableCollections$Map1 is in module java.base of loader 'bootstrap';
523
- // spel.Ex2774 is in unnamed module of loader
524
- // org.springframework.expression.spel.standard.SpelCompiler$ChildClassLoader @359b650b)
615
+ assertThat (expression .getValue (context , map )).isEqualTo (2 );
616
+
617
+ // custom CompilableMapAccessor via explicit #this & array index
618
+ expression = parser .parseExpression ("#this.foo[1]" );
619
+
620
+ assertThat (expression .getValue (context , map )).isEqualTo (2 );
621
+ assertCanCompile (expression );
525
622
assertThat (expression .getValue (context , map )).isEqualTo (2 );
526
623
527
624
// map key access & array index
@@ -644,7 +741,9 @@ void typeReference() {
644
741
assertThat (expression .getValue ()).isEqualTo (boolean .class );
645
742
646
743
expression = parse ("T(Missing)" );
647
- assertGetValueFail (expression );
744
+ assertThatExceptionOfType (SpelEvaluationException .class )
745
+ .isThrownBy (expression ::getValue )
746
+ .withMessageEndingWith ("Type cannot be found 'Missing'" );
648
747
assertCantCompile (expression );
649
748
}
650
749
@@ -915,16 +1014,20 @@ void intLiteral() {
915
1014
916
1015
// Code gen is different for -1 .. 6 because there are bytecode instructions specifically for those values
917
1016
918
- // Not an int literal but an opminus with one operand:
919
- // expression = parser.parseExpression("-1");
920
- // assertCanCompile(expression);
921
- // assertEquals(-1, expression.getValue());
1017
+ // Not an int literal but an opMinus with one operand:
1018
+ expression = parser .parseExpression ("-1" );
1019
+ expression .getValue (Integer .class );
1020
+ assertCanCompile (expression );
1021
+ assertThat (expression .getValue ()).isEqualTo (-1 );
1022
+
922
1023
expression = parser .parseExpression ("0" );
923
1024
assertCanCompile (expression );
924
1025
assertThat (expression .getValue ()).isEqualTo (0 );
1026
+
925
1027
expression = parser .parseExpression ("2" );
926
1028
assertCanCompile (expression );
927
1029
assertThat (expression .getValue ()).isEqualTo (2 );
1030
+
928
1031
expression = parser .parseExpression ("7" );
929
1032
assertCanCompile (expression );
930
1033
assertThat (expression .getValue ()).isEqualTo (7 );
@@ -1374,23 +1477,6 @@ void elvis() {
1374
1477
assertCanCompile (expression );
1375
1478
}
1376
1479
1377
- @ Test
1378
- void variableReference_root () {
1379
- String s = "hello" ;
1380
- Expression expression = parser .parseExpression ("#root" );
1381
- String resultI = expression .getValue (s , String .class );
1382
- assertCanCompile (expression );
1383
- String resultC = expression .getValue (s , String .class );
1384
- assertThat (resultI ).isEqualTo (s );
1385
- assertThat (resultC ).isEqualTo (s );
1386
-
1387
- expression = parser .parseExpression ("#root" );
1388
- int i = (Integer ) expression .getValue (42 );
1389
- assertThat (i ).isEqualTo (42 );
1390
- assertCanCompile (expression );
1391
- i = (Integer ) expression .getValue (42 );
1392
- assertThat (i ).isEqualTo (42 );
1393
- }
1394
1480
1395
1481
public static String concat (String a , String b ) {
1396
1482
return a +b ;
@@ -1776,34 +1862,6 @@ void functionReferenceVarargs() throws Exception {
1776
1862
assertThat (expression .getValue (ctx )).isEqualTo ("abc" );
1777
1863
}
1778
1864
1779
- @ Test
1780
- void variableReference_userDefined () {
1781
- EvaluationContext ctx = new StandardEvaluationContext ();
1782
- ctx .setVariable ("target" , "abc" );
1783
- expression = parser .parseExpression ("#target" );
1784
- assertThat (expression .getValue (ctx )).isEqualTo ("abc" );
1785
- assertCanCompile (expression );
1786
- assertThat (expression .getValue (ctx )).isEqualTo ("abc" );
1787
- ctx .setVariable ("target" , "123" );
1788
- assertThat (expression .getValue (ctx )).isEqualTo ("123" );
1789
- ctx .setVariable ("target" , 42 );
1790
- assertThatExceptionOfType (SpelEvaluationException .class )
1791
- .isThrownBy (() -> expression .getValue (ctx ))
1792
- .withCauseInstanceOf (ClassCastException .class );
1793
-
1794
- ctx .setVariable ("target" , "abc" );
1795
- expression = parser .parseExpression ("#target.charAt(0)" );
1796
- assertThat (expression .getValue (ctx )).isEqualTo ('a' );
1797
- assertCanCompile (expression );
1798
- assertThat (expression .getValue (ctx )).isEqualTo ('a' );
1799
- ctx .setVariable ("target" , "1" );
1800
- assertThat (expression .getValue (ctx )).isEqualTo ('1' );
1801
- ctx .setVariable ("target" , 42 );
1802
- assertThatExceptionOfType (SpelEvaluationException .class )
1803
- .isThrownBy (() -> expression .getValue (ctx ))
1804
- .withCauseInstanceOf (ClassCastException .class );
1805
- }
1806
-
1807
1865
@ Test
1808
1866
void opLt () {
1809
1867
expression = parse ("3.0d < 4.0d" );
@@ -5402,21 +5460,23 @@ private SpelNodeImpl getAst() {
5402
5460
}
5403
5461
5404
5462
private void assertCanCompile (Expression expression ) {
5405
- assertThat (SpelCompiler .compile (expression )).isTrue ();
5463
+ assertThat (SpelCompiler .compile (expression ))
5464
+ .as (() -> "Expression <%s> should be compilable"
5465
+ .formatted (((SpelExpression ) expression ).toStringAST ()))
5466
+ .isTrue ();
5406
5467
}
5407
5468
5408
5469
private void assertCantCompile (Expression expression ) {
5409
- assertThat (SpelCompiler .compile (expression )).isFalse ();
5470
+ assertThat (SpelCompiler .compile (expression ))
5471
+ .as (() -> "Expression <%s> should not be compilable"
5472
+ .formatted (((SpelExpression ) expression ).toStringAST ()))
5473
+ .isFalse ();
5410
5474
}
5411
5475
5412
5476
private Expression parse (String expression ) {
5413
5477
return parser .parseExpression (expression );
5414
5478
}
5415
5479
5416
- private void assertGetValueFail (Expression expression ) {
5417
- assertThatException ().isThrownBy (expression ::getValue );
5418
- }
5419
-
5420
5480
5421
5481
// Nested types
5422
5482
0 commit comments