View Javadoc

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