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