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 */
25package com.sun.beans.finder;
26
27import java.lang.reflect.Executable;
28import java.lang.reflect.Modifier;
29
30import java.util.HashMap;
31import 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 */
43abstract 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}
208