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