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