View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.shared.util;
20  
21  import javax.el.ExpressionFactory;
22  import javax.faces.FacesException;
23  import javax.faces.context.FacesContext;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.lang.reflect.Array;
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.InvocationTargetException;
29  import java.net.URL;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.Enumeration;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.logging.Level;
39  import java.util.logging.Logger;
40  
41  
42  public final class ClassUtils
43  {
44      //~ Static fields/initializers -----------------------------------------------------------------
45  
46      //private static final Log log                  = LogFactory.getLog(ClassUtils.class);
47      private static final Logger log                  = Logger.getLogger(ClassUtils.class.getName());
48  
49      public static final Class BOOLEAN_ARRAY_CLASS = boolean[].class;
50      public static final Class BYTE_ARRAY_CLASS    = byte[].class;
51      public static final Class CHAR_ARRAY_CLASS    = char[].class;
52      public static final Class SHORT_ARRAY_CLASS   = short[].class;
53      public static final Class INT_ARRAY_CLASS     = int[].class;
54      public static final Class LONG_ARRAY_CLASS    = long[].class;
55      public static final Class FLOAT_ARRAY_CLASS   = float[].class;
56      public static final Class DOUBLE_ARRAY_CLASS  = double[].class;
57      public static final Class OBJECT_ARRAY_CLASS  = Object[].class;
58      public static final Class BOOLEAN_OBJECT_ARRAY_CLASS = Boolean[].class;
59      public static final Class BYTE_OBJECT_ARRAY_CLASS = Byte[].class;
60      public static final Class CHARACTER_OBJECT_ARRAY_CLASS = Character[].class;
61      public static final Class SHORT_OBJECT_ARRAY_CLASS = Short[].class;
62      public static final Class INTEGER_OBJECT_ARRAY_CLASS = Integer[].class;
63      public static final Class LONG_OBJECT_ARRAY_CLASS = Long[].class;
64      public static final Class FLOAT_OBJECT_ARRAY_CLASS = Float[].class;
65      public static final Class DOUBLE_OBJECT_ARRAY_CLASS = Double[].class;
66      public static final Class STRING_OBJECT_ARRAY_CLASS = String[].class;
67  
68      public static ClassLoaderExtension [] classLoadingExtensions = new ClassLoaderExtension[0];
69  
70  
71  
72      public static final Map COMMON_TYPES = new HashMap(64);
73      static
74      {
75          COMMON_TYPES.put("byte", Byte.TYPE);
76          COMMON_TYPES.put("char", Character.TYPE);
77          COMMON_TYPES.put("double", Double.TYPE);
78          COMMON_TYPES.put("float", Float.TYPE);
79          COMMON_TYPES.put("int", Integer.TYPE);
80          COMMON_TYPES.put("long", Long.TYPE);
81          COMMON_TYPES.put("short", Short.TYPE);
82          COMMON_TYPES.put("boolean", Boolean.TYPE);
83          COMMON_TYPES.put("void", Void.TYPE);
84          COMMON_TYPES.put("java.lang.Object", Object.class);
85          COMMON_TYPES.put("java.lang.Boolean", Boolean.class);
86          COMMON_TYPES.put("java.lang.Byte", Byte.class);
87          COMMON_TYPES.put("java.lang.Character", Character.class);
88          COMMON_TYPES.put("java.lang.Short", Short.class);
89          COMMON_TYPES.put("java.lang.Integer", Integer.class);
90          COMMON_TYPES.put("java.lang.Long", Long.class);
91          COMMON_TYPES.put("java.lang.Float", Float.class);
92          COMMON_TYPES.put("java.lang.Double", Double.class);
93          COMMON_TYPES.put("java.lang.String", String.class);
94  
95          COMMON_TYPES.put("byte[]", BYTE_ARRAY_CLASS);
96          COMMON_TYPES.put("char[]", CHAR_ARRAY_CLASS);
97          COMMON_TYPES.put("double[]", DOUBLE_ARRAY_CLASS);
98          COMMON_TYPES.put("float[]", FLOAT_ARRAY_CLASS);
99          COMMON_TYPES.put("int[]", INT_ARRAY_CLASS);
100         COMMON_TYPES.put("long[]", LONG_ARRAY_CLASS);
101         COMMON_TYPES.put("short[]", SHORT_ARRAY_CLASS);
102         COMMON_TYPES.put("boolean[]", BOOLEAN_ARRAY_CLASS);
103         COMMON_TYPES.put("java.lang.Object[]", OBJECT_ARRAY_CLASS);
104         COMMON_TYPES.put("java.lang.Boolean[]", BOOLEAN_OBJECT_ARRAY_CLASS);
105         COMMON_TYPES.put("java.lang.Byte[]", BYTE_OBJECT_ARRAY_CLASS);
106         COMMON_TYPES.put("java.lang.Character[]", CHARACTER_OBJECT_ARRAY_CLASS);
107         COMMON_TYPES.put("java.lang.Short[]", SHORT_OBJECT_ARRAY_CLASS);
108         COMMON_TYPES.put("java.lang.Integer[]", INTEGER_OBJECT_ARRAY_CLASS);
109         COMMON_TYPES.put("java.lang.Long[]", LONG_OBJECT_ARRAY_CLASS);
110         COMMON_TYPES.put("java.lang.Float[]", FLOAT_OBJECT_ARRAY_CLASS);
111         COMMON_TYPES.put("java.lang.Double[]", DOUBLE_OBJECT_ARRAY_CLASS);
112         COMMON_TYPES.put("java.lang.String[]", STRING_OBJECT_ARRAY_CLASS);
113         // array of void is not a valid type
114     }
115 
116     /** utility class, do not instantiate */
117     private ClassUtils()
118     {
119         // utility class, disable instantiation
120     }
121 
122     //~ Methods ------------------------------------------------------------------------------------
123 
124     public synchronized static void addClassLoadingExtension(ClassLoaderExtension extension, boolean top)
125     {
126       /**
127        * now at the first look this looks somewhat strange
128        * but to get the best performance access we assign new native
129        * arrays to our static variable
130        * 
131        * we have to synchronized nevertheless because if two threads try to register
132        * loaders at the same time none of them should get lost
133        */
134         ClassLoaderExtension [] retVal = new ClassLoaderExtension[classLoadingExtensions.length+1];
135         ArrayList extensions = new ArrayList(classLoadingExtensions.length+1);
136 
137         if(!top)
138         {
139             extensions.addAll(Arrays.asList(classLoadingExtensions));
140         }
141         extensions.add(extension);
142         if(top)
143         {
144             extensions.addAll(Arrays.asList(classLoadingExtensions));
145         }    
146 
147         classLoadingExtensions = (ClassLoaderExtension []) extensions.toArray(retVal);
148     }
149 
150     /**
151      * Tries a Class.loadClass with the context class loader of the current thread first and
152      * automatically falls back to the ClassUtils class loader (i.e. the loader of the
153      * myfaces.jar lib) if necessary.
154      *
155      * @param type fully qualified name of a non-primitive non-array class
156      * @return the corresponding Class
157      * @throws NullPointerException if type is null
158      * @throws ClassNotFoundException
159      */
160     public static Class classForName(String type)
161         throws ClassNotFoundException
162     {
163         //we now assign the array to safekeep the reference on
164         // the local variable stack, that way
165         //we can avoid synchronisation calls
166         ClassLoaderExtension [] loaderPlugins = classLoadingExtensions;
167 
168         int plugins = loaderPlugins.length;
169         for(int cnt = 0; cnt < loaderPlugins.length; cnt ++)
170         {
171             ClassLoaderExtension extension = loaderPlugins[cnt];
172             Class retVal = extension.forName(type);
173             if(retVal != null)
174             {
175                 return retVal;
176             }
177         }
178 
179 
180         if (type == null)
181         {
182             throw new NullPointerException("type");
183         }
184         try
185         {
186             // Try WebApp ClassLoader first
187             return Class.forName(type,
188                                  false, // do not initialize for faster startup
189                                  getContextClassLoader());
190         }
191         catch (ClassNotFoundException ignore)
192         {
193             // fallback: Try ClassLoader for ClassUtils (i.e. the myfaces.jar lib)
194             return Class.forName(type,
195                                  false, // do not initialize for faster startup
196                                  ClassUtils.class.getClassLoader());
197         }
198     }
199 
200     /**
201      * Same as {@link #classForName(String)}, but throws a RuntimeException
202      * (FacesException) instead of a ClassNotFoundException.
203      *
204      * @return the corresponding Class
205      * @throws NullPointerException if type is null
206      * @throws FacesException if class not found
207      */
208     public static Class simpleClassForName(String type)
209     {
210         return simpleClassForName(type, true);
211     }
212 
213     /**
214      * Same as {link {@link #simpleClassForName(String)}, but will only
215      * log the exception and rethrow a RunTimeException if logException is true.
216      *
217      * @param type
218      * @param logException - true to log/throw FacesException, false to avoid logging/throwing FacesException
219      * @return the corresponding Class
220      * @throws FacesException if class not found and logException is true
221      */
222     public static Class simpleClassForName(String type, boolean logException)
223     {
224         Class returnClass = null;
225         try
226         {
227             returnClass = classForName(type);
228         }
229         catch (ClassNotFoundException e)
230         {
231             if (logException)
232             {
233                 log.log(Level.SEVERE, "Class " + type + " not found", e);
234                 throw new FacesException(e);
235             }
236         }
237         return returnClass;
238     }
239 
240 
241     /**
242      * Similar as {@link #classForName(String)}, but also supports primitive types
243      * and arrays as specified for the JavaType element in the JavaServer Faces Config DTD.
244      *
245      * @param type fully qualified class name or name of a primitive type, both optionally
246      *             followed by "[]" to indicate an array type
247      * @return the corresponding Class
248      * @throws NullPointerException if type is null
249      * @throws ClassNotFoundException
250      */
251     public static Class javaTypeToClass(String type)
252         throws ClassNotFoundException
253     {
254         if (type == null)
255         {
256             throw new NullPointerException("type");
257         }
258 
259         // try common types and arrays of common types first
260         Class clazz = (Class) COMMON_TYPES.get(type);
261         if (clazz != null)
262         {
263             return clazz;
264         }
265 
266         int len = type.length();
267         if (len > 2 && type.charAt(len - 1) == ']' && type.charAt(len - 2) == '[')
268         {
269             String componentType = type.substring(0, len - 2);
270             Class componentTypeClass = classForName(componentType);
271             return Array.newInstance(componentTypeClass, 0).getClass();
272         }
273 
274         return classForName(type);
275         
276     }
277 
278     /**
279      * This method is similar to shared ClassUtils.javaTypeToClass,
280      * but the default package for the type is java.lang
281      *
282      * @param type
283      * @return
284      * @throws ClassNotFoundException
285      */
286     public static Class javaDefaultTypeToClass(String type)
287             throws ClassNotFoundException
288     {
289         if (type == null)
290         {
291             throw new NullPointerException("type");
292         }
293 
294         // try common types and arrays of common types first
295         Class clazz = (Class) ClassUtils.COMMON_TYPES.get(type);
296         if (clazz != null)
297         {
298             return clazz;
299         }
300 
301         int len = type.length();
302         if (len > 2 && type.charAt(len - 1) == ']' && type.charAt(len - 2) == '[')
303         {
304             String componentType = type.substring(0, len - 2);
305             Class componentTypeClass = ClassUtils.classForName(componentType);
306             return Array.newInstance(componentTypeClass, 0).getClass();
307         }
308 
309         if (type.indexOf('.') == -1)
310         {
311             type = "java.lang." + type;
312         }
313         return ClassUtils.classForName(type);
314     }
315 
316     /**
317      * Same as {@link #javaTypeToClass(String)}, but throws a RuntimeException
318      * (FacesException) instead of a ClassNotFoundException.
319      *
320      * @return the corresponding Class
321      * @throws NullPointerException if type is null
322      * @throws FacesException if class not found
323      */
324     public static Class simpleJavaTypeToClass(String type)
325     {
326         try
327         {
328             return javaTypeToClass(type);
329         }
330         catch (ClassNotFoundException e)
331         {
332             log.log(Level.SEVERE, "Class " + type + " not found", e);
333             throw new FacesException(e);
334         }
335     }
336 
337     public static URL getResource(String resource)
338     {
339         URL url = getContextClassLoader().getResource(resource);
340         if (url == null)
341         {
342             url = ClassUtils.class.getClassLoader().getResource(resource);
343         }
344         return url;
345     }
346 
347     public static InputStream getResourceAsStream(String resource)
348     {
349         InputStream stream = getContextClassLoader()
350                                 .getResourceAsStream(resource);
351         if (stream == null)
352         {
353             // fallback
354             stream = ClassUtils.class.getClassLoader().getResourceAsStream(resource);
355         }
356         return stream;
357     }
358 
359     /**
360      * @param resource       Name of resource(s) to find in classpath
361      * @param defaultObject  The default object to use to determine the class loader 
362      *                       (if none associated with current thread.)
363      * @return Iterator over URL Objects
364      */
365     public static Iterator getResources(String resource, Object defaultObject)
366     {
367         try
368         {
369             Enumeration resources = getCurrentLoader(defaultObject).getResources(resource);
370             List lst = new ArrayList();
371             while (resources.hasMoreElements())
372             {
373                 lst.add(resources.nextElement());
374             }
375             return lst.iterator();
376         }
377         catch (IOException e)
378         {
379             log.log(Level.SEVERE, e.getMessage(), e);
380             throw new FacesException(e);
381         }
382     }
383 
384 
385     public static Object newInstance(String type)
386         throws FacesException
387     {
388         if (type == null)
389         {
390             return null;
391         }
392         return newInstance(simpleClassForName(type));
393     }
394 
395     public static Object newInstance(String type, Class expectedType) throws FacesException
396     {
397         return newInstance(type, expectedType == null ? null : new Class[] {expectedType});
398     }
399 
400     public static Object newInstance(String type, Class[] expectedTypes)
401     {
402         if (type == null)
403         {
404             return null;
405         }
406         
407         Class clazzForName = simpleClassForName(type);
408         
409         if(expectedTypes != null)
410         {
411             for (int i = 0, size = expectedTypes.length; i < size; i++)
412             {
413                 if (!expectedTypes[i].isAssignableFrom(clazzForName))
414                 {
415                     throw new FacesException("'" + type + "' does not implement expected type '" + expectedTypes[i]
416                             + "'");
417                 }
418             }
419         }
420         
421         return newInstance(clazzForName);
422     }
423 
424     public static <T> T newInstance(Class<T> clazz)
425         throws FacesException
426     {
427         try
428         {
429             return clazz.newInstance();
430         }
431         catch(NoClassDefFoundError e)
432         {
433             log.log(Level.SEVERE, "Class : "+clazz.getName()+" not found.",e);
434             throw new FacesException(e);
435         }
436         catch (InstantiationException e)
437         {
438             log.log(Level.SEVERE, e.getMessage(), e);
439             throw new FacesException(e);
440         }
441         catch (IllegalAccessException e)
442         {
443             log.log(Level.SEVERE, e.getMessage(), e);
444             throw new FacesException(e);
445         }
446     }
447 
448     public static <T> T newInstance(Class<T> clazz,
449                                     Class<?>[] constructorArgClasses,
450                                     Object... constructorArgs) throws NoSuchMethodException
451     {
452         if (constructorArgs.length == 0)
453         {
454             // no args given - use normal newInstance()
455             return newInstance(clazz);
456         }
457 
458         // try to get a fitting constructor (throws NoSuchMethodException)
459         Constructor constructor = clazz.getConstructor(constructorArgClasses);
460 
461         try
462         {
463             // actually create instance
464             return (T) constructor.newInstance(constructorArgs);
465         }
466         catch (Exception e)
467         {
468             throw new FacesException(e);
469         }
470     }
471 
472     public static Object convertToType(Object value, Class desiredClass)
473     {
474         if (value == null)
475         {
476             return null;
477         }
478 
479         try
480         {
481             ExpressionFactory expFactory = FacesContext.getCurrentInstance().getApplication().getExpressionFactory();
482             return expFactory.coerceToType(value, desiredClass);
483         }
484         catch (Exception e)
485         {
486             String message = "Cannot coerce " + value.getClass().getName()
487                              + " to " + desiredClass.getName();
488             log.log(Level.SEVERE, message, e);
489             throw new FacesException(message, e);
490         }
491     }
492 
493     /**
494      * Gets the ClassLoader associated with the current thread.  Returns the class loader associated with
495      * the specified default object if no context loader is associated with the current thread.
496      *
497      * @param defaultObject The default object to use to determine the class loader 
498      *        (if none associated with current thread.)
499      * @return ClassLoader
500      */
501     protected static ClassLoader getCurrentLoader(Object defaultObject)
502     {
503         ClassLoader loader = getContextClassLoader();
504         if(loader == null)
505         {
506             loader = defaultObject.getClass().getClassLoader();
507         }
508         return loader;
509     }
510     
511     /**
512      * Gets the ClassLoader associated with the current thread.  Includes a check for priviledges 
513      * against java2 security to ensure no security related exceptions are encountered. 
514      *
515      * @since 3.0.6
516      * @return ClassLoader
517      */
518     public static ClassLoader getContextClassLoader()
519     {
520         // call into the same method on ClassLoaderUtils.  no need for duplicate code maintenance. 
521         return ClassLoaderUtils.getContextClassLoader();
522     }
523     
524     /**
525      * Creates ApplicationObjects like NavigationHandler or StateManager and creates 
526      * the right wrapping chain of the ApplicationObjects known as the decorator pattern. 
527      * @param <T>
528      * @param interfaceClass The class from which the implementation has to inherit from.
529      * @param classNamesIterator All the class names of the actual ApplicationObject implementations
530      *                           from the faces-config.xml.
531      * @param defaultObject The default implementation for the given ApplicationObject.
532      * @return
533      */    
534     public static <T> T buildApplicationObject(Class<T> interfaceClass, 
535             Collection<String> classNamesIterator, T defaultObject)
536     {
537         return buildApplicationObject(interfaceClass, null, null, classNamesIterator, defaultObject);
538     }
539 
540     /**
541      * Creates ApplicationObjects like NavigationHandler or StateManager and creates 
542      * the right wrapping chain of the ApplicationObjects known as the decorator pattern. 
543      * @param <T>
544      * @param interfaceClass The class from which the implementation has to inherit from.
545      * @param extendedInterfaceClass A subclass of interfaceClass which specifies a more
546      *                               detailed implementation.
547      * @param extendedInterfaceWrapperClass A wrapper class for the case that you have an ApplicationObject
548      *                                      which only implements the interfaceClass but not the 
549      *                                      extendedInterfaceClass.
550      * @param classNamesIterator All the class names of the actual ApplicationObject implementations
551      *                           from the faces-config.xml.
552      * @param defaultObject The default implementation for the given ApplicationObject.
553      * @return
554      */
555     @SuppressWarnings("unchecked")
556     public static <T> T buildApplicationObject(Class<T> interfaceClass, Class<? extends T> extendedInterfaceClass,
557             Class<? extends T> extendedInterfaceWrapperClass,
558             Collection<String> classNamesIterator, T defaultObject)
559     {
560         T current = defaultObject;
561         
562 
563         for (String implClassName : classNamesIterator)
564         {
565             Class<? extends T> implClass = ClassUtils.simpleClassForName(implClassName);
566 
567             // check, if class is of expected interface type
568             if (!interfaceClass.isAssignableFrom(implClass))
569             {
570                 throw new IllegalArgumentException("Class " + implClassName + " is no " + interfaceClass.getName());
571             }
572 
573             if (current == null)
574             {
575                 // nothing to decorate
576                 current = (T) ClassUtils.newInstance(implClass);
577             }
578             else
579             {
580                 // let's check if class supports the decorator pattern
581                 T newCurrent = null;
582                 try
583                 {
584                     Constructor<? extends T> delegationConstructor = null;
585                     
586                     // first, if there is a extendedInterfaceClass,
587                     // try to find a constructor that uses that
588                     if (extendedInterfaceClass != null 
589                             && extendedInterfaceClass.isAssignableFrom(current.getClass()))
590                     {
591                         try
592                         {
593                             delegationConstructor = 
594                                     implClass.getConstructor(new Class[] {extendedInterfaceClass});
595                         }
596                         catch (NoSuchMethodException mnfe)
597                         {
598                             // just eat it
599                         }
600                     }
601                     if (delegationConstructor == null)
602                     {
603                         // try to find the constructor with the "normal" interfaceClass
604                         delegationConstructor = 
605                                 implClass.getConstructor(new Class[] {interfaceClass});
606                     }
607                     // impl class supports decorator pattern at this point
608                     try
609                     {
610                         // create new decorator wrapping current
611                         newCurrent = delegationConstructor.newInstance(new Object[] { current });
612                     }
613                     catch (InstantiationException e)
614                     {
615                         log.log(Level.SEVERE, e.getMessage(), e);
616                         throw new FacesException(e);
617                     }
618                     catch (IllegalAccessException e)
619                     {
620                         log.log(Level.SEVERE, e.getMessage(), e);
621                         throw new FacesException(e);
622                     }
623                     catch (InvocationTargetException e)
624                     {
625                         log.log(Level.SEVERE, e.getMessage(), e);
626                         throw new FacesException(e);
627                     }
628                 }
629                 catch (NoSuchMethodException e)
630                 {
631                     // no decorator pattern support
632                     newCurrent = (T) ClassUtils.newInstance(implClass);
633                 }
634                 
635                 // now we have a new current object (newCurrent)
636                 // --> find out if it is assignable from extendedInterfaceClass
637                 // and if not, wrap it in a backwards compatible wrapper (if available)
638                 if (extendedInterfaceWrapperClass != null
639                         && !extendedInterfaceClass.isAssignableFrom(newCurrent.getClass()))
640                 {
641                     try
642                     {
643                         Constructor<? extends T> wrapperConstructor
644                                 = extendedInterfaceWrapperClass.getConstructor(
645                                         new Class[] {interfaceClass, extendedInterfaceClass});
646                         newCurrent = wrapperConstructor.newInstance(new Object[] {newCurrent, current});
647                     }
648                     catch (NoSuchMethodException e)
649                     {
650                         log.log(Level.SEVERE, e.getMessage(), e);
651                         throw new FacesException(e);
652                     }
653                     catch (InstantiationException e)
654                     {
655                         log.log(Level.SEVERE, e.getMessage(), e);
656                         throw new FacesException(e);
657                     }
658                     catch (IllegalAccessException e)
659                     {
660                         log.log(Level.SEVERE, e.getMessage(), e);
661                         throw new FacesException(e);
662                     }
663                     catch (InvocationTargetException e)
664                     {
665                         log.log(Level.SEVERE, e.getMessage(), e);
666                         throw new FacesException(e);
667                     }
668                 }
669                 
670                 current = newCurrent;
671             }
672         }
673 
674         return current;
675     }
676 }