/* |
|
* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package com.sun.beans.finder; |
|
import java.lang.reflect.Executable; |
|
import java.lang.reflect.Modifier; |
|
import java.util.HashMap; |
|
import java.util.Map; |
|
/** |
|
* This abstract class provides functionality |
|
* to find a public method or constructor |
|
* with specified parameter types. |
|
* It supports a variable number of parameters. |
|
* |
|
* @since 1.7 |
|
* |
|
* @author Sergey A. Malenkov |
|
*/ |
|
abstract class AbstractFinder<T extends Executable> { |
|
private final Class<?>[] args; |
|
/** |
|
* Creates finder for array of classes of arguments. |
|
* If a particular element of array equals {@code null}, |
|
* than the appropriate pair of classes |
|
* does not take into consideration. |
|
* |
|
* @param args array of classes of arguments |
|
*/ |
|
protected AbstractFinder(Class<?>[] args) { |
|
this.args = args; |
|
} |
|
/** |
|
* Checks validness of the method. |
|
* At least the valid method should be public. |
|
* |
|
* @param method the object that represents method |
|
* @return {@code true} if the method is valid, |
|
* {@code false} otherwise |
|
*/ |
|
protected boolean isValid(T method) { |
|
return Modifier.isPublic(method.getModifiers()); |
|
} |
|
/** |
|
* Performs a search in the {@code methods} array. |
|
* The one method is selected from the array of the valid methods. |
|
* The list of parameters of the selected method shows |
|
* the best correlation with the list of arguments |
|
* specified at class initialization. |
|
* If more than one method is both accessible and applicable |
|
* to a method invocation, it is necessary to choose one |
|
* to provide the descriptor for the run-time method dispatch. |
|
* The most specific method should be chosen. |
|
* |
|
* @param methods the array of methods to search within |
|
* @return the object that represents found method |
|
* @throws NoSuchMethodException if no method was found or several |
|
* methods meet the search criteria |
|
* @see #isAssignable |
|
*/ |
|
final T find(T[] methods) throws NoSuchMethodException { |
|
Map<T, Class<?>[]> map = new HashMap<T, Class<?>[]>(); |
|
T oldMethod = null; |
|
Class<?>[] oldParams = null; |
|
boolean ambiguous = false; |
|
for (T newMethod : methods) { |
|
if (isValid(newMethod)) { |
|
Class<?>[] newParams = newMethod.getParameterTypes(); |
|
if (newParams.length == this.args.length) { |
|
PrimitiveWrapperMap.replacePrimitivesWithWrappers(newParams); |
|
if (isAssignable(newParams, this.args)) { |
|
if (oldMethod == null) { |
|
oldMethod = newMethod; |
|
oldParams = newParams; |
|
} else { |
|
boolean useNew = isAssignable(oldParams, newParams); |
|
boolean useOld = isAssignable(newParams, oldParams); |
|
if (useOld && useNew) { |
|
// only if parameters are equal |
|
useNew = !newMethod.isSynthetic(); |
|
useOld = !oldMethod.isSynthetic(); |
|
} |
|
if (useOld == useNew) { |
|
ambiguous = true; |
|
} else if (useNew) { |
|
oldMethod = newMethod; |
|
oldParams = newParams; |
|
ambiguous = false; |
|
} |
|
} |
|
} |
|
} |
|
if (newMethod.isVarArgs()) { |
|
int length = newParams.length - 1; |
|
if (length <= this.args.length) { |
|
Class<?>[] array = new Class<?>[this.args.length]; |
|
System.arraycopy(newParams, 0, array, 0, length); |
|
if (length < this.args.length) { |
|
Class<?> type = newParams[length].getComponentType(); |
|
if (type.isPrimitive()) { |
|
type = PrimitiveWrapperMap.getType(type.getName()); |
|
} |
|
for (int i = length; i < this.args.length; i++) { |
|
array[i] = type; |
|
} |
|
} |
|
map.put(newMethod, array); |
|
} |
|
} |
|
} |
|
} |
|
for (T newMethod : methods) { |
|
Class<?>[] newParams = map.get(newMethod); |
|
if (newParams != null) { |
|
if (isAssignable(newParams, this.args)) { |
|
if (oldMethod == null) { |
|
oldMethod = newMethod; |
|
oldParams = newParams; |
|
} else { |
|
boolean useNew = isAssignable(oldParams, newParams); |
|
boolean useOld = isAssignable(newParams, oldParams); |
|
if (useOld && useNew) { |
|
// only if parameters are equal |
|
useNew = !newMethod.isSynthetic(); |
|
useOld = !oldMethod.isSynthetic(); |
|
} |
|
if (useOld == useNew) { |
|
if (oldParams == map.get(oldMethod)) { |
|
ambiguous = true; |
|
} |
|
} else if (useNew) { |
|
oldMethod = newMethod; |
|
oldParams = newParams; |
|
ambiguous = false; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if (ambiguous) { |
|
throw new NoSuchMethodException("Ambiguous methods are found"); |
|
} |
|
if (oldMethod == null) { |
|
throw new NoSuchMethodException("Method is not found"); |
|
} |
|
return oldMethod; |
|
} |
|
/** |
|
* Determines if every class in {@code min} array is either the same as, |
|
* or is a superclass of, the corresponding class in {@code max} array. |
|
* The length of every array must equal the number of arguments. |
|
* This comparison is performed in the {@link #find} method |
|
* before the first call of the isAssignable method. |
|
* If an argument equals {@code null} |
|
* the appropriate pair of classes does not take into consideration. |
|
* |
|
* @param min the array of classes to be checked |
|
* @param max the array of classes that is used to check |
|
* @return {@code true} if all classes in {@code min} array |
|
* are assignable from corresponding classes in {@code max} array, |
|
* {@code false} otherwise |
|
* |
|
* @see Class#isAssignableFrom |
|
*/ |
|
private boolean isAssignable(Class<?>[] min, Class<?>[] max) { |
|
for (int i = 0; i < this.args.length; i++) { |
|
if (null != this.args[i]) { |
|
if (!min[i].isAssignableFrom(max[i])) { |
|
return false; |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
} |