diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 595bddad9de..e514529a030 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -42,135 +42,123 @@ import org.springframework.expression.spel.SpelMessage; */ public class MethodReference extends SpelNodeImpl { - private final String name; - private final boolean nullSafe; + private final String name; + private volatile CachedMethodExecutor cachedExecutor; - public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) { + + public MethodReference(boolean nullSafe, String methodName, int pos, + SpelNodeImpl... arguments) { super(pos, arguments); - this.name = methodName; this.nullSafe = nullSafe; + this.name = methodName; } + public final String getName() { return this.name; } @Override protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { - TypedValue currentContext = state.getActiveContextObject(); - Object[] arguments = new Object[getChildCount()]; - for (int i = 0; i < arguments.length; i++) { - // Make the root object the active context again for evaluating the parameter - // expressions - try { - state.pushActiveContextObject(state.getRootContextObject()); - arguments[i] = this.children[i].getValueInternal(state).getValue(); - } - finally { - state.popActiveContextObject(); - } - } - if (currentContext.getValue() == null) { - if (this.nullSafe) { - return ValueRef.NullValueRef.instance; - } - else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, - FormatHelper.formatMethodForMessage(this.name, getTypes(arguments))); - } + Object[] arguments = getArguments(state); + if (state.getActiveContextObject().getValue() == null) { + throwIfNotNullSafe(getArgumentTypes(arguments)); + return ValueRef.NullValueRef.instance; } - return new MethodValueRef(state,state.getEvaluationContext(),state.getActiveContextObject().getValue(),arguments); + return new MethodValueRef(state); } @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue currentContext = state.getActiveContextObject(); - Object[] arguments = new Object[getChildCount()]; - for (int i = 0; i < arguments.length; i++) { - // Make the root object the active context again for evaluating the parameter - // expressions - try { - state.pushActiveContextObject(state.getRootContextObject()); - arguments[i] = this.children[i].getValueInternal(state).getValue(); - } - finally { - state.popActiveContextObject(); - } - } - TypedValue activeContextObject = state.getActiveContextObject(); - TypeDescriptor target = (activeContextObject == null ? null - : activeContextObject.getTypeDescriptor()); - List argumentTypes = getTypes(arguments); - if (currentContext.getValue() == null) { - if (this.nullSafe) { - return TypedValue.NULL; - } - else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, - FormatHelper.formatMethodForMessage(this.name, argumentTypes)); - } + EvaluationContext evaluationContext = state.getEvaluationContext(); + Object value = state.getActiveContextObject().getValue(); + TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor(); + Object[] arguments = getArguments(state); + return getValueInternal(evaluationContext, value, arguments, targetType); + } + + private TypedValue getValueInternal(EvaluationContext evaluationContext, + Object value, Object[] arguments, TypeDescriptor targetType) { + List argumentTypes = getArgumentTypes(arguments); + + if (value == null) { + throwIfNotNullSafe(argumentTypes); + return TypedValue.NULL; } - MethodExecutor executorToUse = getCachedExecutor(target, argumentTypes); + MethodExecutor executorToUse = getCachedExecutor(targetType, argumentTypes); + if (executorToUse != null) { try { - return executorToUse.execute(state.getEvaluationContext(), - state.getActiveContextObject().getValue(), arguments); + return executorToUse.execute(evaluationContext, value, arguments); } catch (AccessException ae) { // Two reasons this can occur: // 1. the method invoked actually threw a real exception - // 2. the method invoked was not passed the arguments it expected and has become 'stale' + // 2. the method invoked was not passed the arguments it expected and + // has become 'stale' - // In the first case we should not retry, in the second case we should see if there is a - // better suited method. + // In the first case we should not retry, in the second case we should see + // if there is a better suited method. - // To determine which situation it is, the AccessException will contain a cause. - // If the cause is an InvocationTargetException, a user exception was thrown inside the method. + // To determine the situation, the AccessException will contain a cause. + // If the cause is an InvocationTargetException, a user exception was + // thrown inside the method. // Otherwise the method could not be invoked. - throwSimpleExceptionIfPossible(state, ae); + throwSimpleExceptionIfPossible(value, ae); - // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found + // at this point we know it wasn't a user problem so worth a retry if a + // better candidate can be found this.cachedExecutor = null; } } // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(this.name, argumentTypes, state); - this.cachedExecutor = new CachedMethodExecutor(executorToUse, target, argumentTypes); + executorToUse = findAccessorForMethod(this.name, argumentTypes, value, evaluationContext); + this.cachedExecutor = new CachedMethodExecutor(executorToUse, targetType, + argumentTypes); try { - return executorToUse.execute(state.getEvaluationContext(), - state.getActiveContextObject().getValue(), arguments); + return executorToUse.execute(evaluationContext, + value, arguments); } - catch (AccessException ae) { + catch (AccessException ex) { // Same unwrapping exception handling as above in above catch block - throwSimpleExceptionIfPossible(state, ae); - throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, - this.name, state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage()); + throwSimpleExceptionIfPossible(value, ex); + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name, + value.getClass().getName(), + ex.getMessage()); } } - /** - * Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException, - * throw the RuntimeException directly. - */ - private void throwSimpleExceptionIfPossible(ExpressionState state, AccessException ae) { - if (ae.getCause() instanceof InvocationTargetException) { - Throwable rootCause = ae.getCause().getCause(); - if (rootCause instanceof RuntimeException) { - throw (RuntimeException) rootCause; + private void throwIfNotNullSafe(List argumentTypes) { + if (!this.nullSafe) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, + FormatHelper.formatMethodForMessage(this.name, argumentTypes)); + } + } + + private Object[] getArguments(ExpressionState state) { + Object[] arguments = new Object[getChildCount()]; + for (int i = 0; i < arguments.length; i++) { + // Make the root object the active context again for evaluating the parameter + // expressions + try { + state.pushActiveContextObject(state.getRootContextObject()); + arguments[i] = this.children[i].getValueInternal(state).getValue(); + } + finally { + state.popActiveContextObject(); } - throw new ExpressionInvocationTargetException(getStartPosition(), - "A problem occurred when trying to execute method '" + this.name + - "' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'", - rootCause); } + return arguments; } - private List getTypes(Object... arguments) { + private List getArgumentTypes(Object... arguments) { List descriptors = new ArrayList(arguments.length); for (Object argument : arguments) { descriptors.add(TypeDescriptor.forObject(argument)); @@ -178,37 +166,25 @@ public class MethodReference extends SpelNodeImpl { return Collections.unmodifiableList(descriptors); } - @Override - public String toStringAST() { - StringBuilder sb = new StringBuilder(); - sb.append(this.name).append("("); - for (int i = 0; i < getChildCount(); i++) { - if (i > 0) { - sb.append(","); - } - sb.append(getChild(i).toStringAST()); + private MethodExecutor getCachedExecutor(TypeDescriptor target, + List argumentTypes) { + if (this.cachedExecutor != null && this.cachedExecutor.isSuitable(target, argumentTypes)) { + return this.cachedExecutor.get(); } - sb.append(")"); - return sb.toString(); + this.cachedExecutor = null; + return null; } private MethodExecutor findAccessorForMethod(String name, - List argumentTypes, ExpressionState state) - throws SpelEvaluationException { - return findAccessorForMethod(name, argumentTypes, - state.getActiveContextObject().getValue(), state.getEvaluationContext()); - } + List argumentTypes, Object contextObject, + EvaluationContext evaluationContext) throws SpelEvaluationException { - private MethodExecutor findAccessorForMethod(String name, - List argumentTypes, Object contextObject, EvaluationContext eContext) - throws SpelEvaluationException { - - List methodResolvers = eContext.getMethodResolvers(); + List methodResolvers = evaluationContext.getMethodResolvers(); if (methodResolvers != null) { for (MethodResolver methodResolver : methodResolvers) { try { - MethodExecutor methodExecutor = methodResolver.resolve(eContext, - contextObject, name, argumentTypes); + MethodExecutor methodExecutor = methodResolver.resolve( + evaluationContext, contextObject, name, argumentTypes); if (methodExecutor != null) { return methodExecutor; } @@ -220,89 +196,71 @@ public class MethodReference extends SpelNodeImpl { } } } - throw new SpelEvaluationException( - getStartPosition(), - SpelMessage.METHOD_NOT_FOUND, + + throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND, FormatHelper.formatMethodForMessage(name, argumentTypes), - FormatHelper.formatClassNameForMessage(contextObject instanceof Class ? ((Class) contextObject) - : contextObject.getClass())); + FormatHelper.formatClassNameForMessage( + contextObject instanceof Class ? ((Class) contextObject) + : contextObject.getClass())); } - private MethodExecutor getCachedExecutor(TypeDescriptor target, - List argumentTypes) { - if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(target, argumentTypes)) { - this.cachedExecutor = null; - return null; + /** + * Decode the AccessException, throwing a lightweight evaluation exception or, if the + * cause was a RuntimeException, throw the RuntimeException directly. + */ + private void throwSimpleExceptionIfPossible(Object value, AccessException ae) { + if (ae.getCause() instanceof InvocationTargetException) { + Throwable rootCause = ae.getCause().getCause(); + if (rootCause instanceof RuntimeException) { + throw (RuntimeException) rootCause; + } + throw new ExpressionInvocationTargetException(getStartPosition(), + "A problem occurred when trying to execute method '" + this.name + + "' on object of type '" + + value.getClass().getName() + "'", + rootCause); } - return this.cachedExecutor.get(); } + @Override + public String toStringAST() { + StringBuilder sb = new StringBuilder(); + sb.append(this.name).append("("); + for (int i = 0; i < getChildCount(); i++) { + if (i > 0) { + sb.append(","); + } + sb.append(getChild(i).toStringAST()); + } + sb.append(")"); + return sb.toString(); + } + + private class MethodValueRef implements ValueRef { - private final ExpressionState state; - private final EvaluationContext evaluationContext; + private EvaluationContext evaluationContext; - private final Object target; + private Object value; private TypeDescriptor targetType; - private final Object[] arguments; - - private List argumentTypes; + private Object[] arguments; - MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, - Object object, Object[] arguments) { - this.state = state; - this.evaluationContext = evaluationContext; - this.target = object; - this.targetType = TypeDescriptor.valueOf(target.getClass()); - this.arguments = arguments; - this.argumentTypes = getTypes(this.arguments); + MethodValueRef(ExpressionState state) { + this.evaluationContext = state.getEvaluationContext(); + this.value = state.getActiveContextObject().getValue(); + this.targetType = state.getActiveContextObject().getTypeDescriptor(); + this.arguments = getArguments(state); } @Override public TypedValue getValue() { - MethodExecutor executorToUse = getCachedExecutor(this.targetType, - this.argumentTypes); - if (executorToUse != null) { - try { - return executorToUse.execute(this.evaluationContext, this.target, this.arguments); - } - catch (AccessException ae) { - // Two reasons this can occur: - // 1. the method invoked actually threw a real exception - // 2. the method invoked was not passed the arguments it expected and has become 'stale' - - // In the first case we should not retry, in the second case we should see if there is a - // better suited method. - - // To determine which situation it is, the AccessException will contain a cause. - // If the cause is an InvocationTargetException, a user exception was thrown inside the method. - // Otherwise the method could not be invoked. - throwSimpleExceptionIfPossible(this.state, ae); - - // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found - MethodReference.this.cachedExecutor = null; - } - } - - // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(MethodReference.this.name, argumentTypes, this.target, this.evaluationContext); - MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.targetType, this.argumentTypes); - try { - return executorToUse.execute(this.evaluationContext, this.target, this.arguments); - } - catch (AccessException ex) { - // Same unwrapping exception handling as above in above catch block - throwSimpleExceptionIfPossible(this.state, ex); - throw new SpelEvaluationException(getStartPosition(), ex, - SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, - MethodReference.this.name, this.state.getActiveContextObject().getValue().getClass().getName(), - ex.getMessage()); - } + return MethodReference.this.getValueInternal(this.evaluationContext, + this.value, this.arguments, this.targetType); } @Override @@ -316,6 +274,7 @@ public class MethodReference extends SpelNodeImpl { } } + private static class CachedMethodExecutor { private final MethodExecutor methodExecutor; @@ -324,6 +283,7 @@ public class MethodReference extends SpelNodeImpl { private final List argumentTypes; + public CachedMethodExecutor(MethodExecutor methodExecutor, TypeDescriptor target, List argumentTypes) { this.methodExecutor = methodExecutor; @@ -331,7 +291,9 @@ public class MethodReference extends SpelNodeImpl { this.argumentTypes = argumentTypes; } - public boolean isSuitable(TypeDescriptor target, List argumentTypes) { + + public boolean isSuitable(TypeDescriptor target, + List argumentTypes) { return (this.methodExecutor != null && this.target != null && this.target.equals(target) && this.argumentTypes.equals(argumentTypes)); }