View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel.ast;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.springframework.asm.MethodVisitor;
25  import org.springframework.core.convert.TypeDescriptor;
26  import org.springframework.expression.AccessException;
27  import org.springframework.expression.EvaluationContext;
28  import org.springframework.expression.EvaluationException;
29  import org.springframework.expression.PropertyAccessor;
30  import org.springframework.expression.TypedValue;
31  import org.springframework.expression.spel.CodeFlow;
32  import org.springframework.expression.spel.CompilablePropertyAccessor;
33  import org.springframework.expression.spel.ExpressionState;
34  import org.springframework.expression.spel.SpelEvaluationException;
35  import org.springframework.expression.spel.SpelMessage;
36  import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
37  
38  /**
39   * Represents a simple property or field reference.
40   *
41   * @author Andy Clement
42   * @author Juergen Hoeller
43   * @author Clark Duplichien
44   * @since 3.0
45   */
46  public class PropertyOrFieldReference extends SpelNodeImpl {
47  
48  	private final boolean nullSafe;
49  
50  	private final String name;
51  
52  	private volatile PropertyAccessor cachedReadAccessor;
53  
54  	private volatile PropertyAccessor cachedWriteAccessor;
55  
56  
57  	public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) {
58  		super(pos);
59  		this.nullSafe = nullSafe;
60  		this.name = propertyOrFieldName;
61  	}
62  
63  
64  	public boolean isNullSafe() {
65  		return this.nullSafe;
66  	}
67  
68  	public String getName() {
69  		return this.name;
70  	}
71  
72  
73  	@Override
74  	public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
75  		return new AccessorLValue(this, state.getActiveContextObject(), state.getEvaluationContext(),
76  				state.getConfiguration().isAutoGrowNullReferences());
77  	}
78  
79  	@Override
80  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
81  		TypedValue tv = getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(),
82  				state.getConfiguration().isAutoGrowNullReferences());
83  		if (this.cachedReadAccessor instanceof CompilablePropertyAccessor) {
84  			CompilablePropertyAccessor accessor = (CompilablePropertyAccessor) this.cachedReadAccessor;
85  			this.exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
86  		}
87  		return tv;
88  	}
89  
90  	private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext evalContext,
91  			boolean isAutoGrowNullReferences) throws EvaluationException {
92  
93  		TypedValue result = readProperty(contextObject, evalContext, this.name);
94  
95  		// Dynamically create the objects if the user has requested that optional behavior
96  		if (result.getValue() == null && isAutoGrowNullReferences &&
97  				nextChildIs(Indexer.class, PropertyOrFieldReference.class)) {
98  			TypeDescriptor resultDescriptor = result.getTypeDescriptor();
99  			// Creating lists and maps
100 			if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
101 				// Create a new collection or map ready for the indexer
102 				if (resultDescriptor.getType().equals(List.class)) {
103 					try {
104 						if (isWritableProperty(this.name, contextObject, evalContext)) {
105 							List<?> newList = ArrayList.class.newInstance();
106 							writeProperty(contextObject, evalContext, this.name, newList);
107 							result = readProperty(contextObject, evalContext, this.name);
108 						}
109 					}
110 					catch (InstantiationException ex) {
111 						throw new SpelEvaluationException(getStartPosition(), ex,
112 								SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
113 					}
114 					catch (IllegalAccessException ex) {
115 						throw new SpelEvaluationException(getStartPosition(), ex,
116 								SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
117 					}
118 				}
119 				else {
120 					try {
121 						if (isWritableProperty(this.name,contextObject, evalContext)) {
122 							Map<?,?> newMap = HashMap.class.newInstance();
123 							writeProperty(contextObject, evalContext, this.name, newMap);
124 							result = readProperty(contextObject, evalContext, this.name);
125 						}
126 					}
127 					catch (InstantiationException ex) {
128 						throw new SpelEvaluationException(getStartPosition(), ex,
129 								SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
130 					}
131 					catch (IllegalAccessException ex) {
132 						throw new SpelEvaluationException(getStartPosition(), ex,
133 								SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
134 					}
135 				}
136 			}
137 			else {
138 				// 'simple' object
139 				try {
140 					if (isWritableProperty(this.name,contextObject, evalContext)) {
141 						Object newObject  = result.getTypeDescriptor().getType().newInstance();
142 						writeProperty(contextObject, evalContext, this.name, newObject);
143 						result = readProperty(contextObject, evalContext, this.name);
144 					}
145 				}
146 				catch (InstantiationException ex) {
147 					throw new SpelEvaluationException(getStartPosition(), ex,
148 							SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT, result.getTypeDescriptor().getType());
149 				}
150 				catch (IllegalAccessException ex) {
151 					throw new SpelEvaluationException(getStartPosition(), ex,
152 							SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT, result.getTypeDescriptor().getType());
153 				}
154 			}
155 		}
156 		return result;
157 	}
158 
159 	@Override
160 	public void setValue(ExpressionState state, Object newValue) throws EvaluationException {
161 		writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue);
162 	}
163 
164 	@Override
165 	public boolean isWritable(ExpressionState state) throws EvaluationException {
166 		return isWritableProperty(this.name, state.getActiveContextObject(), state.getEvaluationContext());
167 	}
168 
169 	@Override
170 	public String toStringAST() {
171 		return this.name;
172 	}
173 
174 	/**
175 	 * Attempt to read the named property from the current context object.
176 	 * @return the value of the property
177 	 * @throws EvaluationException if any problem accessing the property or it cannot be found
178 	 */
179 	private TypedValue readProperty(TypedValue contextObject, EvaluationContext evalContext, String name)
180 			throws EvaluationException {
181 
182 		Object targetObject = contextObject.getValue();
183 		if (targetObject == null && this.nullSafe) {
184 			return TypedValue.NULL;
185 		}
186 
187 		PropertyAccessor accessorToUse = this.cachedReadAccessor;
188 		if (accessorToUse != null) {
189 			try {
190 				return accessorToUse.read(evalContext, contextObject.getValue(), name);
191 			}
192 			catch (AccessException ex) {
193 				// this is OK - it may have gone stale due to a class change,
194 				// let's try to get a new one and call it before giving up
195 				this.cachedReadAccessor = null;
196 			}
197 		}
198 
199 		List<PropertyAccessor> accessorsToTry =
200 				getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
201 		// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
202 		// get the accessor and use it. If they are not cacheable but report they can read the property
203 		// then ask them to read it
204 		if (accessorsToTry != null) {
205 			try {
206 				for (PropertyAccessor accessor : accessorsToTry) {
207 					if (accessor.canRead(evalContext, contextObject.getValue(), name)) {
208 						if (accessor instanceof ReflectivePropertyAccessor) {
209 							accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
210 									evalContext, contextObject.getValue(), name);
211 						}
212 						this.cachedReadAccessor = accessor;
213 						return accessor.read(evalContext, contextObject.getValue(), name);
214 					}
215 				}
216 			}
217 			catch (AccessException ex) {
218 				throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_DURING_PROPERTY_READ, name, ex.getMessage());
219 			}
220 		}
221 		if (contextObject.getValue() == null) {
222 			throw new SpelEvaluationException(SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL, name);
223 		}
224 		else {
225 			throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, name,
226 					FormatHelper.formatClassNameForMessage(getObjectClass(contextObject.getValue())));
227 		}
228 	}
229 
230 	private void writeProperty(TypedValue contextObject, EvaluationContext evalContext, String name, Object newValue)
231 			throws EvaluationException {
232 
233 		if (contextObject.getValue() == null && this.nullSafe) {
234 			return;
235 		}
236 
237 		PropertyAccessor accessorToUse = this.cachedWriteAccessor;
238 		if (accessorToUse != null) {
239 			try {
240 				accessorToUse.write(evalContext, contextObject.getValue(), name, newValue);
241 				return;
242 			}
243 			catch (AccessException ex) {
244 				// this is OK - it may have gone stale due to a class change,
245 				// let's try to get a new one and call it before giving up
246 				this.cachedWriteAccessor = null;
247 			}
248 		}
249 
250 		List<PropertyAccessor> accessorsToTry =
251 				getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
252 		if (accessorsToTry != null) {
253 			try {
254 				for (PropertyAccessor accessor : accessorsToTry) {
255 					if (accessor.canWrite(evalContext, contextObject.getValue(), name)) {
256 						this.cachedWriteAccessor = accessor;
257 						accessor.write(evalContext, contextObject.getValue(), name, newValue);
258 						return;
259 					}
260 				}
261 			}
262 			catch (AccessException ex) {
263 				throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
264 						name, ex.getMessage());
265 			}
266 		}
267 		if (contextObject.getValue() == null) {
268 			throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL, name);
269 		}
270 		else {
271 			throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE, name,
272 					FormatHelper.formatClassNameForMessage(getObjectClass(contextObject.getValue())));
273 		}
274 	}
275 
276 	public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext evalContext)
277 			throws EvaluationException {
278 
279 		List<PropertyAccessor> accessorsToTry =
280 				getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
281 		if (accessorsToTry != null) {
282 			for (PropertyAccessor accessor : accessorsToTry) {
283 				try {
284 					if (accessor.canWrite(evalContext, contextObject.getValue(), name)) {
285 						return true;
286 					}
287 				}
288 				catch (AccessException ex) {
289 					// let others try
290 				}
291 			}
292 		}
293 		return false;
294 	}
295 
296 	// TODO when there is more time, remove this and use the version in AstUtils
297 	/**
298 	 * Determines the set of property resolvers that should be used to try and access a property
299 	 * on the specified target type. The resolvers are considered to be in an ordered list,
300 	 * however in the returned list any that are exact matches for the input target type (as
301 	 * opposed to 'general' resolvers that could work for any type) are placed at the start of the
302 	 * list. In addition, there are specific resolvers that exactly name the class in question
303 	 * and resolvers that name a specific class but it is a supertype of the class we have.
304 	 * These are put at the end of the specific resolvers set and will be tried after exactly
305 	 * matching accessors but before generic accessors.
306 	 * @param contextObject the object upon which property access is being attempted
307 	 * @return a list of resolvers that should be tried in order to access the property
308 	 */
309 	private List<PropertyAccessor> getPropertyAccessorsToTry(Object contextObject, List<PropertyAccessor> propertyAccessors) {
310 		Class<?> targetType = (contextObject != null ? contextObject.getClass() : null);
311 
312 		List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
313 		List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
314 		for (PropertyAccessor resolver : propertyAccessors) {
315 			Class<?>[] targets = resolver.getSpecificTargetClasses();
316 			if (targets == null) {
317 				// generic resolver that says it can be used for any type
318 				generalAccessors.add(resolver);
319 			}
320 			else if (targetType != null) {
321 				for (Class<?> clazz : targets) {
322 					if (clazz == targetType) {
323 						specificAccessors.add(resolver);
324 						break;
325 					}
326 					else if (clazz.isAssignableFrom(targetType)) {
327 						generalAccessors.add(resolver);
328 					}
329 				}
330 			}
331 		}
332 		List<PropertyAccessor> resolvers = new ArrayList<PropertyAccessor>();
333 		resolvers.addAll(specificAccessors);
334 		generalAccessors.removeAll(specificAccessors);
335 		resolvers.addAll(generalAccessors);
336 		return resolvers;
337 	}
338 	
339 	@Override
340 	public boolean isCompilable() {
341 		return (this.cachedReadAccessor instanceof CompilablePropertyAccessor &&
342 				((CompilablePropertyAccessor) this.cachedReadAccessor).isCompilable());
343 	}
344 	
345 	@Override
346 	public void generateCode(MethodVisitor mv, CodeFlow cf) {
347 		((CompilablePropertyAccessor) this.cachedReadAccessor).generateCode(this.name, mv, cf);
348 		cf.pushDescriptor(this.exitTypeDescriptor);
349 	}
350 
351 
352 	private static class AccessorLValue implements ValueRef {
353 
354 		private final PropertyOrFieldReference ref;
355 
356 		private final TypedValue contextObject;
357 
358 		private final EvaluationContext evalContext;
359 
360 		private final boolean autoGrowNullReferences;
361 
362 		public AccessorLValue(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject,
363 				EvaluationContext evalContext, boolean autoGrowNullReferences) {
364 			this.ref = propertyOrFieldReference;
365 			this.contextObject = activeContextObject;
366 			this.evalContext = evalContext;
367 			this.autoGrowNullReferences = autoGrowNullReferences;
368 		}
369 
370 		@Override
371 		public TypedValue getValue() {
372 			TypedValue value = this.ref.getValueInternal(this.contextObject, this.evalContext, this.autoGrowNullReferences);
373 			if (this.ref.cachedReadAccessor instanceof CompilablePropertyAccessor) {
374 				CompilablePropertyAccessor accessor = (CompilablePropertyAccessor) this.ref.cachedReadAccessor;
375 				this.ref.exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
376 			}
377 			return value;
378 		}
379 
380 		@Override
381 		public void setValue(Object newValue) {
382 			this.ref.writeProperty(this.contextObject, this.evalContext, this.ref.name, newValue);
383 		}
384 
385 		@Override
386 		public boolean isWritable() {
387 			return this.ref.isWritableProperty(this.ref.name, this.contextObject, this.evalContext);
388 		}
389 	}
390 
391 }