View Javadoc

1   /*
2    * $Id: ClassUtils.java 19191 2010-08-25 21:05:23Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.net.URLClassLoader;
25  import java.security.AccessController;
26  import java.security.CodeSource;
27  import java.security.PrivilegedAction;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  /**
39   * Extend the Apache Commons ClassUtils to provide additional functionality.
40   * <p/>
41   * <p>This class is useful for loading resources and classes in a fault tolerant manner
42   * that works across different applications servers. The resource and classloading
43   * methods are SecurityManager friendly.</p>
44   */
45  // @ThreadSafe
46  public class ClassUtils extends org.apache.commons.lang.ClassUtils
47  {
48      public static final Object[] NO_ARGS = new Object[]{};
49      public static final Class<?>[] NO_ARGS_TYPE = new Class<?>[]{};
50  
51      private static final Map<Class<?>, Class<?>> wrapperToPrimitiveMap = new HashMap<Class<?>, Class<?>>();
52  
53      static
54      {
55          wrapperToPrimitiveMap.put(Boolean.class, Boolean.TYPE);
56          wrapperToPrimitiveMap.put(Byte.class, Byte.TYPE);
57          wrapperToPrimitiveMap.put(Character.class, Character.TYPE);
58          wrapperToPrimitiveMap.put(Short.class, Short.TYPE);
59          wrapperToPrimitiveMap.put(Integer.class, Integer.TYPE);
60          wrapperToPrimitiveMap.put(Long.class, Long.TYPE);
61          wrapperToPrimitiveMap.put(Double.class, Double.TYPE);
62          wrapperToPrimitiveMap.put(Float.class, Float.TYPE);
63          wrapperToPrimitiveMap.put(Void.TYPE, Void.TYPE);
64      }
65  
66      public static boolean isConcrete(Class<?> clazz)
67      {
68          if (clazz == null)
69          {
70              throw new IllegalArgumentException("clazz may not be null");
71          }
72          return !(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()));
73      }
74  
75      /**
76       * Load a given resource. <p/> This method will try to load the resource using
77       * the following methods (in order):
78       * <ul>
79       * <li>From
80       * {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()}
81       * <li>From
82       * {@link Class#getClassLoader() ClassUtils.class.getClassLoader()}
83       * <li>From the {@link Class#getClassLoader() callingClass.getClassLoader() }
84       * </ul>
85       *
86       * @param resourceName The name of the resource to load
87       * @param callingClass The Class object of the calling object
88       *
89       * @return A URL pointing to the resource to load or null if the resource is not found
90       */
91      public static URL getResource(final String resourceName, final Class<?> callingClass)
92      {
93          URL url = AccessController.doPrivileged(new PrivilegedAction<URL>()
94          {
95              public URL run()
96              {
97                  final ClassLoader cl = Thread.currentThread().getContextClassLoader();
98                  return cl != null ? cl.getResource(resourceName) : null;
99              }
100         });
101 
102         if (url == null)
103         {
104             url = AccessController.doPrivileged(new PrivilegedAction<URL>()
105             {
106                 public URL run()
107                 {
108                     return ClassUtils.class.getClassLoader().getResource(resourceName);
109                 }
110             });
111         }
112 
113         if (url == null)
114         {
115             url = AccessController.doPrivileged(new PrivilegedAction<URL>()
116             {
117                 public URL run()
118                 {
119                     return callingClass.getClassLoader().getResource(resourceName);
120                 }
121             });
122         }
123 
124         return url;
125     }
126 
127     public static Enumeration<URL> getResources(final String resourceName, final Class<?> callingClass)
128     {
129         Enumeration<URL> enumeration = AccessController.doPrivileged(new PrivilegedAction<Enumeration<URL>>()
130         {
131             public Enumeration<URL> run()
132             {
133                 try
134                 {
135                     final ClassLoader cl = Thread.currentThread().getContextClassLoader();
136                     return cl != null ? cl.getResources(resourceName) : null;
137                 }
138                 catch (IOException e)
139                 {
140                     return null;
141                 }
142             }
143         });
144 
145         if (enumeration == null)
146         {
147             enumeration = AccessController.doPrivileged(new PrivilegedAction<Enumeration<URL>>()
148             {
149                 public Enumeration<URL> run()
150                 {
151                     try
152                     {
153                         return ClassUtils.class.getClassLoader().getResources(resourceName);
154                     }
155                     catch (IOException e)
156                     {
157                         return null;
158                     }
159                 }
160             });
161         }
162 
163         if (enumeration == null)
164         {
165             enumeration = AccessController.doPrivileged(new PrivilegedAction<Enumeration<URL>>()
166             {
167                 public Enumeration<URL> run()
168                 {
169                     try
170                     {
171                         return callingClass.getClassLoader().getResources(resourceName);
172                     }
173                     catch (IOException e)
174                     {
175                         return null;
176                     }
177                 }
178             });
179         }
180 
181         return enumeration;
182     }
183 
184     /**
185      * Load a class with a given name. <p/> It will try to load the class in the
186      * following order:
187      * <ul>
188      * <li>From
189      * {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()}
190      * <li>Using the basic {@link Class#forName(java.lang.String) }
191      * <li>From
192      * {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()}
193      * <li>From the {@link Class#getClassLoader() callingClass.getClassLoader() }
194      * </ul>
195      *
196      * @param className    The name of the class to load
197      * @param callingClass The Class object of the calling object
198      * @return The Class instance
199      * @throws ClassNotFoundException If the class cannot be found anywhere.
200      */
201     public static Class loadClass(final String className, final Class<?> callingClass) throws ClassNotFoundException
202     {
203         return loadClass(className, callingClass, Object.class);
204     }
205     /**
206      * Load a class with a given name. <p/> It will try to load the class in the
207      * following order:
208      * <ul>
209      * <li>From
210      * {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()}
211      * <li>Using the basic {@link Class#forName(java.lang.String) }
212      * <li>From
213      * {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()}
214      * <li>From the {@link Class#getClassLoader() callingClass.getClassLoader() }
215      * </ul>
216      *
217      * @param className    The name of the class to load
218      * @param callingClass The Class object of the calling object
219      * @param type the class type to expect to load
220      * @return The Class instance
221      * @throws ClassNotFoundException If the class cannot be found anywhere.
222      */
223     public static <T extends Class> T loadClass(final String className, final Class<?> callingClass, T type) throws ClassNotFoundException
224     {
225         Class<?> clazz = AccessController.doPrivileged(new PrivilegedAction<Class<?>>()
226         {
227             public Class<?> run()
228             {
229                 try
230                 {
231                     final ClassLoader cl = Thread.currentThread().getContextClassLoader();
232                     return cl != null ? cl.loadClass(className) : null;
233 
234                 }
235                 catch (ClassNotFoundException e)
236                 {
237                     return null;
238                 }
239             }
240         });
241 
242         if (clazz == null)
243         {
244             clazz = AccessController.doPrivileged(new PrivilegedAction<Class<?>>()
245             {
246                 public Class<?> run()
247                 {
248                     try
249                     {
250                         return Class.forName(className);
251                     }
252                     catch (ClassNotFoundException e)
253                     {
254                         return null;
255                     }
256                 }
257             });
258         }
259 
260         if (clazz == null)
261         {
262             clazz = AccessController.doPrivileged(new PrivilegedAction<Class<?>>()
263             {
264                 public Class<?> run()
265                 {
266                     try
267                     {
268                         return ClassUtils.class.getClassLoader().loadClass(className);
269                     }
270                     catch (ClassNotFoundException e)
271                     {
272                         return null;
273                     }
274                 }
275             });
276         }
277 
278         if (clazz == null)
279         {
280             clazz = AccessController.doPrivileged(new PrivilegedAction<Class<?>>()
281             {
282                 public Class<?> run()
283                 {
284                     try
285                     {
286                         return callingClass.getClassLoader().loadClass(className);
287                     }
288                     catch (ClassNotFoundException e)
289                     {
290                         return null;
291                     }
292                 }
293             });
294         }
295 
296         if (clazz == null)
297         {
298             throw new ClassNotFoundException(className);
299         }
300 
301         if(type.isAssignableFrom(clazz))
302         {
303             return (T)clazz;
304         }
305         else
306         {
307             throw new IllegalArgumentException(String.format("Loaded class '%s' is not assignable from type '%s'", clazz.getName(), type.getName()));
308         }
309     }
310 
311     /**
312      * Load a class with a given name from the given classloader.
313      *
314      * @param className the name of the class to load
315      * @param classLoader the loader to load it from
316      * @return the instance of the class
317      * @throws ClassNotFoundException if the class is not available in the class loader
318      */
319     public static Class loadClass(final String className, final ClassLoader classLoader)
320             throws ClassNotFoundException
321     {
322         return classLoader.loadClass(className);
323     }
324 
325 
326     /**
327      * Ensure that the given class is properly initialized when the argument is passed in
328      * as .class literal. This method can never fail unless the bytecode is corrupted or
329      * the VM is otherwise seriously confused.
330      *
331      * @param clazz the Class to be initialized
332      * @return the same class but initialized
333      */
334     public static Class<?> initializeClass(Class<?> clazz)
335     {
336         try
337         {
338             return getClass(clazz.getName(), true);
339         }
340         catch (ClassNotFoundException e)
341         {
342             IllegalStateException ise = new IllegalStateException();
343             ise.initCause(e);
344             throw ise;
345         }
346     }
347 
348     public static <T> T instanciateClass(Class<? extends T> clazz, Object... constructorArgs)
349             throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException,
350             IllegalAccessException, InvocationTargetException
351     {
352         Class<?>[] args;
353         if (constructorArgs != null)
354         {
355             args = new Class[constructorArgs.length];
356             for (int i = 0; i < constructorArgs.length; i++)
357             {
358                 if (constructorArgs[i] == null)
359                 {
360                     args[i] = null;
361                 }
362                 else
363                 {
364                     args[i] = constructorArgs[i].getClass();
365                 }
366             }
367         }
368         else
369         {
370             args = new Class[0];
371         }
372 
373         // try the arguments as given
374         //Constructor ctor = clazz.getConstructor(args);
375         Constructor<?> ctor = getConstructor(clazz, args);
376 
377         if (ctor == null)
378         {
379             // try again but adapt value classes to primitives
380             ctor = getConstructor(clazz, wrappersToPrimitives(args));
381         }
382 
383         if (ctor == null)
384         {
385             StringBuffer argsString = new StringBuffer(100);
386             for (Class<?> arg : args)
387             {
388                 argsString.append(arg.getName()).append(", ");
389             }
390             throw new NoSuchMethodException("could not find constructor on class: " + clazz + ", with matching arg params: "
391                     + argsString);
392         }
393 
394         return (T)ctor.newInstance(constructorArgs);
395     }
396 
397     public static Object instanciateClass(String name, Object... constructorArgs)
398             throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
399             InstantiationException, IllegalAccessException, InvocationTargetException
400     {
401         return instanciateClass(name, constructorArgs, (ClassLoader) null);
402     }
403 
404     public static Object instanciateClass(String name, Object[] constructorArgs, Class<?> callingClass)
405             throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
406             InstantiationException, IllegalAccessException, InvocationTargetException
407     {
408         Class<?> clazz = loadClass(name, callingClass);
409         return instanciateClass(clazz, constructorArgs);
410     }
411 
412     public static Object instanciateClass(String name, Object[] constructorArgs, ClassLoader classLoader)
413             throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
414             InstantiationException, IllegalAccessException, InvocationTargetException
415     {
416         Class<?> clazz;
417         if (classLoader != null)
418         {
419             clazz = loadClass(name, classLoader);
420         }
421         else
422         {
423             clazz = loadClass(name, ClassUtils.class);
424         }
425         if (clazz == null)
426         {
427             throw new ClassNotFoundException(name);
428         }
429         return instanciateClass(clazz, constructorArgs);
430     }
431 
432     public static Class<?>[] getParameterTypes(Object bean, String methodName)
433     {
434         if (!methodName.startsWith("set"))
435         {
436             methodName = "set" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
437         }
438 
439         Method methods[] = bean.getClass().getMethods();
440 
441         for (int i = 0; i < methods.length; i++)
442         {
443             if (methods[i].getName().equals(methodName))
444             {
445                 return methods[i].getParameterTypes();
446             }
447         }
448 
449         return new Class[]{};
450     }
451 
452     /**
453      * Returns a matching method for the given name and parameters on the given class
454      * If the parameterTypes arguments is null it will return the first matching
455      * method on the class.
456      *
457      * @param clazz          the class to find the method on
458      * @param name           the method name to find
459      * @param parameterTypes an array of argument types or null
460      * @return the Method object or null if none was found
461      */
462     public static Method getMethod(Class<?> clazz, String name, Class<?>[] parameterTypes)
463     {
464         Method[] methods = clazz.getMethods();
465         for (int i = 0; i < methods.length; i++)
466         {
467             if (methods[i].getName().equals(name))
468             {
469                 if (parameterTypes == null)
470                 {
471                     return methods[i];
472                 }
473                 else if (compare(methods[i].getParameterTypes(), parameterTypes, true))
474                 {
475                     return methods[i];
476                 }
477             }
478         }
479         return null;
480     }
481 
482     public static Constructor getConstructor(Class clazz, Class[] paramTypes)
483     {
484         return getConstructor(clazz, paramTypes, false);
485     }
486     
487     /**
488      *  Returns available constructor in the target class that as the parameters specified.
489      *
490      * @param clazz the class to search
491      * @param paramTypes the param types to match against
492      * @param exactMatch should exact types be used (i.e. equals rather than isAssignableFrom.)
493      * @return The matching constructor or null if no matching constructor is found
494      */
495     public static Constructor getConstructor(Class clazz, Class[] paramTypes, boolean exactMatch)
496     {
497         Constructor[] ctors = clazz.getConstructors();
498         for (int i = 0; i < ctors.length; i++)
499         {
500             Class[] types = ctors[i].getParameterTypes();
501             if (types.length == paramTypes.length)
502             {
503                 int matchCount = 0;
504                 for (int x = 0; x < types.length; x++)
505                 {
506                     if (paramTypes[x] == null)
507                     {
508                         matchCount++;
509                     }
510                     else
511                     {
512                         if (exactMatch)
513                         {
514                             if (paramTypes[x].equals(types[x]) || types[x].equals(paramTypes[x]))
515                             {
516                                 matchCount++;
517                             }
518                         }
519                         else
520                         {
521                             if (paramTypes[x].isAssignableFrom(types[x])
522                                 || types[x].isAssignableFrom(paramTypes[x]))
523                             {
524                                 matchCount++;
525                             }
526                         }
527                     }
528                 }
529                 if (matchCount == types.length)
530                 {
531                     return ctors[i];
532                 }
533             }
534         }
535         return null;
536     }
537 
538     /**
539      * A helper method that will find all matching methods on a class with the given
540      * parameter type
541      *
542      * @param implementation     the class to build methods on
543      * @param parameterTypes     the argument param types to look for
544      * @param voidOk             whether void methods shouldbe included in the found list
545      * @param matchOnObject      determines whether parameters of Object type are matched
546      *                           when they are of Object.class type
547      * @param ignoredMethodNames a Set of method names to ignore. Often 'equals' is
548      *                           not a desired match. This argument can be null.
549      * @return a List of methods on the class that match the criteria. If there are
550      *         none, an empty list is returned
551      */
552     public static List<Method> getSatisfiableMethods(Class<?> implementation,
553                                              Class<?>[] parameterTypes,
554                                              boolean voidOk,
555                                              boolean matchOnObject,
556                                              Set<String> ignoredMethodNames)
557     {
558         return getSatisfiableMethods(implementation, parameterTypes, voidOk, matchOnObject, ignoredMethodNames, null);
559     }
560 
561     /**
562      * A helper method that will find all matching methods on a class with the given
563      * parameter type
564      *
565      * @param implementation     the class to build methods on
566      * @param parameterTypes     the argument param types to look for
567      * @param voidOk             whether void methods shouldbe included in the found list
568      * @param matchOnObject      determines whether parameters of Object type are matched
569      *                           when they are of Object.class type
570      * @param ignoredMethodNames a Set of method names to ignore. Often 'equals' is
571      *                           not a desired match. This argument can be null.
572      * @param filter             Wildcard expression filter that allows methods to be matched using wildcards i.e. 'get*'
573      * @return a List of methods on the class that match the criteria. If there are
574      *         none, an empty list is returned
575      */
576     public static List<Method> getSatisfiableMethods(Class<?> implementation,
577                                              Class<?>[] parameterTypes,
578                                              boolean voidOk,
579                                              boolean matchOnObject,
580                                              Collection<String> ignoredMethodNames,
581                                              WildcardFilter filter)
582     {
583         List<Method> result = new ArrayList<Method>();
584 
585         if (ignoredMethodNames == null)
586         {
587             ignoredMethodNames = Collections.emptySet();
588         }
589 
590         Method[] methods = implementation.getMethods();
591         for (int i = 0; i < methods.length; i++)
592         {
593             Method method = methods[i];
594             //supporting wildcards
595             if (filter != null && filter.accept(method.getName()))
596             {
597                 continue;
598             }
599             Class<?>[] methodParams = method.getParameterTypes();
600 
601             if (compare(methodParams, parameterTypes, matchOnObject))
602             {
603                 if (!ignoredMethodNames.contains(method.getName()))
604                 {
605                     String returnType = method.getReturnType().getName();
606                     if ((returnType.equals("void") && voidOk) || !returnType.equals("void"))
607                     {
608                         result.add(method);
609                     }
610                 }
611             }
612         }
613 
614         return result;
615     }
616 
617     /**
618      * Match all method son a class with a defined return type
619      * @param implementation the class to search
620      * @param returnType the return type to match
621      * @param matchOnObject whether {@link Object} methods should be matched
622      * @param ignoredMethodNames a set of method names to ignore
623      * @return the list of methods that matched the return type and criteria. If none are found an empty result is returned
624      */
625     public static List<Method> getSatisfiableMethodsWithReturnType(Class implementation,
626                                                            Class returnType,
627                                                            boolean matchOnObject,
628                                                            Set<String> ignoredMethodNames)
629     {
630         List<Method> result = new ArrayList<Method>();
631 
632         if (ignoredMethodNames == null)
633         {
634             ignoredMethodNames = Collections.emptySet();
635         }
636 
637         Method[] methods = implementation.getMethods();
638         for (int i = 0; i < methods.length; i++)
639         {
640             Method method = methods[i];
641             Class returns = method.getReturnType();
642 
643             if (compare(new Class[]{returns}, new Class[]{returnType}, matchOnObject))
644             {
645                 if (!ignoredMethodNames.contains(method.getName()))
646                 {
647                     result.add(method);
648                 }
649             }
650         }
651 
652         return result;
653     }
654 
655     /**
656      * Can be used by serice endpoints to select which service to use based on what's
657      * loaded on the classpath
658      *
659      * @param className    The class name to look for
660      * @param currentClass the calling class
661      * @return true if the class is on the path
662      */
663     public static boolean isClassOnPath(String className, Class currentClass)
664     {
665         try
666         {
667             return (loadClass(className, currentClass) != null);
668         }
669         catch (ClassNotFoundException e)
670         {
671             return false;
672         }
673     }
674 
675     /**
676      * Used for creating an array of class types for an array or single object
677      *
678      * @param object single object or array. If this parameter is null or a zero length
679      *               array then {@link #NO_ARGS_TYPE} is returned
680      * @return an array of class types for the object
681      */
682     public static Class<?>[] getClassTypes(Object object)
683     {
684         if (object == null)
685         {
686             return NO_ARGS_TYPE;
687         }
688 
689         Class<?>[] types;
690 
691         if (object instanceof Object[])
692         {
693             Object[] objects = (Object[]) object;
694             if (objects.length == 0)
695             {
696                 return NO_ARGS_TYPE;
697             }
698             types = new Class[objects.length];
699             for (int i = 0; i < objects.length; i++)
700             {
701                 Object o = objects[i];
702                 if (o != null)
703                 {
704                     types[i] = o.getClass();
705                 }
706             }
707         }
708         else
709         {
710             types = new Class[]{object.getClass()};
711         }
712 
713         return types;
714     }
715 
716     public static String getClassName(Class clazz)
717     {
718         if (clazz == null)
719         {
720             return null;
721         }
722         String name = clazz.getName();
723         return name.substring(name.lastIndexOf(".") + 1);
724     }
725 
726     public static boolean compare(Class[] c1, Class[] c2, boolean matchOnObject)
727     {
728         if (c1.length != c2.length)
729         {
730             return false;
731         }
732         for (int i = 0; i < c1.length; i++)
733         {
734             if ((c1[i] == null) || (c2[i] == null))
735             {
736                 return false;
737             }
738             if (c1[i].equals(Object.class) && !matchOnObject)
739             {
740                 return false;
741             }
742             if (!c1[i].isAssignableFrom(c2[i]))
743             {
744                 return false;
745             }
746         }
747         return true;
748     }
749 
750     public static Class wrapperToPrimitive(Class wrapper)
751     {
752         return (Class) MapUtils.getObject(wrapperToPrimitiveMap, wrapper, wrapper);
753     }
754 
755     public static Class[] wrappersToPrimitives(Class[] wrappers)
756     {
757         if (wrappers == null)
758         {
759             return null;
760         }
761 
762         if (wrappers.length == 0)
763         {
764             return wrappers;
765         }
766 
767         Class[] primitives = new Class[wrappers.length];
768 
769         for (int i = 0; i < wrappers.length; i++)
770         {
771             primitives[i] = (Class) MapUtils.getObject(wrapperToPrimitiveMap, wrappers[i], wrappers[i]);
772         }
773 
774         return primitives;
775     }
776 
777     /**
778      * Provide a simple-to-understand class name (with access to only Java 1.4 API).
779      *
780      * @param clazz The class whose name we will generate
781      * @return A readable name for the class
782      */
783     public static String getSimpleName(Class clazz)
784     {
785         if (null == clazz)
786         {
787             return "null";
788         }
789         else
790         {
791             return classNameHelper(new BufferedReader(new CharArrayReader(clazz.getName().toCharArray())));
792         }
793     }
794 
795     private static String classNameHelper(Reader encodedName)
796     {
797         // I did consider separating this data from the code, but I could not find a
798         // solution that was as clear to read, or clearly motivated (these data are not
799         // used elsewhere).
800 
801         try
802         {
803             encodedName.mark(1);
804             switch (encodedName.read())
805             {
806                 case -1:
807                     return "null";
808                 case 'Z':
809                     return "boolean";
810                 case 'B':
811                     return "byte";
812                 case 'C':
813                     return "char";
814                 case 'D':
815                     return "double";
816                 case 'F':
817                     return "float";
818                 case 'I':
819                     return "int";
820                 case 'J':
821                     return "long";
822                 case 'S':
823                     return "short";
824                 case '[':
825                     return classNameHelper(encodedName) + "[]";
826                 case 'L':
827                     return shorten(new BufferedReader(encodedName).readLine());
828                 default:
829                     encodedName.reset();
830                     return shorten(new BufferedReader(encodedName).readLine());
831             }
832         }
833         catch (IOException e)
834         {
835             return "unknown type: " + e.getMessage();
836         }
837     }
838 
839     /**
840      * @param clazz A class name (with possible package and trailing semicolon)
841      * @return The short name for the class
842      */
843     private static String shorten(String clazz)
844     {
845         if (null != clazz && clazz.endsWith(";"))
846         {
847             clazz = clazz.substring(0, clazz.length() - 1);
848         }
849         if (null != clazz && clazz.lastIndexOf(".") > -1)
850         {
851             clazz = clazz.substring(clazz.lastIndexOf(".") + 1, clazz.length());
852         }
853         return clazz;
854     }
855 
856     /**
857      * Simple helper for writing object equalities.
858      *
859      * TODO Is there a better place for this?
860      * @param a object to compare
861      * @param b object to be compared to
862      * @return true if the objects are equal (value or reference), false otherwise
863      */
864     public static boolean equal(Object a, Object b)
865     {
866         if (null == a)
867         {
868             return null == b;
869         }
870         else
871         {
872             return null != b && a.equals(b);
873         }
874     }
875 
876     public static int hash(Object[] state)
877     {
878         int hash = 0;
879         for (int i = 0; i < state.length; ++i)
880         {
881             hash = hash * 31 + (null == state[i] ? 0 : state[i].hashCode());
882         }
883         return hash;
884     }
885 
886     public static void addLibrariesToClasspath(List urls) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
887     {
888         ClassLoader sys = ClassLoader.getSystemClassLoader();
889         if (!(sys instanceof URLClassLoader))
890         {
891             throw new IllegalArgumentException(
892                     "PANIC: Mule has been started with an unsupported classloader: " + sys.getClass().getName()
893                             + ". " + "Please report this error to user<at>mule<dot>codehaus<dot>org");
894         }
895 
896         // system classloader is in this case the one that launched the application,
897         // which is usually something like a JDK-vendor proprietary AppClassLoader
898         URLClassLoader sysCl = (URLClassLoader) sys;
899 
900         /*
901         * IMPORTANT NOTE: The more 'natural' way would be to create a custom
902         * URLClassLoader and configure it, but then there's a chicken-and-egg
903         * problem, as all classes MuleBootstrap depends on would have been loaded by
904         * a parent classloader, and not ours. There's no straightforward way to
905         * change this, and is documented in a Sun's classloader guide. The solution
906         * would've involved overriding the ClassLoader.findClass() method and
907         * modifying the semantics to be child-first, but that way we are calling for
908         * trouble. Hacking the primordial classloader is a bit brutal, but works
909         * perfectly in case of running from the command-line as a standalone app.
910         * All Mule embedding options then delegate the classpath config to the
911         * embedder (a developer embedding Mule in the app), thus classloaders are
912         * not modified in those scenarios.
913         */
914 
915         // get a Method ref from the normal class, but invoke on a proprietary parent
916         // object,
917         // as this method is usually protected in those classloaders
918         Class refClass = URLClassLoader.class;
919         Method methodAddUrl = refClass.getDeclaredMethod("addURL", new Class[]{URL.class});
920         methodAddUrl.setAccessible(true);
921         for (Iterator it = urls.iterator(); it.hasNext();)
922         {
923             URL url = (URL) it.next();
924             methodAddUrl.invoke(sysCl, url);
925         }
926     }
927 
928     // this is a shorter version of the snippet from:
929     // http://www.davidflanagan.com/blog/2005_06.html#000060
930     // (see comments; DF's "manual" version works fine too)
931     public static URL getClassPathRoot(Class clazz)
932     {
933         CodeSource cs = clazz.getProtectionDomain().getCodeSource();
934         return (cs != null ? cs.getLocation() : null);
935     }
936 }