001/*
002 * Copyright 2002-2018 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.support;
018
019import java.lang.reflect.Method;
020import java.lang.reflect.Modifier;
021
022import org.springframework.core.MethodParameter;
023import org.springframework.core.convert.TypeDescriptor;
024import org.springframework.expression.AccessException;
025import org.springframework.expression.EvaluationContext;
026import org.springframework.expression.MethodExecutor;
027import org.springframework.expression.TypedValue;
028import org.springframework.lang.Nullable;
029import org.springframework.util.ClassUtils;
030import org.springframework.util.ReflectionUtils;
031
032/**
033 * {@link MethodExecutor} that works via reflection.
034 *
035 * @author Andy Clement
036 * @author Juergen Hoeller
037 * @since 3.0
038 */
039public class ReflectiveMethodExecutor implements MethodExecutor {
040
041        private final Method originalMethod;
042
043        private final Method methodToInvoke;
044
045        @Nullable
046        private final Integer varargsPosition;
047
048        private boolean computedPublicDeclaringClass = false;
049
050        @Nullable
051        private Class<?> publicDeclaringClass;
052
053        private boolean argumentConversionOccurred = false;
054
055
056        /**
057         * Create a new executor for the given method.
058         * @param method the method to invoke
059         */
060        public ReflectiveMethodExecutor(Method method) {
061                this.originalMethod = method;
062                this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method);
063                if (method.isVarArgs()) {
064                        this.varargsPosition = method.getParameterCount() - 1;
065                }
066                else {
067                        this.varargsPosition = null;
068                }
069        }
070
071
072        /**
073         * Return the original method that this executor has been configured for.
074         */
075        public final Method getMethod() {
076                return this.originalMethod;
077        }
078
079        /**
080         * Find the first public class in the methods declaring class hierarchy that declares this method.
081         * Sometimes the reflective method discovery logic finds a suitable method that can easily be
082         * called via reflection but cannot be called from generated code when compiling the expression
083         * because of visibility restrictions. For example if a non-public class overrides toString(),
084         * this helper method will walk up the type hierarchy to find the first public type that declares
085         * the method (if there is one!). For toString() it may walk as far as Object.
086         */
087        @Nullable
088        public Class<?> getPublicDeclaringClass() {
089                if (!this.computedPublicDeclaringClass) {
090                        this.publicDeclaringClass =
091                                        discoverPublicDeclaringClass(this.originalMethod, this.originalMethod.getDeclaringClass());
092                        this.computedPublicDeclaringClass = true;
093                }
094                return this.publicDeclaringClass;
095        }
096
097        @Nullable
098        private Class<?> discoverPublicDeclaringClass(Method method, Class<?> clazz) {
099                if (Modifier.isPublic(clazz.getModifiers())) {
100                        try {
101                                clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
102                                return clazz;
103                        }
104                        catch (NoSuchMethodException ex) {
105                                // Continue below...
106                        }
107                }
108                if (clazz.getSuperclass() != null) {
109                        return discoverPublicDeclaringClass(method, clazz.getSuperclass());
110                }
111                return null;
112        }
113
114        public boolean didArgumentConversionOccur() {
115                return this.argumentConversionOccurred;
116        }
117
118
119        @Override
120        public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
121                try {
122                        this.argumentConversionOccurred = ReflectionHelper.convertArguments(
123                                        context.getTypeConverter(), arguments, this.originalMethod, this.varargsPosition);
124                        if (this.originalMethod.isVarArgs()) {
125                                arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(
126                                                this.originalMethod.getParameterTypes(), arguments);
127                        }
128                        ReflectionUtils.makeAccessible(this.methodToInvoke);
129                        Object value = this.methodToInvoke.invoke(target, arguments);
130                        return new TypedValue(value, new TypeDescriptor(new MethodParameter(this.originalMethod, -1)).narrow(value));
131                }
132                catch (Exception ex) {
133                        throw new AccessException("Problem invoking method: " + this.methodToInvoke, ex);
134                }
135        }
136
137}