001/* 002 * Copyright 2002-2020 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.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.lang.reflect.Proxy; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026 027import org.springframework.asm.Label; 028import org.springframework.asm.MethodVisitor; 029import org.springframework.core.convert.TypeDescriptor; 030import org.springframework.expression.AccessException; 031import org.springframework.expression.EvaluationContext; 032import org.springframework.expression.EvaluationException; 033import org.springframework.expression.ExpressionInvocationTargetException; 034import org.springframework.expression.MethodExecutor; 035import org.springframework.expression.MethodResolver; 036import org.springframework.expression.TypedValue; 037import org.springframework.expression.spel.CodeFlow; 038import org.springframework.expression.spel.ExpressionState; 039import org.springframework.expression.spel.SpelEvaluationException; 040import org.springframework.expression.spel.SpelMessage; 041import org.springframework.expression.spel.support.ReflectiveMethodExecutor; 042import org.springframework.expression.spel.support.ReflectiveMethodResolver; 043import org.springframework.util.ObjectUtils; 044 045/** 046 * Expression language AST node that represents a method reference. 047 * 048 * @author Andy Clement 049 * @author Juergen Hoeller 050 * @since 3.0 051 */ 052public class MethodReference extends SpelNodeImpl { 053 054 private final String name; 055 056 private final boolean nullSafe; 057 058 private String originalPrimitiveExitTypeDescriptor; 059 060 private volatile CachedMethodExecutor cachedExecutor; 061 062 063 public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) { 064 super(pos, arguments); 065 this.name = methodName; 066 this.nullSafe = nullSafe; 067 } 068 069 070 public final String getName() { 071 return this.name; 072 } 073 074 @Override 075 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { 076 Object[] arguments = getArguments(state); 077 if (state.getActiveContextObject().getValue() == null) { 078 throwIfNotNullSafe(getArgumentTypes(arguments)); 079 return ValueRef.NullValueRef.INSTANCE; 080 } 081 return new MethodValueRef(state, arguments); 082 } 083 084 @Override 085 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { 086 EvaluationContext evaluationContext = state.getEvaluationContext(); 087 Object value = state.getActiveContextObject().getValue(); 088 TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor(); 089 Object[] arguments = getArguments(state); 090 TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments); 091 updateExitTypeDescriptor(); 092 return result; 093 } 094 095 private TypedValue getValueInternal(EvaluationContext evaluationContext, 096 Object value, TypeDescriptor targetType, Object[] arguments) { 097 098 List<TypeDescriptor> argumentTypes = getArgumentTypes(arguments); 099 if (value == null) { 100 throwIfNotNullSafe(argumentTypes); 101 return TypedValue.NULL; 102 } 103 104 MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes); 105 if (executorToUse != null) { 106 try { 107 return executorToUse.execute(evaluationContext, value, arguments); 108 } 109 catch (AccessException ex) { 110 // Two reasons this can occur: 111 // 1. the method invoked actually threw a real exception 112 // 2. the method invoked was not passed the arguments it expected and 113 // has become 'stale' 114 115 // In the first case we should not retry, in the second case we should see 116 // if there is a better suited method. 117 118 // To determine the situation, the AccessException will contain a cause. 119 // If the cause is an InvocationTargetException, a user exception was 120 // thrown inside the method. Otherwise the method could not be invoked. 121 throwSimpleExceptionIfPossible(value, ex); 122 123 // At this point we know it wasn't a user problem so worth a retry if a 124 // better candidate can be found. 125 this.cachedExecutor = null; 126 } 127 } 128 129 // either there was no accessor or it no longer existed 130 executorToUse = findAccessorForMethod(argumentTypes, value, evaluationContext); 131 this.cachedExecutor = new CachedMethodExecutor( 132 executorToUse, (value instanceof Class ? (Class<?>) value : null), targetType, argumentTypes); 133 try { 134 return executorToUse.execute(evaluationContext, value, arguments); 135 } 136 catch (AccessException ex) { 137 // Same unwrapping exception handling as above in above catch block 138 throwSimpleExceptionIfPossible(value, ex); 139 throw new SpelEvaluationException(getStartPosition(), ex, 140 SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name, 141 value.getClass().getName(), ex.getMessage()); 142 } 143 } 144 145 private void throwIfNotNullSafe(List<TypeDescriptor> argumentTypes) { 146 if (!this.nullSafe) { 147 throw new SpelEvaluationException(getStartPosition(), 148 SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, 149 FormatHelper.formatMethodForMessage(this.name, argumentTypes)); 150 } 151 } 152 153 private Object[] getArguments(ExpressionState state) { 154 Object[] arguments = new Object[getChildCount()]; 155 for (int i = 0; i < arguments.length; i++) { 156 // Make the root object the active context again for evaluating the parameter expressions 157 try { 158 state.pushActiveContextObject(state.getScopeRootContextObject()); 159 arguments[i] = this.children[i].getValueInternal(state).getValue(); 160 } 161 finally { 162 state.popActiveContextObject(); 163 } 164 } 165 return arguments; 166 } 167 168 private List<TypeDescriptor> getArgumentTypes(Object... arguments) { 169 List<TypeDescriptor> descriptors = new ArrayList<TypeDescriptor>(arguments.length); 170 for (Object argument : arguments) { 171 descriptors.add(TypeDescriptor.forObject(argument)); 172 } 173 return Collections.unmodifiableList(descriptors); 174 } 175 176 private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value, 177 TypeDescriptor target, List<TypeDescriptor> argumentTypes) { 178 179 List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers(); 180 if (methodResolvers == null || methodResolvers.size() != 1 || 181 !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) { 182 // Not a default ReflectiveMethodResolver - don't know whether caching is valid 183 return null; 184 } 185 186 CachedMethodExecutor executorToCheck = this.cachedExecutor; 187 if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) { 188 return executorToCheck.get(); 189 } 190 this.cachedExecutor = null; 191 return null; 192 } 193 194 private MethodExecutor findAccessorForMethod(List<TypeDescriptor> argumentTypes, Object targetObject, 195 EvaluationContext evaluationContext) throws SpelEvaluationException { 196 197 AccessException accessException = null; 198 List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers(); 199 for (MethodResolver methodResolver : methodResolvers) { 200 try { 201 MethodExecutor methodExecutor = methodResolver.resolve( 202 evaluationContext, targetObject, this.name, argumentTypes); 203 if (methodExecutor != null) { 204 return methodExecutor; 205 } 206 } 207 catch (AccessException ex) { 208 accessException = ex; 209 break; 210 } 211 } 212 213 String method = FormatHelper.formatMethodForMessage(this.name, argumentTypes); 214 String className = FormatHelper.formatClassNameForMessage( 215 targetObject instanceof Class ? ((Class<?>) targetObject) : targetObject.getClass()); 216 if (accessException != null) { 217 throw new SpelEvaluationException( 218 getStartPosition(), accessException, SpelMessage.PROBLEM_LOCATING_METHOD, method, className); 219 } 220 else { 221 throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND, method, className); 222 } 223 } 224 225 /** 226 * Decode the AccessException, throwing a lightweight evaluation exception or, 227 * if the cause was a RuntimeException, throw the RuntimeException directly. 228 */ 229 private void throwSimpleExceptionIfPossible(Object value, AccessException ex) { 230 if (ex.getCause() instanceof InvocationTargetException) { 231 Throwable rootCause = ex.getCause().getCause(); 232 if (rootCause instanceof RuntimeException) { 233 throw (RuntimeException) rootCause; 234 } 235 throw new ExpressionInvocationTargetException(getStartPosition(), 236 "A problem occurred when trying to execute method '" + this.name + 237 "' on object of type [" + value.getClass().getName() + "]", rootCause); 238 } 239 } 240 241 private void updateExitTypeDescriptor() { 242 CachedMethodExecutor executorToCheck = this.cachedExecutor; 243 if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor) { 244 Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod(); 245 String descriptor = CodeFlow.toDescriptor(method.getReturnType()); 246 if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) { 247 this.originalPrimitiveExitTypeDescriptor = descriptor; 248 this.exitTypeDescriptor = CodeFlow.toBoxedDescriptor(descriptor); 249 } 250 else { 251 this.exitTypeDescriptor = descriptor; 252 } 253 } 254 } 255 256 @Override 257 public String toStringAST() { 258 StringBuilder sb = new StringBuilder(this.name); 259 sb.append("("); 260 for (int i = 0; i < getChildCount(); i++) { 261 if (i > 0) { 262 sb.append(","); 263 } 264 sb.append(getChild(i).toStringAST()); 265 } 266 sb.append(")"); 267 return sb.toString(); 268 } 269 270 /** 271 * A method reference is compilable if it has been resolved to a reflectively accessible method 272 * and the child nodes (arguments to the method) are also compilable. 273 */ 274 @Override 275 public boolean isCompilable() { 276 CachedMethodExecutor executorToCheck = this.cachedExecutor; 277 if (executorToCheck == null || executorToCheck.hasProxyTarget() || 278 !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) { 279 return false; 280 } 281 282 for (SpelNodeImpl child : this.children) { 283 if (!child.isCompilable()) { 284 return false; 285 } 286 } 287 288 ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) executorToCheck.get(); 289 if (executor.didArgumentConversionOccur()) { 290 return false; 291 } 292 Class<?> clazz = executor.getMethod().getDeclaringClass(); 293 if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) { 294 return false; 295 } 296 297 return true; 298 } 299 300 @Override 301 public void generateCode(MethodVisitor mv, CodeFlow cf) { 302 CachedMethodExecutor executorToCheck = this.cachedExecutor; 303 if (executorToCheck == null || !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) { 304 throw new IllegalStateException("No applicable cached executor found: " + executorToCheck); 305 } 306 307 ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor) executorToCheck.get(); 308 Method method = methodExecutor.getMethod(); 309 boolean isStaticMethod = Modifier.isStatic(method.getModifiers()); 310 String descriptor = cf.lastDescriptor(); 311 312 Label skipIfNull = null; 313 if (descriptor == null && !isStaticMethod) { 314 // Nothing on the stack but something is needed 315 cf.loadTarget(mv); 316 } 317 if ((descriptor != null || !isStaticMethod) && this.nullSafe) { 318 mv.visitInsn(DUP); 319 skipIfNull = new Label(); 320 Label continueLabel = new Label(); 321 mv.visitJumpInsn(IFNONNULL, continueLabel); 322 CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor); 323 mv.visitJumpInsn(GOTO, skipIfNull); 324 mv.visitLabel(continueLabel); 325 } 326 if (descriptor != null && isStaticMethod) { 327 // Something on the stack when nothing is needed 328 mv.visitInsn(POP); 329 } 330 331 if (CodeFlow.isPrimitive(descriptor)) { 332 CodeFlow.insertBoxIfNecessary(mv, descriptor.charAt(0)); 333 } 334 335 String classDesc = (Modifier.isPublic(method.getDeclaringClass().getModifiers()) ? 336 method.getDeclaringClass().getName().replace('.', '/') : 337 methodExecutor.getPublicDeclaringClass().getName().replace('.', '/')); 338 if (!isStaticMethod && (descriptor == null || !descriptor.substring(1).equals(classDesc))) { 339 CodeFlow.insertCheckCast(mv, "L" + classDesc); 340 } 341 342 generateCodeForArguments(mv, cf, method, this.children); 343 mv.visitMethodInsn( 344 (isStaticMethod ? INVOKESTATIC : (isJava8DefaultMethod(method) ? INVOKEINTERFACE : INVOKEVIRTUAL)), 345 classDesc, method.getName(), CodeFlow.createSignatureDescriptor(method), 346 method.getDeclaringClass().isInterface()); 347 cf.pushDescriptor(this.exitTypeDescriptor); 348 349 if (this.originalPrimitiveExitTypeDescriptor != null) { 350 // The output of the accessor will be a primitive but from the block above it might be null, 351 // so to have a 'common stack' element at skipIfNull target we need to box the primitive 352 CodeFlow.insertBoxIfNecessary(mv, this.originalPrimitiveExitTypeDescriptor); 353 } 354 if (skipIfNull != null) { 355 mv.visitLabel(skipIfNull); 356 } 357 } 358 359 private static boolean isJava8DefaultMethod(Method method) { 360 return (method.getDeclaringClass().isInterface() && Modifier.isPublic(method.getModifiers()) && 361 !Modifier.isAbstract(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())); 362 } 363 364 365 private class MethodValueRef implements ValueRef { 366 367 private final EvaluationContext evaluationContext; 368 369 private final Object value; 370 371 private final TypeDescriptor targetType; 372 373 private final Object[] arguments; 374 375 public MethodValueRef(ExpressionState state, Object[] arguments) { 376 this.evaluationContext = state.getEvaluationContext(); 377 this.value = state.getActiveContextObject().getValue(); 378 this.targetType = state.getActiveContextObject().getTypeDescriptor(); 379 this.arguments = arguments; 380 } 381 382 @Override 383 public TypedValue getValue() { 384 TypedValue result = MethodReference.this.getValueInternal( 385 this.evaluationContext, this.value, this.targetType, this.arguments); 386 updateExitTypeDescriptor(); 387 return result; 388 } 389 390 @Override 391 public void setValue(Object newValue) { 392 throw new IllegalAccessError(); 393 } 394 395 @Override 396 public boolean isWritable() { 397 return false; 398 } 399 } 400 401 402 private static class CachedMethodExecutor { 403 404 private final MethodExecutor methodExecutor; 405 406 private final Class<?> staticClass; 407 408 private final TypeDescriptor target; 409 410 private final List<TypeDescriptor> argumentTypes; 411 412 public CachedMethodExecutor(MethodExecutor methodExecutor, Class<?> staticClass, 413 TypeDescriptor target, List<TypeDescriptor> argumentTypes) { 414 415 this.methodExecutor = methodExecutor; 416 this.staticClass = staticClass; 417 this.target = target; 418 this.argumentTypes = argumentTypes; 419 } 420 421 public boolean isSuitable(Object value, TypeDescriptor target, List<TypeDescriptor> argumentTypes) { 422 return ((this.staticClass == null || this.staticClass == value) && 423 ObjectUtils.nullSafeEquals(this.target, target) && this.argumentTypes.equals(argumentTypes)); 424 } 425 426 public boolean hasProxyTarget() { 427 return (this.target != null && Proxy.isProxyClass(this.target.getType())); 428 } 429 430 public MethodExecutor get() { 431 return this.methodExecutor; 432 } 433 } 434 435}