1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.expression.spel.ast;
18
19 import java.lang.reflect.Field;
20 import java.lang.reflect.Member;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Modifier;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.springframework.asm.MethodVisitor;
28 import org.springframework.core.convert.TypeDescriptor;
29 import org.springframework.expression.AccessException;
30 import org.springframework.expression.EvaluationContext;
31 import org.springframework.expression.EvaluationException;
32 import org.springframework.expression.PropertyAccessor;
33 import org.springframework.expression.TypeConverter;
34 import org.springframework.expression.TypedValue;
35 import org.springframework.expression.spel.CodeFlow;
36 import org.springframework.expression.spel.ExpressionState;
37 import org.springframework.expression.spel.SpelEvaluationException;
38 import org.springframework.expression.spel.SpelMessage;
39 import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
40
41
42
43
44
45
46
47
48
49
50
51
52 public class Indexer extends SpelNodeImpl {
53
54 private static enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT}
55
56
57
58
59
60
61
62 private String cachedReadName;
63
64 private Class<?> cachedReadTargetType;
65
66 private PropertyAccessor cachedReadAccessor;
67
68
69
70
71
72
73 private String cachedWriteName;
74
75 private Class<?> cachedWriteTargetType;
76
77 private PropertyAccessor cachedWriteAccessor;
78
79 private IndexedType indexedType;
80
81
82 public Indexer(int pos, SpelNodeImpl expr) {
83 super(pos, expr);
84 }
85
86
87 @Override
88 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
89 return getValueRef(state).getValue();
90 }
91
92 @Override
93 public void setValue(ExpressionState state, Object newValue) throws EvaluationException {
94 getValueRef(state).setValue(newValue);
95 }
96
97 @Override
98 public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
99 return true;
100 }
101
102
103 @Override
104 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
105 TypedValue context = state.getActiveContextObject();
106 Object targetObject = context.getValue();
107 TypeDescriptor targetDescriptor = context.getTypeDescriptor();
108 TypedValue indexValue = null;
109 Object index = null;
110
111
112
113 if (targetObject instanceof Map && (this.children[0] instanceof PropertyOrFieldReference)) {
114 PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0];
115 index = reference.getName();
116 indexValue = new TypedValue(index);
117 }
118 else {
119
120
121 try {
122 state.pushActiveContextObject(state.getRootContextObject());
123 indexValue = this.children[0].getValueInternal(state);
124 index = indexValue.getValue();
125 }
126 finally {
127 state.popActiveContextObject();
128 }
129 }
130
131
132 if (targetObject instanceof Map) {
133 Object key = index;
134 if (targetDescriptor.getMapKeyTypeDescriptor() != null) {
135 key = state.convertValue(key, targetDescriptor.getMapKeyTypeDescriptor());
136 }
137 this.indexedType = IndexedType.MAP;
138 return new MapIndexingValueRef(state.getTypeConverter(), (Map<?, ?>) targetObject, key, targetDescriptor);
139 }
140
141 if (targetObject == null) {
142 throw new SpelEvaluationException(getStartPosition(), SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
143 }
144
145
146
147 if (targetObject.getClass().isArray() || targetObject instanceof Collection || targetObject instanceof String) {
148 int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
149 if (targetObject.getClass().isArray()) {
150 this.indexedType = IndexedType.ARRAY;
151 return new ArrayIndexingValueRef(state.getTypeConverter(), targetObject, idx, targetDescriptor);
152 }
153 else if (targetObject instanceof Collection) {
154 if (targetObject instanceof List) {
155 this.indexedType = IndexedType.LIST;
156 }
157 return new CollectionIndexingValueRef((Collection<?>) targetObject, idx, targetDescriptor,
158 state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(),
159 state.getConfiguration().getMaximumAutoGrowSize());
160 }
161 else {
162 this.indexedType = IndexedType.STRING;
163 return new StringIndexingLValue((String) targetObject, idx, targetDescriptor);
164 }
165 }
166
167
168
169 if (String.class.equals(indexValue.getTypeDescriptor().getType())) {
170 this.indexedType = IndexedType.OBJECT;
171 return new PropertyIndexingValueRef(targetObject, (String) indexValue.getValue(),
172 state.getEvaluationContext(), targetDescriptor);
173 }
174
175 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
176 targetDescriptor.toString());
177 }
178
179 @Override
180 public boolean isCompilable() {
181 if (this.indexedType == IndexedType.ARRAY) {
182 return (this.exitTypeDescriptor != null);
183 }
184 else if (this.indexedType == IndexedType.LIST) {
185 return this.children[0].isCompilable();
186 }
187 else if (this.indexedType == IndexedType.MAP) {
188 return (this.children[0] instanceof PropertyOrFieldReference || this.children[0].isCompilable());
189 }
190 else if (this.indexedType == IndexedType.OBJECT) {
191
192 if (this.cachedReadAccessor != null &&
193 (this.cachedReadAccessor instanceof ReflectivePropertyAccessor.OptimalPropertyAccessor) &&
194 (getChild(0) instanceof StringLiteral)) {
195 return true;
196 }
197 }
198 return false;
199 }
200
201 @Override
202 public void generateCode(MethodVisitor mv, CodeFlow cf) {
203 String descriptor = cf.lastDescriptor();
204 if (descriptor == null) {
205
206 cf.loadTarget(mv);
207 }
208
209 if (this.indexedType == IndexedType.ARRAY) {
210 int insn;
211 if ("D".equals(this.exitTypeDescriptor)) {
212 mv.visitTypeInsn(CHECKCAST, "[D");
213 insn = DALOAD;
214 }
215 else if ("F".equals(this.exitTypeDescriptor)) {
216 mv.visitTypeInsn(CHECKCAST, "[F");
217 insn = FALOAD;
218 }
219 else if ("J".equals(this.exitTypeDescriptor)) {
220 mv.visitTypeInsn(CHECKCAST, "[J");
221 insn = LALOAD;
222 }
223 else if ("I".equals(this.exitTypeDescriptor)) {
224 mv.visitTypeInsn(CHECKCAST, "[I");
225 insn = IALOAD;
226 }
227 else if ("S".equals(this.exitTypeDescriptor)) {
228 mv.visitTypeInsn(CHECKCAST, "[S");
229 insn = SALOAD;
230 }
231 else if ("B".equals(this.exitTypeDescriptor)) {
232 mv.visitTypeInsn(CHECKCAST, "[B");
233 insn = BALOAD;
234 }
235 else if ("C".equals(this.exitTypeDescriptor)) {
236 mv.visitTypeInsn(CHECKCAST, "[C");
237 insn = CALOAD;
238 }
239 else {
240 mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor +
241 (CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";"));
242
243 insn = AALOAD;
244 }
245 SpelNodeImpl index = this.children[0];
246 cf.enterCompilationScope();
247 index.generateCode(mv, cf);
248 cf.exitCompilationScope();
249 mv.visitInsn(insn);
250 }
251 else if (this.indexedType == IndexedType.LIST) {
252 mv.visitTypeInsn(CHECKCAST, "java/util/List");
253 cf.enterCompilationScope();
254 this.children[0].generateCode(mv, cf);
255 cf.exitCompilationScope();
256 mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true);
257 }
258 else if (this.indexedType == IndexedType.MAP) {
259 mv.visitTypeInsn(CHECKCAST, "java/util/Map");
260
261
262 if ((this.children[0] instanceof PropertyOrFieldReference)) {
263 PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0];
264 String mapKeyName = reference.getName();
265 mv.visitLdcInsn(mapKeyName);
266 }
267 else {
268 cf.enterCompilationScope();
269 this.children[0].generateCode(mv, cf);
270 cf.exitCompilationScope();
271 }
272 mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
273 }
274 else if (this.indexedType == IndexedType.OBJECT) {
275 ReflectivePropertyAccessor.OptimalPropertyAccessor accessor =
276 (ReflectivePropertyAccessor.OptimalPropertyAccessor) this.cachedReadAccessor;
277 Member member = accessor.member;
278 boolean isStatic = Modifier.isStatic(member.getModifiers());
279 String memberDeclaringClassSlashedDescriptor = member.getDeclaringClass().getName().replace('.', '/');
280 if (!isStatic) {
281 if (descriptor == null) {
282 cf.loadTarget(mv);
283 }
284 if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
285 mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
286 }
287 }
288 if (member instanceof Field) {
289 mv.visitFieldInsn(isStatic ? GETSTATIC : GETFIELD, memberDeclaringClassSlashedDescriptor,
290 member.getName(), CodeFlow.toJvmDescriptor(((Field) member).getType()));
291 }
292 else {
293 mv.visitMethodInsn(isStatic? INVOKESTATIC : INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor,
294 member.getName(), CodeFlow.createSignatureDescriptor((Method) member), false);
295 }
296 }
297
298 cf.pushDescriptor(this.exitTypeDescriptor);
299 }
300
301 @Override
302 public String toStringAST() {
303 StringBuilder sb = new StringBuilder("[");
304 for (int i = 0; i < getChildCount(); i++) {
305 if (i > 0) {
306 sb.append(",");
307 }
308 sb.append(getChild(i).toStringAST());
309 }
310 sb.append("]");
311 return sb.toString();
312 }
313
314
315 private void setArrayElement(TypeConverter converter, Object ctx, int idx, Object newValue,
316 Class<?> arrayComponentType) throws EvaluationException {
317
318 if (arrayComponentType == Double.TYPE) {
319 double[] array = (double[]) ctx;
320 checkAccess(array.length, idx);
321 array[idx] = convertValue(converter, newValue, Double.class);
322 }
323 else if (arrayComponentType == Float.TYPE) {
324 float[] array = (float[]) ctx;
325 checkAccess(array.length, idx);
326 array[idx] = convertValue(converter, newValue, Float.class);
327 }
328 else if (arrayComponentType == Long.TYPE) {
329 long[] array = (long[]) ctx;
330 checkAccess(array.length, idx);
331 array[idx] = convertValue(converter, newValue, Long.class);
332 }
333 else if (arrayComponentType == Integer.TYPE) {
334 int[] array = (int[]) ctx;
335 checkAccess(array.length, idx);
336 array[idx] = convertValue(converter, newValue, Integer.class);
337 }
338 else if (arrayComponentType == Short.TYPE) {
339 short[] array = (short[]) ctx;
340 checkAccess(array.length, idx);
341 array[idx] = convertValue(converter, newValue, Short.class);
342 }
343 else if (arrayComponentType == Byte.TYPE) {
344 byte[] array = (byte[]) ctx;
345 checkAccess(array.length, idx);
346 array[idx] = convertValue(converter, newValue, Byte.class);
347 }
348 else if (arrayComponentType == Character.TYPE) {
349 char[] array = (char[]) ctx;
350 checkAccess(array.length, idx);
351 array[idx] = convertValue(converter, newValue, Character.class);
352 }
353 else if (arrayComponentType == Boolean.TYPE) {
354 boolean[] array = (boolean[]) ctx;
355 checkAccess(array.length, idx);
356 array[idx] = convertValue(converter, newValue, Boolean.class);
357 }
358 else {
359 Object[] array = (Object[]) ctx;
360 checkAccess(array.length, idx);
361 array[idx] = convertValue(converter, newValue, arrayComponentType);
362 }
363 }
364
365 private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException {
366 Class<?> arrayComponentType = ctx.getClass().getComponentType();
367 if (arrayComponentType == Double.TYPE) {
368 double[] array = (double[]) ctx;
369 checkAccess(array.length, idx);
370 this.exitTypeDescriptor = "D";
371 return array[idx];
372 }
373 else if (arrayComponentType == Float.TYPE) {
374 float[] array = (float[]) ctx;
375 checkAccess(array.length, idx);
376 this.exitTypeDescriptor = "F";
377 return array[idx];
378 }
379 else if (arrayComponentType == Long.TYPE) {
380 long[] array = (long[]) ctx;
381 checkAccess(array.length, idx);
382 this.exitTypeDescriptor = "J";
383 return array[idx];
384 }
385 else if (arrayComponentType == Integer.TYPE) {
386 int[] array = (int[]) ctx;
387 checkAccess(array.length, idx);
388 this.exitTypeDescriptor = "I";
389 return array[idx];
390 }
391 else if (arrayComponentType == Short.TYPE) {
392 short[] array = (short[]) ctx;
393 checkAccess(array.length, idx);
394 this.exitTypeDescriptor = "S";
395 return array[idx];
396 }
397 else if (arrayComponentType == Byte.TYPE) {
398 byte[] array = (byte[]) ctx;
399 checkAccess(array.length, idx);
400 this.exitTypeDescriptor = "B";
401 return array[idx];
402 }
403 else if (arrayComponentType == Character.TYPE) {
404 char[] array = (char[]) ctx;
405 checkAccess(array.length, idx);
406 this.exitTypeDescriptor = "C";
407 return array[idx];
408 }
409 else if (arrayComponentType == Boolean.TYPE) {
410 boolean[] array = (boolean[]) ctx;
411 checkAccess(array.length, idx);
412 this.exitTypeDescriptor = "Z";
413 return array[idx];
414 }
415 else {
416 Object[] array = (Object[]) ctx;
417 checkAccess(array.length, idx);
418 Object retValue = array[idx];
419 this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType);
420 return retValue;
421 }
422 }
423
424 private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
425 if (index > arrayLength) {
426 throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS,
427 arrayLength, index);
428 }
429 }
430
431 @SuppressWarnings("unchecked")
432 private <T> T convertValue(TypeConverter converter, Object value, Class<T> targetType) {
433 return (T) converter.convertValue(value, TypeDescriptor.forObject(value), TypeDescriptor.valueOf(targetType));
434 }
435
436
437 private class ArrayIndexingValueRef implements ValueRef {
438
439 private final TypeConverter typeConverter;
440
441 private final Object array;
442
443 private final int index;
444
445 private final TypeDescriptor typeDescriptor;
446
447 ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) {
448 this.typeConverter = typeConverter;
449 this.array = array;
450 this.index = index;
451 this.typeDescriptor = typeDescriptor;
452 }
453
454 @Override
455 public TypedValue getValue() {
456 Object arrayElement = accessArrayElement(this.array, this.index);
457 return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement));
458 }
459
460 @Override
461 public void setValue(Object newValue) {
462 setArrayElement(this.typeConverter, this.array, this.index, newValue,
463 this.typeDescriptor.getElementTypeDescriptor().getType());
464 }
465
466 @Override
467 public boolean isWritable() {
468 return true;
469 }
470 }
471
472
473 @SuppressWarnings({"rawtypes", "unchecked"})
474 private class MapIndexingValueRef implements ValueRef {
475
476 private final TypeConverter typeConverter;
477
478 private final Map map;
479
480 private final Object key;
481
482 private final TypeDescriptor mapEntryDescriptor;
483
484 public MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, TypeDescriptor mapEntryDescriptor) {
485 this.typeConverter = typeConverter;
486 this.map = map;
487 this.key = key;
488 this.mapEntryDescriptor = mapEntryDescriptor;
489 }
490
491 @Override
492 public TypedValue getValue() {
493 Object value = this.map.get(this.key);
494 exitTypeDescriptor = CodeFlow.toDescriptor(Object.class);
495 return new TypedValue(value, this.mapEntryDescriptor.getMapValueTypeDescriptor(value));
496 }
497
498 @Override
499 public void setValue(Object newValue) {
500 if (this.mapEntryDescriptor.getMapValueTypeDescriptor() != null) {
501 newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
502 this.mapEntryDescriptor.getMapValueTypeDescriptor());
503 }
504 this.map.put(this.key, newValue);
505 }
506
507 @Override
508 public boolean isWritable() {
509 return true;
510 }
511 }
512
513
514 private class PropertyIndexingValueRef implements ValueRef {
515
516 private final Object targetObject;
517
518 private final String name;
519
520 private final EvaluationContext evaluationContext;
521
522 private final TypeDescriptor targetObjectTypeDescriptor;
523
524 public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext,
525 TypeDescriptor targetObjectTypeDescriptor) {
526 this.targetObject = targetObject;
527 this.name = value;
528 this.evaluationContext = evaluationContext;
529 this.targetObjectTypeDescriptor = targetObjectTypeDescriptor;
530 }
531
532 @Override
533 public TypedValue getValue() {
534 Class<?> targetObjectRuntimeClass = getObjectClass(this.targetObject);
535 try {
536 if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) &&
537 Indexer.this.cachedReadTargetType != null &&
538 Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) {
539
540 return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name);
541 }
542 List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(
543 targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors());
544 if (accessorsToTry != null) {
545 for (PropertyAccessor accessor : accessorsToTry) {
546 if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) {
547 if (accessor instanceof ReflectivePropertyAccessor) {
548 accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
549 this.evaluationContext, this.targetObject, this.name);
550 }
551 Indexer.this.cachedReadAccessor = accessor;
552 Indexer.this.cachedReadName = this.name;
553 Indexer.this.cachedReadTargetType = targetObjectRuntimeClass;
554 if (accessor instanceof ReflectivePropertyAccessor.OptimalPropertyAccessor) {
555 ReflectivePropertyAccessor.OptimalPropertyAccessor optimalAccessor =
556 (ReflectivePropertyAccessor.OptimalPropertyAccessor) accessor;
557 Member member = optimalAccessor.member;
558 if (member instanceof Field) {
559 Indexer.this.exitTypeDescriptor = CodeFlow.toDescriptor(((Field)member).getType());
560 }
561 else {
562 Indexer.this.exitTypeDescriptor = CodeFlow.toDescriptor(((Method)member).getReturnType());
563 }
564 }
565 return accessor.read(this.evaluationContext, this.targetObject, this.name);
566 }
567 }
568 }
569 }
570 catch (AccessException ex) {
571 throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
572 this.targetObjectTypeDescriptor.toString());
573 }
574 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
575 this.targetObjectTypeDescriptor.toString());
576 }
577
578 @Override
579 public void setValue(Object newValue) {
580 Class<?> contextObjectClass = getObjectClass(this.targetObject);
581 try {
582 if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) &&
583 Indexer.this.cachedWriteTargetType != null &&
584 Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) {
585
586 Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
587 return;
588 }
589 List<PropertyAccessor> accessorsToTry =
590 AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.evaluationContext.getPropertyAccessors());
591 if (accessorsToTry != null) {
592 for (PropertyAccessor accessor : accessorsToTry) {
593 if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) {
594 Indexer.this.cachedWriteName = this.name;
595 Indexer.this.cachedWriteTargetType = contextObjectClass;
596 Indexer.this.cachedWriteAccessor = accessor;
597 accessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
598 return;
599 }
600 }
601 }
602 }
603 catch (AccessException ex) {
604 throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
605 this.name, ex.getMessage());
606 }
607 }
608
609 @Override
610 public boolean isWritable() {
611 return true;
612 }
613 }
614
615
616 @SuppressWarnings({ "rawtypes", "unchecked" })
617 private class CollectionIndexingValueRef implements ValueRef {
618
619 private final Collection collection;
620
621 private final int index;
622
623 private final TypeDescriptor collectionEntryDescriptor;
624
625 private final TypeConverter typeConverter;
626
627 private final boolean growCollection;
628
629 private final int maximumSize;
630
631 public CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor,
632 TypeConverter typeConverter, boolean growCollection, int maximumSize) {
633 this.collection = collection;
634 this.index = index;
635 this.collectionEntryDescriptor = collectionEntryTypeDescriptor;
636 this.typeConverter = typeConverter;
637 this.growCollection = growCollection;
638 this.maximumSize = maximumSize;
639 }
640
641 @Override
642 public TypedValue getValue() {
643 growCollectionIfNecessary();
644 if (this.collection instanceof List) {
645 Object o = ((List) this.collection).get(this.index);
646 exitTypeDescriptor = CodeFlow.toDescriptor(Object.class);
647 return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
648 }
649 int pos = 0;
650 for (Object o : this.collection) {
651 if (pos == this.index) {
652 return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
653 }
654 pos++;
655 }
656 throw new IllegalStateException("Failed to find indexed element " + this.index + ": " + this.collection);
657 }
658
659 @Override
660 public void setValue(Object newValue) {
661 growCollectionIfNecessary();
662 if (this.collection instanceof List) {
663 List list = (List) this.collection;
664 if (this.collectionEntryDescriptor.getElementTypeDescriptor() != null) {
665 newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
666 this.collectionEntryDescriptor.getElementTypeDescriptor());
667 }
668 list.set(this.index, newValue);
669 }
670 else {
671 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
672 this.collectionEntryDescriptor.toString());
673 }
674 }
675
676 private void growCollectionIfNecessary() {
677 if (this.index >= this.collection.size()) {
678 if (!this.growCollection) {
679 throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
680 this.collection.size(), this.index);
681 }
682 if (this.index >= this.maximumSize) {
683 throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION);
684 }
685 if (this.collectionEntryDescriptor.getElementTypeDescriptor() == null) {
686 throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
687 }
688 TypeDescriptor elementType = this.collectionEntryDescriptor.getElementTypeDescriptor();
689 try {
690 int newElements = this.index - this.collection.size();
691 while (newElements >= 0) {
692 (this.collection).add(elementType.getType().newInstance());
693 newElements--;
694 }
695 }
696 catch (Exception ex) {
697 throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
698 }
699 }
700 }
701
702 @Override
703 public boolean isWritable() {
704 return true;
705 }
706 }
707
708
709 private class StringIndexingLValue implements ValueRef {
710
711 private final String target;
712
713 private final int index;
714
715 private final TypeDescriptor typeDescriptor;
716
717 public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) {
718 this.target = target;
719 this.index = index;
720 this.typeDescriptor = typeDescriptor;
721 }
722
723 @Override
724 public TypedValue getValue() {
725 if (this.index >= this.target.length()) {
726 throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
727 this.target.length(), this.index);
728 }
729 return new TypedValue(String.valueOf(this.target.charAt(this.index)));
730 }
731
732 @Override
733 public void setValue(Object newValue) {
734 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
735 this.typeDescriptor.toString());
736 }
737
738 @Override
739 public boolean isWritable() {
740 return true;
741 }
742 }
743
744 }