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