1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
40
41
42
43
44
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
96 if (result.getValue() == null && isAutoGrowNullReferences &&
97 nextChildIs(Indexer.class, PropertyOrFieldReference.class)) {
98 TypeDescriptor resultDescriptor = result.getTypeDescriptor();
99
100 if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
101
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
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
176
177
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
194
195 this.cachedReadAccessor = null;
196 }
197 }
198
199 List<PropertyAccessor> accessorsToTry =
200 getPropertyAccessorsToTry(contextObject.getValue(), evalContext.getPropertyAccessors());
201
202
203
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
245
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
290 }
291 }
292 }
293 return false;
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
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
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 }