001/*
002 * Copyright 2002-2017 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.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.springframework.asm.Label;
025import org.springframework.asm.MethodVisitor;
026import org.springframework.core.convert.TypeDescriptor;
027import org.springframework.expression.AccessException;
028import org.springframework.expression.EvaluationContext;
029import org.springframework.expression.EvaluationException;
030import org.springframework.expression.PropertyAccessor;
031import org.springframework.expression.TypedValue;
032import org.springframework.expression.spel.CodeFlow;
033import org.springframework.expression.spel.CompilablePropertyAccessor;
034import org.springframework.expression.spel.ExpressionState;
035import org.springframework.expression.spel.SpelEvaluationException;
036import org.springframework.expression.spel.SpelMessage;
037import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
038
039/**
040 * Represents a simple property or field reference.
041 *
042 * @author Andy Clement
043 * @author Juergen Hoeller
044 * @author Clark Duplichien
045 * @since 3.0
046 */
047public class PropertyOrFieldReference extends SpelNodeImpl {
048
049        private final boolean nullSafe;
050
051        private String originalPrimitiveExitTypeDescriptor = null;
052
053        private final String name;
054
055        private volatile PropertyAccessor cachedReadAccessor;
056
057        private volatile PropertyAccessor cachedWriteAccessor;
058
059
060        public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) {
061                super(pos);
062                this.nullSafe = nullSafe;
063                this.name = propertyOrFieldName;
064        }
065
066
067        public boolean isNullSafe() {
068                return this.nullSafe;
069        }
070
071        public String getName() {
072                return this.name;
073        }
074
075
076        @Override
077        public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
078                return new AccessorLValue(this, state.getActiveContextObject(), state.getEvaluationContext(),
079                                state.getConfiguration().isAutoGrowNullReferences());
080        }
081
082        @Override
083        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
084                TypedValue tv = getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(),
085                                state.getConfiguration().isAutoGrowNullReferences());
086                PropertyAccessor accessorToUse = this.cachedReadAccessor;
087                if (accessorToUse instanceof CompilablePropertyAccessor) {
088                        CompilablePropertyAccessor accessor = (CompilablePropertyAccessor) accessorToUse;
089                        setExitTypeDescriptor(CodeFlow.toDescriptor(accessor.getPropertyType()));
090                }
091                return tv;
092        }
093
094        private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext evalContext,
095                        boolean isAutoGrowNullReferences) throws EvaluationException {
096
097                TypedValue result = readProperty(contextObject, evalContext, this.name);
098
099                // Dynamically create the objects if the user has requested that optional behavior
100                if (result.getValue() == null && isAutoGrowNullReferences &&
101                                nextChildIs(Indexer.class, PropertyOrFieldReference.class)) {
102                        TypeDescriptor resultDescriptor = result.getTypeDescriptor();
103                        // Create a new collection or map ready for the indexer
104                        if (List.class == resultDescriptor.getType()) {
105                                try {
106                                        if (isWritableProperty(this.name, contextObject, evalContext)) {
107                                                List<?> newList = ArrayList.class.newInstance();
108                                                writeProperty(contextObject, evalContext, this.name, newList);
109                                                result = readProperty(contextObject, evalContext, this.name);
110                                        }
111                                }
112                                catch (InstantiationException ex) {
113                                        throw new SpelEvaluationException(getStartPosition(), ex,
114                                                        SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
115                                }
116                                catch (IllegalAccessException ex) {
117                                        throw new SpelEvaluationException(getStartPosition(), ex,
118                                                        SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
119                                }
120                        }
121                        else if (Map.class == resultDescriptor.getType()) {
122                                try {
123                                        if (isWritableProperty(this.name,contextObject, evalContext)) {
124                                                Map<?,?> newMap = HashMap.class.newInstance();
125                                                writeProperty(contextObject, evalContext, this.name, newMap);
126                                                result = readProperty(contextObject, evalContext, this.name);
127                                        }
128                                }
129                                catch (InstantiationException ex) {
130                                        throw new SpelEvaluationException(getStartPosition(), ex,
131                                                        SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
132                                }
133                                catch (IllegalAccessException ex) {
134                                        throw new SpelEvaluationException(getStartPosition(), ex,
135                                                        SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
136                                }
137                        }
138                        else {
139                                // 'simple' object
140                                try {
141                                        if (isWritableProperty(this.name,contextObject, evalContext)) {
142                                                Object newObject  = result.getTypeDescriptor().getType().newInstance();
143                                                writeProperty(contextObject, evalContext, this.name, newObject);
144                                                result = readProperty(contextObject, evalContext, this.name);
145                                        }
146                                }
147                                catch (InstantiationException ex) {
148                                        throw new SpelEvaluationException(getStartPosition(), ex,
149                                                        SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT, result.getTypeDescriptor().getType());
150                                }
151                                catch (IllegalAccessException ex) {
152                                        throw new SpelEvaluationException(getStartPosition(), ex,
153                                                        SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT, result.getTypeDescriptor().getType());
154                                }
155                        }
156                }
157                return result;
158        }
159
160        @Override
161        public void setValue(ExpressionState state, Object newValue) throws EvaluationException {
162                writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue);
163        }
164
165        @Override
166        public boolean isWritable(ExpressionState state) throws EvaluationException {
167                return isWritableProperty(this.name, state.getActiveContextObject(), state.getEvaluationContext());
168        }
169
170        @Override
171        public String toStringAST() {
172                return this.name;
173        }
174
175        /**
176         * Attempt to read the named property from the current context object.
177         * @return the value of the property
178         * @throws EvaluationException if any problem accessing the property or it cannot be found
179         */
180        private TypedValue readProperty(TypedValue contextObject, EvaluationContext evalContext, String name)
181                        throws EvaluationException {
182
183                Object targetObject = contextObject.getValue();
184                if (targetObject == null && this.nullSafe) {
185                        return TypedValue.NULL;
186                }
187
188                PropertyAccessor accessorToUse = this.cachedReadAccessor;
189                if (accessorToUse != null) {
190                        if (evalContext.getPropertyAccessors().contains(accessorToUse)) {
191                                try {
192                                        return accessorToUse.read(evalContext, contextObject.getValue(), name);
193                                }
194                                catch (Exception ex) {
195                                        // This is OK - it may have gone stale due to a class change,
196                                        // let's try to get a new one and call it before giving up...
197                                }
198                        }
199                        this.cachedReadAccessor = null;
200                }
201
202                List<PropertyAccessor> accessorsToTry =
203                                getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
204                // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
205                // get the accessor and use it. If they are not cacheable but report they can read the property
206                // then ask them to read it
207                if (accessorsToTry != null) {
208                        try {
209                                for (PropertyAccessor accessor : accessorsToTry) {
210                                        if (accessor.canRead(evalContext, contextObject.getValue(), name)) {
211                                                if (accessor instanceof ReflectivePropertyAccessor) {
212                                                        accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
213                                                                        evalContext, contextObject.getValue(), name);
214                                                }
215                                                this.cachedReadAccessor = accessor;
216                                                return accessor.read(evalContext, contextObject.getValue(), name);
217                                        }
218                                }
219                        }
220                        catch (Exception ex) {
221                                throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_DURING_PROPERTY_READ, name, ex.getMessage());
222                        }
223                }
224                if (contextObject.getValue() == null) {
225                        throw new SpelEvaluationException(SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL, name);
226                }
227                else {
228                        throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, name,
229                                        FormatHelper.formatClassNameForMessage(getObjectClass(contextObject.getValue())));
230                }
231        }
232
233        private void writeProperty(TypedValue contextObject, EvaluationContext evalContext, String name, Object newValue)
234                        throws EvaluationException {
235
236                if (contextObject.getValue() == null && this.nullSafe) {
237                        return;
238                }
239
240                PropertyAccessor accessorToUse = this.cachedWriteAccessor;
241                if (accessorToUse != null) {
242                        if (evalContext.getPropertyAccessors().contains(accessorToUse)) {
243                                try {
244                                        accessorToUse.write(evalContext, contextObject.getValue(), name, newValue);
245                                        return;
246                                }
247                                catch (Exception ex) {
248                                        // This is OK - it may have gone stale due to a class change,
249                                        // let's try to get a new one and call it before giving up...
250                                }
251                        }
252                        this.cachedWriteAccessor = null;
253                }
254
255                List<PropertyAccessor> accessorsToTry =
256                                getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
257                if (accessorsToTry != null) {
258                        try {
259                                for (PropertyAccessor accessor : accessorsToTry) {
260                                        if (accessor.canWrite(evalContext, contextObject.getValue(), name)) {
261                                                this.cachedWriteAccessor = accessor;
262                                                accessor.write(evalContext, contextObject.getValue(), name, newValue);
263                                                return;
264                                        }
265                                }
266                        }
267                        catch (AccessException ex) {
268                                throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
269                                                name, ex.getMessage());
270                        }
271                }
272                if (contextObject.getValue() == null) {
273                        throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL, name);
274                }
275                else {
276                        throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE, name,
277                                        FormatHelper.formatClassNameForMessage(getObjectClass(contextObject.getValue())));
278                }
279        }
280
281        public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext evalContext)
282                        throws EvaluationException {
283
284                List<PropertyAccessor> accessorsToTry =
285                                getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
286                if (accessorsToTry != null) {
287                        for (PropertyAccessor accessor : accessorsToTry) {
288                                try {
289                                        if (accessor.canWrite(evalContext, contextObject.getValue(), name)) {
290                                                return true;
291                                        }
292                                }
293                                catch (AccessException ex) {
294                                        // let others try
295                                }
296                        }
297                }
298                return false;
299        }
300
301        /**
302         * Determines the set of property resolvers that should be used to try and access a property
303         * on the specified target type. The resolvers are considered to be in an ordered list,
304         * however in the returned list any that are exact matches for the input target type (as
305         * opposed to 'general' resolvers that could work for any type) are placed at the start of the
306         * list. In addition, there are specific resolvers that exactly name the class in question
307         * and resolvers that name a specific class but it is a supertype of the class we have.
308         * These are put at the end of the specific resolvers set and will be tried after exactly
309         * matching accessors but before generic accessors.
310         * @param contextObject the object upon which property access is being attempted
311         * @return a list of resolvers that should be tried in order to access the property
312         */
313        private List<PropertyAccessor> getPropertyAccessorsToTry(Object contextObject, List<PropertyAccessor> propertyAccessors) {
314                Class<?> targetType = (contextObject != null ? contextObject.getClass() : null);
315
316                List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
317                List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
318                for (PropertyAccessor resolver : propertyAccessors) {
319                        Class<?>[] targets = resolver.getSpecificTargetClasses();
320                        if (targets == null) {
321                                // generic resolver that says it can be used for any type
322                                generalAccessors.add(resolver);
323                        }
324                        else if (targetType != null) {
325                                for (Class<?> clazz : targets) {
326                                        if (clazz == targetType) {
327                                                specificAccessors.add(resolver);
328                                                break;
329                                        }
330                                        else if (clazz.isAssignableFrom(targetType)) {
331                                                generalAccessors.add(resolver);
332                                        }
333                                }
334                        }
335                }
336                List<PropertyAccessor> resolvers = new ArrayList<PropertyAccessor>();
337                resolvers.addAll(specificAccessors);
338                generalAccessors.removeAll(specificAccessors);
339                resolvers.addAll(generalAccessors);
340                return resolvers;
341        }
342        
343        @Override
344        public boolean isCompilable() {
345                PropertyAccessor accessorToUse = this.cachedReadAccessor;
346                return (accessorToUse instanceof CompilablePropertyAccessor &&
347                                ((CompilablePropertyAccessor) accessorToUse).isCompilable());
348        }
349        
350        @Override
351        public void generateCode(MethodVisitor mv, CodeFlow cf) {
352                PropertyAccessor accessorToUse = this.cachedReadAccessor;
353                if (!(accessorToUse instanceof CompilablePropertyAccessor)) {
354                        throw new IllegalStateException("Property accessor is not compilable: " + accessorToUse);
355                }
356                Label skipIfNull = null;
357                if (nullSafe) {
358                        mv.visitInsn(DUP);
359                        skipIfNull = new Label();
360                        Label continueLabel = new Label();
361                        mv.visitJumpInsn(IFNONNULL,continueLabel);
362                        CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
363                        mv.visitJumpInsn(GOTO, skipIfNull);
364                        mv.visitLabel(continueLabel);
365                }
366                ((CompilablePropertyAccessor) accessorToUse).generateCode(this.name, mv, cf);
367                cf.pushDescriptor(this.exitTypeDescriptor);
368                if (originalPrimitiveExitTypeDescriptor != null) {
369                        // The output of the accessor is a primitive but from the block above it might be null,
370                        // so to have a common stack element type at skipIfNull target it is necessary
371                        // to box the primitive
372                        CodeFlow.insertBoxIfNecessary(mv, originalPrimitiveExitTypeDescriptor);
373                }
374                if (skipIfNull != null) {
375                        mv.visitLabel(skipIfNull);
376                }
377        }
378
379        void setExitTypeDescriptor(String descriptor) {
380                // If this property or field access would return a primitive - and yet
381                // it is also marked null safe - then the exit type descriptor must be
382                // promoted to the box type to allow a null value to be passed on
383                if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) {
384                        this.originalPrimitiveExitTypeDescriptor = descriptor;
385                        this.exitTypeDescriptor = CodeFlow.toBoxedDescriptor(descriptor);
386                }
387                else {
388                        this.exitTypeDescriptor = descriptor;
389                }
390        }
391
392
393        private static class AccessorLValue implements ValueRef {
394
395                private final PropertyOrFieldReference ref;
396
397                private final TypedValue contextObject;
398
399                private final EvaluationContext evalContext;
400
401                private final boolean autoGrowNullReferences;
402
403                public AccessorLValue(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject,
404                                EvaluationContext evalContext, boolean autoGrowNullReferences) {
405                        this.ref = propertyOrFieldReference;
406                        this.contextObject = activeContextObject;
407                        this.evalContext = evalContext;
408                        this.autoGrowNullReferences = autoGrowNullReferences;
409                }
410
411                @Override
412                public TypedValue getValue() {
413                        TypedValue value =
414                                        this.ref.getValueInternal(this.contextObject, this.evalContext, this.autoGrowNullReferences);
415                        PropertyAccessor accessorToUse = this.ref.cachedReadAccessor;
416                        if (accessorToUse instanceof CompilablePropertyAccessor) {
417                                this.ref.setExitTypeDescriptor(CodeFlow.toDescriptor(((CompilablePropertyAccessor) accessorToUse).getPropertyType()));
418                        }
419                        return value;
420                }
421
422                @Override
423                public void setValue(Object newValue) {
424                        this.ref.writeProperty(this.contextObject, this.evalContext, this.ref.name, newValue);
425                }
426
427                @Override
428                public boolean isWritable() {
429                        return this.ref.isWritableProperty(this.ref.name, this.contextObject, this.evalContext);
430                }
431        }
432
433}