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