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}