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