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 java.lang.reflect.Executable;
  28 import java.lang.reflect.Modifier;
  29 
  30 import java.util.HashMap;
  31 import java.util.Map;
  32 
  33 /**
  34  * This abstract class provides functionality
  35  * to find a public method or constructor
  36  * with specified parameter types.
  37  * It supports a variable number of parameters.
  38  *
  39  * @since 1.7
  40  *
  41  * @author Sergey A. Malenkov
  42  */
  43 abstract class AbstractFinder<T extends Executable> {
  44     private final Class<?>[] args;
  45 
  46     /**
  47      * Creates finder for array of classes of arguments.
  48      * If a particular element of array equals {@code null},
  49      * than the appropriate pair of classes
  50      * does not take into consideration.
  51      *
  52      * @param args  array of classes of arguments
  53      */
  54     protected AbstractFinder(Class<?>[] args) {
  55         this.args = args;
  56     }
  57 
  58     /**
  59      * Checks validness of the method.
  60      * At least the valid method should be public.
  61      *
  62      * @param method  the object that represents method
  63      * @return {@code true} if the method is valid,
  64      *         {@code false} otherwise
  65      */
  66     protected boolean isValid(T method) {
  67         return Modifier.isPublic(method.getModifiers());
  68     }
  69 
  70     /**
  71      * Performs a search in the {@code methods} array.
  72      * The one method is selected from the array of the valid methods.
  73      * The list of parameters of the selected method shows
  74      * the best correlation with the list of arguments
  75      * specified at class initialization.
  76      * If more than one method is both accessible and applicable
  77      * to a method invocation, it is necessary to choose one
  78      * to provide the descriptor for the run-time method dispatch.
  79      * The most specific method should be chosen.
  80      *
  81      * @param methods  the array of methods to search within
  82      * @return the object that represents found method
  83      * @throws NoSuchMethodException if no method was found or several
  84      *                               methods meet the search criteria
  85      * @see #isAssignable
  86      */
  87     final T find(T[] methods) throws NoSuchMethodException {
  88         Map<T, Class<?>[]> map = new HashMap<T, Class<?>[]>();
  89 
  90         T oldMethod = null;
  91         Class<?>[] oldParams = null;
  92         boolean ambiguous = false;
  93 
  94         for (T newMethod : methods) {
  95             if (isValid(newMethod)) {
  96                 Class<?>[] newParams = newMethod.getParameterTypes();
  97                 if (newParams.length == this.args.length) {
  98                     PrimitiveWrapperMap.replacePrimitivesWithWrappers(newParams);
  99                     if (isAssignable(newParams, this.args)) {
 100                         if (oldMethod == null) {
 101                             oldMethod = newMethod;
 102                             oldParams = newParams;
 103                         } else {
 104                             boolean useNew = isAssignable(oldParams, newParams);
 105                             boolean useOld = isAssignable(newParams, oldParams);
 106 
 107                             if (useOld && useNew) {
 108                                 // only if parameters are equal
 109                                 useNew = !newMethod.isSynthetic();
 110                                 useOld = !oldMethod.isSynthetic();
 111                             }
 112                             if (useOld == useNew) {
 113                                 ambiguous = true;
 114                             } else if (useNew) {
 115                                 oldMethod = newMethod;
 116                                 oldParams = newParams;
 117                                 ambiguous = false;
 118                             }
 119                         }
 120                     }
 121                 }
 122                 if (newMethod.isVarArgs()) {
 123                     int length = newParams.length - 1;
 124                     if (length <= this.args.length) {
 125                         Class<?>[] array = new Class<?>[this.args.length];
 126                         System.arraycopy(newParams, 0, array, 0, length);
 127                         if (length < this.args.length) {
 128                             Class<?> type = newParams[length].getComponentType();
 129                             if (type.isPrimitive()) {
 130                                 type = PrimitiveWrapperMap.getType(type.getName());
 131                             }
 132                             for (int i = length; i < this.args.length; i++) {
 133                                 array[i] = type;
 134                             }
 135                         }
 136                         map.put(newMethod, array);
 137                     }
 138                 }
 139             }
 140         }
 141         for (T newMethod : methods) {
 142             Class<?>[] newParams = map.get(newMethod);
 143             if (newParams != null) {
 144                 if (isAssignable(newParams, this.args)) {
 145                     if (oldMethod == null) {
 146                         oldMethod = newMethod;
 147                         oldParams = newParams;
 148                     } else {
 149                         boolean useNew = isAssignable(oldParams, newParams);
 150                         boolean useOld = isAssignable(newParams, oldParams);
 151 
 152                         if (useOld && useNew) {
 153                             // only if parameters are equal
 154                             useNew = !newMethod.isSynthetic();
 155                             useOld = !oldMethod.isSynthetic();
 156                         }
 157                         if (useOld == useNew) {
 158                             if (oldParams == map.get(oldMethod)) {
 159                                 ambiguous = true;
 160                             }
 161                         } else if (useNew) {
 162                             oldMethod = newMethod;
 163                             oldParams = newParams;
 164                             ambiguous = false;
 165                         }
 166                     }
 167                 }
 168             }
 169         }
 170 
 171         if (ambiguous) {
 172             throw new NoSuchMethodException("Ambiguous methods are found");
 173         }
 174         if (oldMethod == null) {
 175             throw new NoSuchMethodException("Method is not found");
 176         }
 177         return oldMethod;
 178     }
 179 
 180     /**
 181      * Determines if every class in {@code min} array is either the same as,
 182      * or is a superclass of, the corresponding class in {@code max} array.
 183      * The length of every array must equal the number of arguments.
 184      * This comparison is performed in the {@link #find} method
 185      * before the first call of the isAssignable method.
 186      * If an argument equals {@code null}
 187      * the appropriate pair of classes does not take into consideration.
 188      *
 189      * @param min  the array of classes to be checked
 190      * @param max  the array of classes that is used to check
 191      * @return {@code true} if all classes in {@code min} array
 192      *         are assignable from corresponding classes in {@code max} array,
 193      *         {@code false} otherwise
 194      *
 195      * @see Class#isAssignableFrom
 196      */
 197     private boolean isAssignable(Class<?>[] min, Class<?>[] max) {
 198         for (int i = 0; i < this.args.length; i++) {
 199             if (null != this.args[i]) {
 200                 if (!min[i].isAssignableFrom(max[i])) {
 201                     return false;
 202                 }
 203             }
 204         }
 205         return true;
 206     }
 207 }