View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2015 LegSem.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the GNU Lesser Public License v2.1
5    * which accompanies this distribution, and is available at
6    * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
7    * 
8    * Contributors:
9    *     LegSem - initial API and implementation
10   ******************************************************************************/
11  package com.legstar.coxb.util;
12  
13  import java.lang.reflect.InvocationTargetException;
14  import java.lang.reflect.Method;
15  import java.util.Collection;
16  import java.util.List;
17  
18  /**
19   * A helper class for dynamic class loading.
20   * 
21   */
22  public final class ClassUtil {
23  
24      /** Utility class. */
25      private ClassUtil() {
26  
27      }
28  
29      /**
30       * Rather than using the Class.forName mechanism first, this uses
31       * Thread.getContextClassLoader instead. In a Servlet context such as
32       * Tomcat, this allows JAXB classes for instance to be loaded from the web
33       * application (webapp) location while this code might have been loaded from
34       * shared/lib. If Thread.getContextClassLoader fails to locate the class
35       * then we give a last chance to Class.forName.
36       * 
37       * @param packageName the package containing the class
38       * @param className the class name
39       * @return the class
40       * @throws ClassNotFoundException if class is not accessible from any class
41       *             loader
42       */
43      public static Class < ? > loadClass(final String packageName,
44              final String className) throws ClassNotFoundException {
45          return loadClass(toQualifiedClassName(packageName, className));
46      }
47  
48      /**
49       * Rather than using the Class.forName mechanism first, this uses
50       * Thread.getContextClassLoader instead. In a Servlet context such as
51       * Tomcat, this allows JAXB classes for instance to be loaded from the web
52       * application (webapp) location while this code might have been loaded from
53       * shared/lib. If Thread.getContextClassLoader fails to locate the class
54       * then we give a last chance to Class.forName.
55       * 
56       * @param qualifiedClassName the class name to load
57       * @return the class
58       * @throws ClassNotFoundException if class is not accessible from any class
59       *             loader
60       */
61      public static Class < ? > loadClass(final String qualifiedClassName)
62              throws ClassNotFoundException {
63          ClassLoader contextClassLoader = Thread.currentThread()
64                  .getContextClassLoader();
65          if (contextClassLoader == null) {
66              return Class.forName(qualifiedClassName);
67          }
68          try {
69              return contextClassLoader.loadClass(qualifiedClassName);
70          } catch (ClassNotFoundException e) {
71              return Class.forName(qualifiedClassName);
72          }
73      }
74  
75      /**
76       * Dynamically create an instance of a java class. This assumes the classes
77       * are available on the classpath and returns a new instance.
78       * 
79       * @param packageName the package containing the class
80       * @param className the class name
81       * @return a java object
82       * @throws ClassLoadingException if object cannot be instantiated
83       */
84      public static Object newObject(final String packageName,
85              final String className) throws ClassLoadingException {
86          return newObject(toQualifiedClassName(packageName, className));
87  
88      }
89  
90      /**
91       * Dynamically create an instance of a java class. This assumes the classes
92       * are available on the classpath and returns a new instance.
93       * 
94       * @param qualifiedClassName the qualified class name
95       * @return a java object
96       * @throws ClassLoadingException if object cannot be instantiated
97       */
98      public static Object newObject(final String qualifiedClassName)
99              throws ClassLoadingException {
100 
101         try {
102             Class < ? > clazz = loadClass(qualifiedClassName);
103             return clazz.newInstance();
104         } catch (InstantiationException e) {
105             throw new ClassLoadingException(e);
106         } catch (IllegalAccessException e) {
107             throw new ClassLoadingException(e);
108         } catch (ClassNotFoundException e) {
109             throw new ClassLoadingException(e);
110         }
111     }
112 
113     /**
114      * Qualifies a class name.
115      * 
116      * @param packageName the package name, null if none
117      * @param className the class name
118      * @return a qualified class name
119      */
120     public static String toQualifiedClassName(final String packageName,
121             final String className) {
122         return (packageName == null) ? className : packageName + '.'
123                 + className;
124     }
125 
126     /**
127      * Separate package name and class name from a qualified class name.
128      * 
129      * @param qualifiedClassName qualified class name
130      * @return a structure with both parts
131      */
132     public static ClassName toClassName(final String qualifiedClassName) {
133         ClassName className = new ClassName();
134         int pos = qualifiedClassName.lastIndexOf('.');
135         className.packageName = (pos == -1) ? null : qualifiedClassName
136                 .substring(0, pos);
137         className.className = (pos == -1) ? qualifiedClassName
138                 : qualifiedClassName.substring(pos + 1);
139         return className;
140     }
141 
142     /**
143      * A simple vehicle for class name and package name.
144      * 
145      */
146     public static class ClassName {
147         /** The class name. */
148         public String className;
149         /** The package name (null if no package name). */
150         public String packageName;
151     }
152 
153     /**
154      * The ObjectFactory exports a create method for each complex type in the
155      * object tree. Returns a jaxb object creator method.
156      * 
157      * @param objectFactory JAXB Objectfactory
158      * @param jaxbTypeName the jaxb object type name
159      * @return the creator method
160      * @throws ClassMethodException if getter method does not exist
161      */
162     public static Method getCreatorMethod(final Object objectFactory,
163             final String jaxbTypeName) throws ClassMethodException {
164         try {
165             Method creator = objectFactory.getClass().getMethod(
166                     "create" + toPropertyType(jaxbTypeName));
167             return creator;
168         } catch (NoSuchMethodException e) {
169             throw (new ClassMethodException(e));
170         }
171     }
172 
173     /**
174      * Returns a jaxb property getter method.
175      * 
176      * @param parentObject JAXB Object owning the property
177      * @param getterPrefix the getter method prefix (get or is)
178      * @param jaxbName the property name to get
179      * @return the getter method
180      * @throws ClassMethodException if getter method does not exist
181      */
182     public static Method getGetterMethod(final Object parentObject,
183             final String getterPrefix, final String jaxbName)
184             throws ClassMethodException {
185         String getterName = getGetterName(getterPrefix, jaxbName);
186         try {
187             Method getter = parentObject.getClass().getMethod(getterName);
188             return getter;
189         } catch (NoSuchMethodException e) {
190             throw (new ClassMethodException(e));
191         }
192     }
193 
194     /**
195      * Build a conventional bean getter method name from a field name.
196      * 
197      * @param getterPrefix the getter method name prefix (get or is)
198      * @param fieldName the field name
199      * @return a getter method name
200      */
201     public static String getGetterName(final String getterPrefix,
202             final String fieldName) {
203         return getterPrefix + Character.toUpperCase(fieldName.charAt(0))
204                 + fieldName.substring(1);
205     }
206 
207     /**
208      * Returns a jaxb property setter method.
209      * 
210      * @param parentObject JAXB Object owning the property
211      * @param jaxbName the property name to get
212      * @param jaxbType the property type
213      * @return the getter method
214      * @throws ClassMethodException if getter method does not exist
215      */
216     public static Method getSetterMethod(final Object parentObject,
217             final String jaxbName, final Class < ? > jaxbType)
218             throws ClassMethodException {
219         String setterName = getSetterMethodName(jaxbName);
220         try {
221             Class < ? >[] param = { jaxbType };
222             Method setter = parentObject.getClass()
223                     .getMethod(setterName, param);
224             return setter;
225         } catch (NoSuchMethodException e) {
226             throw (new ClassMethodException(e));
227         }
228     }
229 
230     /**
231      * Construct a setter method name for a JAXB property.
232      * 
233      * @param jaxbName the JAXB property name
234      * @return the setter method name
235      */
236     public static String getSetterMethodName(final String jaxbName) {
237         return "set" + NameUtil.upperFirstChar(jaxbName)
238                 + jaxbName.substring(1);
239     }
240 
241     /**
242      * Helper method to extract the type name as used by JAXB create methods.
243      * <p/>
244      * Property type (as returned from Field.getGenericType()) can take 4 forms:
245      * <ul>
246      * <li>type</li>
247      * <li>package.type</li>
248      * <li>java.util.List &lt; package.type &gt;</li>
249      * <li>type$nestedType</li>
250      * </ul>
251      * Furthermore, primitive types are returned as their Object equivalent.
252      * 
253      * 
254      * @param genericType as returned from hostField.getGenericType()
255      * @return a normalized string representation of the type
256      */
257     public static String toPropertyType(final String genericType) {
258 
259         String type;
260         int lp = genericType.lastIndexOf('.');
261         if (lp == -1) {
262             type = genericType;
263         } else {
264             type = genericType.substring(lp + 1);
265             if (type.charAt(type.length() - 1) == '>') {
266                 type = type.substring(0, type.length() - 1);
267             }
268         }
269         // type = type.replace("$", ".");
270 
271         if (type.compareTo("byte") == 0) {
272             return "Byte";
273         } else if (type.compareTo("[B") == 0) {
274             return "byte[]";
275         } else if (type.compareTo("short") == 0) {
276             return "Short";
277         } else if (type.compareTo("int") == 0) {
278             return "Integer";
279         } else if (type.compareTo("long") == 0) {
280             return "Long";
281         } else if (type.compareTo("float") == 0) {
282             return "Float";
283         } else if (type.compareTo("double") == 0) {
284             return "Double";
285         } else if (type.compareTo("boolean") == 0) {
286             return "Boolean";
287         }
288         return type;
289     }
290 
291     /**
292      * This method gets a property object from a parent object.
293      * 
294      * @param parentObject JAXB Object owning the property
295      * @param jaxbName the property name to get
296      * @return instance of the property as returned from a getter method
297      * @throws ClassInvokeException if property cannot be accessed
298      * @deprecated use {@link invokeGetProperty(Object parentObject, String
299      *             jaxbName, Class < ? > jaxbType)}
300      */
301     public static Object invokeGetProperty(final Object parentObject,
302             final String jaxbName) throws ClassInvokeException {
303         Object result;
304         try {
305             /*
306              * The concrete object reference is obtained thru the corresponding
307              * parent getter method
308              */
309             Method getter = getGetterMethod(parentObject, "get", jaxbName);
310             result = getter.invoke(parentObject);
311         } catch (IllegalAccessException e) {
312             throw (new ClassInvokeException(e));
313         } catch (InvocationTargetException e) {
314             throw (new ClassInvokeException(e));
315         } catch (ClassMethodException e) {
316             throw (new ClassInvokeException(e));
317         }
318         return result;
319     }
320 
321     /**
322      * This method gets a property object from a parent object.
323      * 
324      * @param parentObject JAXB Object owning the property
325      * @param jaxbName the property name to get
326      * @param maxOccurs the maximum number of occurrences
327      * @return instance of the property as returned from a getter method
328      * @throws ClassInvokeException if property cannot be accessed
329      */
330     public static Object invokeGetProperty(final Object parentObject,
331             final String jaxbName, final Class < ? > jaxbType,
332             final int maxOccurs) throws ClassInvokeException {
333         Object result;
334         try {
335             /*
336              * The concrete object reference is obtained thru the corresponding
337              * parent getter method
338              */
339             Method getter = getGetterMethod(parentObject,
340                     getGetterPrefix(jaxbType, maxOccurs), jaxbName);
341             result = getter.invoke(parentObject);
342         } catch (IllegalAccessException e) {
343             throw (new ClassInvokeException(e));
344         } catch (InvocationTargetException e) {
345             throw (new ClassInvokeException(e));
346         } catch (ClassMethodException e) {
347             throw (new ClassInvokeException(e));
348         }
349         return result;
350     }
351 
352     /**
353      * Evaluates the getter method prefix for a field, based on the field type.
354      * 
355      * @param fieldType the field type
356      * @param maxOccurs the maximum number of occurrences
357      * @return the prefix to form a getter method
358      */
359     public static String getGetterPrefix(final Class < ? > fieldType,
360             final int maxOccurs) {
361         String getterPrefix = "get";
362         if (maxOccurs <= 1) {
363             if (fieldType == boolean.class) {
364                 getterPrefix = "is";
365             } else if (fieldType == Boolean.class) {
366                 getterPrefix = "is";
367             }
368         }
369         return getterPrefix;
370     }
371 
372     /**
373      * This method sets a property from a parent object to a given value.
374      * 
375      * @param parentObject JAXB Object owning the property
376      * @param jaxbName the property name to set
377      * @param value value to set property to
378      * @param javaType the java class the value belongs to
379      * @throws ClassInvokeException if property cannot be set
380      */
381     @SuppressWarnings({ "rawtypes" })
382     public static void invokeSetProperty(final Object parentObject,
383             final String jaxbName, final Object value,
384             final Class < ? > javaType) throws ClassInvokeException {
385 
386         try {
387             /*
388              * When object to set is a List, we use the getXXX().AddAll
389              * technique
390              */
391             if (value instanceof List) {
392                 Method getter = getGetterMethod(parentObject, "get", jaxbName);
393                 Class < ? > listClass = getter.getReturnType();
394                 Method addAll = listClass.getMethod("addAll", Collection.class);
395                 addAll.invoke(getter.invoke(parentObject), (Collection) value);
396             } else {
397                 Method setter = getSetterMethod(parentObject, jaxbName,
398                         javaType);
399                 setter.invoke(parentObject, value);
400             }
401         } catch (IllegalAccessException e) {
402             throw (new ClassInvokeException(e));
403         } catch (NoSuchMethodException e) {
404             throw (new ClassInvokeException(e));
405         } catch (InvocationTargetException e) {
406             throw (new ClassInvokeException(e));
407         } catch (ClassMethodException e) {
408             throw (new ClassInvokeException(e));
409         }
410     }
411 
412     /**
413      * This method creates a new instance of a property type in a parent object
414      * using a JAXB ObjectFactory. Given the property type name, the method
415      * constructs an ObjectFactory call for this property type. This method
416      * works for complex types only since the ObjectFactory will have create
417      * methods only for those.
418      * 
419      * @param objectFactory a JAXB ObjectFactory
420      * @param parentObject instance of the JAXB parent class
421      * @param jaxbName complex property name
422      * @param jaxbTypeName complex property type name
423      * @return an instance of a JAXB object
424      * @throws ClassInvokeException if JAXB Object cannot be instanciated
425      */
426     public static Object addComplexProperty(final Object objectFactory,
427             final Object parentObject, final String jaxbName,
428             final String jaxbTypeName) throws ClassInvokeException {
429 
430         try {
431             Method creator = getCreatorMethod(objectFactory, jaxbTypeName);
432             Object result = creator.invoke(objectFactory);
433 
434             /*
435              * Add a reference to the object just created to the parent object
436              * using its setter method
437              */
438             Method setter = getSetterMethod(parentObject, jaxbName,
439                     creator.getReturnType());
440             setter.invoke(parentObject, result);
441             return result;
442         } catch (IllegalAccessException e) {
443             throw new ClassInvokeException(e);
444         } catch (InvocationTargetException e) {
445             throw new ClassInvokeException(e);
446         } catch (ClassMethodException e) {
447             throw new ClassInvokeException(e);
448         }
449     }
450 
451     /**
452      * This method gets an indexed property object from a parent array object.
453      * Assumes the parent inherits from java.util.List and implement a
454      * get(index) method.
455      * 
456      * @param parentObject instance of the JAXB parent list class
457      * @param index item index in list
458      * @return an instance of a JAXB object
459      * @throws ClassInvokeException if JAXB Object cannot be returned
460      */
461     public static Object invokeGetIndexedProperty(final Object parentObject,
462             final int index) throws ClassInvokeException {
463 
464         Object result;
465         try {
466             /*
467              * To get an instance of the item identified by index, we use the
468              * get(i) method of ArrayList
469              */
470             Method getter = parentObject.getClass().getMethod("get", int.class);
471             result = getter.invoke(parentObject, index);
472         } catch (IllegalAccessException e) {
473             throw new ClassInvokeException(e);
474         } catch (NoSuchMethodException e) {
475             throw new ClassInvokeException(e);
476         } catch (InvocationTargetException e) {
477             /* If requested item does not exist in array simply return null */
478             if (e.getTargetException().getClass().getName()
479                     .compareTo("java.lang.IndexOutOfBoundsException") == 0) {
480                 return null;
481             } else {
482                 throw new ClassInvokeException(e);
483             }
484         }
485         return result;
486     }
487 
488     /**
489      * This creates a new complex property item, identified by an index in a
490      * parent object array. This method assumes the items are complex objects
491      * for which the JAXB ObjectFactory implements a create method.
492      * 
493      * @param objectFactory a JAXB ObjectFactory
494      * @param parentObject instance of the JAXB parent class
495      * @param jaxbTypeName complex property type name
496      * @param index item index in list
497      * @return an instance of a JAXB object
498      * @throws ClassInvokeException if JAXB Object cannot be instanciated
499      */
500     public static Object addComplexIndexedProperty(final Object objectFactory,
501             final Object parentObject, final String jaxbTypeName,
502             final int index) throws ClassInvokeException {
503 
504         try {
505             Method creator = getCreatorMethod(objectFactory, jaxbTypeName);
506             Object result = creator.invoke(objectFactory);
507 
508             /*
509              * Add reference to object just created to the parent array using
510              * the add(index, Object) method
511              */
512             Class < ? >[] param = { int.class, Object.class };
513             Method add = parentObject.getClass().getMethod("add", param);
514             add.invoke(parentObject, index, result);
515             return result;
516         } catch (IllegalAccessException e) {
517             throw new ClassInvokeException(e);
518         } catch (NoSuchMethodException e) {
519             throw new ClassInvokeException(e);
520         } catch (InvocationTargetException e) {
521             throw new ClassInvokeException(e);
522         } catch (ClassMethodException e) {
523             throw new ClassInvokeException(e);
524         }
525     }
526 
527     /**
528      * This creates a new simple property item, identified by an index in a
529      * parent object array.
530      * 
531      * @param parentObject instance of the JAXB parent list class
532      * @param index item index in list
533      * @param value value to set item to
534      * @throws ClassInvokeException if item cannot be set
535      */
536     public static void addSimpleIndexedProperty(final Object parentObject,
537             final int index, final Object value) throws ClassInvokeException {
538 
539         try {
540             /*
541              * Add reference to object just created to the parent array using
542              * the add(index, Object) method
543              */
544             Class < ? >[] param = { int.class, Object.class };
545             Method setter = parentObject.getClass().getMethod("add", param);
546             setter.invoke(parentObject, index, value);
547         } catch (IllegalAccessException e) {
548             throw new ClassInvokeException(e);
549         } catch (NoSuchMethodException e) {
550             throw new ClassInvokeException(e);
551         } catch (InvocationTargetException e) {
552             throw new ClassInvokeException(e);
553         }
554     }
555 
556     /**
557      * This will determine if a type name for a simple element is one of java's
558      * native types and assume it is an enum otherwise.
559      * 
560      * @param type the element java type
561      * @return true if it should be considered an enum
562      */
563     public static boolean isEnum(final String type) {
564         if (type == null) {
565             return false;
566         }
567         if (type.compareToIgnoreCase("String") == 0) {
568             return false;
569         } else if (type.compareToIgnoreCase("[B") == 0) {
570             return false;
571         } else if (type.compareToIgnoreCase("byte[]") == 0) {
572             return false;
573         } else if (type.compareToIgnoreCase("short") == 0) {
574             return false;
575         } else if (type.compareToIgnoreCase("int") == 0) {
576             return false;
577         } else if (type.compareToIgnoreCase("Integer") == 0) {
578             return false;
579         } else if (type.compareToIgnoreCase("long") == 0) {
580             return false;
581         } else if (type.compareToIgnoreCase("float") == 0) {
582             return false;
583         } else if (type.compareToIgnoreCase("double") == 0) {
584             return false;
585         } else if (type.compareToIgnoreCase("BigInteger") == 0) {
586             return false;
587         } else if (type.compareToIgnoreCase("BigDecimal") == 0) {
588             return false;
589         } else if (type.compareToIgnoreCase("byte") == 0) {
590             return false;
591         } else if (type.compareToIgnoreCase("boolean") == 0) {
592             return false;
593         }
594         return true;
595     }
596 
597 }