001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.ast; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Member; 021import java.lang.reflect.Method; 022import java.lang.reflect.Modifier; 023import java.util.Collection; 024import java.util.List; 025import java.util.Map; 026 027import org.springframework.asm.MethodVisitor; 028import org.springframework.core.convert.TypeDescriptor; 029import org.springframework.expression.AccessException; 030import org.springframework.expression.EvaluationContext; 031import org.springframework.expression.EvaluationException; 032import org.springframework.expression.PropertyAccessor; 033import org.springframework.expression.TypeConverter; 034import org.springframework.expression.TypedValue; 035import org.springframework.expression.spel.CodeFlow; 036import org.springframework.expression.spel.ExpressionState; 037import org.springframework.expression.spel.SpelEvaluationException; 038import org.springframework.expression.spel.SpelMessage; 039import org.springframework.expression.spel.support.ReflectivePropertyAccessor; 040 041/** 042 * An Indexer can index into some proceeding structure to access a particular piece of it. 043 * Supported structures are: strings / collections (lists/sets) / arrays. 044 * 045 * @author Andy Clement 046 * @author Phillip Webb 047 * @author Stephane Nicoll 048 * @since 3.0 049 */ 050// TODO support multidimensional arrays 051// TODO support correct syntax for multidimensional [][][] and not [,,,] 052public class Indexer extends SpelNodeImpl { 053 054 private enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT} 055 056 057 // These fields are used when the indexer is being used as a property read accessor. 058 // If the name and target type match these cached values then the cachedReadAccessor 059 // is used to read the property. If they do not match, the correct accessor is 060 // discovered and then cached for later use. 061 062 private String cachedReadName; 063 064 private Class<?> cachedReadTargetType; 065 066 private PropertyAccessor cachedReadAccessor; 067 068 // These fields are used when the indexer is being used as a property write accessor. 069 // If the name and target type match these cached values then the cachedWriteAccessor 070 // is used to write the property. If they do not match, the correct accessor is 071 // discovered and then cached for later use. 072 073 private String cachedWriteName; 074 075 private Class<?> cachedWriteTargetType; 076 077 private PropertyAccessor cachedWriteAccessor; 078 079 private IndexedType indexedType; 080 081 082 public Indexer(int pos, SpelNodeImpl expr) { 083 super(pos, expr); 084 } 085 086 087 @Override 088 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { 089 return getValueRef(state).getValue(); 090 } 091 092 @Override 093 public void setValue(ExpressionState state, Object newValue) throws EvaluationException { 094 getValueRef(state).setValue(newValue); 095 } 096 097 @Override 098 public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException { 099 return true; 100 } 101 102 103 @Override 104 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { 105 TypedValue context = state.getActiveContextObject(); 106 Object target = context.getValue(); 107 TypeDescriptor targetDescriptor = context.getTypeDescriptor(); 108 TypedValue indexValue; 109 Object index; 110 111 // This first part of the if clause prevents a 'double dereference' of the property (SPR-5847) 112 if (target instanceof Map && (this.children[0] instanceof PropertyOrFieldReference)) { 113 PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0]; 114 index = reference.getName(); 115 indexValue = new TypedValue(index); 116 } 117 else { 118 // In case the map key is unqualified, we want it evaluated against the root object 119 // so temporarily push that on whilst evaluating the key 120 try { 121 state.pushActiveContextObject(state.getRootContextObject()); 122 indexValue = this.children[0].getValueInternal(state); 123 index = indexValue.getValue(); 124 } 125 finally { 126 state.popActiveContextObject(); 127 } 128 } 129 130 // Raise a proper exception in case of a null target 131 if (target == null) { 132 throw new SpelEvaluationException(getStartPosition(), SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); 133 } 134 135 // Indexing into a Map 136 if (target instanceof Map) { 137 Object key = index; 138 if (targetDescriptor.getMapKeyTypeDescriptor() != null) { 139 key = state.convertValue(key, targetDescriptor.getMapKeyTypeDescriptor()); 140 } 141 this.indexedType = IndexedType.MAP; 142 return new MapIndexingValueRef(state.getTypeConverter(), (Map<?, ?>) target, key, targetDescriptor); 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 (target.getClass().isArray() || target instanceof Collection || target instanceof String) { 148 int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); 149 if (target.getClass().isArray()) { 150 this.indexedType = IndexedType.ARRAY; 151 return new ArrayIndexingValueRef(state.getTypeConverter(), target, idx, targetDescriptor); 152 } 153 else if (target instanceof Collection) { 154 if (target instanceof List) { 155 this.indexedType = IndexedType.LIST; 156 } 157 return new CollectionIndexingValueRef((Collection<?>) target, 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) target, 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 == indexValue.getTypeDescriptor().getType()) { 170 this.indexedType = IndexedType.OBJECT; 171 return new PropertyIndexingValueRef( 172 target, (String) index, state.getEvaluationContext(), targetDescriptor); 173 } 174 175 throw new SpelEvaluationException( 176 getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetDescriptor); 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 no compilation possible) 192 return (this.cachedReadAccessor != null && 193 this.cachedReadAccessor instanceof ReflectivePropertyAccessor.OptimalPropertyAccessor && 194 getChild(0) instanceof StringLiteral); 195 } 196 return false; 197 } 198 199 @Override 200 public void generateCode(MethodVisitor mv, CodeFlow cf) { 201 String descriptor = cf.lastDescriptor(); 202 if (descriptor == null) { 203 // Stack is empty, should use context object 204 cf.loadTarget(mv); 205 } 206 207 if (this.indexedType == IndexedType.ARRAY) { 208 int insn; 209 if ("D".equals(this.exitTypeDescriptor)) { 210 mv.visitTypeInsn(CHECKCAST, "[D"); 211 insn = DALOAD; 212 } 213 else if ("F".equals(this.exitTypeDescriptor)) { 214 mv.visitTypeInsn(CHECKCAST, "[F"); 215 insn = FALOAD; 216 } 217 else if ("J".equals(this.exitTypeDescriptor)) { 218 mv.visitTypeInsn(CHECKCAST, "[J"); 219 insn = LALOAD; 220 } 221 else if ("I".equals(this.exitTypeDescriptor)) { 222 mv.visitTypeInsn(CHECKCAST, "[I"); 223 insn = IALOAD; 224 } 225 else if ("S".equals(this.exitTypeDescriptor)) { 226 mv.visitTypeInsn(CHECKCAST, "[S"); 227 insn = SALOAD; 228 } 229 else if ("B".equals(this.exitTypeDescriptor)) { 230 mv.visitTypeInsn(CHECKCAST, "[B"); 231 insn = BALOAD; 232 } 233 else if ("C".equals(this.exitTypeDescriptor)) { 234 mv.visitTypeInsn(CHECKCAST, "[C"); 235 insn = CALOAD; 236 } 237 else { 238 mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor + 239 (CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";")); 240 //depthPlusOne(exitTypeDescriptor)+"Ljava/lang/Object;"); 241 insn = AALOAD; 242 } 243 SpelNodeImpl index = this.children[0]; 244 cf.enterCompilationScope(); 245 index.generateCode(mv, cf); 246 cf.exitCompilationScope(); 247 mv.visitInsn(insn); 248 } 249 250 else if (this.indexedType == IndexedType.LIST) { 251 mv.visitTypeInsn(CHECKCAST, "java/util/List"); 252 cf.enterCompilationScope(); 253 this.children[0].generateCode(mv, cf); 254 cf.exitCompilationScope(); 255 mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true); 256 } 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( 273 INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true); 274 } 275 276 else if (this.indexedType == IndexedType.OBJECT) { 277 ReflectivePropertyAccessor.OptimalPropertyAccessor accessor = 278 (ReflectivePropertyAccessor.OptimalPropertyAccessor) this.cachedReadAccessor; 279 Member member = accessor.member; 280 boolean isStatic = Modifier.isStatic(member.getModifiers()); 281 String classDesc = member.getDeclaringClass().getName().replace('.', '/'); 282 283 if (!isStatic) { 284 if (descriptor == null) { 285 cf.loadTarget(mv); 286 } 287 if (descriptor == null || !classDesc.equals(descriptor.substring(1))) { 288 mv.visitTypeInsn(CHECKCAST, classDesc); 289 } 290 } 291 292 if (member instanceof Method) { 293 mv.visitMethodInsn((isStatic? INVOKESTATIC : INVOKEVIRTUAL), classDesc, member.getName(), 294 CodeFlow.createSignatureDescriptor((Method) member), false); 295 } 296 else { 297 mv.visitFieldInsn((isStatic ? GETSTATIC : GETFIELD), classDesc, member.getName(), 298 CodeFlow.toJvmDescriptor(((Field) member).getType())); 299 } 300 } 301 302 cf.pushDescriptor(this.exitTypeDescriptor); 303 } 304 305 @Override 306 public String toStringAST() { 307 StringBuilder sb = new StringBuilder("["); 308 for (int i = 0; i < getChildCount(); i++) { 309 if (i > 0) { 310 sb.append(","); 311 } 312 sb.append(getChild(i).toStringAST()); 313 } 314 sb.append("]"); 315 return sb.toString(); 316 } 317 318 319 private void setArrayElement(TypeConverter converter, Object ctx, int idx, Object newValue, 320 Class<?> arrayComponentType) throws EvaluationException { 321 322 if (arrayComponentType == Boolean.TYPE) { 323 boolean[] array = (boolean[]) ctx; 324 checkAccess(array.length, idx); 325 array[idx] = convertValue(converter, newValue, Boolean.class); 326 } 327 else if (arrayComponentType == Byte.TYPE) { 328 byte[] array = (byte[]) ctx; 329 checkAccess(array.length, idx); 330 array[idx] = convertValue(converter, newValue, Byte.class); 331 } 332 else if (arrayComponentType == Character.TYPE) { 333 char[] array = (char[]) ctx; 334 checkAccess(array.length, idx); 335 array[idx] = convertValue(converter, newValue, Character.class); 336 } 337 else if (arrayComponentType == Double.TYPE) { 338 double[] array = (double[]) ctx; 339 checkAccess(array.length, idx); 340 array[idx] = convertValue(converter, newValue, Double.class); 341 } 342 else if (arrayComponentType == Float.TYPE) { 343 float[] array = (float[]) ctx; 344 checkAccess(array.length, idx); 345 array[idx] = convertValue(converter, newValue, Float.class); 346 } 347 else if (arrayComponentType == Integer.TYPE) { 348 int[] array = (int[]) ctx; 349 checkAccess(array.length, idx); 350 array[idx] = convertValue(converter, newValue, Integer.class); 351 } 352 else if (arrayComponentType == Long.TYPE) { 353 long[] array = (long[]) ctx; 354 checkAccess(array.length, idx); 355 array[idx] = convertValue(converter, newValue, Long.class); 356 } 357 else if (arrayComponentType == Short.TYPE) { 358 short[] array = (short[]) ctx; 359 checkAccess(array.length, idx); 360 array[idx] = convertValue(converter, newValue, Short.class); 361 } 362 else { 363 Object[] array = (Object[]) ctx; 364 checkAccess(array.length, idx); 365 array[idx] = convertValue(converter, newValue, arrayComponentType); 366 } 367 } 368 369 private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException { 370 Class<?> arrayComponentType = ctx.getClass().getComponentType(); 371 if (arrayComponentType == Boolean.TYPE) { 372 boolean[] array = (boolean[]) ctx; 373 checkAccess(array.length, idx); 374 this.exitTypeDescriptor = "Z"; 375 return array[idx]; 376 } 377 else if (arrayComponentType == Byte.TYPE) { 378 byte[] array = (byte[]) ctx; 379 checkAccess(array.length, idx); 380 this.exitTypeDescriptor = "B"; 381 return array[idx]; 382 } 383 else if (arrayComponentType == Character.TYPE) { 384 char[] array = (char[]) ctx; 385 checkAccess(array.length, idx); 386 this.exitTypeDescriptor = "C"; 387 return array[idx]; 388 } 389 else if (arrayComponentType == Double.TYPE) { 390 double[] array = (double[]) ctx; 391 checkAccess(array.length, idx); 392 this.exitTypeDescriptor = "D"; 393 return array[idx]; 394 } 395 else if (arrayComponentType == Float.TYPE) { 396 float[] array = (float[]) ctx; 397 checkAccess(array.length, idx); 398 this.exitTypeDescriptor = "F"; 399 return array[idx]; 400 } 401 else if (arrayComponentType == Integer.TYPE) { 402 int[] array = (int[]) ctx; 403 checkAccess(array.length, idx); 404 this.exitTypeDescriptor = "I"; 405 return array[idx]; 406 } 407 else if (arrayComponentType == Long.TYPE) { 408 long[] array = (long[]) ctx; 409 checkAccess(array.length, idx); 410 this.exitTypeDescriptor = "J"; 411 return array[idx]; 412 } 413 else if (arrayComponentType == Short.TYPE) { 414 short[] array = (short[]) ctx; 415 checkAccess(array.length, idx); 416 this.exitTypeDescriptor = "S"; 417 return array[idx]; 418 } 419 else { 420 Object[] array = (Object[]) ctx; 421 checkAccess(array.length, idx); 422 Object retValue = array[idx]; 423 this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType); 424 return retValue; 425 } 426 } 427 428 private void checkAccess(int arrayLength, int index) throws SpelEvaluationException { 429 if (index > arrayLength) { 430 throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, 431 arrayLength, index); 432 } 433 } 434 435 @SuppressWarnings("unchecked") 436 private <T> T convertValue(TypeConverter converter, Object value, Class<T> targetType) { 437 return (T) converter.convertValue(value, TypeDescriptor.forObject(value), TypeDescriptor.valueOf(targetType)); 438 } 439 440 441 private class ArrayIndexingValueRef implements ValueRef { 442 443 private final TypeConverter typeConverter; 444 445 private final Object array; 446 447 private final int index; 448 449 private final TypeDescriptor typeDescriptor; 450 451 ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) { 452 this.typeConverter = typeConverter; 453 this.array = array; 454 this.index = index; 455 this.typeDescriptor = typeDescriptor; 456 } 457 458 @Override 459 public TypedValue getValue() { 460 Object arrayElement = accessArrayElement(this.array, this.index); 461 return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement)); 462 } 463 464 @Override 465 public void setValue(Object newValue) { 466 setArrayElement(this.typeConverter, this.array, this.index, newValue, 467 this.typeDescriptor.getElementTypeDescriptor().getType()); 468 } 469 470 @Override 471 public boolean isWritable() { 472 return true; 473 } 474 } 475 476 477 @SuppressWarnings({"rawtypes", "unchecked"}) 478 private class MapIndexingValueRef implements ValueRef { 479 480 private final TypeConverter typeConverter; 481 482 private final Map map; 483 484 private final Object key; 485 486 private final TypeDescriptor mapEntryDescriptor; 487 488 public MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, TypeDescriptor mapEntryDescriptor) { 489 this.typeConverter = typeConverter; 490 this.map = map; 491 this.key = key; 492 this.mapEntryDescriptor = mapEntryDescriptor; 493 } 494 495 @Override 496 public TypedValue getValue() { 497 Object value = this.map.get(this.key); 498 exitTypeDescriptor = CodeFlow.toDescriptor(Object.class); 499 return new TypedValue(value, this.mapEntryDescriptor.getMapValueTypeDescriptor(value)); 500 } 501 502 @Override 503 public void setValue(Object newValue) { 504 if (this.mapEntryDescriptor.getMapValueTypeDescriptor() != null) { 505 newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue), 506 this.mapEntryDescriptor.getMapValueTypeDescriptor()); 507 } 508 this.map.put(this.key, newValue); 509 } 510 511 @Override 512 public boolean isWritable() { 513 return true; 514 } 515 } 516 517 518 private class PropertyIndexingValueRef implements ValueRef { 519 520 private final Object targetObject; 521 522 private final String name; 523 524 private final EvaluationContext evaluationContext; 525 526 private final TypeDescriptor targetObjectTypeDescriptor; 527 528 public PropertyIndexingValueRef(Object targetObject, String value, 529 EvaluationContext evaluationContext, TypeDescriptor targetObjectTypeDescriptor) { 530 531 this.targetObject = targetObject; 532 this.name = value; 533 this.evaluationContext = evaluationContext; 534 this.targetObjectTypeDescriptor = targetObjectTypeDescriptor; 535 } 536 537 @Override 538 public TypedValue getValue() { 539 Class<?> targetObjectRuntimeClass = getObjectClass(this.targetObject); 540 try { 541 if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) && 542 Indexer.this.cachedReadTargetType != null && 543 Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) { 544 // It is OK to use the cached accessor 545 return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name); 546 } 547 List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry( 548 targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors()); 549 if (accessorsToTry != null) { 550 for (PropertyAccessor accessor : accessorsToTry) { 551 if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { 552 if (accessor instanceof ReflectivePropertyAccessor) { 553 accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor( 554 this.evaluationContext, this.targetObject, this.name); 555 } 556 Indexer.this.cachedReadAccessor = accessor; 557 Indexer.this.cachedReadName = this.name; 558 Indexer.this.cachedReadTargetType = targetObjectRuntimeClass; 559 if (accessor instanceof ReflectivePropertyAccessor.OptimalPropertyAccessor) { 560 ReflectivePropertyAccessor.OptimalPropertyAccessor optimalAccessor = 561 (ReflectivePropertyAccessor.OptimalPropertyAccessor) accessor; 562 Member member = optimalAccessor.member; 563 Indexer.this.exitTypeDescriptor = CodeFlow.toDescriptor(member instanceof Method ? 564 ((Method) member).getReturnType() : ((Field) member).getType()); 565 } 566 return accessor.read(this.evaluationContext, this.targetObject, this.name); 567 } 568 } 569 } 570 } 571 catch (AccessException ex) { 572 throw new SpelEvaluationException(getStartPosition(), ex, 573 SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, this.targetObjectTypeDescriptor.toString()); 574 } 575 throw new SpelEvaluationException(getStartPosition(), 576 SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, this.targetObjectTypeDescriptor.toString()); 577 } 578 579 @Override 580 public void setValue(Object newValue) { 581 Class<?> contextObjectClass = getObjectClass(this.targetObject); 582 try { 583 if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) && 584 Indexer.this.cachedWriteTargetType != null && 585 Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) { 586 // It is OK to use the cached accessor 587 Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue); 588 return; 589 } 590 List<PropertyAccessor> accessorsToTry = 591 AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.evaluationContext.getPropertyAccessors()); 592 if (accessorsToTry != null) { 593 for (PropertyAccessor accessor : accessorsToTry) { 594 if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) { 595 Indexer.this.cachedWriteName = this.name; 596 Indexer.this.cachedWriteTargetType = contextObjectClass; 597 Indexer.this.cachedWriteAccessor = accessor; 598 accessor.write(this.evaluationContext, this.targetObject, this.name, newValue); 599 return; 600 } 601 } 602 } 603 } 604 catch (AccessException ex) { 605 throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE, 606 this.name, ex.getMessage()); 607 } 608 } 609 610 @Override 611 public boolean isWritable() { 612 return true; 613 } 614 } 615 616 617 @SuppressWarnings({ "rawtypes", "unchecked" }) 618 private class CollectionIndexingValueRef implements ValueRef { 619 620 private final Collection collection; 621 622 private final int index; 623 624 private final TypeDescriptor collectionEntryDescriptor; 625 626 private final TypeConverter typeConverter; 627 628 private final boolean growCollection; 629 630 private final int maximumSize; 631 632 public CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryDescriptor, 633 TypeConverter typeConverter, boolean growCollection, int maximumSize) { 634 635 this.collection = collection; 636 this.index = index; 637 this.collectionEntryDescriptor = collectionEntryDescriptor; 638 this.typeConverter = typeConverter; 639 this.growCollection = growCollection; 640 this.maximumSize = maximumSize; 641 } 642 643 @Override 644 public TypedValue getValue() { 645 growCollectionIfNecessary(); 646 if (this.collection instanceof List) { 647 Object o = ((List) this.collection).get(this.index); 648 exitTypeDescriptor = CodeFlow.toDescriptor(Object.class); 649 return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o)); 650 } 651 int pos = 0; 652 for (Object o : this.collection) { 653 if (pos == this.index) { 654 return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o)); 655 } 656 pos++; 657 } 658 throw new IllegalStateException("Failed to find indexed element " + this.index + ": " + this.collection); 659 } 660 661 @Override 662 public void setValue(Object newValue) { 663 growCollectionIfNecessary(); 664 if (this.collection instanceof List) { 665 List list = (List) this.collection; 666 if (this.collectionEntryDescriptor.getElementTypeDescriptor() != null) { 667 newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue), 668 this.collectionEntryDescriptor.getElementTypeDescriptor()); 669 } 670 list.set(this.index, newValue); 671 } 672 else { 673 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, 674 this.collectionEntryDescriptor.toString()); 675 } 676 } 677 678 private void growCollectionIfNecessary() { 679 if (this.index >= this.collection.size()) { 680 if (!this.growCollection) { 681 throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, 682 this.collection.size(), this.index); 683 } 684 if (this.index >= this.maximumSize) { 685 throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION); 686 } 687 if (this.collectionEntryDescriptor.getElementTypeDescriptor() == null) { 688 throw new SpelEvaluationException( 689 getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); 690 } 691 TypeDescriptor elementType = this.collectionEntryDescriptor.getElementTypeDescriptor(); 692 try { 693 int newElements = this.index - this.collection.size(); 694 while (newElements >= 0) { 695 this.collection.add(elementType.getType().newInstance()); 696 newElements--; 697 } 698 } 699 catch (Throwable ex) { 700 throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); 701 } 702 } 703 } 704 705 @Override 706 public boolean isWritable() { 707 return true; 708 } 709 } 710 711 712 private class StringIndexingLValue implements ValueRef { 713 714 private final String target; 715 716 private final int index; 717 718 private final TypeDescriptor typeDescriptor; 719 720 public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) { 721 this.target = target; 722 this.index = index; 723 this.typeDescriptor = typeDescriptor; 724 } 725 726 @Override 727 public TypedValue getValue() { 728 if (this.index >= this.target.length()) { 729 throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, 730 this.target.length(), this.index); 731 } 732 return new TypedValue(String.valueOf(this.target.charAt(this.index))); 733 } 734 735 @Override 736 public void setValue(Object newValue) { 737 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, 738 this.typeDescriptor.toString()); 739 } 740 741 @Override 742 public boolean isWritable() { 743 return true; 744 } 745 } 746 747}