View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  /* *
24   * ObjectUtil.java
25   *
26   * Created: 2 nov. 2004
27   *
28   * @author Benjamin Poussin - poussin@codelutin.com
29   *
30   *
31   * Mise a jour: $Date$
32   * par : */
33  
34  package org.nuiton.util;
35  
36  import org.apache.commons.beanutils.BeanUtils;
37  import org.apache.commons.beanutils.ConvertUtils;
38  import org.apache.commons.beanutils.MethodUtils;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  import java.io.ByteArrayInputStream;
43  import java.io.ByteArrayOutputStream;
44  import java.io.ObjectInputStream;
45  import java.io.ObjectOutputStream;
46  import java.lang.reflect.Array;
47  import java.lang.reflect.Constructor;
48  import java.lang.reflect.InvocationTargetException;
49  import java.lang.reflect.Method;
50  import java.util.ArrayList;
51  import java.util.Arrays;
52  import java.util.Collection;
53  import java.util.LinkedList;
54  import java.util.List;
55  
56  import static org.nuiton.i18n.I18n.t;
57  
58  /**
59   * Outils pour manipuler des objets. Création d'un objet à partir d'une chaîne
60   * le décrivant, conversion d'un objet en Object, récupération de méthodes
61   * à partir de leur nom, de constructeurs à partir de leurs paramètres...
62   *
63   * Created: 4 novembre 2004
64   *
65   * @author Benjamin Poussin - poussin@codelutin.com
66   *
67   */
68  public class ObjectUtil { // ObjectUtil
69  
70      /** Logger. */
71      private static final Log log = LogFactory.getLog(ObjectUtil.class);
72  
73      /** Used to know what is separator between class and method*/
74      public static final String CLASS_METHOD_SEPARATOR = "#";
75  
76      protected static final Integer ZERO = 0;
77  
78      protected static final Character ZEROC = (char) 0;
79  
80      protected static final Float ZEROF = 0f;
81  
82      protected static final Long ZEROL = 0l;
83  
84      protected static final Double ZEROD = 0.;
85  
86      protected static final Byte ZEROB = 0;
87  
88      /**
89       * ObjectUtil constructor
90       * private because of this class is a static class : nobody
91       * can make an instance of this class
92       */
93      private ObjectUtil() {
94      }
95  
96      /**
97       * Invoke constructor on clazz to create new instance. Try to find argument
98       * for constructor in args parameter.
99       *
100      * @param <E> FIXME
101      * @param clazz         class of object to instanciate
102      * @param args          all possible parameter that constructor can used
103      * @param nullIfMissing if no suitable class or object found in args,
104      *                      use null value (no exception)
105      * @return new instance
106      * @throws IllegalArgumentException if something is wrong during instanciation
107      */
108     public static <E> E newInstance(Class<E> clazz,
109                                     Collection<?> args,
110                                     boolean nullIfMissing) {
111         Constructor<E>[] constructors =
112                 (Constructor<E>[]) clazz.getConstructors();
113         if (constructors.length != 1) {
114             throw new IllegalArgumentException(
115                     t("nuitonutil.error.class.with.more.than.one.constructor",
116                       clazz));
117         }
118 
119         // copy collection into modifiable list to add new object
120         List<?> container = new LinkedList(args);
121 
122         Constructor<E> constructor = constructors[0];
123         Class<?>[] paramTypes = constructor.getParameterTypes();
124         Object[] params = new Object[paramTypes.length];
125 
126         for (int i = 0; i < paramTypes.length; i++) {
127             Object o = choiceArgument(paramTypes[i], container, nullIfMissing);
128             params[i] = o;
129         }
130 
131         try {
132             E result = constructor.newInstance(params);
133             return result;
134         } catch (Exception eee) {
135             throw new IllegalArgumentException(t(
136                     t("nuitonutil.error.cant.instanciate.class",
137                       clazz, Arrays.toString(params))), eee);
138         }
139     }
140 
141     /**
142      * Permet de matcher un type d'argument attendu clazz parmi un ensemble
143      * possible de candidat. Les candidats peuvent etre des classes qu'il faudra
144      * instancier pour satisfaire le type demande.
145      *
146      * @param clazz         le type recherché
147      * @param args          la liste des arguments ou des types
148      * @param nullIfMissing pour retourner nulle si l'argument n'est pas trouvé
149      * @return le type d'argument trouvé
150      */
151     static protected Object choiceArgument(Class<?> clazz,
152                                            List args,
153                                            boolean nullIfMissing) {
154         Object result = null;
155         boolean addResult = false;
156         for (Object o : args) {
157             if (o != null) {
158                 if (o instanceof Class<?> &&
159                     clazz.isAssignableFrom((Class<?>) o)) {
160 
161                     // cas on l'on trouve une class dans arg qui une fois
162                     // instancier convient
163                     result = newInstance((Class<?>) o, args, nullIfMissing);
164                     addResult = true;
165                     break;
166                 } else if (clazz.isInstance(o)) {
167 
168                     // cas on l'on retrouve un objet assignable pour ce type
169                     result = o;
170                     break;
171                 }
172             }
173         }
174 
175         if (addResult) {
176             // on ajoute en tete pour qu'il soit retrouve dans les premiers
177             // et non pas reinstancier une nouvelle fois si on en a besoin
178             // a nouveau
179             args.add(0, result);
180         }
181 
182         // si on ne retrouve rien, result est reste a null
183         if (result == null && !nullIfMissing) {
184             throw new IllegalArgumentException(t(
185                     t("nuitonutil.error.unfound.assignable.argument",
186                       clazz, args)));
187         }
188         return result;
189     }
190 
191     /**
192      * Create new object from string like org.nuiton.Toto(name=machine, int=10)
193      * where machine and int is properties on org.nuiton.Toto object.
194      * Conversion between 10 in string and 10 as integer as automaticaly done
195      *
196      * For String property you can use ex:
197      * <ul>
198      * <li> name="my string with , in string"</li>
199      * <li> name='my string with , in string'</li>
200      * </ul>
201      * @param classnameAndProperties FIXME
202      * @return the instanciated object FIXME
203      * @throws ClassNotFoundException FIXME
204      * @throws IllegalAccessException FIXME
205      * @throws InstantiationException FIXME
206      * @throws NoSuchMethodException FIXME
207      * @throws InvocationTargetException FIXME
208      */
209     public static Object create(String classnameAndProperties) throws
210             ClassNotFoundException,
211             InstantiationException,
212             IllegalAccessException,
213             InvocationTargetException,
214             NoSuchMethodException {
215         int p = classnameAndProperties.indexOf('(');
216         int l = classnameAndProperties.lastIndexOf(')');
217         String[] properties = null;
218         String classname;
219         if (p != -1) {
220             String tmp = classnameAndProperties.substring(p + 1, l);
221             properties = StringUtil.split(tmp, ",");
222             classname = classnameAndProperties.substring(0, p);
223         } else {
224             classname = classnameAndProperties;
225         }
226         ClassLoader loader = Thread.currentThread().getContextClassLoader();
227         Class<?> clazz = loader.loadClass(classname);
228         Object o = clazz.getConstructor().newInstance();
229         if (properties != null) {
230             for (String prop : properties) {
231                 int e = prop.indexOf('=');
232                 String propName = prop.substring(0, e).trim();
233                 String propValue = prop.substring(e + 1).trim();
234                 if (propValue.charAt(0) == '"' &&
235                     propValue.charAt(propValue.length() - 1) == '"') {
236                     propValue = propValue.substring(1, propValue.length() - 1);
237                 } else if (propValue.charAt(0) == '\'' &&
238                            propValue.charAt(propValue.length() - 1) == '\'') {
239                     propValue = propValue.substring(1, propValue.length() - 1);
240                 }
241                 BeanUtils.setProperty(o, propName, propValue);
242             }
243         }
244         return o;
245     }
246 
247     static protected Object convert(String v, Class<?> clazz) {
248         Object t = ConvertUtils.convert(v, clazz);
249 
250         if (t != null &&
251             !String.class.getName().equals(clazz.getName()) &&
252             String.class.getName().equals(t.getClass().getName())) {
253             throw new IllegalArgumentException(String.format(
254                     "Can convert argument to correct type. %s can't be" +
255                     " converted from String to %s conversion is done to %s",
256                     v, clazz.getName(), t.getClass().getName()));
257         }
258         return t;
259     }
260 
261     /**
262      * Clone object by introspection because Cloneable interface don't permit
263      * to call clone :(. This methode replace next code that don't work :(
264      *
265      * <pre>
266      * if (o instanceof Cloneable) {
267      *   Object n = ((Cloneable)o).clone();
268      * }
269      * </pre>
270      *
271      * @param <E> FIXME
272      * @param e object to clone
273      * @return new instance of E
274      * @throws CloneNotSupportedException if some error occur during clone
275      */
276     public static <E extends Cloneable> E clone(E e) throws CloneNotSupportedException {
277         try {
278             E result = (E) MethodUtils.invokeExactMethod(e, "clone", null);
279             return result;
280         } catch (Exception eee) {
281             // on est oblige de faire un log, car CloneNotSupportedException
282             // ne prend pas d'exception en arguement :(
283             log.error("Can't clone object", eee);
284             throw new CloneNotSupportedException();
285         }
286     }
287 
288     /**
289      * Use serialization/deserialization to do deep clone of object
290      *
291      * @param <E> FIXME
292      * @param e object to clone
293      * @return new instance of E
294      * @throws CloneNotSupportedException if some error occur during clone
295      */
296     public static <E> E deepClone(E e) throws CloneNotSupportedException {
297         try {
298             ByteArrayOutputStream bos = new ByteArrayOutputStream();
299             ObjectOutputStream oos = new ObjectOutputStream(bos);
300             try {
301                 oos.writeObject(e);
302             } finally {
303                 oos.close();
304             }
305 
306             ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
307             E result;
308             ObjectInputStream ois = new ObjectInputStream(bis);
309             try {
310                 result = (E) ois.readObject();
311             } finally {
312                 ois.close();
313             }
314 
315             return result;
316         } catch (Exception eee) {
317             // on est oblige de faire un log, car CloneNotSupportedException
318             // ne prend pas d'exception en arguement :(
319             log.error("Can't clone object", eee);
320             throw new CloneNotSupportedException();
321         }
322     }
323 
324     /**
325      * Call method m with params as String. Each param is converted to required type for
326      * method with beanutils converter
327      *
328      * @param o      object where method must be call
329      * @param m      method to call
330      * @param params parameters for method call
331      * @return returned method's value
332      * @throws IllegalAccessException FIXME
333      * @throws IllegalArgumentException FIXME
334      * @throws InvocationTargetException FIXME
335      * @throws InstantiationException FIXME
336      */
337     public static Object call(Object o, Method m, String... params)
338             throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
339         Class<?>[] types = m.getParameterTypes();
340         if (!m.isVarArgs() && params.length != types.length) {
341             throw new IllegalArgumentException(String.format(
342                     "Bad number params we have %1$s parameters and waiting %2$s.",
343                     params.length, types.length));
344         }
345 
346         int last = types.length;
347         if (m.isVarArgs()) {
348             // on traite le dernier differement
349             last--;
350         }
351 
352         Object[] parameters = new Object[types.length];
353         for (int i = 0; i < last; i++) {
354             String v = params[i];
355             Class<?> clazz = types[i];
356             Object t = convert(v, clazz);
357             parameters[i] = t;
358         }
359 
360         if (m.isVarArgs()) {
361             Class<?> clazz = types[last]; // get var args type
362             clazz = clazz.getComponentType(); // get array component type
363             List<Object> tmp = new ArrayList<Object>();
364             for (int i = last; i < params.length; i++) {
365                 String v = params[i];
366                 Object t = convert(v, clazz);
367                 tmp.add(t);
368             }
369             parameters[last] = tmp.toArray((Object[]) Array.newInstance(clazz, tmp.size()));
370         }
371 
372         if (log.isDebugEnabled()) {
373             log.debug(t("nuitonutil.debug.objectutil.invoke", m, Arrays.toString(parameters)));
374         }
375         Object result = m.invoke(o, parameters);
376         return result;
377     }
378 
379     /**
380      * Get all methods with name given in argument without check parameters.
381      *
382      * @param clazz      where to search method
383      * @param methodName method name to search
384      * @param ignoreCase if true, ignore difference in method name case
385      * @return list of detected methods
386      */
387     public static List<Method> getMethod(Class<?> clazz,
388                                          String methodName,
389                                          boolean ignoreCase) {
390         List<Method> result = new ArrayList<Method>();
391 
392         Method[] methods = clazz.getMethods();
393         for (Method m : methods) {
394             if (ignoreCase && methodName.equalsIgnoreCase(m.getName()) ||
395                 methodName.equals(m.getName())) {
396                 result.add(m);
397             }
398         }
399 
400         return result;
401     }
402 
403     /**
404      * List method that match name, name must be [package.][class][#][method]
405      * if package, class or method missing, exception throw
406      *
407      * @param name name of the method
408      * @param ignoreCase check exact method name if false
409      * @return list of method that match name
410      * @since 2.6.9
411      */
412     static public List<Method> getMethod(String name, boolean ignoreCase) {
413         Class<?> clazz;
414         String className;
415         String methodName;
416 
417         // looking for method name
418         int sep = name.lastIndexOf(CLASS_METHOD_SEPARATOR);
419         if (sep == -1) {
420             throw new IllegalArgumentException(String.format(
421                     "Can't find method in %s", name));
422         } else {
423             className = name.substring(0, sep);
424             methodName = name.substring(sep + 1);
425         }
426 
427         // looking for class name
428         try {
429             clazz = Class.forName(className);
430         } catch (ClassNotFoundException eee) {
431             throw new IllegalArgumentException(String.format(
432                     "Can't find class %s", className));
433         }
434 
435         List<Method> result = ObjectUtil.getMethod(clazz, methodName, ignoreCase);
436         return result;
437     }
438 
439     public static Object newInstance(String constructorWithParams) throws ClassNotFoundException {
440         int p = constructorWithParams.indexOf('(');
441         int l = constructorWithParams.lastIndexOf(')');
442         String[] params = null;
443         String classname;
444         if (p != -1) {
445             String tmp = constructorWithParams.substring(p + 1, l);
446             params = StringUtil.split(tmp, ",");
447             classname = constructorWithParams.substring(0, p);
448         } else {
449             classname = constructorWithParams;
450         }
451         Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
452         Object result = newInstance(clazz, params);
453         return result;
454     }
455 
456     /**
457      * Create new instance of clazz, call constructor with params as String.
458      * Each param is converted to required type for
459      * constructor with beanutils converter, first constructor that permit
460      * instanciation is used
461      *
462      * @param <T>    type to instanciate
463      * @param clazz  class to instanciate
464      * @param params parameters for constructor call
465      * @return new instance of clazz
466      * @throws IllegalArgumentException FIXME
467      */
468     public static <T> T newInstance(Class<T> clazz, String... params)
469             throws IllegalArgumentException {
470         if (params == null) {
471             params = StringUtil.EMPTY_STRING_ARRAY;
472         }
473         List<Constructor<T>> constructors = getConstructor(clazz, params.length);
474 
475         for (Constructor<T> c : constructors) {
476             try {
477                 Class<?>[] types = c.getParameterTypes();
478 
479                 int last = types.length;
480                 if (c.isVarArgs()) {
481                     // on traite le dernier differement
482                     last--;
483                 }
484 
485                 Object[] parameters = new Object[types.length];
486                 for (int i = 0; i < last; i++) {
487                     String v = params[i];
488                     Class<?> argClazz = types[i];
489                     Object t = convert(v, argClazz);
490                     parameters[i] = t;
491                 }
492 
493                 if (c.isVarArgs()) {
494 
495                     // get var args type
496                     Class<?> argClazz = types[last];
497 
498                     // get array component type
499                     argClazz = argClazz.getComponentType();
500                     List<Object> tmp = new ArrayList<Object>();
501                     for (int i = last; i < params.length; i++) {
502                         String v = params[i];
503                         Object t = convert(v, argClazz);
504                         tmp.add(t);
505                     }
506                     parameters[last] =
507                             tmp.toArray((Object[]) Array.newInstance(
508                                     argClazz, tmp.size()));
509                 }
510 
511                 if (log.isDebugEnabled()) {
512                     log.debug(t("nuitonutil.debug.objectutil.create",
513                                 clazz, Arrays.toString(parameters)));
514                 }
515                 T result = c.newInstance(parameters);
516 
517                 return result;
518             } catch (Exception eee) {
519                 // this constructors don't work, try next
520                 if (log.isDebugEnabled()) {
521                     log.debug("Creation failed try with next constructor");
522                 }
523             }
524         }
525         throw new IllegalArgumentException(
526                 t("nuitonutil.debug.objectutil.instantiate",
527                   clazz, Arrays.toString(params)));
528     }
529 
530     /**
531      * Get all constructors that support paramNumber as parameters numbers.
532      * Varargs is supported
533      *
534      * @param <T>         le type de la classe a inspecter
535      * @param clazz       la classe sur lequel rechercher le constructeur
536      * @param paramNumber le nombre de parametre souhaite pour le constructeur,
537      *                    -1 indique que tous les constructeur sont souhaite.
538      * @return list of constructors
539      */
540     @SuppressWarnings("unchecked")
541     public static <T> List<Constructor<T>> getConstructor(Class<T> clazz,
542                                                           int paramNumber) {
543         List<Constructor<T>> result = new ArrayList<Constructor<T>>();
544         Constructor<T>[] constructors =
545                 (Constructor<T>[]) clazz.getConstructors();
546         for (Constructor<T> c : constructors) {
547             if (paramNumber < 0 ||
548                 c.isVarArgs() &&
549                 c.getParameterTypes().length <= paramNumber - 1 ||
550                 c.getParameterTypes().length == paramNumber) {
551                 result.add(c);
552             }
553         }
554 
555         return result;
556     }
557 
558     /**
559      * Tests if the given value is null according to default value for
560      * primitive types if nedded.
561      *
562      * @param value the value to test
563      * @return {@code true} if value is null or default value on a primitive
564      * @since 1.1.5
565      */
566     public static boolean isNullValue(Object value) {
567         if (value == null) {
568             return true;
569         }
570         Class<?> type = value.getClass();
571 
572         //FIXME-TC20100212 : this case can not be, due to auto-boxing mecanism
573         if (type.isPrimitive()) {
574             type = MethodUtils.getPrimitiveWrapper(type);
575 
576             if (Boolean.class.isAssignableFrom(type)) {
577                 return Boolean.FALSE.equals(value);
578             }
579             if (Integer.class.isAssignableFrom(type)) {
580                 return ZERO.equals(value);
581             }
582             if (Character.class.isAssignableFrom(type)) {
583                 return ZEROC.equals(value);
584             }
585             if (Float.class.isAssignableFrom(type)) {
586                 return ZEROF.equals(value);
587             }
588             if (Long.class.isAssignableFrom(type)) {
589                 return ZEROL.equals(value);
590             }
591             if (Double.class.isAssignableFrom(type)) {
592                 return ZEROD.equals(value);
593             }
594             if (Byte.class.isAssignableFrom(type)) {
595                 return ZEROB.equals(value);
596             }
597         }
598         return false;
599     }
600 
601     public static boolean isNullValue(boolean value) {
602         return Boolean.FALSE.equals(value);
603     }
604 
605     public static boolean isNullValue(byte value) {
606         return value == ZEROB;
607     }
608 
609     public static boolean isNullValue(int value) {
610         return value == ZEROB;
611     }
612 
613     public static boolean isNullValue(char value) {
614         return value == ZEROC;
615     }
616 
617     public static boolean isNullValue(float value) {
618         return value == ZEROF;
619     }
620 
621     public static boolean isNullValue(double value) {
622         return value == ZEROD;
623     }
624 
625     /**
626      * Verifie si la classe est de type primitif.
627      *
628      * @param clazz nom de la classe a tester
629      * @return vrai si le classe est de type primitif faux sinon
630      */
631     public static boolean isPrimitive(Class<?> clazz) {
632         return clazz.isPrimitive() || clazz == Boolean.class
633                || clazz == Byte.class || clazz == Character.class
634                || clazz == Short.class || clazz == Integer.class
635                || clazz == Long.class || clazz == Float.class
636                || clazz == Double.class;
637     }
638 
639 } // ObjectUtil
640