View Javadoc

1   /*
2    * $Id: ClassUtils.java 12253 2008-07-08 04:49:39Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.util;
12  
13  import org.mule.routing.filters.WildcardFilter;
14  
15  import java.io.BufferedReader;
16  import java.io.CharArrayReader;
17  import java.io.IOException;
18  import java.io.Reader;
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.net.URL;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Enumeration;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  /**
36   * Extend the Apache Commons ClassUtils to provide additional functionality.
37   * <p/>
38   * <p>This class is useful for loading resources and classes in a fault tolerant manner
39   * that works across different applications servers. The resource and classloading
40   * methods are SecurityManager friendly.</p>
41   */
42  // @ThreadSafe
43  public class ClassUtils extends org.apache.commons.lang.ClassUtils
44  {
45      public static final Object[] NO_ARGS = new Object[]{};
46      public static final Class[] NO_ARGS_TYPE = new Class[]{};
47  
48      private static final Map wrapperToPrimitiveMap = new HashMap();
49  
50      static
51      {
52          wrapperToPrimitiveMap.put(Boolean.class, Boolean.TYPE);
53          wrapperToPrimitiveMap.put(Byte.class, Byte.TYPE);
54          wrapperToPrimitiveMap.put(Character.class, Character.TYPE);
55          wrapperToPrimitiveMap.put(Short.class, Short.TYPE);
56          wrapperToPrimitiveMap.put(Integer.class, Integer.TYPE);
57          wrapperToPrimitiveMap.put(Long.class, Long.TYPE);
58          wrapperToPrimitiveMap.put(Double.class, Double.TYPE);
59          wrapperToPrimitiveMap.put(Float.class, Float.TYPE);
60          wrapperToPrimitiveMap.put(Void.TYPE, Void.TYPE);
61      }
62  
63      public static boolean isConcrete(Class clazz)
64      {
65          if (clazz == null)
66          {
67              throw new IllegalArgumentException("clazz may not be null");
68          }
69          return !(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()));
70      }
71  
72      /**
73       * Load a given resource. <p/> This method will try to load the resource using
74       * the following methods (in order):
75       * <ul>
76       * <li>From
77       * {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()}
78       * <li>From
79       * {@link Class#getClassLoader() ClassUtils.class.getClassLoader()}
80       * <li>From the {@link Class#getClassLoader() callingClass.getClassLoader() }
81       * </ul>
82       *
83       * @param resourceName The name of the resource to load
84       * @param callingClass The Class object of the calling object
85       */
86      public static URL getResource(final String resourceName, final Class callingClass)
87      {
88          URL url = (URL) AccessController.doPrivileged(new PrivilegedAction()
89          {
90              public Object run()
91              {
92                  final ClassLoader cl = Thread.currentThread().getContextClassLoader();
93                  return cl != null ? cl.getResource(resourceName) : null;
94              }
95          });
96  
97          if (url == null)
98          {
99              url = (URL) AccessController.doPrivileged(new PrivilegedAction()
100             {
101                 public Object run()
102                 {
103                     return ClassUtils.class.getClassLoader().getResource(resourceName);
104                 }
105             });
106         }
107 
108         if (url == null)
109         {
110             url = (URL) AccessController.doPrivileged(new PrivilegedAction()
111             {
112                 public Object run()
113                 {
114                     return callingClass.getClassLoader().getResource(resourceName);
115                 }
116             });
117         }
118 
119         return url;
120     }
121 
122     public static Enumeration getResources(final String resourceName, final Class callingClass)
123     {
124         Enumeration enumeration = (Enumeration) AccessController.doPrivileged(new PrivilegedAction()
125         {
126             public Object run()
127             {
128                 try
129                 {
130                     final ClassLoader cl = Thread.currentThread().getContextClassLoader();
131                     return cl != null ? cl.getResources(resourceName) : null;
132                 }
133                 catch (IOException e)
134                 {
135                     return null;
136                 }
137             }
138         });
139 
140         if (enumeration == null)
141         {
142             enumeration = (Enumeration) AccessController.doPrivileged(new PrivilegedAction()
143             {
144                 public Object run()
145                 {
146                     try
147                     {
148                         return ClassUtils.class.getClassLoader().getResources(resourceName);
149                     }
150                     catch (IOException e)
151                     {
152                         return null;
153                     }
154                 }
155             });
156         }
157 
158         if (enumeration == null)
159         {
160             enumeration = (Enumeration) AccessController.doPrivileged(new PrivilegedAction()
161             {
162                 public Object run()
163                 {
164                     try
165                     {
166                         return callingClass.getClassLoader().getResources(resourceName);
167                     }
168                     catch (IOException e)
169                     {
170                         return null;
171                     }
172                 }
173             });
174         }
175 
176         return enumeration;
177     }
178 
179     /**
180      * Load a class with a given name. <p/> It will try to load the class in the
181      * following order:
182      * <ul>
183      * <li>From
184      * {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()}
185      * <li>Using the basic {@link Class#forName(java.lang.String) }
186      * <li>From
187      * {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()}
188      * <li>From the {@link Class#getClassLoader() callingClass.getClassLoader() }
189      * </ul>
190      *
191      * @param className    The name of the class to load
192      * @param callingClass The Class object of the calling object
193      * @throws ClassNotFoundException If the class cannot be found anywhere.
194      */
195     public static Class loadClass(final String className, final Class callingClass)
196             throws ClassNotFoundException
197     {
198         Class clazz = (Class) AccessController.doPrivileged(new PrivilegedAction()
199         {
200             public Object run()
201             {
202                 try
203                 {
204                     final ClassLoader cl = Thread.currentThread().getContextClassLoader();
205                     return cl != null ? cl.loadClass(className) : null;
206 
207                 }
208                 catch (ClassNotFoundException e)
209                 {
210                     return null;
211                 }
212             }
213         });
214 
215         if (clazz == null)
216         {
217             clazz = (Class) AccessController.doPrivileged(new PrivilegedAction()
218             {
219                 public Object run()
220                 {
221                     try
222                     {
223                         return Class.forName(className);
224                     }
225                     catch (ClassNotFoundException e)
226                     {
227                         return null;
228                     }
229                 }
230             });
231         }
232 
233         if (clazz == null)
234         {
235             clazz = (Class) AccessController.doPrivileged(new PrivilegedAction()
236             {
237                 public Object run()
238                 {
239                     try
240                     {
241                         return ClassUtils.class.getClassLoader().loadClass(className);
242                     }
243                     catch (ClassNotFoundException e)
244                     {
245                         return null;
246                     }
247                 }
248             });
249         }
250 
251         if (clazz == null)
252         {
253             clazz = (Class) AccessController.doPrivileged(new PrivilegedAction()
254             {
255                 public Object run()
256                 {
257                     try
258                     {
259                         return callingClass.getClassLoader().loadClass(className);
260                     }
261                     catch (ClassNotFoundException e)
262                     {
263                         return null;
264                     }
265                 }
266             });
267         }
268 
269         if (clazz == null)
270         {
271             throw new ClassNotFoundException(className);
272         }
273 
274         return clazz;
275     }
276 
277     /** Prints the current classloader hierarchy - useful for debugging. */
278     public static void printClassLoader()
279     {
280         System.out.println("ClassLoaderUtils.printClassLoader");
281         printClassLoader(Thread.currentThread().getContextClassLoader());
282     }
283 
284     /**
285      * Prints the classloader hierarchy from a given classloader - useful for
286      * debugging.
287      */
288     public static void printClassLoader(ClassLoader cl)
289     {
290         System.out.println("ClassLoaderUtils.printClassLoader(cl = " + cl + ")");
291 
292         if (cl != null)
293         {
294             printClassLoader(cl.getParent());
295         }
296     }
297 
298     /**
299      * Ensure that the given class is properly initialized when the argument is passed in
300      * as .class literal. This method can never fail unless the bytecode is corrupted or
301      * the VM is otherwise seriously confused.
302      * 
303      * @param clazz the Class to be initialized
304      * @return the same class but initialized
305      */
306     public static Class initializeClass(Class clazz)
307     {
308         try
309         {
310             return getClass(clazz.getName(), true);
311         }
312         catch (ClassNotFoundException e)
313         {
314             IllegalStateException ise = new IllegalStateException();
315             ise.initCause(e);
316             throw ise;
317         }
318     }
319 
320     public static Object instanciateClass(Class clazz, Object[] constructorArgs)
321             throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException,
322             IllegalAccessException, InvocationTargetException
323     {
324         Class[] args;
325         if (constructorArgs != null)
326         {
327             args = new Class[constructorArgs.length];
328             for (int i = 0; i < constructorArgs.length; i++)
329             {
330                 if (constructorArgs[i] == null)
331                 {
332                     args[i] = null;
333                 }
334                 else
335                 {
336                     args[i] = constructorArgs[i].getClass();
337                 }
338             }
339         }
340         else
341         {
342             args = new Class[0];
343         }
344 
345         // try the arguments as given
346         Constructor ctor = getConstructor(clazz, args);
347 
348         if (ctor == null)
349         {
350             // try again but adapt value classes to primitives
351             ctor = getConstructor(clazz, wrappersToPrimitives(args));
352         }
353 
354         if (ctor == null)
355         {
356             StringBuffer argsString = new StringBuffer(100);
357             for (int i = 0; i < args.length; i++)
358             {
359                 argsString.append(args[i].getName()).append(", ");
360             }
361             throw new NoSuchMethodException("could not find constructor with matching arg params: "
362                     + argsString);
363         }
364 
365         return ctor.newInstance(constructorArgs);
366     }
367 
368     public static Object instanciateClass(String name, Object[] constructorArgs)
369             throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
370             InstantiationException, IllegalAccessException, InvocationTargetException
371     {
372         Class clazz = loadClass(name, ClassUtils.class);
373         return instanciateClass(clazz, constructorArgs);
374 
375     }
376 
377     public static Object instanciateClass(String name, Object[] constructorArgs, Class callingClass)
378             throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
379             InstantiationException, IllegalAccessException, InvocationTargetException
380     {
381         Class clazz = loadClass(name, callingClass);
382         return instanciateClass(clazz, constructorArgs);
383     }
384 
385     public static Class[] getParameterTypes(Object bean, String methodName)
386     {
387         if (!methodName.startsWith("set"))
388         {
389             methodName = "set" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
390         }
391 
392         Method methods[] = bean.getClass().getMethods();
393 
394         for (int i = 0; i < methods.length; i++)
395         {
396             if (methods[i].getName().equals(methodName))
397             {
398                 return methods[i].getParameterTypes();
399             }
400         }
401 
402         return new Class[]{};
403     }
404 
405     /**
406      * Returns a matching method for the given name and parameters on the given class
407      * If the parameterTypes arguments is null it will return the first matching
408      * method on the class.
409      *
410      * @param clazz          the class to find the method on
411      * @param name           the method name to find
412      * @param parameterTypes an array of argument types or null
413      * @return the Method object or null if none was found
414      */
415     public static Method getMethod(Class clazz, String name, Class[] parameterTypes)
416     {
417         Method[] methods = clazz.getMethods();
418         for (int i = 0; i < methods.length; i++)
419         {
420             if (methods[i].getName().equals(name))
421             {
422                 if (parameterTypes == null)
423                 {
424                     return methods[i];
425                 }
426                 else if (compare(methods[i].getParameterTypes(), parameterTypes, true))
427                 {
428                     return methods[i];
429                 }
430             }
431         }
432         return null;
433     }
434 
435     public static Constructor getConstructor(Class clazz, Class[] paramTypes)
436     {
437         Constructor[] ctors = clazz.getConstructors();
438         for (int i = 0; i < ctors.length; i++)
439         {
440             Class[] types = ctors[i].getParameterTypes();
441             if (types.length == paramTypes.length)
442             {
443                 boolean match = true;
444                 for (int x = 0; x < types.length; x++)
445                 {
446                     if (paramTypes[x] == null)
447                     {
448                         match = true;
449                     }
450                     else
451                     {
452                         match = types[x].isAssignableFrom(paramTypes[x]);
453                     }
454                 }
455                 if (match)
456                 {
457                     return ctors[i];
458                 }
459             }
460         }
461         return null;
462     }
463 
464     /**
465      * A helper method that will find all matching methods on a class with the given
466      * parameter type
467      *
468      * @param implementation     the class to build methods on
469      * @param parameterTypes     the argument param types to look for
470      * @param voidOk             whether void methods shouldbe included in the found list
471      * @param matchOnObject      determines whether parameters of Object type are matched
472      *                           when they are of Object.class type
473      * @param ignoredMethodNames a Set of method names to ignore. Often 'equals' is
474      *                           not a desired match. This argument can be null.
475      * @return a List of methods on the class that match the criteria. If there are
476      *         none, an empty list is returned
477      */
478     public static List getSatisfiableMethods(Class implementation,
479                                              Class[] parameterTypes,
480                                              boolean voidOk,
481                                              boolean matchOnObject,
482                                              Set ignoredMethodNames)
483     {
484         return getSatisfiableMethods(implementation, parameterTypes, voidOk, matchOnObject, ignoredMethodNames, null);
485     }
486 
487     /**
488      * A helper method that will find all matching methods on a class with the given
489      * parameter type
490      *
491      * @param implementation     the class to build methods on
492      * @param parameterTypes     the argument param types to look for
493      * @param voidOk             whether void methods shouldbe included in the found list
494      * @param matchOnObject      determines whether parameters of Object type are matched
495      *                           when they are of Object.class type
496      * @param ignoredMethodNames a Set of method names to ignore. Often 'equals' is
497      *                           not a desired match. This argument can be null.
498      * @return a List of methods on the class that match the criteria. If there are
499      *         none, an empty list is returned
500      */
501     public static List getSatisfiableMethods(Class implementation,
502                                              Class[] parameterTypes,
503                                              boolean voidOk,
504                                              boolean matchOnObject,
505                                              Collection ignoredMethodNames,
506                                              WildcardFilter filter)
507     {
508 
509 
510         List result = new ArrayList();
511 
512         if (ignoredMethodNames == null)
513         {
514             ignoredMethodNames = Collections.EMPTY_SET;
515         }
516 
517         Method[] methods = implementation.getMethods();
518         for (int i = 0; i < methods.length; i++)
519         {
520             Method method = methods[i];
521             //supporting wildcards
522             if (filter != null && filter.accept(method.getName()))
523             {
524                 continue;
525             }
526             Class[] methodParams = method.getParameterTypes();
527 
528             if (compare(methodParams, parameterTypes, matchOnObject))
529             {
530                 if (!ignoredMethodNames.contains(method.getName()))
531                 {
532                     String returnType = method.getReturnType().getName();
533                     if ((returnType.equals("void") && voidOk) || !returnType.equals("void"))
534                     {
535                         result.add(method);
536                     }
537                 }
538             }
539         }
540 
541         return result;
542     }
543 
544     public static List getSatisfiableMethodsWithReturnType(Class implementation,
545                                                            Class returnType,
546                                                            boolean matchOnObject,
547                                                            Set ignoredMethodNames)
548     {
549         List result = new ArrayList();
550 
551         if (ignoredMethodNames == null)
552         {
553             ignoredMethodNames = Collections.EMPTY_SET;
554         }
555 
556         Method[] methods = implementation.getMethods();
557         for (int i = 0; i < methods.length; i++)
558         {
559             Method method = methods[i];
560             Class returns = method.getReturnType();
561 
562             if (compare(new Class[]{returns}, new Class[]{returnType}, matchOnObject))
563             {
564                 if (!ignoredMethodNames.contains(method.getName()))
565                 {
566                     result.add(method);
567                 }
568             }
569         }
570 
571         return result;
572     }
573 
574     /**
575      * Can be used by serice endpoints to select which service to use based on what's
576      * loaded on the classpath
577      *
578      * @param className    The class name to look for
579      * @param currentClass the calling class
580      * @return true if the class is on the path
581      */
582     public static boolean isClassOnPath(String className, Class currentClass)
583     {
584         try
585         {
586             return (loadClass(className, currentClass) != null);
587         }
588         catch (ClassNotFoundException e)
589         {
590             return false;
591         }
592     }
593 
594     /**
595      * Used for creating an array of class types for an array or single object
596      *
597      * @param object single object or array. If this parameter is null or a zero length
598      *               array then {@link #NO_ARGS_TYPE} is returned
599      * @return an array of class types for the object
600      */
601     public static Class[] getClassTypes(Object object)
602     {
603         if (object == null)
604         {
605             return NO_ARGS_TYPE;
606         }
607 
608         Class[] types;
609 
610         if (object instanceof Object[])
611         {
612             Object[] objects = (Object[]) object;
613             if (objects.length == 0)
614             {
615                 return NO_ARGS_TYPE;
616             }
617             types = new Class[objects.length];
618             for (int i = 0; i < objects.length; i++)
619             {
620                 types[i] = objects[i].getClass();
621             }
622         }
623         else
624         {
625             types = new Class[]{object.getClass()};
626         }
627 
628         return types;
629     }
630 
631     public static String getClassName(Class clazz)
632     {
633         if (clazz == null)
634         {
635             return null;
636         }
637         String name = clazz.getName();
638         return name.substring(name.lastIndexOf(".") + 1);
639     }
640 
641     public static boolean compare(Class[] c1, Class[] c2, boolean matchOnObject)
642     {
643         if (c1.length != c2.length)
644         {
645             return false;
646         }
647         for (int i = 0; i < c1.length; i++)
648         {
649             if (c1[i].equals(Object.class) && !matchOnObject)
650             {
651                 return false;
652             }
653             if (!c1[i].isAssignableFrom(c2[i]))
654             {
655 
656                 return false;
657             }
658         }
659         return true;
660     }
661 
662     public static Class wrapperToPrimitive(Class wrapper)
663     {
664         return (Class) MapUtils.getObject(wrapperToPrimitiveMap, wrapper, wrapper);
665     }
666 
667     public static Class[] wrappersToPrimitives(Class[] wrappers)
668     {
669         if (wrappers == null)
670         {
671             return null;
672         }
673 
674         if (wrappers.length == 0)
675         {
676             return wrappers;
677         }
678 
679         Class[] primitives = new Class[wrappers.length];
680 
681         for (int i = 0; i < wrappers.length; i++)
682         {
683             primitives[i] = (Class) MapUtils.getObject(wrapperToPrimitiveMap, wrappers[i], wrappers[i]);
684         }
685 
686         return primitives;
687     }
688 
689     /**
690      * Provide a simple-to-understand class name (with access to only Java 1.4 API).
691      *
692      * @param clazz The class whose name we will generate
693      * @return A readable name for the class
694      */
695     public static String getSimpleName(Class clazz)
696     {
697         if (null == clazz)
698         {
699             return "null";
700         }
701         else
702         {
703             return classNameHelper(new BufferedReader(new CharArrayReader(clazz.getName().toCharArray())));
704         }
705     }
706 
707     private static String classNameHelper(Reader encodedName)
708     {
709         // I did consider separating this data from the code, but I could not find a
710         // solution that was as clear to read, or clearly motivated (these data are not
711         // used elsewhere).
712 
713         try
714         {
715             encodedName.mark(1);
716             switch (encodedName.read())
717             {
718                 case -1:
719                     return "null";
720                 case 'Z':
721                     return "boolean";
722                 case 'B':
723                     return "byte";
724                 case 'C':
725                     return "char";
726                 case 'D':
727                     return "double";
728                 case 'F':
729                     return "float";
730                 case 'I':
731                     return "int";
732                 case 'J':
733                     return "long";
734                 case 'S':
735                     return "short";
736                 case '[':
737                     return classNameHelper(encodedName) + "[]";
738                 case 'L':
739                     return shorten(new BufferedReader(encodedName).readLine());
740                 default:
741                     encodedName.reset();
742                     return shorten(new BufferedReader(encodedName).readLine());
743             }
744         }
745         catch (IOException e)
746         {
747             return "unknown type: " + e.getMessage();
748         }
749     }
750 
751     /**
752      * @param clazz A class name (with possible package and trailing semicolon)
753      * @return The short name for the class
754      */
755     private static String shorten(String clazz)
756     {
757         if (null != clazz && clazz.endsWith(";"))
758         {
759             clazz = clazz.substring(0, clazz.length() - 1);
760         }
761         if (null != clazz && clazz.lastIndexOf(".") > -1)
762         {
763             clazz = clazz.substring(clazz.lastIndexOf(".") + 1, clazz.length());
764         }
765         return clazz;
766     }
767 
768     /**
769      * Is there a better place for this?  Simple helper for writing object equalities.
770      */
771     public static boolean equal(Object a, Object b)
772     {
773         if (null == a)
774         {
775             return null == b;
776         }
777         else
778         {
779             return null != b && a.equals(b);
780         }
781     }
782 
783     public static int hash(Object[] state)
784     {
785         int hash = 0;
786         for (int i = 0; i < state.length; ++i)
787         {
788             hash = hash * 31 + (null == state[i] ? 0 : state[i].hashCode());
789         }
790         return hash;
791     }
792 
793 }