25
25
import org .junit .jupiter .api .Test ;
26
26
27
27
import org .springframework .expression .Expression ;
28
+ import org .springframework .expression .IndexAccessor ;
28
29
import org .springframework .expression .spel .CompilableMapAccessor ;
29
30
import org .springframework .expression .spel .SpelEvaluationException ;
30
31
import org .springframework .expression .spel .SpelMessage ;
45
46
*/
46
47
class SimpleEvaluationContextTests {
47
48
49
+ private static final IndexAccessor colorsIndexAccessor =
50
+ new ReflectiveIndexAccessor (Colors .class , int .class , "get" , "set" );
51
+
48
52
private final SpelExpressionParser parser = new SpelExpressionParser ();
49
53
50
54
private final Model model = new Model ();
51
55
52
56
53
57
@ Test
54
58
void forReadWriteDataBinding () {
55
- SimpleEvaluationContext context = SimpleEvaluationContext .forReadWriteDataBinding ().build ();
59
+ SimpleEvaluationContext context = SimpleEvaluationContext .forReadWriteDataBinding ()
60
+ .withIndexAccessors (colorsIndexAccessor )
61
+ .build ();
56
62
57
63
assertReadWriteMode (context );
58
64
}
59
65
60
66
@ Test
61
67
void forReadOnlyDataBinding () {
62
- SimpleEvaluationContext context = SimpleEvaluationContext .forReadOnlyDataBinding ().build ();
68
+ SimpleEvaluationContext context = SimpleEvaluationContext .forReadOnlyDataBinding ()
69
+ .withIndexAccessors (colorsIndexAccessor )
70
+ .build ();
63
71
64
72
assertCommonReadOnlyModeBehavior (context );
65
73
@@ -96,12 +104,16 @@ void forReadOnlyDataBinding() {
96
104
97
105
// Object Index
98
106
assertAssignmentDisabled (context , "['name'] = 'rejected'" );
107
+
108
+ // Custom Index
109
+ assertAssignmentDisabled (context , "colors[4] = 'rejected'" );
99
110
}
100
111
101
112
@ Test
102
113
void forPropertyAccessorsInReadWriteMode () {
103
114
SimpleEvaluationContext context = SimpleEvaluationContext
104
115
.forPropertyAccessors (new CompilableMapAccessor (), DataBindingPropertyAccessor .forReadWriteAccess ())
116
+ .withIndexAccessors (colorsIndexAccessor )
105
117
.build ();
106
118
107
119
assertReadWriteMode (context );
@@ -126,12 +138,13 @@ void forPropertyAccessorsInReadWriteMode() {
126
138
@ Test
127
139
void forPropertyAccessorsInMixedReadOnlyMode () {
128
140
SimpleEvaluationContext context = SimpleEvaluationContext
129
- .forPropertyAccessors (new CompilableMapAccessor (), DataBindingPropertyAccessor .forReadOnlyAccess ())
141
+ .forPropertyAccessors (new CompilableMapAccessor (true ), DataBindingPropertyAccessor .forReadOnlyAccess ())
142
+ .withIndexAccessors (colorsIndexAccessor )
130
143
.build ();
131
144
132
145
assertCommonReadOnlyModeBehavior (context );
133
146
134
- // Map -- with key as property name supported by CompilableMapAccessor
147
+ // Map -- with key as property name supported by CompilableMapAccessor with allowWrite = true.
135
148
136
149
Expression expression ;
137
150
expression = parser .parseExpression ("map.yellow" );
@@ -156,20 +169,24 @@ void forPropertyAccessorsInMixedReadOnlyMode() {
156
169
.satisfies (ex -> assertThat (ex .getMessageCode ()).isEqualTo (SpelMessage .PROPERTY_OR_FIELD_NOT_WRITABLE ));
157
170
158
171
// Array Index
159
- parser .parseExpression ("array[0]" ).setValue (context , model , "foo" );
160
- assertThat (model .array ).containsExactly ("foo" );
172
+ expression = parser .parseExpression ("array[0] = 'quux'" );
173
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("quux" );
174
+ assertThat (model .array ).containsExactly ("quux" );
161
175
162
176
// List Index
163
- parser .parseExpression ("list[0]" ).setValue (context , model , "cat" );
164
- assertThat (model .list ).containsExactly ("cat" );
177
+ expression = parser .parseExpression ("list[0] = 'elephant'" );
178
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("elephant" );
179
+ assertThat (model .list ).containsExactly ("elephant" );
165
180
166
181
// Map Index -- key as String
167
- parser .parseExpression ("map['red']" ).setValue (context , model , "cherry" );
168
- assertThat (model .map ).containsOnly (entry ("red" , "cherry" ), entry ("yellow" , "banana" ));
182
+ expression = parser .parseExpression ("map['red'] = 'strawberry'" );
183
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("strawberry" );
184
+ assertThat (model .map ).containsOnly (entry ("red" , "strawberry" ), entry ("yellow" , "banana" ));
169
185
170
186
// Map Index -- key as pseudo property name
171
- parser .parseExpression ("map[yellow]" ).setValue (context , model , "lemon" );
172
- assertThat (model .map ).containsOnly (entry ("red" , "cherry" ), entry ("yellow" , "lemon" ));
187
+ expression = parser .parseExpression ("map[yellow] = 'star fruit'" );
188
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("star fruit" );
189
+ assertThat (model .map ).containsOnly (entry ("red" , "strawberry" ), entry ("yellow" , "star fruit" ));
173
190
174
191
// String Index
175
192
// The Indexer does not support writes when indexing into a String.
@@ -178,10 +195,17 @@ void forPropertyAccessorsInMixedReadOnlyMode() {
178
195
.satisfies (ex -> assertThat (ex .getMessageCode ()).isEqualTo (SpelMessage .INDEXING_NOT_SUPPORTED_FOR_TYPE ));
179
196
180
197
// Object Index
198
+ // Although this goes through the Indexer, the PropertyAccessorValueRef actually uses
199
+ // registered PropertyAccessors to perform the write access, and that is disabled here.
181
200
assertThatSpelEvaluationException ()
182
201
.isThrownBy (() -> parser .parseExpression ("['name'] = 'rejected'" ).getValue (context , model ))
183
202
.satisfies (ex -> assertThat (ex .getMessageCode ()).isEqualTo (SpelMessage .INDEXING_NOT_SUPPORTED_FOR_TYPE ));
184
203
204
+ // Custom Index
205
+ expression = parser .parseExpression ("colors[5] = 'indigo'" );
206
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("indigo" );
207
+ assertThat (model .colors .get (5 )).isEqualTo ("indigo" );
208
+
185
209
// WRITE -- via increment and decrement operators
186
210
187
211
assertIncrementAndDecrementWritesForIndexedStructures (context );
@@ -216,6 +240,10 @@ private void assertReadWriteMode(SimpleEvaluationContext context) {
216
240
parser .parseExpression ("map[yellow]" ).setValue (context , model , "lemon" );
217
241
assertThat (model .map ).containsOnly (entry ("red" , "cherry" ), entry ("yellow" , "lemon" ));
218
242
243
+ // Custom Index
244
+ parser .parseExpression ("colors[4]" ).setValue (context , model , "purple" );
245
+ assertThat (model .colors .get (4 )).isEqualTo ("purple" );
246
+
219
247
// READ
220
248
assertReadAccess (context );
221
249
@@ -270,6 +298,12 @@ private void assertReadWriteMode(SimpleEvaluationContext context) {
270
298
expression = parser .parseExpression ("['name']" );
271
299
assertThat (expression .getValue (context , model , String .class )).isEqualTo ("new name" );
272
300
301
+ // Custom Index
302
+ expression = parser .parseExpression ("colors[5] = 'indigo'" );
303
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("indigo" );
304
+ expression = parser .parseExpression ("colors[5]" );
305
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("indigo" );
306
+
273
307
// WRITE -- via increment and decrement operators
274
308
275
309
assertIncrementAndDecrementWritesForProperties (context );
@@ -309,6 +343,10 @@ private void assertCommonReadOnlyModeBehavior(SimpleEvaluationContext context) {
309
343
parser .parseExpression ("map[yellow]" ).setValue (context , model , "lemon" );
310
344
assertThat (model .map ).containsOnly (entry ("red" , "cherry" ), entry ("yellow" , "lemon" ));
311
345
346
+ // Custom Index
347
+ parser .parseExpression ("colors[4]" ).setValue (context , model , "purple" );
348
+ assertThat (model .colors .get (4 )).isEqualTo ("purple" );
349
+
312
350
// Since the setValue() attempts for "name" and "count" failed above, we have to set
313
351
// them directly for assertReadAccess().
314
352
model .name = "test" ;
@@ -354,6 +392,10 @@ private void assertReadAccess(SimpleEvaluationContext context) {
354
392
// Object Index
355
393
expression = parser .parseExpression ("['name']" );
356
394
assertThat (expression .getValue (context , model , String .class )).isEqualTo ("test" );
395
+
396
+ // Custom Index
397
+ expression = parser .parseExpression ("colors[4]" );
398
+ assertThat (expression .getValue (context , model , String .class )).isEqualTo ("purple" );
357
399
}
358
400
359
401
private void assertIncrementAndDecrementWritesForProperties (SimpleEvaluationContext context ) {
@@ -433,6 +475,7 @@ static class Model {
433
475
private final int [] numbers = {99 };
434
476
private final List <String > list = new ArrayList <>();
435
477
private final Map <String , String > map = new HashMap <>();
478
+ private final Colors colors = new Colors ();
436
479
437
480
Model () {
438
481
this .list .add ("replace me" );
@@ -472,6 +515,32 @@ public Map<String, String> getMap() {
472
515
return this .map ;
473
516
}
474
517
518
+ public Colors getColors () {
519
+ return this .colors ;
520
+ }
521
+ }
522
+
523
+ static class Colors {
524
+
525
+ private final Map <Integer , String > map = new HashMap <>();
526
+
527
+ {
528
+ this .map .put (1 , "red" );
529
+ this .map .put (2 , "green" );
530
+ this .map .put (3 , "blue" );
531
+ }
532
+
533
+ public String get (int index ) {
534
+ if (!this .map .containsKey (index )) {
535
+ throw new IndexOutOfBoundsException ("No color for index " + index );
536
+ }
537
+ return this .map .get (index );
538
+ }
539
+
540
+ public void set (int index , String color ) {
541
+ this .map .put (index , color );
542
+ }
543
+
475
544
}
476
545
477
546
}
0 commit comments