001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jxpath.util;
018
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.util.Arrays;
023
024import org.apache.commons.jxpath.ExpressionContext;
025import org.apache.commons.jxpath.JXPathException;
026
027/**
028 * Method lookup utilities, which find static and non-static methods as well
029 * as constructors based on a name and list of parameters.
030 *
031 * @author Dmitri Plotnikov
032 * @version $Revision: 917195 $ $Date: 2010-02-28 17:21:14 +0100 (So, 28 Feb 2010) $
033 */
034public class MethodLookupUtils {
035
036    private static final int NO_MATCH = 0;
037    private static final int APPROXIMATE_MATCH = 1;
038    private static final int EXACT_MATCH = 2;
039
040    /**
041     * Look up a constructor.
042     * @param targetClass the class constructed
043     * @param parameters arguments
044     * @return Constructor found if any.
045     */
046    public static Constructor lookupConstructor(
047        Class targetClass,
048        Object[] parameters) {
049        boolean tryExact = true;
050        int count = parameters == null ? 0 : parameters.length;
051        Class[] types = new Class[count];
052        for (int i = 0; i < count; i++) {
053            Object param = parameters[i];
054            if (param != null) {
055                types[i] = param.getClass();
056            }
057            else {
058                types[i] = null;
059                tryExact = false;
060            }
061        }
062
063        Constructor constructor = null;
064
065        if (tryExact) {
066            // First - without type conversion
067            try {
068                constructor = targetClass.getConstructor(types);
069                if (constructor != null) {
070                    return constructor;
071                }
072            }
073            catch (NoSuchMethodException ex) { //NOPMD
074                // Ignore
075            }
076        }
077
078        int currentMatch = 0;
079        boolean ambiguous = false;
080
081        // Then - with type conversion
082        Constructor[] constructors = targetClass.getConstructors();
083        for (int i = 0; i < constructors.length; i++) {
084            int match =
085                matchParameterTypes(
086                    constructors[i].getParameterTypes(),
087                    parameters);
088            if (match != NO_MATCH) {
089                if (match > currentMatch) {
090                    constructor = constructors[i];
091                    currentMatch = match;
092                    ambiguous = false;
093                }
094                else if (match == currentMatch) {
095                    ambiguous = true;
096                }
097            }
098        }
099        if (ambiguous) {
100            throw new JXPathException(
101                "Ambiguous constructor " + Arrays.asList(parameters));
102        }
103        return constructor;
104    }
105
106    /**
107     * Look up a static method.
108     * @param targetClass the owning class
109     * @param name method name
110     * @param parameters method parameters
111     * @return Method found if any
112     */
113    public static Method lookupStaticMethod(
114        Class targetClass,
115        String name,
116        Object[] parameters) {
117        boolean tryExact = true;
118        int count = parameters == null ? 0 : parameters.length;
119        Class[] types = new Class[count];
120        for (int i = 0; i < count; i++) {
121            Object param = parameters[i];
122            if (param != null) {
123                types[i] = param.getClass();
124            }
125            else {
126                types[i] = null;
127                tryExact = false;
128            }
129        }
130
131        Method method = null;
132
133        if (tryExact) {
134            // First - without type conversion
135            try {
136                method = targetClass.getMethod(name, types);
137                if (method != null
138                    && Modifier.isStatic(method.getModifiers())) {
139                    return method;
140                }
141            }
142            catch (NoSuchMethodException ex) { //NOPMD
143                // Ignore
144            }
145        }
146
147        int currentMatch = 0;
148        boolean ambiguous = false;
149
150        // Then - with type conversion
151        Method[] methods = targetClass.getMethods();
152        for (int i = 0; i < methods.length; i++) {
153            if (Modifier.isStatic(methods[i].getModifiers())
154                && methods[i].getName().equals(name)) {
155                int match =
156                    matchParameterTypes(
157                        methods[i].getParameterTypes(),
158                        parameters);
159                if (match != NO_MATCH) {
160                    if (match > currentMatch) {
161                        method = methods[i];
162                        currentMatch = match;
163                        ambiguous = false;
164                    }
165                    else if (match == currentMatch) {
166                        ambiguous = true;
167                    }
168                }
169            }
170        }
171        if (ambiguous) {
172            throw new JXPathException("Ambiguous method call: " + name);
173        }
174        return method;
175    }
176
177    /**
178     * Look up a method.
179     * @param targetClass owning class
180     * @param name method name
181     * @param parameters method parameters
182     * @return Method found if any
183     */
184    public static Method lookupMethod(
185        Class targetClass,
186        String name,
187        Object[] parameters) {
188        if (parameters == null
189            || parameters.length < 1
190            || parameters[0] == null) {
191            return null;
192        }
193
194        if (matchType(targetClass, parameters[0]) == NO_MATCH) {
195            return null;
196        }
197
198        targetClass = TypeUtils.convert(parameters[0], targetClass).getClass();
199
200        boolean tryExact = true;
201        int count = parameters.length - 1;
202        Class[] types = new Class[count];
203        Object[] arguments = new Object[count];
204        for (int i = 0; i < count; i++) {
205            Object param = parameters[i + 1];
206            arguments[i] = param;
207            if (param != null) {
208                types[i] = param.getClass();
209            }
210            else {
211                types[i] = null;
212                tryExact = false;
213            }
214        }
215
216        Method method = null;
217
218        if (tryExact) {
219            // First - without type conversion
220            try {
221                method = targetClass.getMethod(name, types);
222                if (method != null
223                    && !Modifier.isStatic(method.getModifiers())) {
224                    return method;
225                }
226            }
227            catch (NoSuchMethodException ex) { //NOPMD
228                // Ignore
229            }
230        }
231
232        int currentMatch = 0;
233        boolean ambiguous = false;
234
235        // Then - with type conversion
236        Method[] methods = targetClass.getMethods();
237        for (int i = 0; i < methods.length; i++) {
238            if (!Modifier.isStatic(methods[i].getModifiers())
239                && methods[i].getName().equals(name)) {
240                int match =
241                    matchParameterTypes(
242                        methods[i].getParameterTypes(),
243                        arguments);
244                if (match != NO_MATCH) {
245                    if (match > currentMatch) {
246                        method = methods[i];
247                        currentMatch = match;
248                        ambiguous = false;
249                    }
250                    else if (match == currentMatch) {
251                        ambiguous = true;
252                    }
253                }
254            }
255        }
256        if (ambiguous) {
257            throw new JXPathException("Ambiguous method call: " + name);
258        }
259        return method;
260    }
261
262    /**
263     * Return a match code of objects to types.
264     * @param types Class[] of expected types
265     * @param parameters Object[] to attempt to match
266     * @return int code
267     */
268    private static int matchParameterTypes(
269        Class[] types,
270        Object[] parameters) {
271        int pi = 0;
272        if (types.length >= 1
273            && ExpressionContext.class.isAssignableFrom(types[0])) {
274            pi++;
275        }
276        int length = parameters == null ? 0 : parameters.length;
277        if (types.length != length + pi) {
278            return NO_MATCH;
279        }
280        int totalMatch = EXACT_MATCH;
281        for (int i = 0; i < length; i++) {
282            int match = matchType(types[i + pi], parameters[i]);
283            if (match == NO_MATCH) {
284                return NO_MATCH;
285            }
286            if (match < totalMatch) {
287                totalMatch = match;
288            }
289        }
290        return totalMatch;
291    }
292
293    /**
294     * Return a match code between an object and type.
295     * @param expected class to test
296     * @param object object to test
297     * @return int code
298     */
299    private static int matchType(Class expected, Object object) {
300        if (object == null) {
301            return APPROXIMATE_MATCH;
302        }
303
304        Class actual = object.getClass();
305
306        if (expected.equals(actual)) {
307            return EXACT_MATCH;
308        }
309        if (expected.isAssignableFrom(actual)) {
310            return EXACT_MATCH;
311        }
312
313        if (TypeUtils.canConvert(object, expected)) {
314            return APPROXIMATE_MATCH;
315        }
316
317        return NO_MATCH;
318    }
319}