1 /*
2 * Copyright 2004,2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.bsf.util;
18
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.util.Enumeration;
23 import java.util.Vector;
24
25 /**
26 * This file is a collection of reflection utilities for dealing with
27 * methods and constructors.
28 *
29 * @author Sanjiva Weerawarana
30 * @author Joseph Kesselman
31 */
32 public class MethodUtils {
33
34 /** Internal Class for getEntryPoint(). Implements 15.11.2.2 MORE
35 SPECIFIC rules.
36
37 Retains a list of methods (already known to match the
38 arguments). As each method is added, we check against past entries
39 to determine which if any is "more specific" -- defined as having
40 _all_ its arguments (not just a preponderance) be
41 method-convertable into those of another. If such a relationship
42 is found, the more-specific method is retained and the
43 less-specific method is discarded. At the end, if this has yielded
44 a single winner it is considered the Most Specific Method and
45 hence the one that should be invoked. Otherwise, a
46 NoSuchMethodException is thrown.
47
48 PERFORMANCE VERSUS ARCHITECTURE: Arguably, this should "have-a"
49 Vector. But the code is 6% smaller, and possibly faster, if we
50 code it as "is-a" Vector. Since it's an inner class, nobody's
51 likely to abuse the privilage.
52
53 Note: "Static" in the case of an inner class means "Does not
54 reference instance data in the outer class", and is required since
55 our caller is a static method. */
56 private static class MoreSpecific
57 extends Vector
58 {
59 /** Submit an entry-point to the list. May be discarded if a past
60 entry is more specific, or may cause others to be discarded it
61 if is more specific.
62
63 newEntry: Method or Constructor under consideration.
64 */
65 void addItem (Object newEntry)
66 {
67 if(size()==0)
68 addElement(newEntry);
69 else
70 {
71 Class[] newargs=entryGetParameterTypes(newEntry);
72 boolean keep=true;
73 for (Enumeration e = elements();
74 keep & e.hasMoreElements() ;
75 )
76 {
77 Object oldEntry=e.nextElement();
78 // CAVEAT: Implicit references to enclosing class!
79 Class[] oldargs=entryGetParameterTypes(oldEntry);
80 if(areMethodConvertable(oldargs,newargs))
81 removeElement(oldEntry); // New more specific; discard old
82 else if(areMethodConvertable(newargs,oldargs))
83 keep=false; // Old more specific; discard new
84 // Else they're tied. Keep both and hope someone beats both.
85 }
86 if(keep)
87 addElement(newEntry);
88 }
89 }
90
91 /** Obtain the single Most Specific entry-point. If there is no clear
92 winner, or if the list is empty, throw NoSuchMethodException.
93
94 Arguments describe the call we were hoping to resolve. They are
95 used to throw a nice verbose exception if something goes wrong.
96 */
97 Object getMostSpecific(Class targetClass,String methodName,
98 Class[] argTypes,boolean isStaticReference)
99 throws NoSuchMethodException
100 {
101 if(size()==1)
102 return firstElement();
103 if(size()>1)
104 {
105 StringBuffer buf=new StringBuffer();
106 Enumeration e=elements();
107 buf.append(e.nextElement());
108 while(e.hasMoreElements())
109 buf.append(" and ").append(e.nextElement());
110 throw new NoSuchMethodException (callToString(targetClass,
111 methodName,
112 argTypes,
113 isStaticReference)+
114 " is ambiguous. It matches "+
115 buf.toString());
116 }
117 return null;
118 }
119 }
120
121 /** Convenience method: Test an entire parameter-list/argument-list pair
122 for isMethodConvertable(), qv.
123 */
124 static private boolean areMethodConvertable(Class[] parms,Class[] args)
125 {
126 if(parms.length!=args.length)
127 return false;
128
129 for(int i=0;i<parms.length;++i)
130 if(!isMethodConvertable(parms[i],args[i]))
131 return false;
132
133 return true;
134 }
135 /** Internal subroutine for getEntryPoint(): Format arguments as a
136 string describing the function being searched for. Used in
137 verbose exceptions. */
138 private static String callToString(Class targetClass,String methodName,
139 Class[] argTypes,boolean isStaticReference)
140 {
141 StringBuffer buf = new StringBuffer();
142 if(isStaticReference)
143 buf.append("static ");
144 buf.append(StringUtils.getClassName(targetClass));
145 if(methodName!=null)
146 buf.append(".").append(methodName);
147 buf.append("(");
148 if (argTypes != null && argTypes.length>0) {
149 if(false)
150 {
151 // ????? Sanjiva has an ArrayToString method. Using it would
152 // save a few bytes, at cost of giving up some reusability.
153 }
154 else
155 {
156 buf.append(StringUtils.getClassName(argTypes[0]));
157 for (int i = 1; i < argTypes.length; i++) {
158 buf.append(",").append(StringUtils.getClassName(argTypes[i]));
159 }
160 }
161 }
162 else
163 buf.append("[none]");
164 buf.append(")");
165 return buf.toString();
166 }
167 /** Utility function: obtain common data from either Method or
168 Constructor. (In lieu of an EntryPoint interface.) */
169 static int entryGetModifiers(Object entry)
170 {
171 return (entry instanceof Method)
172 ? ((Method)entry).getModifiers()
173 : ((Constructor)entry).getModifiers();
174 }
175 // The common lookup code would be much easier if Method and
176 // Constructor shared an "EntryPoint" Interface. Unfortunately, even
177 // though their APIs are almost identical, they don't. These calls
178 // are a workaround... at the cost of additional runtime overhead
179 // and some extra bytecodes.
180 //
181 // (A JDK bug report has been submitted requesting that they add the
182 // Interface; it would be easy, harmless, and useful.)
183
184 /** Utility function: obtain common data from either Method or
185 Constructor. (In lieu of an EntryPoint interface.) */
186 static String entryGetName(Object entry)
187 {
188 return (entry instanceof Method)
189 ? ((Method)entry).getName()
190 : ((Constructor)entry).getName();
191 }
192 /** Utility function: obtain common data from either Method or
193 Constructor. (In lieu of an EntryPoint interface.) */
194 static Class[] entryGetParameterTypes(Object entry)
195 {
196 return (entry instanceof Method)
197 ? ((Method)entry).getParameterTypes()
198 : ((Constructor)entry).getParameterTypes();
199 }
200 /** Utility function: obtain common data from either Method or
201 Constructor. (In lieu of an EntryPoint interface.) */
202 static String entryToString(Object entry)
203 {
204 return (entry instanceof Method)
205 ? ((Method)entry).toString()
206 : ((Constructor)entry).toString();
207 }
208 //////////////////////////////////////////////////////////////////////////
209
210 /** Class.getConstructor() finds only the entry point (if any)
211 _exactly_ matching the specified argument types. Our implmentation
212 can decide between several imperfect matches, using the same
213 search algorithm as the Java compiler.
214
215 Note that all constructors are static by definition, so
216 isStaticReference is true.
217
218 @exception NoSuchMethodException if constructor not found.
219 */
220 static public Constructor getConstructor(Class targetClass, Class[] argTypes)
221 throws SecurityException, NoSuchMethodException
222 {
223 return (Constructor) getEntryPoint(targetClass,null,argTypes,true);
224 }
225 //////////////////////////////////////////////////////////////////////////
226
227 /**
228 * Search for entry point, per Java Language Spec 1.0
229 * as amended, verified by comparison against compiler behavior.
230 *
231 * @param targetClass Class object for the class to be queried.
232 * @param methodName Name of method to invoke, or null for constructor.
233 * Only Public methods will be accepted.
234 * @param argTypes Classes of intended arguments. Note that primitives
235 * must be specified via their TYPE equivalents,
236 * rather than as their wrapper classes -- Integer.TYPE
237 * rather than Integer. "null" may be passed in as an
238 * indication that you intend to invoke the method with
239 * a literal null argument and therefore can accept
240 * any object type in this position.
241 * @param isStaticReference If true, and if the target is a Class object,
242 * only static methods will be accepted as valid matches.
243 *
244 * @return a Method or Constructor of the appropriate signature
245 *
246 * @exception SecurityException if security violation
247 * @exception NoSuchMethodException if no such method
248 */
249 static private Object getEntryPoint(Class targetClass,
250 String methodName,
251 Class[] argTypes,
252 boolean isStaticReference)
253 throws SecurityException, NoSuchMethodException
254 {
255 // 15.11.1: OBTAIN STARTING CLASS FOR SEARCH
256 Object m=null;
257
258 // 15.11.2 DETERMINE ARGUMENT SIGNATURE
259 // (Passed in as argTypes array.)
260
261 // Shortcut: If an exact match exists, return it.
262 try {
263 if(methodName!=null)
264 {
265 m=targetClass.getMethod (methodName, argTypes);
266 if(isStaticReference &&
267 !Modifier.isStatic(entryGetModifiers(m)) )
268 {
269 throw
270 new NoSuchMethodException (callToString (targetClass,
271 methodName,
272 argTypes,
273 isStaticReference)+
274 " resolved to instance " + m);
275 }
276 return m;
277 }
278 else
279 return targetClass.getConstructor (argTypes);
280
281 } catch (NoSuchMethodException e) {
282 // no-args has no alternatives!
283 if(argTypes==null || argTypes.length==0)
284 {
285 throw
286 new NoSuchMethodException (callToString (targetClass,
287 methodName,
288 argTypes,
289 isStaticReference)+
290 " not found.");
291 }
292 // Else fall through.
293 }
294
295 // Well, _that_ didn't work. Time to search for the Most Specific
296 // matching function. NOTE that conflicts are possible!
297
298 // 15.11.2.1 ACCESSIBLE: We apparently need to gather from two
299 // sources to be sure we have both instance and static methods.
300 Object[] methods;
301 if(methodName!=null)
302 {
303 methods=targetClass.getMethods();
304 }
305 else
306 {
307 methods=targetClass.getConstructors();
308 }
309 if(0==methods.length)
310 {
311 throw new NoSuchMethodException("No methods!");
312 }
313
314 MoreSpecific best=new MoreSpecific();
315 for(int i=0;i<methods.length;++i)
316 {
317 Object mi=methods[i];
318 if (
319 // 15.11.2.1 ACCESSIBLE: Method is public.
320 Modifier.isPublic(entryGetModifiers(mi))
321 &&
322 // 15.11.2.1 APPLICABLE: Right method name (or c'tor)
323 (methodName==null || entryGetName(mi).equals(methodName) )
324 &&
325 // 15.11.2.1 APPLICABLE: Parameters match arguments
326 areMethodConvertable(entryGetParameterTypes(mi),argTypes)
327 )
328 // 15.11.2.2 MORE SPECIFIC displace less specific.
329 best.addItem(mi);
330 }
331
332 // May throw NoSuchMethodException; we pass in info needed to
333 // create a useful exception
334 m=best.getMostSpecific(targetClass,methodName,argTypes,isStaticReference);
335
336 // 15.11.3 APPROPRIATE: Class invocation can call only static
337 // methods. Note that the defined order of evaluation permits a
338 // call to be resolved to an inappropriate method and then
339 // rejected, rather than finding the best of the appropriate
340 // methods.
341 //
342 // Constructors are never static, so we don't test them.
343 if(m==null)
344 {
345 throw new NoSuchMethodException (callToString(targetClass,
346 methodName,
347 argTypes,
348 isStaticReference)+
349 " -- no signature match");
350 }
351
352 if( methodName!=null &&
353 isStaticReference &&
354 !Modifier.isStatic(entryGetModifiers(m)) )
355 {
356 throw new NoSuchMethodException (callToString(targetClass,
357 methodName,
358 argTypes,
359 isStaticReference)+
360 " resolved to instance: "+m);
361 }
362
363 return m;
364 }
365 //////////////////////////////////////////////////////////////////////////
366
367 /* Class.getMethod() finds only the entry point (if any) _exactly_
368 matching the specified argument types. Our implmentation can
369 decide between several imperfect matches, using the same search
370 algorithm as the Java compiler.
371
372 This version more closely resembles Class.getMethod() -- we always
373 ask the Class for the method. It differs in testing for
374 appropriateness before returning the method; if the query is
375 being made via a static reference, only static methods will be
376 found and returned. */
377 static public Method getMethod(Class target,String methodName,
378 Class[] argTypes,boolean isStaticReference)
379 throws SecurityException, NoSuchMethodException
380 {
381 return (Method)getEntryPoint(target,methodName,argTypes,isStaticReference);
382 }
383 //////////////////////////////////////////////////////////////////////////
384
385 /**
386 * Class.getMethod() finds only the entry point (if any) _exactly_
387 * matching the specified argument types. Our implmentation can
388 * decide between several imperfect matches, using the same search
389 * algorithm as the Java compiler.
390 *
391 * This version emulates the compiler behavior by allowing lookup to
392 * be performed against either a class or an instance -- classname.foo()
393 * must be a static method call, instance.foo() can invoke either static
394 * or instance methods.
395 *
396 * @param target object on which call is to be made
397 * @param methodName name of method I'm lookin' for
398 * @param argTypes array of argument types of method
399 *
400 * @return the desired method
401 *
402 * @exception SecurityException if security violation
403 * @exception NoSuchMethodException if no such method
404 */
405 static public Method getMethod(Object target,String methodName,
406 Class[] argTypes)
407 throws SecurityException, NoSuchMethodException
408 {
409 boolean staticRef=target instanceof Class;
410 return getMethod( staticRef ? (Class)target : target.getClass(),
411 methodName,argTypes,staticRef);
412 }
413 /** Determine whether a given type can accept assignments of another
414 type. Note that class.isAssignable() is _not_ a complete test!
415 (This method is not needed by getMethod() or getConstructor(), but
416 is provided as a convenience for other users.)
417
418 parm: The type given in the method's signature.
419 arg: The type we want to pass in.
420
421 Legal ASSIGNMENT CONVERSIONS (5.2) are METHOD CONVERSIONS (5.3)
422 plus implicit narrowing of int to byte, short or char. */
423 static private boolean isAssignmentConvertable(Class parm,Class arg)
424 {
425 return
426 (arg.equals(Integer.TYPE) &&
427 (parm.equals(Byte.TYPE) ||
428 parm.equals(Short.TYPE) ||
429 parm.equals(Character.TYPE)
430 )
431 ) ||
432 isMethodConvertable(parm,arg);
433 }
434 /** Determine whether a given method parameter type can accept
435 arguments of another type.
436
437 parm: The type given in the method's signature.
438 arg: The type we want to pass in.
439
440 Legal METHOD CONVERSIONS (5.3) are Identity, Widening Primitive
441 Conversion, or Widening Reference Conversion. NOTE that this is a
442 subset of the legal ASSIGNMENT CONVERSIONS (5.2) -- in particular,
443 we can't implicitly narrow int to byte, short or char.
444
445 SPECIAL CASE: In order to permit invoking methods with literal
446 "null" values, setting the arg Class to null will be taken as a
447 request to match any Class type. POSSIBLE PROBLEM: This may match
448 a primitive type, which really should not accept a null value... but
449 I'm not sure how best to distinguish those, short of enumerating them
450 */
451 static private boolean isMethodConvertable(Class parm, Class arg)
452 {
453 if (parm.equals(arg)) // If same class, short-circuit now!
454 return true;
455
456 // Accept any type EXCEPT primitives (which can't have null values).
457 if (arg == null)
458 {
459 return !parm.isPrimitive();
460 }
461
462 // Arrays are convertable if their elements are convertable
463 // ????? Does this have to be done before isAssignableFrom, or
464 // does it successfully handle arrays of primatives?
465 while(parm.isArray())
466 {
467 if(!arg.isArray())
468 return false; // Unequal array depth
469 else
470 {
471 parm=parm.getComponentType();
472 arg=arg.getComponentType();
473 }
474 }
475 if(arg.isArray())
476 return false; // Unequal array depth
477
478 // Despite its name, the 1.1.6 docs say that this function does
479 // NOT return true for all legal ASSIGNMENT CONVERSIONS
480 // (5.2):
481 // "Specifically, this method tests whether the type
482 // represented by the specified class can be converted
483 // to the type represented by this Class object via
484 // an identity conversion or via a widening reference
485 // conversion."
486 if(parm.isAssignableFrom(arg))
487 return true;
488
489 // That leaves us the Widening Primitives case. Four possibilities:
490 // void (can only convert to void), boolean (can only convert to boolean),
491 // numeric (which are sequenced) and char (which inserts itself into the
492 // numerics by promoting to int or larger)
493
494 if(parm.equals(Void.TYPE) || parm.equals(Boolean.TYPE) ||
495 arg.equals(Void.TYPE) || arg.equals(Boolean.TYPE))
496 return false;
497
498 Class[] primTypes={ Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE,
499 Long.TYPE, Float.TYPE, Double.TYPE };
500 int parmscore,argscore;
501
502 for(parmscore=0;parmscore<primTypes.length;++parmscore)
503 if (parm.equals(primTypes[parmscore]))
504 break;
505 if(parmscore>=primTypes.length)
506 return false; // Off the end
507
508 for(argscore=0;argscore<primTypes.length;++argscore)
509 if (arg.equals(primTypes[argscore]))
510 break;
511 if(argscore>=primTypes.length)
512 return false; // Off the end
513
514 // OK if ordered AND NOT char-to-smaller-than-int
515 return (argscore<parmscore && (argscore!=0 || parmscore>2) );
516 }
517 }