1 /*
   2  * Copyright (c) 2008, 2012, 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.WeakCache;
  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 sun.reflect.misc.ReflectUtil.isPackageAccessible;
  37 
  38 /**
  39  * This utility class provides {@code static} methods
  40  * to find a public method with specified name and parameter types
  41  * in specified class.
  42  *
  43  * @since 1.7
  44  *
  45  * @author Sergey A. Malenkov
  46  */
  47 public final class MethodFinder extends AbstractFinder<Method> {
  48     private static final WeakCache<Signature, Method> CACHE = new WeakCache<Signature, Method>();
  49 
  50     /**
  51      * Finds public method (static or non-static)
  52      * that is accessible from public class.
  53      *
  54      * @param type  the class that can have method
  55      * @param name  the name of method to find
  56      * @param args  parameter types that is used to find method
  57      * @return object that represents found method
  58      * @throws NoSuchMethodException if method could not be found
  59      *                               or some methods are found
  60      */
  61     public static Method findMethod(Class<?> type, String name, Class<?>...args) throws NoSuchMethodException {
  62         if (name == null) {
  63             throw new IllegalArgumentException("Method name is not set");
  64         }
  65         PrimitiveWrapperMap.replacePrimitivesWithWrappers(args);
  66         Signature signature = new Signature(type, name, args);
  67 
  68         Method method = CACHE.get(signature);
  69         if (method != null) {
  70             return method;
  71         }
  72         method = findAccessibleMethod(new MethodFinder(name, args).find(type.getMethods()));
  73         CACHE.put(signature, method);
  74         return method;
  75     }
  76 
  77     /**
  78      * Finds public non-static method
  79      * that is accessible from public class.
  80      *
  81      * @param type  the class that can have method
  82      * @param name  the name of method to find
  83      * @param args  parameter types that is used to find method
  84      * @return object that represents found method
  85      * @throws NoSuchMethodException if method could not be found
  86      *                               or some methods are found
  87      */
  88     public static Method findInstanceMethod(Class<?> type, String name, Class<?>... args) throws NoSuchMethodException {
  89         Method method = findMethod(type, name, args);
  90         if (Modifier.isStatic(method.getModifiers())) {
  91             throw new NoSuchMethodException("Method '" + name + "' is static");
  92         }
  93         return method;
  94     }
  95 
  96     /**
  97      * Finds public static method
  98      * that is accessible from public class.
  99      *
 100      * @param type  the class that can have method
 101      * @param name  the name of method to find
 102      * @param args  parameter types that is used to find method
 103      * @return object that represents found method
 104      * @throws NoSuchMethodException if method could not be found
 105      *                               or some methods are found
 106      */
 107     public static Method findStaticMethod(Class<?> type, String name, Class<?>...args) throws NoSuchMethodException {
 108         Method method = findMethod(type, name, args);
 109         if (!Modifier.isStatic(method.getModifiers())) {
 110             throw new NoSuchMethodException("Method '" + name + "' is not static");
 111         }
 112         return method;
 113     }
 114 
 115     /**
 116      * Finds method that is accessible from public class or interface through class hierarchy.
 117      *
 118      * @param method  object that represents found method
 119      * @return object that represents accessible method
 120      * @throws NoSuchMethodException if method is not accessible or is not found
 121      *                               in specified superclass or interface
 122      */
 123     public static Method findAccessibleMethod(Method method) throws NoSuchMethodException {
 124         Class<?> type = method.getDeclaringClass();
 125         if (Modifier.isPublic(type.getModifiers()) && isPackageAccessible(type)) {
 126             return method;
 127         }
 128         if (Modifier.isStatic(method.getModifiers())) {
 129             throw new NoSuchMethodException("Method '" + method.getName() + "' is not accessible");
 130         }
 131         for (Type generic : type.getGenericInterfaces()) {
 132             try {
 133                 return findAccessibleMethod(method, generic);
 134             }
 135             catch (NoSuchMethodException exception) {
 136                 // try to find in superclass or another interface
 137             }
 138         }
 139         return findAccessibleMethod(method, type.getGenericSuperclass());
 140     }
 141 
 142     /**
 143      * Finds method that accessible from specified class.
 144      *
 145      * @param method  object that represents found method
 146      * @param generic generic type that is used to find accessible method
 147      * @return object that represents accessible method
 148      * @throws NoSuchMethodException if method is not accessible or is not found
 149      *                               in specified superclass or interface
 150      */
 151     private static Method findAccessibleMethod(Method method, Type generic) throws NoSuchMethodException {
 152         String name = method.getName();
 153         Class<?>[] params = method.getParameterTypes();
 154         if (generic instanceof Class) {
 155             Class<?> type = (Class<?>) generic;
 156             return findAccessibleMethod(type.getMethod(name, params));
 157         }
 158         if (generic instanceof ParameterizedType) {
 159             ParameterizedType pt = (ParameterizedType) generic;
 160             Class<?> type = (Class<?>) pt.getRawType();
 161             for (Method m : type.getMethods()) {
 162                 if (m.getName().equals(name)) {
 163                     Class<?>[] pts = m.getParameterTypes();
 164                     if (pts.length == params.length) {
 165                         if (Arrays.equals(params, pts)) {
 166                             return findAccessibleMethod(m);
 167                         }
 168                         Type[] gpts = m.getGenericParameterTypes();
 169                         if (params.length == gpts.length) {
 170                             if (Arrays.equals(params, TypeResolver.erase(TypeResolver.resolve(pt, gpts)))) {
 171                                 return findAccessibleMethod(m);
 172                             }
 173                         }
 174                     }
 175                 }
 176             }
 177         }
 178         throw new NoSuchMethodException("Method '" + name + "' is not accessible");
 179     }
 180 
 181 
 182     private final String name;
 183 
 184     /**
 185      * Creates method finder with specified array of parameter types.
 186      *
 187      * @param name  the name of method to find
 188      * @param args  the array of parameter types
 189      */
 190     private MethodFinder(String name, Class<?>[] args) {
 191         super(args);
 192         this.name = name;
 193     }
 194 
 195     /**
 196      * Returns an array of {@code Class} objects
 197      * that represent the formal parameter types of the method.
 198      * Returns an empty array if the method takes no parameters.
 199      *
 200      * @param method  the object that represents method
 201      * @return the parameter types of the method
 202      */
 203     @Override
 204     protected Class<?>[] getParameters(Method method) {
 205         return method.getParameterTypes();
 206     }
 207 
 208     /**
 209      * Returns {@code true} if and only if the method
 210      * was declared to take a variable number of arguments.
 211      *
 212      * @param method  the object that represents method
 213      * @return {@code true} if the method was declared
 214      *         to take a variable number of arguments;
 215      *         {@code false} otherwise
 216      */
 217     @Override
 218     protected boolean isVarArgs(Method method) {
 219         return method.isVarArgs();
 220     }
 221 
 222     /**
 223      * Checks validness of the method.
 224      * The valid method should be public and
 225      * should have the specified name.
 226      *
 227      * @param method  the object that represents method
 228      * @return {@code true} if the method is valid,
 229      *         {@code false} otherwise
 230      */
 231     @Override
 232     protected boolean isValid(Method method) {
 233         return !method.isBridge() && Modifier.isPublic(method.getModifiers()) && method.getName().equals(this.name);
 234     }
 235 }