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  */
25 package com.sun.beans.finder;
26 
27 import com.sun.beans.TypeResolver;
28 import com.sun.beans.util.Cache;
29 
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Modifier;
32 import java.lang.reflect.ParameterizedType;
33 import java.lang.reflect.Type;
34 import java.util.Arrays;
35 
36 import static com.sun.beans.util.Cache.Kind.SOFT;
37 import 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  */
48 public 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      */
findMethod(Class<?> type, String name, Class<?>...args)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      */
findInstanceMethod(Class<?> type, String name, Class<?>... args)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      */
findStaticMethod(Class<?> type, String name, Class<?>...args)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      */
findAccessibleMethod(Method method)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      */
findAccessibleMethod(Method method, Type generic)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      */
MethodFinder(String name, Class<?>[] args)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
isValid(Method method)221     protected boolean isValid(Method method) {
222         return super.isValid(method) && method.getName().equals(this.name);
223     }
224 }
225