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}