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 */
017
018package org.apache.commons.jxpath.util;
019
020import java.lang.reflect.Constructor;
021import java.lang.reflect.Method;
022import java.lang.reflect.Modifier;
023import java.util.Arrays;
024
025import org.apache.commons.jxpath.ExpressionContext;
026import org.apache.commons.jxpath.JXPathException;
027
028/**
029 * Method lookup utilities, which find static and non-static methods as well as constructors based on a name and list of parameters.
030 */
031public class MethodLookupUtils {
032
033    private static final int NO_MATCH = 0;
034    private static final int APPROXIMATE_MATCH = 1;
035    private static final int EXACT_MATCH = 2;
036
037    /**
038     * Look up a constructor.
039     *
040     * @param targetClass the class constructed
041     * @param parameters  arguments
042     * @return Constructor found if any.
043     */
044    public static Constructor lookupConstructor(final Class targetClass, final Object[] parameters) {
045        boolean tryExact = true;
046        final int count = parameters == null ? 0 : parameters.length;
047        final Class[] types = new Class[count];
048        for (int i = 0; i < count; i++) {
049            final Object param = parameters[i];
050            if (param != null) {
051                types[i] = param.getClass();
052            } else {
053                types[i] = null;
054                tryExact = false;
055            }
056        }
057        Constructor constructor = null;
058        if (tryExact) {
059            // First - without type conversion
060            try {
061                constructor = targetClass.getConstructor(types);
062                if (constructor != null) {
063                    return constructor;
064                }
065            } catch (final NoSuchMethodException ignore) { // NOPMD
066                // Ignore
067            }
068        }
069        int currentMatch = 0;
070        boolean ambiguous = false;
071        // Then - with type conversion
072        final Constructor[] constructors = targetClass.getConstructors();
073        for (final Constructor constructor2 : constructors) {
074            final int match = matchParameterTypes(constructor2.getParameterTypes(), parameters);
075            if (match != NO_MATCH) {
076                if (match > currentMatch) {
077                    constructor = constructor2;
078                    currentMatch = match;
079                    ambiguous = false;
080                } else if (match == currentMatch) {
081                    ambiguous = true;
082                }
083            }
084        }
085        if (ambiguous) {
086            throw new JXPathException("Ambiguous constructor " + Arrays.asList(parameters));
087        }
088        return constructor;
089    }
090
091    /**
092     * Look up a method.
093     *
094     * @param targetClass owning class
095     * @param name        method name
096     * @param parameters  method parameters
097     * @return Method found if any
098     */
099    public static Method lookupMethod(Class targetClass, final String name, final Object[] parameters) {
100        if (parameters == null || parameters.length < 1 || parameters[0] == null) {
101            return null;
102        }
103        if (matchType(targetClass, parameters[0]) == NO_MATCH) {
104            return null;
105        }
106        targetClass = TypeUtils.convert(parameters[0], targetClass).getClass();
107        boolean tryExact = true;
108        final int count = parameters.length - 1;
109        final Class[] types = new Class[count];
110        final Object[] arguments = new Object[count];
111        for (int i = 0; i < count; i++) {
112            final Object param = parameters[i + 1];
113            arguments[i] = param;
114            if (param != null) {
115                types[i] = param.getClass();
116            } else {
117                types[i] = null;
118                tryExact = false;
119            }
120        }
121        Method method = null;
122        if (tryExact) {
123            // First - without type conversion
124            try {
125                method = targetClass.getMethod(name, types);
126                if (method != null && !Modifier.isStatic(method.getModifiers())) {
127                    return method;
128                }
129            } catch (final NoSuchMethodException ignore) { // NOPMD
130                // Ignore
131            }
132        }
133        int currentMatch = 0;
134        boolean ambiguous = false;
135        // Then - with type conversion
136        final Method[] methods = targetClass.getMethods();
137        for (final Method method2 : methods) {
138            if (!Modifier.isStatic(method2.getModifiers()) && method2.getName().equals(name)) {
139                final int match = matchParameterTypes(method2.getParameterTypes(), arguments);
140                if (match != NO_MATCH) {
141                    if (match > currentMatch) {
142                        method = method2;
143                        currentMatch = match;
144                        ambiguous = false;
145                    } else if (match == currentMatch) {
146                        ambiguous = true;
147                    }
148                }
149            }
150        }
151        if (ambiguous) {
152            throw new JXPathException("Ambiguous method call: " + name);
153        }
154        return method;
155    }
156
157    /**
158     * Look up a static method.
159     *
160     * @param targetClass the owning class
161     * @param name        method name
162     * @param parameters  method parameters
163     * @return Method found if any
164     */
165    public static Method lookupStaticMethod(final Class targetClass, final String name, final Object[] parameters) {
166        boolean tryExact = true;
167        final int count = parameters == null ? 0 : parameters.length;
168        final Class[] types = new Class[count];
169        for (int i = 0; i < count; i++) {
170            final Object param = parameters[i];
171            if (param != null) {
172                types[i] = param.getClass();
173            } else {
174                types[i] = null;
175                tryExact = false;
176            }
177        }
178        Method method = null;
179        if (tryExact) {
180            // First - without type conversion
181            try {
182                method = targetClass.getMethod(name, types);
183                if (method != null && Modifier.isStatic(method.getModifiers())) {
184                    return method;
185                }
186            } catch (final NoSuchMethodException ignore) { // NOPMD
187                // Ignore
188            }
189        }
190        int currentMatch = 0;
191        boolean ambiguous = false;
192        // Then - with type conversion
193        final Method[] methods = targetClass.getMethods();
194        for (final Method method2 : methods) {
195            if (Modifier.isStatic(method2.getModifiers()) && method2.getName().equals(name)) {
196                final int match = matchParameterTypes(method2.getParameterTypes(), parameters);
197                if (match != NO_MATCH) {
198                    if (match > currentMatch) {
199                        method = method2;
200                        currentMatch = match;
201                        ambiguous = false;
202                    } else if (match == currentMatch) {
203                        ambiguous = true;
204                    }
205                }
206            }
207        }
208        if (ambiguous) {
209            throw new JXPathException("Ambiguous method call: " + name);
210        }
211        return method;
212    }
213
214    /**
215     * Return a match code of objects to types.
216     *
217     * @param types      Class[] of expected types
218     * @param parameters Object[] to attempt to match
219     * @return int code
220     */
221    private static int matchParameterTypes(final Class[] types, final Object[] parameters) {
222        int pi = 0;
223        if (types.length >= 1 && ExpressionContext.class.isAssignableFrom(types[0])) {
224            pi++;
225        }
226        final int length = parameters == null ? 0 : parameters.length;
227        if (types.length != length + pi) {
228            return NO_MATCH;
229        }
230        int totalMatch = EXACT_MATCH;
231        for (int i = 0; i < length; i++) {
232            final int match = matchType(types[i + pi], parameters[i]);
233            if (match == NO_MATCH) {
234                return NO_MATCH;
235            }
236            if (match < totalMatch) {
237                totalMatch = match;
238            }
239        }
240        return totalMatch;
241    }
242
243    /**
244     * Return a match code between an object and type.
245     *
246     * @param expected class to test
247     * @param object   object to test
248     * @return int code
249     */
250    private static int matchType(final Class expected, final Object object) {
251        if (object == null) {
252            return APPROXIMATE_MATCH;
253        }
254        final Class actual = object.getClass();
255        if (expected.equals(actual)) {
256            return EXACT_MATCH;
257        }
258        if (expected.isAssignableFrom(actual)) {
259            return EXACT_MATCH;
260        }
261        if (TypeUtils.canConvert(object, expected)) {
262            return APPROXIMATE_MATCH;
263        }
264        return NO_MATCH;
265    }
266
267    /**
268     * Constructs a new instance.
269     *
270     * @deprecated Will be private in the next major version.
271     */
272    @Deprecated
273    public MethodLookupUtils() {
274        // empty
275    }
276}