    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}