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    }