001    package echopoint.util;
002    
003    /*
004     * This file is part of the Echo Point Project.  This project is a collection
005     * of Components that have extended the Echo Web Application Framework.
006     *
007     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
008     *
009     * The contents of this file are subject to the Mozilla Public License Version
010     * 1.1 (the "License"); you may not use this file except in compliance with
011     * the License. You may obtain a copy of the License at
012     * http://www.mozilla.org/MPL/
013     *
014     * Software distributed under the License is distributed on an "AS IS" basis,
015     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
016     * for the specific language governing rights and limitations under the
017     * License.
018     *
019     * Alternatively, the contents of this file may be used under the terms of
020     * either the GNU General Public License Version 2 or later (the "GPL"), or
021     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
022     * in which case the provisions of the GPL or the LGPL are applicable instead
023     * of those above. If you wish to allow use of your version of this file only
024     * under the terms of either the GPL or the LGPL, and not to allow others to
025     * use your version of this file under the terms of the MPL, indicate your
026     * decision by deleting the provisions above and replace them with the notice
027     * and other provisions required by the GPL or the LGPL. If you do not delete
028     * the provisions above, a recipient may use your version of this file under
029     * the terms of any one of the MPL, the GPL or the LGPL.
030     */
031    
032    import java.lang.reflect.Constructor;
033    import java.lang.reflect.Field;
034    import java.lang.reflect.InvocationTargetException;
035    import java.lang.reflect.Member;
036    import java.lang.reflect.Method;
037    import java.lang.reflect.Modifier;
038    import java.util.ArrayList;
039    import java.util.Arrays;
040    import java.util.Collections;
041    import java.util.Comparator;
042    import java.util.Iterator;
043    import java.util.List;
044    
045    /**
046     * <code>ReflectionKit</code> provides methods that help when using reflection
047     * on Java code.
048     */
049    public class ReflectionKit {
050    
051            private static abstract class BaseReflectionKitComparator implements Comparator {
052                    /*
053                     * Does the standard nullness checks and returns Integer.MAX_VALUE to
054                     * indicate that they are both non null objects and hence further checks
055                     * may be needed.
056                     */
057                    int compareForNullness(Object o1, Object o2) {
058                            if (o1 == null && o2 == null)
059                                    return 0;
060                            if (o1 == null && o2 != null)
061                                    return -1;
062                            if (o1 != null && o2 == null)
063                                    return 1;
064                            return Integer.MAX_VALUE;
065                    }
066    
067                    /**
068                     * Normalises the return code into -1, 0 or 1 as required by {@link Comparator}.
069                     * @param rc - the rc in question
070                     * @return -1, 0 or 1 as required by {@link Comparator}
071                     */
072                    protected int normaliseRC(int rc) {
073                            if (rc < 0) {
074                                    return -1;
075                            } else if (rc > 0) {
076                                    return 1;
077                            } else {
078                                    return 0;
079                            }
080                    }
081    
082    
083                    /**
084                     * The compares the two <code>{@link Class}</code> values against
085                     * each other in the following order :
086                     *
087                     * <ol>
088                     * <li>c1.equals(c2) return 0</li>
089                     * <li>c2 is assignable c1, return -1</li>
090                     * <li>c1 is assignable c2, return 1</li>
091                     * <li>finally c1.getName() compared to c2.getName()</li>
092                     * <li></li>
093                     * </ol>
094                     */
095                    protected int compareByClassDerivation(Class c1, Class c2) {
096                            if (c1 == null || c2 == null) {
097                                    return compareForNullness(c1, c2);
098                            }
099                            if (c1.equals(c2))
100                                    return 0;
101                            if (c2.isAssignableFrom(c1) || c1.isAssignableFrom(c2)) {
102                                    if (c2.isAssignableFrom(c1))
103                                            return -1;
104                                    else if (c1.isAssignableFrom(c2))
105                                            return 1;
106                            }
107                            int rc = c1.getName().compareTo(c2.getName());
108                            return normaliseRC(rc);
109    
110                    }
111            }
112    
113            /**
114             * A Comparator that can be used when comparing and sorting Class objects by
115             * class name. NOTE: this is only based on class name nor class derivation.
116             * <p>
117             * In short it sorts in "Class Name" order only.
118             */
119            public static class ClassNameComparator extends BaseReflectionKitComparator implements Comparator {
120                    public int compare(Object o1, Object o2) {
121                            if (o1 == null || o2 == null) {
122                                    return compareForNullness(o1, o2);
123                            }
124                            Class c1 = (Class) o1;
125                            Class c2 = (Class) o2;
126                            int rc = c1.getName().compareTo(c2.getName());
127                            return normaliseRC(rc);
128                    }
129            }
130    
131            /**
132             * A Comparator that can be used when comparing and sorting Class objects by
133             * most specific class order. If c2 is derived from c1 then it is sorted
134             * before c1. If the classes are unrelated, then its done by class
135             * alphabetic name.
136             * <p>
137             * In short it sorts in "Class Derivation / Class Name" order.
138             */
139            public static class ClassDerivationComparator extends BaseReflectionKitComparator implements Comparator {
140                    public int compare(Object o1, Object o2) {
141                            if (o1 == null || o2 == null) {
142                                    return compareForNullness(o1, o2);
143                            }
144                            return compareByClassDerivation((Class) o1, (Class) o2);
145                    }
146            }
147    
148            /**
149             * A <code>{@link Comparator}</code> that can be used when comparing and
150             * sorting <code>{@link Member}</code> objects by name, modifier and
151             * finally declaring class order.
152             *
153             * NOTE : <code>{@link java.lang.reflect.Member}</code> is the base class
154             * for {@link Constructor}, {@link Field} and {@link Method} and hence you
155             * can sort any of these types with this <code>Comparator</code>.
156             * <p>
157             * If the <code>Member</code>'s names are the same they are then sorted
158             * by modifier and parameters and finally by declaring class with the most
159             * specific class first.
160             * <p>
161             * In short it sorts in "Member / Modifiers / Parameters / Declaring Class"
162             * order.
163             */
164            public static class MemberClassComparator extends BaseReflectionKitComparator implements Comparator {
165    
166                    /**
167                     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
168                     */
169                    public int compare(Object o1, Object o2) {
170                            if (o1 == null || o2 == null) {
171                                    return compareForNullness(o1, o2);
172                            }
173                            Member m1 = (Member) o1;
174                            Member m2 = (Member) o2;
175    
176                            int rc = compareByModifiers(m1.getModifiers(), m2.getModifiers());
177                            if (rc == 0) {
178                                    if (m1 instanceof Method && m2 instanceof Method) {
179                                            rc = compareByMethod((Method) m1, (Method) m2);
180                                    }
181                                    if (m1 instanceof Field && m2 instanceof Field) {
182                                            rc = compareByField((Field) m1, (Field) m2);
183                                    }
184                                    if (m1 instanceof Constructor && m2 instanceof Constructor) {
185                                            rc = compareByConstructor((Constructor) m1, (Constructor) m2);
186                                    }
187                            }
188                            if (rc == 0) {
189                                    rc = compareByClassDerivation(m1.getDeclaringClass(), m2.getDeclaringClass());
190                            }
191                            return normaliseRC(rc);
192                    }
193    
194                    /**
195                     * The compares the two <code>{@link Modifier}</code> values against
196                     * each other in the following order
197                     * <ol>
198                     * <li>public</li>
199                     * <li>protected</li>
200                     * <li>abstract</li>
201                     * <li>final</li>
202                     * <li>native</li>
203                     * <li>interface</li>
204                     * <li>static</li>
205                     * </ol>
206                     *
207                     * @param modifier1 -
208                     *            the modifier of the first <code>Member</code>
209                     * @param modifier2 -
210                     *            the modifier of the second <code>Member</code>
211                     * @return -1, 0 or 1 as per {@link Comparator}
212                     */
213                    protected int compareByModifiers(int modifier1, int modifier2) {
214                            int weights[] = new int[2];
215                            int modifiers[] = new int[] { modifier1, modifier2 };
216                            for (int i = 0; i < modifiers.length; i++) {
217                                    int mod = modifiers[i];
218                                    if (Modifier.isPublic(mod))
219                                            weights[i] |= 0x10000000;
220                                    if (Modifier.isProtected(mod))
221                                            weights[i] |= 0x01000000;
222                                    if (Modifier.isProtected(mod))
223                                            weights[i] |= 0x00100000;
224                                    if (Modifier.isAbstract(mod))
225                                            weights[i] |= 0x00010000;
226                                    if (Modifier.isFinal(mod))
227                                            weights[i] |= 0x00001000;
228                                    if (Modifier.isNative(mod))
229                                            weights[i] |= 0x00000100;
230                                    if (Modifier.isInterface(mod))
231                                            weights[i] |= 0x10000010;
232                                    if (Modifier.isStatic(mod))
233                                            weights[i] |= 0x00000001;
234                                    // dont worry about the rest
235                            }
236                            int rc = weights[0] - weights[1];
237                            return normaliseRC(rc);
238                    }
239    
240                    /**
241                     * The compares the two <code>{@link Method}</code> values against
242                     * each other in the following order :
243                     *
244                     * <ol>
245                     * <li>Method name</li>
246                     * <li>Method return type</li>
247                     * <li>Method parameter count</li>
248                     * <li>Method parameter types</li>
249                     * </ol>
250                     *
251                     */
252                    protected int compareByMethod(Method m1, Method m2) {
253                            int rc = m1.getName().compareTo(m2.getName());
254                            if (rc == 0) {
255                                    rc = compareByClassDerivation(m1.getReturnType(), m1.getReturnType());
256                                    if (rc == 0) {
257                                            Class[] m1ParamTypes = m1.getParameterTypes();
258                                            Class[] m2ParamTypes = m2.getParameterTypes();
259                                            rc = m1ParamTypes.length - m2ParamTypes.length;
260                                            if (rc == 0) {
261                                                    for (int i = 0; i < m1ParamTypes.length; i++) {
262                                                            rc = compareByClassDerivation(m1ParamTypes[i], m1ParamTypes[i]);
263                                                            if (rc == 0)
264                                                                    break;
265                                                    }
266                                            }
267                                    }
268                            }
269                            return normaliseRC(rc);
270                    }
271    
272                    /**
273                     * The compares the two <code>{@link Field}</code> values against
274                     * each other in the following order :
275                     *
276                     * <ol>
277                     * <li>Field name</li>
278                     * <li>Field type</li>
279                     * </ol>
280                     *
281                     */
282                    protected int compareByField(Field f1, Field f2) {
283                            if (f1 == null || f2 == null) {
284                                    return compareForNullness(f1, f2);
285                            }
286                            int rc = f1.getName().compareTo(f2.getName());
287                            if (rc == 0) {
288                                    rc = compareByClassDerivation(f1.getType(), f1.getType());
289                            }
290                            return normaliseRC(rc);
291                    }
292    
293                    /**
294                     * The compares the two <code>{@link Constructor}</code> values against
295                     * each other in the following order :
296                     *
297                     * <ol>
298                     * <li>The Constructor parameter count</li>
299                     * <li>The Constructor parameter type</li>
300                     * </ol>
301                     *
302                     */
303                    protected int compareByConstructor(Constructor c1, Constructor c2) {
304                            if (c1 == null || c2 == null) {
305                                    return compareForNullness(c1, c2);
306                            }
307                            Class[] c1ParamTypes = c1.getParameterTypes();
308                            Class[] c2ParamTypes = c2.getParameterTypes();
309                            int rc = c1ParamTypes.length - c2ParamTypes.length;
310                            if (rc == 0) {
311                                    for (int i = 0; i < c1ParamTypes.length; i++) {
312                                            rc = compareByClassDerivation(c1ParamTypes[i], c1ParamTypes[i]);
313                                            if (rc == 0)
314                                                    break;
315                                    }
316                            }
317                            return normaliseRC(rc);
318                    }
319    
320            }
321    
322            /**
323             * A <code>{@link Comparator}</code> that can be used when comparing and
324             * sorting <code>{@link Member}</code> objects by most specific declaring
325             * class order, then followed by member name.
326             * <p>
327             * In short it sorts in "Class / Member" order.
328             *
329             * @see echopoint.util.ReflectionKit.MemberClassComparator
330             */
331            public static class ClassMemberComparator extends MemberClassComparator {
332                    /**
333                     * @see echopoint.util.ReflectionKit.MemberClassComparator#compare(java.lang.Object, java.lang.Object)
334                     */
335                    public int compare(Object o1, Object o2) {
336                            if (o1 == null || o2 == null) {
337                                    return compareForNullness(o1, o2);
338                            }
339                            Member m1 = (Member) o1;
340                            Member m2 = (Member) o2;
341    
342                            Class c1 = m1.getDeclaringClass();
343                            Class c2 = m2.getDeclaringClass();
344                            if (c1.equals(c2)) {
345                                    int rc = compareByModifiers(m1.getModifiers(), m2.getModifiers());
346                                    if (rc == 0) {
347                                            if (m1 instanceof Method && m2 instanceof Method) {
348                                                    rc = compareByMethod((Method) m1, (Method) m2);
349                                            }
350                                            if (m1 instanceof Field && m2 instanceof Field) {
351                                                    rc = compareByField((Field) m1, (Field) m2);
352                                            }
353                                            if (m1 instanceof Constructor && m2 instanceof Constructor) {
354                                                    rc = compareByConstructor((Constructor) m1, (Constructor) m2);
355                                            }
356                                    }
357                                    return normaliseRC(rc);
358                            } else {
359                                    return compareByClassDerivation(c1, c2);
360                            }
361                    }
362            }
363    
364            /**
365             * A <code>{@link Comparator}</code> that can be used when comparing and
366             * sorting <code>{@link Method}</code> objects by name, modifier and class
367             * order.
368             * <p>
369             * If the Methods's names are the same they are sorted in declaring class
370             * order with the most specific class first.
371             * <p>
372             * In short it sorts in "Method / Declaring Class" order.
373             */
374            public static class MethodClassComparator extends MemberClassComparator {
375            }
376    
377            /**
378             * A <code>{@link Comparator}</code> that can be used when comparing and
379             * sorting <code>{@link Method}</code> objects by most specific declaring
380             * class order, then followed by method name and parameters.
381             * <p>
382             * In short it sorts in "Class / Method" order.
383             */
384            public static class ClassMethodComparator extends ClassMemberComparator {
385            }
386    
387            /**
388             * A <code>{@link Comparator}</code> that can be used when comparing and
389             * sorting <code>{@link Field}</code> objects by name, modifier and class
390             * order.
391             * <p>
392             * If the Field's names are the same they are sorted in declaring class
393             * order with the most specific class first.
394             * <p>
395             * In short it sorts in "Field / Declaring Class" order.
396             */
397            public static class FieldClassComparator extends MemberClassComparator {
398            }
399    
400            /**
401             * A <code>{@link Comparator}</code> that can be used when comparing and
402             * sorting <code>{@link Field}</code> objects by most specific declaring
403             * class order, then followed by Field name and parameters.
404             * <p>
405             * In short it sorts in "Class / Field" order.
406             */
407            public static class ClassFieldComparator extends ClassMemberComparator {
408            }
409    
410            /**
411             * A <code>{@link Comparator}</code> that can be used when comparing and
412             * sorting <code>{@link Constructor}</code> objects by most specific
413             * declaring class order, then followed by Constructor name and parameters.
414             * <p>
415             * In short it sorts in "Class / Constructor" order.
416             */
417            public static class ClassConstructorComparator extends ClassMemberComparator {
418            }
419    
420            /**
421             * <code>MethodSearchCriteria</code> is an interface used to determine if
422             * a method matches some search criteria.
423             */
424            public static interface MethodSearchCriteria {
425                    public boolean isMethodOK(Class methodClass, Method method);
426            }
427    
428            /** not instantiable */
429            private ReflectionKit() {
430            }
431    
432            /**
433             * Returns true if the method is in fact a Java Bean getter method. ie is
434             * starts with 'get' or 'is' and takes no parameters and returns a value and
435             * is not static.
436             * <p>
437             * Note that it does NOT check for public access because its valid to have a
438             * getter that isnt public.
439             *
440             * @param method -
441             *            the method to examine
442             * @return true if the method is a Java Bean getter.
443             */
444            public static boolean isGetter(Method method) {
445                    if (method == null)
446                            return false;
447                    if (Modifier.isStatic(method.getModifiers()))
448                            return false;
449                    if (method.getReturnType().equals(Void.TYPE))
450                            return false;
451                    if (method.getParameterTypes().length > 0)
452                            return false;
453    
454                    String name = method.getName();
455                    if (name.length() > 2 && name.startsWith("is"))
456                            return true;
457                    if (name.length() > 3 && name.startsWith("get"))
458                            return true;
459                    return false;
460            }
461    
462            /**
463             * Returns true if the method is in fact a Java Bean setter method. ie is
464             * starts with 'set', takes one parameter and has a return value of void and
465             * is not static.
466             * <p>
467             * Note that it does NOT check for public access because its valid to have a
468             * setter that isnt public.
469             *
470             * @param method -
471             *            the method to examine
472             * @return true if the method is a Java Bean setter.
473             */
474            public static boolean isSetter(Method method) {
475                    if (method == null)
476                            return false;
477                    if (Modifier.isStatic(method.getModifiers()))
478                            return false;
479                    if (!method.getReturnType().equals(Void.TYPE))
480                            return false;
481                    if (method.getParameterTypes().length != 1)
482                            return false;
483    
484                    String name = method.getName();
485                    if (name.length() > 3 && name.startsWith("set"))
486                            return true;
487                    return false;
488            }
489    
490            /**
491             * Takes a bean property method name and removes any 'get'/'is'/'set' at the
492             * front and then decapitalizes the rest of the name according to the Java
493             * Bean Spec.
494             *
495             * @param name -
496             *            the name of the method or field name to change
497             * @return - the decapitalized name
498             */
499            public static String decapitalize(String name) {
500                    if (name.startsWith("is"))
501                            name = name.substring(2);
502                    else if (name.startsWith("get"))
503                            name = name.substring(3);
504                    else if (name.startsWith("set"))
505                            name = name.substring(3);
506                    if (name == null || name.length() == 0) {
507                            return name;
508                    }
509                    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) {
510                            return name;
511                    }
512                    char chars[] = name.toCharArray();
513                    chars[0] = Character.toLowerCase(chars[0]);
514                    return new String(chars);
515            }
516    
517            /**
518             * This method will returns member methods of the targetClass that meet a
519             * specified search criteria. The targetClass will be search up to and
520             * including the stopClass.
521             * <p>
522             * The results are sorted by method name within derived class. The most
523             * specific class methods will be returned first.
524             * <p>
525             * If stopClass is not a superclass or superinterface of targetClass, then
526             * Object.class is used.
527             *
528             *
529             * @param targetClass -
530             *            the class to check for methods
531             * @param stopClass -
532             *            the supertype to stop at.
533             * @param methodSearchCriteria -
534             *            the MethodSearchCirteria to use
535             * @return and array of Methods or Method[0] if there are none that match
536             *         the criteria
537             *
538             * @see MethodSearchCriteria
539             */
540            public static Method[] getMethods(Class targetClass, Class stopClass, MethodSearchCriteria methodSearchCriteria) {
541                    if (targetClass == null || stopClass == null)
542                            return new Method[0];
543    
544                    if (!stopClass.isAssignableFrom(targetClass))
545                            stopClass = Object.class;
546    
547                    int methodCount = 0;
548                    int index = 0;
549                    Method[] methods;
550                    Class currentClass = targetClass;
551                    List methodList = new ArrayList();
552                    do {
553                            methods = currentClass.getDeclaredMethods();
554                            List subMethodList = new ArrayList();
555                            if (methods.length > 0) {
556                                    for (int i = 0; i < methods.length; i++) {
557                                            Method m = methods[i];
558                                            // does it meet our matching criteria
559                                            if (methodSearchCriteria.isMethodOK(currentClass, m)) {
560                                                    subMethodList.add(m);
561                                                    methodCount++;
562                                            }
563                                    }
564                                    // sort the methods and add to the master list
565                                    Collections.sort(subMethodList, new ClassMethodComparator());
566                                    for (Iterator iter = subMethodList.iterator(); iter.hasNext();) {
567                                            methodList.add(iter.next());
568                                    }
569                            }
570                            if (currentClass == stopClass)
571                                    break;
572                            currentClass = currentClass.getSuperclass();
573                    } while (currentClass != null);
574    
575                    methods = new Method[methodCount];
576                    for (Iterator iter = methodList.iterator(); iter.hasNext();) {
577                            Method method = (Method) iter.next();
578                            methods[index++] = method;
579                    }
580                    Arrays.sort(methods,new ClassMethodComparator());
581                    return methods;
582            }
583    
584            /**
585             * Returns an array containing Method objects reflecting all the member
586             * methods of the class or interface represented by the targetClass object,
587             * including those declared by the class or interface and and those
588             * inherited from superclasses and superinterfaces up until stopClass.
589             * <p>
590             * All public, protected, default (package) access, and private methods are
591             * returned.
592             * <p>
593             * If stopClass is not a superclass or superinterface of targetClass, then
594             * Object.class is used.
595             *
596             * @param targetClass -
597             *            the class to check for methods
598             * @param stopClass -
599             *            the supertype to stop at.
600             * @return and array of Methods or Method[0] if there are none
601             */
602            public static Method[] getAllDeclaredMethods(Class targetClass, Class stopClass) {
603                    return getMethods(targetClass, stopClass, new MethodSearchCriteria() {
604                            public boolean isMethodOK(Class methodClass, Method method) {
605                                    return true;
606                            }
607                    });
608            }
609    
610            /**
611             * Shorthand method for
612             * ReflectionKit.getAllMethods(targetClass,Object.class);
613             *
614             * @see ReflectionKit#getAllDeclaredMethods(Class, Class)
615             */
616            public static Method[] getAllDeclaredMethods(Class targetClass) {
617                    return getAllDeclaredMethods(targetClass, Object.class);
618            }
619    
620            /**
621             * Returns an array containing Method objects reflecting all the member
622             * methods of the class or interface represented by the targetClass object,
623             * including those declared by the class or interface and and those
624             * inherited from superclasses and superinterfaces up until stopClass.
625             * <p>
626             * Only public methods are returned.
627             * <p>
628             * If stopClass is not a superclass or superinterface of targetClass, then
629             * Object.class is used.
630             *
631             * @param targetClass -
632             *            the class to check for methods
633             * @param stopClass -
634             *            the supertype to stop at.
635             * @return and array of Methods or Method[0] if there are none
636             */
637            public static Method[] getAllPublicMethods(Class targetClass, Class stopClass) {
638                    return getMethods(targetClass, stopClass, new MethodSearchCriteria() {
639                            public boolean isMethodOK(Class methodClass, Method method) {
640                                    return Modifier.isPublic(method.getModifiers());
641                            }
642                    });
643            }
644    
645            /**
646             * Returns an array containing getter Method objects reflecting all the
647             * member methods of the class or interface represented by the targetClass
648             * object, including those declared by the class or interface and and those
649             * inherited from superclasses and superinterfaces up until stopClass.
650             * <p>
651             * Only methods matching the Java Bean specifiction for a getter method are
652             * returned.
653             * <p>
654             * If stopClass is not a superclass or superinterface of targetClass, then
655             * Object.class is used.
656             * <p>
657             * The methods are returned in method name order using the MethodComparator
658             * comparator.
659             *
660             * @param targetClass -
661             *            the class to check for methods
662             * @param stopClass -
663             *            the supertype to stop at.
664             * @return and array of Methods or Method[0] if there are none
665             */
666            public static Method[] getAllBeanGetterMethods(Class targetClass, Class stopClass) {
667                    Method[] methods = getMethods(targetClass, stopClass, new MethodSearchCriteria() {
668                            public boolean isMethodOK(Class methodClass, Method method) {
669                                    return isGetter(method);
670                            }
671                    });
672                    Arrays.sort(methods, new ClassMethodComparator());
673                    return methods;
674            }
675    
676            /**
677             * Returns an array containing setter Method objects reflecting all the
678             * member methods of the class or interface represented by the targetClass
679             * object, including those declared by the class or interface and and those
680             * inherited from superclasses and superinterfaces up until stopClass.
681             * <p>
682             * Only methods matching the Java Bean specifiction for a setter method are
683             * returned.
684             * <p>
685             * If stopClass is not a superclass or superinterface of targetClass, then
686             * Object.class is used.
687             *
688             * @param targetClass -
689             *            the class to check for methods
690             * @param stopClass -
691             *            the supertype to stop at.
692             * @return and array of Methods or Method[0] if there are none
693             */
694            public static Method[] getAllBeanSetterMethods(Class targetClass, Class stopClass) {
695                    Method[] methods = getMethods(targetClass, stopClass, new MethodSearchCriteria() {
696                            public boolean isMethodOK(Class methodClass, Method method) {
697                                    return isSetter(method);
698                            }
699                    });
700                    Arrays.sort(methods, new ClassMethodComparator());
701                    return methods;
702            }
703    
704            /**
705             * Returns an array containing getter and setter Method objects reflecting
706             * all the member methods of the class or interface represented by the
707             * targetClass object, including those declared by the class or interface
708             * and and those inherited from superclasses and superinterfaces up until
709             * stopClass.
710             * <p>
711             * Only methods matching the Java Bean specifiction for a getter or setter
712             * method are returned.
713             * <p>
714             * If stopClass is not a superclass or superinterface of targetClass, then
715             * Object.class is used.
716             *
717             * @param targetClass -
718             *            the class to check for methods
719             * @param stopClass -
720             *            the supertype to stop at.
721             * @return and array of Methods or Method[0] if there are none
722             */
723            public static Method[] getAllBeanMethods(Class targetClass, Class stopClass) {
724                    Method[] methods = getMethods(targetClass, stopClass, new MethodSearchCriteria() {
725                            public boolean isMethodOK(Class methodClass, Method method) {
726                                    return isSetter(method) || isGetter(method);
727                            }
728                    });
729                    Arrays.sort(methods, new MethodClassComparator());
730                    return methods;
731            }
732    
733            /**
734             * Returns the getter method for a given setter method. It is determined by
735             * getting the best matching property name as well as matching return type
736             * to setter parameter type.
737             *
738             * @param beanSetter -
739             *            a bean setter method of class in question
740             * @return the bean getter method or null if it cant be found
741             * @throws IllegalArgumentException -
742             *             if the method passed in is null
743             */
744            public static Method getBeanGetter(Method beanSetter) {
745                    if (beanSetter == null)
746                            throw new IllegalArgumentException("beanSetter must be non null");
747                    String setterName = decapitalize(beanSetter.getName());
748                    Class setterType = beanSetter.getParameterTypes()[0];
749    
750                    Method getters[] = getAllBeanGetterMethods(beanSetter.getDeclaringClass(), Object.class);
751                    for (int i = 0; i < getters.length; i++) {
752                            Method getter = getters[i];
753                            String getterName = decapitalize(getter.getName());
754                            if (getterName.equals(setterName)) {
755                                    if (getter.getReturnType().equals(setterType))
756                                            return getter;
757                            }
758                    }
759                    return null;
760            }
761    
762            /**
763             * @see ReflectionKit#getClassHierarchy(Class, Class)
764             */
765            public static Class[] getClassHierarchy(Class targetClass) {
766                    return getClassHierarchy(targetClass, Object.class);
767            }
768    
769            /**
770             * Returns an array containing their hierarchy of class objects for the
771             * given class object. The array is sorted in most specific class order, ie
772             * the first class is the class object itself, followed by its super class,
773             * all the way down to stopClass.
774             * <p>
775             * If stopClass is not a superclass or superinterface of targetClass, then
776             * Object.class is used.
777             *
778             * @param targetClass -
779             *            the class to start the hierarchial search from
780             * @param stopClass -
781             *            the supertype to stop at.
782             * @return and array of Class or Class[0] if there are none
783             */
784            public static Class[] getClassHierarchy(Class targetClass, Class stopClass) {
785                    if (targetClass == null || stopClass == null)
786                            return new Class[0];
787    
788                    if (targetClass == stopClass)
789                            return new Class[] { targetClass };
790    
791                    if (!stopClass.isAssignableFrom(targetClass))
792                            stopClass = Object.class;
793    
794                    List list = new ArrayList();
795                    list.add(targetClass);
796                    do {
797                            targetClass = targetClass.getSuperclass();
798                            if (targetClass != null)
799                                    list.add(targetClass);
800                            if (targetClass.equals(stopClass))
801                                    break;
802                    } while (targetClass != null);
803                    return (Class[]) list.toArray(new Class[list.size()]);
804            }
805    
806            /**
807             * This method can be called to determine whether an object has the specific
808             * named method. This is useful in allowing you to conditionally call a
809             * method that may be present in a future version of a class. For example
810             * when you code is built to Java 1.3, you could call a Java 1.4 API
811             * conditionally if its present.
812             *
813             * @param methodName -
814             *            the name of the method to invoke
815             * @param paramTypes -
816             *            the types of the methods parameters if this is null then it is
817             *            deemed Class[0]
818             * @param returnType -
819             *            the methods return type, if this is null then it is deemed
820             *            Void.TYPE
821             * @param targetObj -
822             *            the object to invoke the method on
823             * @return - the return value of the method if any or null.
824             *
825             */
826            public static boolean hasMethod(String methodName, Class[] paramTypes, Class returnType, Object targetObj) {
827                    if (targetObj == null)
828                            throw new IllegalArgumentException("You must provide a target Object!");
829                    if (paramTypes == null)
830                            paramTypes = new Class[0];
831                    if (returnType == null)
832                            returnType = Void.TYPE;
833    
834                    final String testMethodName = methodName;
835                    final Class[] testParamTypes = paramTypes;
836                    final Class testReturnType = returnType;
837                    Method[] methods = getMethods(targetObj.getClass(), Object.class, new MethodSearchCriteria() {
838                            /**
839                             * @see echopoint.util.ReflectionKit.MethodSearchCriteria#isMethodOK(java.lang.Class,
840                             *      java.lang.reflect.Method)
841                             */
842                            public boolean isMethodOK(Class methodClass, Method method) {
843                                    if (method.getName().equals(testMethodName)) {
844                                            if (method.getReturnType().equals(testReturnType)) {
845                                                    Class[] paramTypes = method.getParameterTypes();
846                                                    if (paramTypes.length == testParamTypes.length) {
847                                                            for (int i = 0; i < paramTypes.length; i++) {
848                                                                    if (!testParamTypes[i].equals(paramTypes[i]))
849                                                                            return false;
850                                                            }
851                                                            return true;
852                                                    }
853                                            }
854                                    }
855                                    return false;
856                            }
857                    });
858                    if (methods.length > 0) {
859                            return true;
860                    }
861                    return false;
862            }
863    
864            /**
865             * This method can be called to invoke a specific named method of an object.
866             * This is useful in allowing you to conditionally call a method that may be
867             * present in a future version of a class. For example when you code is
868             * built to Java 1.3, you could call a Java 1.4 API conditionally if its
869             * present.
870             *
871             * @param methodName -
872             *            the name of the method to invoke
873             * @param paramTypes -
874             *            the types of the methods parameters if this is null then it is
875             *            deemed Class[0]
876             * @param returnType -
877             *            the methods return type, if this is null then it is deemed
878             *            Void.TYPE
879             * @param targetObj -
880             *            the object to invoke the method on
881             * @param params -
882             *            the parameters for the method if this is null then it is
883             *            deemed Object[0]
884             * @return - the return value of the method if any or null.
885             *
886             */
887            public static Object invokeIfPresent(String methodName, Class[] paramTypes, Class returnType, Object targetObj, Object[] params) {
888                    if (targetObj == null)
889                            throw new IllegalArgumentException("You must provide a target Object!");
890                    if (paramTypes == null)
891                            paramTypes = new Class[0];
892                    if (params == null)
893                            params = new Object[0];
894                    if (returnType == null)
895                            returnType = Void.TYPE;
896    
897                    final String testMethodName = methodName;
898                    final Class[] testParamTypes = paramTypes;
899                    final Class testReturnType = returnType;
900                    Method[] methods = getMethods(targetObj.getClass(), Object.class, new MethodSearchCriteria() {
901                            /**
902                             * @see echopoint.util.ReflectionKit.MethodSearchCriteria#isMethodOK(java.lang.Class,
903                             *      java.lang.reflect.Method)
904                             */
905                            public boolean isMethodOK(Class methodClass, Method method) {
906                                    if (method.getName().equals(testMethodName)) {
907                                            if (method.getReturnType().equals(testReturnType)) {
908                                                    Class[] paramTypes = method.getParameterTypes();
909                                                    if (paramTypes.length == testParamTypes.length) {
910                                                            for (int i = 0; i < paramTypes.length; i++) {
911                                                                    if (!testParamTypes[i].equals(paramTypes[i]))
912                                                                            return false;
913                                                            }
914                                                            return true;
915                                                    }
916                                            }
917                                    }
918                                    return false;
919                            }
920                    });
921                    if (methods.length > 0) {
922                            try {
923                                    return methods[0].invoke(targetObj, params);
924                            } catch (IllegalArgumentException e) {
925                                    throw new RuntimeException(e);
926                            } catch (IllegalAccessException e) {
927                                    throw new RuntimeException(e);
928                            } catch (InvocationTargetException e) {
929                                    throw new RuntimeException(e);
930                            }
931                    }
932                    return null;
933            }
934    }