KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > bsf > util > MethodUtils


1 /*
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2002 The Apache Software Foundation. All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  * notice, this list of conditions and the following disclaimer in
16  * the documentation and/or other materials provided with the
17  * distribution.
18  *
19  * 3. The end-user documentation included with the redistribution, if
20  * any, must include the following acknowlegement:
21  * "This product includes software developed by the
22  * Apache Software Foundation (http://www.apache.org/)."
23  * Alternately, this acknowlegement may appear in the software itself,
24  * if and wherever such third-party acknowlegements normally appear.
25  *
26  * 4. The names "Apache BSF", "Apache", and "Apache Software Foundation"
27  * must not be used to endorse or promote products derived from
28  * this software without prior written permission. For written
29  * permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache"
32  * nor may "Apache" appear in their names without prior written
33  * permission of the Apache Group.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many individuals
50  * on behalf of the Apache Software Foundation and was originally created by
51  * Sanjiva Weerawarana and others at International Business Machines
52  * Corporation. For more information on the Apache Software Foundation,
53  * please see <http://www.apache.org/>.
54  */

55
56 package org.apache.bsf.util;
57
58 import java.util.*;
59 import java.io.*;
60 import java.beans.*;
61 import java.lang.reflect.*;
62
63 /**
64  * This file is a collection of reflection utilities for dealing with
65  * methods and constructors.
66  *
67  * @author Sanjiva Weerawarana
68  * @author Joseph Kesselman
69  */

