View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * An Indexer can index into some proceeding structure to access a particular piece of it.
43   * Supported structures are: strings / collections (lists/sets) / arrays.
44   *
45   * @author Andy Clement
46   * @author Phillip Webb
47   * @author Stephane Nicoll
48   * @since 3.0
49   */
50  // TODO support multidimensional arrays
51  // TODO support correct syntax for multidimensional [][][] and not [,,,]
52  public class Indexer extends SpelNodeImpl {
53  
54  	private static enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT}
55  
56  
57  	// These fields are used when the indexer is being used as a property read accessor.
58  	// If the name and target type match these cached values then the cachedReadAccessor
59  	// is used to read the property. If they do not match, the correct accessor is
60  	// discovered and then cached for later use.
61  
62  	private String cachedReadName;
63  
64  	private Class<?> cachedReadTargetType;
65  
66  	private PropertyAccessor cachedReadAccessor;
67  
68  	// These fields are used when the indexer is being used as a property write accessor.
69  	// If the name and target type match these cached values then the cachedWriteAccessor
70  	// is used to write the property. If they do not match, the correct accessor is
71  	// discovered and then cached for later use.
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 		// This first part of the if clause prevents a 'double dereference' of
112 		// the property (SPR-5847)
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 			// In case the map key is unqualified, we want it evaluated against
120 			// the root object so temporarily push that on whilst evaluating the key
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 		// Indexing into a Map
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 		// if the object is something that looks indexable by an integer,
146 		// attempt to treat the index value as a number
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 		// Try and treat the index value as a property of the context object
168 		// TODO could call the conversion service to convert the value to a String
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 			// If the string name is changing the accessor is clearly going to change (so compilation is not possible)
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 			// Stack is empty, should use context object
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 						//depthPlusOne(exitTypeDescriptor)+"Ljava/lang/Object;");
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 			// Special case when the key is an unquoted string literal that will be parsed as
261 			// a property/field reference
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 					// It is OK to use the cached accessor
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 					// It is OK to use the cached accessor
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 }