    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    package org.apache.commons.jxpath.util;
    018
    019    import java.lang.reflect.Constructor;
    020    import java.lang.reflect.Method;
    021    import java.lang.reflect.Modifier;
    022    import java.util.Arrays;
    023
    024    import org.apache.commons.jxpath.ExpressionContext;
    025    import 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: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
    033     */
    034    public 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                    "Ambigous 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("Ambigous 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("Ambigous 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    }