70 public class MethodUtils {
71
72   /** Internal Class for getEntryPoint(). Implements 15.11.2.2 MORE
73     SPECIFIC rules.
74
75     Retains a list of methods (already known to match the
76     arguments). As each method is added, we check against past entries
77     to determine which if any is "more specific" -- defined as having
78     _all_ its arguments (not just a preponderance) be
79     method-convertable into those of another. If such a relationship
80     is found, the more-specific method is retained and the
81     less-specific method is discarded. At the end, if this has yielded
82     a single winner it is considered the Most Specific Method and
83     hence the one that should be invoked. Otherwise, a
84     NoSuchMethodException is thrown.
85     
86     PERFORMANCE VERSUS ARCHITECTURE: Arguably, this should "have-a"
87     Vector. But the code is 6% smaller, and possibly faster, if we
88     code it as "is-a" Vector. Since it's an inner class, nobody's
89     likely to abuse the privilage.
90     
91     Note: "Static" in the case of an inner class means "Does not
92     reference instance data in the outer class", and is required since
93     our caller is a static method. */

94   private static class MoreSpecific
95   extends Vector
96   {
97     /** Submit an entry-point to the list. May be discarded if a past
98       entry is more specific, or may cause others to be discarded it
99       if is more specific.
100
101       newEntry: Method or Constructor under consideration.
102       */

103     void addItem (Object JavaDoc newEntry)
104     {
105       if(size()==0)
106         addElement(newEntry);
107       else
108         {
109           Class JavaDoc[] newargs=entryGetParameterTypes(newEntry);
110           boolean keep=true;
111           for (Enumeration e = elements();
112                keep & e.hasMoreElements() ;
113                 )
114             {
115               Object JavaDoc oldEntry=e.nextElement();
116               // CAVEAT: Implicit references to enclosing class!
117
Class JavaDoc[] oldargs=entryGetParameterTypes(oldEntry);
118               if(areMethodConvertable(oldargs,newargs))
119                 removeElement(oldEntry); // New more specific; discard old
120
else if(areMethodConvertable(newargs,oldargs))
121                 keep=false; // Old more specific; discard new
122
// Else they're tied. Keep both and hope someone beats both.
123
}
124           if(keep)
125             addElement(newEntry);
126         }
127     }
128
129     /** Obtain the single Most Specific entry-point. If there is no clear
130       winner, or if the list is empty, throw NoSuchMethodException.
131
132       Arguments describe the call we were hoping to resolve. They are
133       used to throw a nice verbose exception if something goes wrong.
134       */

135     Object JavaDoc getMostSpecific(Class JavaDoc targetClass,String JavaDoc methodName,
136                            Class JavaDoc[] argTypes,boolean isStaticReference)
137          throws NoSuchMethodException JavaDoc
138     {
139       if(size()==1)
140         return firstElement();
141       if(size()>1)
142         {
143           StringBuffer JavaDoc buf=new StringBuffer JavaDoc();
144           Enumeration e=elements();
145           buf.append(e.nextElement());
146           while(e.hasMoreElements())
147             buf.append(" and ").append(e.nextElement());
148           throw new NoSuchMethodException JavaDoc (callToString(targetClass,
149                                                         methodName,
150                                                         argTypes,
151                                                         isStaticReference)+
152                                            " is ambiguous. It matches "+
153                                            buf.toString());
154         }
155       return null;
156     }
157   }
158
159   /** Convenience method: Test an entire parameter-list/argument-list pair
160     for isMethodConvertable(), qv.
161     */

162   static private boolean areMethodConvertable(Class JavaDoc[] parms,Class JavaDoc[] args)
163   {
164     if(parms.length!=args.length)
165       return false;
166     
167     for(int i=0;i<parms.length;++i)
168       if(!isMethodConvertable(parms[i],args[i]))
169         return false;
170     
171     return true;
172   }
173   /** Internal subroutine for getEntryPoint(): Format arguments as a
174       string describing the function being searched for. Used in
175       verbose exceptions. */

176   private static String JavaDoc callToString(Class JavaDoc targetClass,String JavaDoc methodName,
177                                     Class JavaDoc[] argTypes,boolean isStaticReference)
178   {
179     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
180     if(isStaticReference)
181       buf.append("static ");
182     buf.append(StringUtils.getClassName(targetClass));
183     if(methodName!=null)
184       buf.append(".").append(methodName);
185     buf.append("(");
186     if (argTypes != null && argTypes.length>0) {
187       if(false)
188         {
189           // ????? Sanjiva has an ArrayToString method. Using it would
190
// save a few bytes, at cost of giving up some reusability.
191
}
192       else
193         {
194           buf.append(StringUtils.getClassName(argTypes[0]));
195           for (int i = 1; i < argTypes.length; i++) {
196             buf.append(",").append(StringUtils.getClassName(argTypes[i]));
197           }
198         }
199     }
200     else
201       buf.append("[none]");
202     buf.append(")");
203     return buf.toString();
204   }
205   /** Utility function: obtain common data from either Method or
206       Constructor. (In lieu of an EntryPoint interface.) */

207   static int entryGetModifiers(Object JavaDoc entry)
208   {
209     return (entry instanceof Method)
210       ? ((Method)entry).getModifiers()
211       : ((Constructor)entry).getModifiers();
212   }
213   // The common lookup code would be much easier if Method and
214
// Constructor shared an "EntryPoint" Interface. Unfortunately, even
215
// though their APIs are almost identical, they don't. These calls
216
// are a workaround... at the cost of additional runtime overhead
217
// and some extra bytecodes.
218
//
219
// (A JDK bug report has been submitted requesting that they add the
220
// Interface; it would be easy, harmless, and useful.)
221

222   /** Utility function: obtain common data from either Method or
223       Constructor. (In lieu of an EntryPoint interface.) */

224   static String JavaDoc entryGetName(Object JavaDoc entry)
225   {
226     return (entry instanceof Method)
227       ? ((Method)entry).getName()
228       : ((Constructor)entry).getName();
229   }
230   /** Utility function: obtain common data from either Method or
231       Constructor. (In lieu of an EntryPoint interface.) */

232   static Class JavaDoc[] entryGetParameterTypes(Object JavaDoc entry)
233   {
234     return (entry instanceof Method)
235       ? ((Method)entry).getParameterTypes()
236       : ((Constructor)entry).getParameterTypes();
237   }
238   /** Utility function: obtain common data from either Method or
239       Constructor. (In lieu of an EntryPoint interface.) */

240   static String JavaDoc entryToString(Object JavaDoc entry)
241   {
242     return (entry instanceof Method)
243       ? ((Method)entry).toString()
244       : ((Constructor)entry).toString();
245   }
246   //////////////////////////////////////////////////////////////////////////
247

248   /** Class.getConstructor() finds only the entry point (if any)
249     _exactly_ matching the specified argument types. Our implmentation
250     can decide between several imperfect matches, using the same
251     search algorithm as the Java compiler.
252
253     Note that all constructors are static by definition, so
254     isStaticReference is true.
255
256     @exception NoSuchMethodException if constructor not found.
257     */

258   static public Constructor getConstructor(Class JavaDoc targetClass, Class JavaDoc[] argTypes)
259        throws SecurityException JavaDoc, NoSuchMethodException JavaDoc
260   {
261     return (Constructor) getEntryPoint(targetClass,null,argTypes,true);
262   }
263   //////////////////////////////////////////////////////////////////////////
264

265   /**
266    * Search for entry point, per Java Language Spec 1.0
267    * as amended, verified by comparison against compiler behavior.
268    *
269    * @param targetClass Class object for the class to be queried.
270    * @param methodName Name of method to invoke, or null for constructor.
271    * Only Public methods will be accepted.
272    * @param argTypes Classes of intended arguments. Note that primitives
273    * must be specified via their TYPE equivalents,
274    * rather than as their wrapper classes -- Integer.TYPE
275    * rather than Integer. "null" may be passed in as an
276    * indication that you intend to invoke the method with
277    * a literal null argument and therefore can accept
278    * any object type in this position.
279    * @param isStaticReference If true, and if the target is a Class object,
280    * only static methods will be accepted as valid matches.
281    *
282    * @return a Method or Constructor of the appropriate signature
283    *
284    * @exception SecurityException if security violation
285    * @exception NoSuchMethodException if no such method
286    */

287   static private Object JavaDoc getEntryPoint(Class JavaDoc targetClass,
288                                       String JavaDoc methodName,
289                                       Class JavaDoc[] argTypes,
290                                       boolean isStaticReference)
291        throws SecurityException JavaDoc, NoSuchMethodException JavaDoc
292   {
293     // 15.11.1: OBTAIN STARTING CLASS FOR SEARCH
294
Object JavaDoc m=null;
295     
296     // 15.11.2 DETERMINE ARGUMENT SIGNATURE
297
// (Passed in as argTypes array.)
298

299     // Shortcut: If an exact match exists, return it.
300
try {
301       if(methodName!=null)
302         {
303           m=targetClass.getMethod (methodName, argTypes);
304           if(isStaticReference &&
305              !Modifier.isStatic(entryGetModifiers(m)) )
306             {
307               throw
308                 new NoSuchMethodException JavaDoc (callToString (targetClass,
309                                                          methodName,
310                                                          argTypes,
311                                                          isStaticReference)+
312                                            " resolved to instance " + m);
313             }
314           return m;
315         }
316       else
317         return targetClass.getConstructor (argTypes);
318           
319     } catch (NoSuchMethodException JavaDoc e) {
320       // no-args has no alternatives!
321
if(argTypes==null || argTypes.length==0)
322       {
323         throw
324           new NoSuchMethodException JavaDoc (callToString (targetClass,
325                                                    methodName,
326                                                    argTypes,
327                                                    isStaticReference)+
328                                      " not found.");
329       }
330       // Else fall through.
331
}
332     
333     // Well, _that_ didn't work. Time to search for the Most Specific
334
// matching function. NOTE that conflicts are possible!
335

336     // 15.11.2.1 ACCESSIBLE: We apparently need to gather from two
337
// sources to be sure we have both instance and static methods.
338
Object JavaDoc[] methods;
339     if(methodName!=null)
340       {
341         methods=targetClass.getMethods();
342       }
343     else
344       {
345         methods=targetClass.getConstructors();
346       }
347     if(0==methods.length)
348       {
349         throw new NoSuchMethodException JavaDoc("No methods!");
350       }
351
352     MoreSpecific best=new MoreSpecific();
353     for(int i=0;i<methods.length;++i)
354       {
355         Object JavaDoc mi=methods[i];
356         if (
357             // 15.11.2.1 ACCESSIBLE: Method is public.
358
Modifier.isPublic(entryGetModifiers(mi))
359             &&
360             // 15.11.2.1 APPLICABLE: Right method name (or c'tor)
361
(methodName==null || entryGetName(mi).equals(methodName) )
362             &&
363             // 15.11.2.1 APPLICABLE: Parameters match arguments
364
areMethodConvertable(entryGetParameterTypes(mi),argTypes)
365              )
366           // 15.11.2.2 MORE SPECIFIC displace less specific.
367
best.addItem(mi);
368       }
369
370     // May throw NoSuchMethodException; we pass in info needed to
371
// create a useful exception
372
m=best.getMostSpecific(targetClass,methodName,argTypes,isStaticReference);
373   
374     // 15.11.3 APPROPRIATE: Class invocation can call only static
375
// methods. Note that the defined order of evaluation permits a
376
// call to be resolved to an inappropriate method and then
377
// rejected, rather than finding the best of the appropriate
378
// methods.
379
//
380
// Constructors are never static, so we don't test them.
381
if(m==null)
382       {
383         throw new NoSuchMethodException JavaDoc (callToString(targetClass,
384                                                       methodName,
385                                                       argTypes,
386                                                       isStaticReference)+
387                                          " -- no signature match");
388       }
389
390     if( methodName!=null &&
391         isStaticReference &&
392         !Modifier.isStatic(entryGetModifiers(m)) )
393       {
394         throw new NoSuchMethodException JavaDoc (callToString(targetClass,
395                                                       methodName,
396                                                       argTypes,
397                                                       isStaticReference)+
398                                          " resolved to instance: "+m);
399       }
400
401     return m;
402   }
403   //////////////////////////////////////////////////////////////////////////
404

405   /* Class.getMethod() finds only the entry point (if any) _exactly_
406     matching the specified argument types. Our implmentation can
407     decide between several imperfect matches, using the same search
408     algorithm as the Java compiler.
409
410     This version more closely resembles Class.getMethod() -- we always
411     ask the Class for the method. It differs in testing for
412     appropriateness before returning the method; if the query is
413     being made via a static reference, only static methods will be
414     found and returned. */

415   static public Method getMethod(Class JavaDoc target,String JavaDoc methodName,
416                                  Class JavaDoc[] argTypes,boolean isStaticReference)
417        throws SecurityException JavaDoc, NoSuchMethodException JavaDoc
418   {
419     return (Method)getEntryPoint(target,methodName,argTypes,isStaticReference);
420   }
421   //////////////////////////////////////////////////////////////////////////
422

423   /**
424    * Class.getMethod() finds only the entry point (if any) _exactly_
425    * matching the specified argument types. Our implmentation can
426    * decide between several imperfect matches, using the same search
427    * algorithm as the Java compiler.
428    *
429    * This version emulates the compiler behavior by allowing lookup to
430    * be performed against either a class or an instance -- classname.foo()
431    * must be a static method call, instance.foo() can invoke either static
432    * or instance methods.
433    *
434    * @param target object on which call is to be made
435    * @param methodName name of method I'm lookin' for
436    * @param argTypes array of argument types of method
437    *
438    * @return the desired method
439    *
440    * @exception SecurityException if security violation
441    * @exception NoSuchMethodException if no such method
442    */

443   static public Method getMethod(Object JavaDoc target,String JavaDoc methodName,
444                                  Class JavaDoc[] argTypes)
445        throws SecurityException JavaDoc, NoSuchMethodException JavaDoc
446   {
447     boolean staticRef=target instanceof Class JavaDoc;
448     return getMethod( staticRef ? (Class JavaDoc)target : target.getClass(),
449                       methodName,argTypes,staticRef);
450   }
451   /** Determine whether a given type can accept assignments of another
452     type. Note that class.isAssignable() is _not_ a complete test!
453     (This method is not needed by getMethod() or getConstructor(), but
454     is provided as a convenience for other users.)
455     
456     parm: The type given in the method's signature.
457     arg: The type we want to pass in.
458
459     Legal ASSIGNMENT CONVERSIONS (5.2) are METHOD CONVERSIONS (5.3)
460     plus implicit narrowing of int to byte, short or char. */

461   static private boolean isAssignmentConvertable(Class JavaDoc parm,Class JavaDoc arg)
462   {
463     return
464       (arg.equals(Integer.TYPE) &&
465        (parm.equals(Byte.TYPE) ||
466         parm.equals(Short.TYPE) ||
467         parm.equals(Character.TYPE)
468          )
469         ) ||
470       isMethodConvertable(parm,arg);
471   }
472   /** Determine whether a given method parameter type can accept
473     arguments of another type.
474
475     parm: The type given in the method's signature.
476     arg: The type we want to pass in.
477
478     Legal METHOD CONVERSIONS (5.3) are Identity, Widening Primitive
479     Conversion, or Widening Reference Conversion. NOTE that this is a
480     subset of the legal ASSIGNMENT CONVERSIONS (5.2) -- in particular,
481     we can't implicitly narrow int to byte, short or char.
482
483     SPECIAL CASE: In order to permit invoking methods with literal
484     "null" values, setting the arg Class to null will be taken as a
485     request to match any Class type. POSSIBLE PROBLEM: This may match
486     a primitive type, which really should not accept a null value... but
487     I'm not sure how best to distinguish those, short of enumerating them
488     */

489   static private boolean isMethodConvertable(Class JavaDoc parm, Class JavaDoc arg)
490   {
491     if (parm.equals(arg)) // If same class, short-circuit now!
492
return true;
493
494     // Accept any type EXCEPT primitives (which can't have null values).
495
if (arg == null)
496     {
497       return !parm.isPrimitive();
498     }
499
500     // Arrays are convertable if their elements are convertable
501
// ????? Does this have to be done before isAssignableFrom, or
502
// does it successfully handle arrays of primatives?
503
while(parm.isArray())
504       {
505         if(!arg.isArray())
506           return false; // Unequal array depth
507
else
508           {
509             parm=parm.getComponentType();
510             arg=arg.getComponentType();
511           }
512       }
513     if(arg.isArray())
514       return false; // Unequal array depth
515

516     // Despite its name, the 1.1.6 docs say that this function does
517
// NOT return true for all legal ASSIGNMENT CONVERSIONS
518
// (5.2):
519
// "Specifically, this method tests whether the type
520
// represented by the specified class can be converted
521
// to the type represented by this Class object via
522
// an identity conversion or via a widening reference
523
// conversion."
524
if(parm.isAssignableFrom(arg))
525       return true;
526
527     // That leaves us the Widening Primitives case. Four possibilities:
528
// void (can only convert to void), boolean (can only convert to boolean),
529
// numeric (which are sequenced) and char (which inserts itself into the
530
// numerics by promoting to int or larger)
531

532     if(parm.equals(Void.TYPE) || parm.equals(Boolean.TYPE) ||
533        arg.equals(Void.TYPE) || arg.equals(Boolean.TYPE))
534       return false;
535     
536     Class JavaDoc[] primTypes={ Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE,
537                         Long.TYPE, Float.TYPE, Double.TYPE };
538     int parmscore,argscore;
539     
540     for(parmscore=0;parmscore<primTypes.length;++parmscore)
541       if (parm.equals(primTypes[parmscore]))
542         break;
543     if(parmscore>=primTypes.length)
544       return false; // Off the end
545

546     for(argscore=0;argscore<primTypes.length;++argscore)
547       if (arg.equals(primTypes[argscore]))
548         break;
549     if(argscore>=primTypes.length)
550       return false; // Off the end
551

552     // OK if ordered AND NOT char-to-smaller-than-int
553
return (argscore<parmscore && (argscore!=0 || parmscore>2) );
554   }
555 }
556
Popular Tags