View Javadoc

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