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 }