MethodFinder.java revision 13901:b2a69d66dc65
1/*
2 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.beans.finder;
26
27import com.sun.beans.TypeResolver;
28import com.sun.beans.util.Cache;
29
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.lang.reflect.ParameterizedType;
33import java.lang.reflect.Type;
34import java.util.Arrays;
35
36import static com.sun.beans.util.Cache.Kind.SOFT;
37import static sun.reflect.misc.ReflectUtil.isPackageAccessible;
38
39/**
40 * This utility class provides {@code static} methods
41 * to find a public method with specified name and parameter types
42 * in specified class.
43 *
44 * @since 1.7
45 *
46 * @author Sergey A. Malenkov
47 */
48public final class MethodFinder extends AbstractFinder<Method> {
49    private static final Cache<Signature, Method> CACHE = new Cache<Signature, Method>(SOFT, SOFT) {
50        @Override
51        public Method create(Signature signature) {
52            try {
53                MethodFinder finder = new MethodFinder(signature.getName(), signature.getArgs());
54                return findAccessibleMethod(finder.find(signature.getType().getMethods()));
55            }
56            catch (Exception exception) {
57                throw new SignatureException(exception);
58            }
59        }
60    };
61
62    /**
63     * Finds public method (static or non-static)
64     * that is accessible from public class.
65     *
66     * @param type  the class that can have method
67     * @param name  the name of method to find
68     * @param args  parameter types that is used to find method
69     * @return object that represents found method
70     * @throws NoSuchMethodException if method could not be found
71     *                               or some methods are found
72     */
73    public static Method findMethod(Class<?> type, String name, Class<?>...args) throws NoSuchMethodException {
74        if (name == null) {
75            throw new IllegalArgumentException("Method name is not set");
76        }
77        PrimitiveWrapperMap.replacePrimitivesWithWrappers(args);
78        Signature signature = new Signature(type, name, args);
79
80        try {
81            Method method = CACHE.get(signature);
82            return (method == null) || isPackageAccessible(method.getDeclaringClass()) ? method : CACHE.create(signature);
83        }
84        catch (SignatureException exception) {
85            throw exception.toNoSuchMethodException("Method '" + name + "' is not found");
86        }
87    }
88
89    /**
90     * Finds public non-static method
91     * that is accessible from public class.
92     *
93     * @param type  the class that can have method
94     * @param name  the name of method to find
95     * @param args  parameter types that is used to find method
96     * @return object that represents found method
97     * @throws NoSuchMethodException if method could not be found
98     *                               or some methods are found
99     */
100    public static Method findInstanceMethod(Class<?> type, String name, Class<?>... args) throws NoSuchMethodException {
101        Method method = findMethod(type, name, args);
102        if (Modifier.isStatic(method.getModifiers())) {
103            throw new NoSuchMethodException("Method '" + name + "' is static");
104        }
105        return method;
106    }
107
108    /**
109     * Finds public static method
110     * that is accessible from public class.
111     *
112     * @param type  the class that can have method
113     * @param name  the name of method to find
114     * @param args  parameter types that is used to find method
115     * @return object that represents found method
116     * @throws NoSuchMethodException if method could not be found
117     *                               or some methods are found
118     */
119    public static Method findStaticMethod(Class<?> type, String name, Class<?>...args) throws NoSuchMethodException {
120        Method method = findMethod(type, name, args);
121        if (!Modifier.isStatic(method.getModifiers())) {
122            throw new NoSuchMethodException("Method '" + name + "' is not static");
123        }
124        return method;
125    }
126
127    /**
128     * Finds method that is accessible from public class or interface through class hierarchy.
129     *
130     * @param method  object that represents found method
131     * @return object that represents accessible method
132     * @throws NoSuchMethodException if method is not accessible or is not found
133     *                               in specified superclass or interface
134     */
135    public static Method findAccessibleMethod(Method method) throws NoSuchMethodException {
136        Class<?> type = method.getDeclaringClass();
137
138        if (!FinderUtils.isExported(type)) {
139            throw new NoSuchMethodException("Method '" + method.getName() + "' is not accessible");
140        }
141        if (Modifier.isPublic(type.getModifiers()) && isPackageAccessible(type)) {
142            return method;
143        }
144        if (Modifier.isStatic(method.getModifiers())) {
145            throw new NoSuchMethodException("Method '" + method.getName() + "' is not accessible");
146        }
147        for (Type generic : type.getGenericInterfaces()) {
148            try {
149                return findAccessibleMethod(method, generic);
150            }
151            catch (NoSuchMethodException exception) {
152                // try to find in superclass or another interface
153            }
154        }
155        return findAccessibleMethod(method, type.getGenericSuperclass());
156    }
157
158    /**
159     * Finds method that accessible from specified class.
160     *
161     * @param method  object that represents found method
162     * @param generic generic type that is used to find accessible method
163     * @return object that represents accessible method
164     * @throws NoSuchMethodException if method is not accessible or is not found
165     *                               in specified superclass or interface
166     */
167    private static Method findAccessibleMethod(Method method, Type generic) throws NoSuchMethodException {
168        String name = method.getName();
169        Class<?>[] params = method.getParameterTypes();
170        if (generic instanceof Class) {
171            Class<?> type = (Class<?>) generic;
172            return findAccessibleMethod(type.getMethod(name, params));
173        }
174        if (generic instanceof ParameterizedType) {
175            ParameterizedType pt = (ParameterizedType) generic;
176            Class<?> type = (Class<?>) pt.getRawType();
177            for (Method m : type.getMethods()) {
178                if (m.getName().equals(name)) {
179                    Class<?>[] pts = m.getParameterTypes();
180                    if (pts.length == params.length) {
181                        if (Arrays.equals(params, pts)) {
182                            return findAccessibleMethod(m);
183                        }
184                        Type[] gpts = m.getGenericParameterTypes();
185                        if (params.length == gpts.length) {
186                            if (Arrays.equals(params, TypeResolver.erase(TypeResolver.resolve(pt, gpts)))) {
187                                return findAccessibleMethod(m);
188                            }
189                        }
190                    }
191                }
192            }
193        }
194        throw new NoSuchMethodException("Method '" + name + "' is not accessible");
195    }
196
197
198    private final String name;
199
200    /**
201     * Creates method finder with specified array of parameter types.
202     *
203     * @param name  the name of method to find
204     * @param args  the array of parameter types
205     */
206    private MethodFinder(String name, Class<?>[] args) {
207        super(args);
208        this.name = name;
209    }
210
211    /**
212     * Checks validness of the method.
213     * The valid method should be public and
214     * should have the specified name.
215     *
216     * @param method  the object that represents method
217     * @return {@code true} if the method is valid,
218     *         {@code false} otherwise
219     */
220    @Override
221    protected boolean isValid(Method method) {
222        return super.isValid(method) && method.getName().equals(this.name);
223    }
224}
225