View Javadoc

1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sourceforge.domian.util;
17  
18  
19  import java.io.Serializable;
20  import java.lang.reflect.AccessibleObject;
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.GenericArrayType;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.lang.reflect.Modifier;
28  import java.lang.reflect.ParameterizedType;
29  import java.lang.reflect.Type;
30  import java.lang.reflect.TypeVariable;
31  import java.math.BigDecimal;
32  import java.security.PrivilegedAction;
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Date;
36  import java.util.HashMap;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.UUID;
41  
42  import org.apache.commons.lang.Validate;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  import net.sourceforge.domian.entity.AbstractEntity;
47  import net.sourceforge.domian.entity.Entity;
48  
49  import static java.lang.Boolean.TRUE;
50  import static java.security.AccessController.doPrivileged;
51  import static org.apache.commons.lang.StringUtils.capitalize;
52  
53  
54  /**
55   * Generic static convenience methods dealing with reflection stuff.
56   *
57   * @author Eirik Torske
58   * @since 0.1
59   */
60  public final class ReflectionUtils {
61  
62      private static final Logger log = LoggerFactory.getLogger(ReflectionUtils.class);
63  
64      /** @return <code>true</code> if the <code>fromClass</code> class parameter can be casted to the <code>toClass</code> class parameter */
65      public static Boolean canCast(final Class fromClass, final Class toClass) {
66          return canCastFrom_To(fromClass, toClass);
67      }
68  
69      /** @return <code>true</code> if the <code>fromType</code> type parameter can be casted to the <code>toType</code> type parameter */
70      public static Boolean canCast(final Type fromType, final Type toType) {
71          return canCastFrom_To(fromType, toType);
72      }
73  
74      /** @return <code>true</code> if the <code>fromClass</code> class parameter can be casted to the <code>toClass</code> class parameter */
75      public static Boolean canCastFrom_To(final Class fromClass, final Class toClass) {
76          if (fromClass == null) {
77              return toClass == null;
78          } else if (toClass == null) {
79              return false;
80          }
81          if (fromClass.isPrimitive()) {
82              if (fromClass.equals(boolean.class)) {
83                  return canCastFrom_To(Boolean.class, toClass);
84  
85              } else if (fromClass.equals(long.class)) {
86                  return canCastFrom_To(Long.class, toClass);
87  
88              } else if (fromClass.equals(int.class)) {
89                  return canCastFrom_To(Integer.class, toClass);
90  
91              } else if (fromClass.equals(double.class)) {
92                  return canCastFrom_To(Double.class, toClass);
93  
94              } else if (fromClass.equals(float.class)) {
95                  return canCastFrom_To(Float.class, toClass);
96  
97              } else if (fromClass.equals(short.class)) {
98                  return canCastFrom_To(Short.class, toClass);
99              }
100         }
101         if (Serializable.class.isAssignableFrom(fromClass) && Serializable.class.isAssignableFrom(toClass)) {
102             if (CharSequence.class.isAssignableFrom(fromClass)) {
103                 if (Number.class.isAssignableFrom(toClass)) {
104                     return TRUE;
105                 }
106             }
107             if (CharSequence.class.isAssignableFrom(toClass)) {
108                 if (Number.class.isAssignableFrom(fromClass)) {
109                     return TRUE;
110                 }
111             }
112         }
113         return fromClass.equals(toClass) || toClass.isAssignableFrom(fromClass);
114     }
115 
116 
117     /** @return <code>true</code> if the <code>fromType</code> type parameter can be casted to the <code>toType</code> type parameter */
118     public static Boolean canCastFrom_To(final Type fromType, final Type toType) {
119         if (fromType instanceof Class && toType instanceof Class) {
120             return canCastFrom_To((Class) fromType, (Class) toType);
121         }
122         if (fromType == null) {
123             return toType == null;
124         } else if (toType == null) {
125             return false;
126         }
127         return fromType.equals(toType);
128     }
129 
130 
131     /** @return <code>true</code> if it is possible to cast on way or another between the two type parameters */
132     public static Boolean canCastAtLeastOneWay(final Type type1, final Type type2) {
133         return canCastFrom_To(type1, type2) || canCastFrom_To(type2, type1);
134     }
135 
136 
137     /** @return <code>true</code> if given number is a decimal number/floating point number */
138     public static Boolean isDecimalNumber(final Object number) {
139         return canCast(number.getClass(), Double.class) ||
140                canCast(number.getClass(), Float.class) ||
141                canCast(number.getClass(), BigDecimal.class);
142     }
143 
144 
145     public static Boolean isDate(final Object candidateObject) {
146         return canCast(candidateObject.getClass(), Date.class);
147     }
148 
149 
150     /** @return the field object with given name and parent class, or <code>null</code> if no field is found */
151     public static <T> Field getFieldByName(final String fieldName, final Class<T> declaringType) {
152         Validate.notNull(fieldName, "Field name parameter cannot be null");
153         Validate.notNull(declaringType, "Type parameter cannot be null");
154 
155         Field field = null;
156         try {
157             field = declaringType.getDeclaredField(fieldName);
158         } catch (NoSuchFieldException e) {
159             // Deliberately left empty
160         }
161         if (field == null) {
162             final Class<? super T> superType = declaringType.getSuperclass();
163             if (superType != null) {
164                 field = getFieldByName(fieldName, superType);
165             }
166         }
167         return field;
168     }
169 
170 
171     /**
172      * @return the method (with no formal parameters) with given name and parent class, or <code>null</code> if no method is found
173      * @since 0.3
174      */
175     public static <T> Method getMethodByName(final String methodName, final Class<T> declaringType) {
176         return getMethodByName(methodName, null, declaringType);
177     }
178 
179 
180     /**
181      * @return the method with given name, parameter types, and parent class, or <code>null</code> if no method is found
182      * @since 0.3
183      */
184     public static <T> Method getMethodByName(final String methodName, final Class[] parameterArray, final Class<T> declaringType) {
185         Validate.notNull(methodName, "Method name parameter cannot be null");
186         Validate.notNull(declaringType, "Type parameter cannot be null");
187 
188         Method method = null;
189         try {
190             method = declaringType.getDeclaredMethod(methodName, parameterArray);
191         } catch (NoSuchMethodException e) {
192             // Deliberately left empty
193         }
194         if (method == null) {
195             final Class<? super T> superType = declaringType.getSuperclass();
196             if (superType != null) {
197                 method = getMethodByName(methodName, parameterArray, superType);
198             }
199         }
200         return method;
201     }
202 
203 
204     /**
205      * If no method is found with the given name, each of the prefixes in the given list will be added until a valid method are found, or no more prefixes are left to try.
206      *
207      * @return the method with given prefix+name, and parent class, or <code>null</code> if no method is found
208      * @since 0.4
209      */
210     public static <T> Method getMethodByNameWithPossiblePrefix(final String methodName,
211                                                                final Class<T> declaringType,
212                                                                final List<String> possibleMethodPrefixes) {
213         Method method = getMethodByName(methodName, declaringType);
214         final Iterator<String> possibleMethodPrefixesIterator = possibleMethodPrefixes.iterator();
215         while (method == null && possibleMethodPrefixesIterator.hasNext()) {
216             method = getMethodByName(possibleMethodPrefixesIterator.next() + capitalize(methodName), declaringType);
217         }
218         return method;
219     }
220 
221 
222     /** @since 0.3 */
223     public static <T> Object invokeMethod(final T object, final String methodName, final Object[] parameterArray) {
224         final Method method;
225         if (parameterArray == null || parameterArray.length < 1) {
226             method = getMethodByName(methodName, null, object.getClass());
227         } else {
228             final Class[] parameterTypeArray = new Class[parameterArray.length];
229             final List<Class<?>> parameterTypeList = new ArrayList<Class<?>>();
230             for (final Object parameter : parameterArray) {
231                 parameterTypeList.add(parameter.getClass());
232             }
233             method = getMethodByName(methodName, parameterTypeList.toArray(parameterTypeArray), object.getClass());
234         }
235         if (method != null) {
236             try {
237                 return method.invoke(object, parameterArray);
238 
239             } catch (IllegalArgumentException e) {
240                 return null;
241             } catch (IllegalAccessException e) {
242                 return null;
243             } catch (InvocationTargetException e) {
244                 return null;
245             }
246         } else {
247             return null;
248         }
249     }
250 
251 
252     /** @since 0.3 */
253     static <T> Object invokeMethod(final T object, final String methodName) {
254         return invokeMethod(object, methodName, null);
255     }
256 
257 
258     /** @since 0.3 */
259     static <T> Boolean invokeBooleanMethod(final T object, final String methodName, final Object[] parameterArray) {
260         final Object retVal = invokeMethod(object, methodName, parameterArray);
261         return retVal != null && retVal instanceof Boolean && (Boolean) retVal;
262     }
263 
264 
265     /** @since 0.3 */
266     static <T> Boolean invokeBooleanMethod(final T object, final String methodName) {
267         return invokeBooleanMethod(object, methodName, null);
268     }
269 
270 
271     /**
272      * @return Map of all fields, and methods (not constructors) in the given object, indexed by their names
273      * @since 0.3
274      */
275     public static Map<String, AccessibleObject> getAllAccessibleObjectsFrom(final Object object) {
276         final Map<String, AccessibleObject> accessibleObjectMap = new HashMap<String, AccessibleObject>();
277         final Method[] methods = object.getClass().getDeclaredMethods();
278         for (final Method method : methods) {
279             accessibleObjectMap.put(method.getName(), method);
280         }
281         final Field[] fields = object.getClass().getDeclaredFields();
282         for (final Field field : fields) {
283             accessibleObjectMap.put(field.getName(), field);
284         }
285         return accessibleObjectMap;
286     }
287 
288 
289     /**
290      * This flag is used by the
291      * <a href="ReflectionUtils.html#cloneOrDeepCopyIfNotImmutable(java.lang.Object)"><code>cloneOrDeepCopyIfNotImmutable()</code></a> method.
292      * <p/>
293      * The default value is <code>true</code>.
294      * If object copying is not wanted, override it by setting this flag to <code>false</code>.
295      * Such an operation is completely sound, given the premis of objects in use being either immutable value objets,
296      * or entity objects having <code>equals</code>/<code>hashCode</code> methods not affected by mutable state.
297      * <p/>
298      * <b>WARNING!
299      * <i>By setting this field to <code>false</code>, certain specifications turn mutable,
300      * e.g. the {@link net.sourceforge.domian.specification.ValueBoundSpecification}.
301      * This is because the state of the object given to the specification (the value that is) may be altered.</i>
302      * </b>
303      * <p/>
304      * <b>WARNING!
305      * <i>For now, this is simply a static non-thread-safe JVM wide flag.</i>
306      * </b>
307      */
308     public static Boolean DO_COPY_OBJECTS = true;
309 
310 
311     /**
312      * Often there is no real necessity to copy entity objects.
313      * Entity objects are inherently mutable, but all state involved in the <code>equals</code>/<code>hashCode</code> methods should not be allowed to change in any way.
314      * In cases where only the <code>equals</code>/<code>hashCode</code> methods are to be invoked, copying the object is not really needed.
315      * <p/>
316      * This flag is used by the
317      * <a href="ReflectionUtils.html#cloneOrDeepCopyIfNotImmutable(java.lang.Object)"><code>cloneOrDeepCopyIfNotImmutable()</code></a> method.
318      * Which objects that are regarded as <i>entities</i> is specified by the <code>isEntity()</code> method
319      * <p/>
320      * The default value is <code>false</code>.
321      * If entity copying is wanted, override it by setting this flag to <code>true</code>.
322      * <p/>
323      * <b>WARNING!
324      * <i>For now, this is simply a static non-thread-safe JVM wide flag.</i>
325      * </b>
326      *
327      * @see <a href="ReflectionUtils.html#isEntity(T)"><code>ReflectionUtils.isEntity(T)</code></a>
328      */
329     public static Boolean DO_COPY_ENTITIES = false;
330 
331 
332     /**
333      * Recursive copying of objects cannot go on forever!
334      * The default treshold when to stop recursively copying fields is set to <b>5</b>.
335      * This means; all kinds of objects given as a value to {@link net.sourceforge.domian.specification.ValueBoundSpecification}s
336      * should not include more than five levels of referencing in its <code>equals()</code> or <code>compareTo()</code> method.
337      * (If you are doing such a thing, you should seriously consider refactoring anyway!)
338      * <p/>
339      * This flag is used by the
340      * <a href="ReflectionUtils.html#cloneOrDeepCopyIfNotImmutable(java.lang.Object)"><code>cloneOrDeepCopyIfNotImmutable()</code></a> method.
341      * <p/>
342      * <b>WARNING!
343      * <i>For now, this is simply a static non-thread-safe JVM wide flag.</i>
344      * </b>
345      */
346     public static int RECURSIVE_COPYING_DEPTH_TRESHOLD = 5;
347 
348 
349     /**
350      * Replicates the given object.
351      *
352      * The given object is cloned if it is {@link Cloneable}.
353      * If not, it is deep-copied, except if the given object is regarded as immutable.
354      * Deep-copying follows the {@code DO_COPY_OBJECTS}, {@code DO_COPY_ENTITIES}, and {@code RECURSIVE_COPYING_DEPTH_TRESHOLD} settings.
355      *
356      * @param object object to replicate
357      * @return a replicated version, or the same object if it is regarded as immutable
358      * @throws SecurityException if a security manager exists and if the caller does not have granted java.lang.reflect.ReflectPermission.suppressAccessChecks
359      * @since 0.4.1
360      */
361     public static <T> T replicate(final T object) {
362         return cloneOrDeepCopyIfNotImmutable(object);
363     }
364 
365 
366     /**
367      * @param object object to clone or deep copy, if necessery
368      * @return a cloned version, deep copied version, or the same object if it is immutable
369      * @throws SecurityException if a security manager exists and if the caller does not have granted java.lang.reflect.ReflectPermission.suppressAccessChecks
370      * @since 0.2.1
371      */
372     public static <T> T cloneOrDeepCopyIfNotImmutable(final T object) {
373         return cloneOrDeepCopyIfNotImmutable(object, DO_COPY_ENTITIES);
374     }
375 
376 
377     /**
378      * @param object         object to clone or deep copy, if necessery
379      * @param doCopyEntities flag to indicate whether copying of entites are wanted or not
380      * @return a cloned version, deep copied version, or the same object if it is immutable
381      * @throws SecurityException if a security manager exists and if the caller does not have granted java.lang.reflect.ReflectPermission.suppressAccessChecks
382      * @since 0.2.1
383      */
384     public static <T> T cloneOrDeepCopyIfNotImmutable(final T object, final Boolean doCopyEntities) {
385         return cloneOrDeepCopyIfNotImmutable(object, new HashMap(), 0, RECURSIVE_COPYING_DEPTH_TRESHOLD, DO_COPY_OBJECTS, doCopyEntities, false);
386     }
387 
388 
389     /**
390      * @return a cloned version, deep copied version, or the same object if it is immutable
391      * @throws SecurityException if a security manager exists and if the caller does not have granted java.lang.reflect.ReflectPermission.suppressAccessChecks
392      * @since 0.2.1
393      */
394     @SuppressWarnings("unchecked")
395     static <T> T cloneOrDeepCopyIfNotImmutable(final T object,
396                                                final Map mapOfObjectsInprocess,
397                                                int recursiveDepth,
398                                                final int recursiveDepthTreshold,
399                                                final boolean doCopyObject,
400                                                final boolean doCopyEntities,
401                                                final boolean entityCopyingInProcess) {
402         if (!doCopyObject) { // Copying overridden...
403             return object;
404         }
405         if (recursiveDepth > recursiveDepthTreshold) { // Recursive treshold reached...
406             return null;
407         }
408         if (object == null) { // Well...
409             return null;
410         }
411         if (isAlreadyBeingProcessed(object, mapOfObjectsInprocess)) { // Return reference of object in process of being copied
412             /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
413             return (T) mapOfObjectsInprocess.get(object);
414         }
415         if (isImmutableObject(object)) { // Don't bother...
416             return object;
417         }
418         if (isEntity(object) && !entityCopyingInProcess) {
419             if (!doCopyEntities) {
420                 return object; // Copying overridden...
421             } else {
422                 T entityCopy = (T) copyEntity((Entity) object, mapOfObjectsInprocess, recursiveDepth, recursiveDepthTreshold, doCopyObject);
423                 if (entityCopy != null) {
424                     return entityCopy;
425                 } else {
426                     log.warn("Unable to deep copy entity object=" + object + "; trying ordinary deep copying...");
427                 }
428             }
429         }
430         if (isCloneableObject(object)) { // Just clone directly...
431             final T clonedObject = doCloneIfNotTheCloningIsJustShallowCopying(object);
432             if (clonedObject != null) {
433                 return clonedObject;
434             }
435         }
436 
437         // Otherwise: do deep copy
438         final T copiedObject;
439 
440         // Collection type
441         if (object instanceof Collection) {
442             try {
443                 /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
444                 final Constructor<T> defaultConstructor = (Constructor<T>) object.getClass().getDeclaredConstructor();
445                 doPrivileged(new PrivilegedAction<Void>() {
446                     public Void run() {
447                         defaultConstructor.setAccessible(true);
448                         return null;
449                     }
450                 });
451                 copiedObject = defaultConstructor.newInstance();
452 
453             } catch (NoSuchMethodException e) {
454                 // Keep until beta or something...
455                 log.warn("Unable to deep copy object=" + object + " due to missing default constructor; just returning object itself... beware of state changes if object is mutable!");
456                 //e.printStackTrace();
457                 return object;
458 
459             } catch (IllegalAccessException e) {
460                 // Keep until beta or something...
461                 e.printStackTrace();
462                 return object;
463 
464             } catch (InvocationTargetException e) {
465                 // Keep until beta or something...
466                 e.printStackTrace();
467                 return object;
468 
469             } catch (InstantiationException e) {
470                 // Keep until beta or something...
471                 e.printStackTrace();
472                 return object;
473 
474             } catch (Throwable t) {
475                 // Keep until beta or something...
476                 log.warn("Unable to deep copy object=" + object + ". Exception caught: " + t.getMessage() + "; just returning object itself... beware of state changes if object is mutable!");
477                 t.printStackTrace();
478                 return object;
479             }
480             ++recursiveDepth;
481             for (final Object element : (Collection) object) {
482                 /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
483                 ((Collection) copiedObject).add(
484                         cloneOrDeepCopyIfNotImmutable(element,
485                                                       mapOfObjectsInprocess,
486                                                       recursiveDepth,
487                                                       recursiveDepthTreshold,
488                                                       doCopyObject,
489                                                       doCopyEntities,
490                                                       false));
491             }
492             return copiedObject;
493         }
494         // Map type
495         if (object instanceof Map) {
496             try {
497                 /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
498                 final Constructor<T> defaultConstructor = (Constructor<T>) object.getClass().getDeclaredConstructor();
499                 doPrivileged(new PrivilegedAction<Void>() {
500                     public Void run() {
501                         defaultConstructor.setAccessible(true);
502                         return null;
503                     }
504                 });
505                 copiedObject = defaultConstructor.newInstance();
506 
507             } catch (NoSuchMethodException e) {
508                 // Keep until beta or something...
509                 log.warn("Unable to deep copy object=" + object + " due to missing default constructor; just returning object itself... beware of state changes if object is mutable!");
510                 //e.printStackTrace();
511                 return object;
512 
513             } catch (IllegalAccessException e) {
514                 // Keep until beta or something...
515                 e.printStackTrace();
516                 return object;
517 
518             } catch (InvocationTargetException e) {
519                 // Keep until beta or something...
520                 e.printStackTrace();
521                 return object;
522 
523             } catch (InstantiationException e) {
524                 // Keep until beta or something...
525                 e.printStackTrace();
526                 return object;
527 
528             } catch (Throwable t) {
529                 // Keep until beta or something...
530                 log.warn("Unable to deep copy object=" + object + ". Exception caught: " + t.getMessage() + "; just returning object itself... beware of state changes if object is mutable!");
531                 t.printStackTrace();
532                 return object;
533             }
534             ++recursiveDepth;
535             for (final Object valueKey : ((Map) object).keySet()) {
536                 final Object value = ((Map) object).get(valueKey);
537                 /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
538                 ((Map) copiedObject).put(cloneOrDeepCopyIfNotImmutable(valueKey,
539                                                                        mapOfObjectsInprocess,
540                                                                        recursiveDepth,
541                                                                        recursiveDepthTreshold,
542                                                                        doCopyObject,
543                                                                        doCopyEntities,
544                                                                        false),
545                                          cloneOrDeepCopyIfNotImmutable(value,
546                                                                        mapOfObjectsInprocess,
547                                                                        recursiveDepth,
548                                                                        recursiveDepthTreshold,
549                                                                        doCopyObject,
550                                                                        doCopyEntities,
551                                                                        false));
552             }
553             return copiedObject;
554 
555         }
556         // Array type
557         if (object.getClass().isArray()) {
558             final int arrayLength = Array.getLength(object);
559             /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
560             copiedObject = (T) Array.newInstance(object.getClass().getComponentType(), arrayLength);
561             ++recursiveDepth;
562             for (int arrayElementIndex = 0; arrayElementIndex < arrayLength; ++arrayElementIndex) {
563                 final Object arrayElement = Array.get(object, arrayElementIndex);
564                 if (arrayElement != null) {
565                     Array.set(copiedObject,
566                               arrayElementIndex,
567                               cloneOrDeepCopyIfNotImmutable(arrayElement,
568                                                             mapOfObjectsInprocess,
569                                                             recursiveDepth,
570                                                             recursiveDepthTreshold,
571                                                             doCopyObject,
572                                                             doCopyEntities,
573                                                             false));
574                 }
575             }
576             return copiedObject;
577         }
578         // Ordinary type
579         try {
580             /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
581             final Constructor<T> defaultConstructor = (Constructor<T>) object.getClass().getDeclaredConstructor();
582             doPrivileged(new PrivilegedAction<Void>() {
583                 public Void run() {
584                     defaultConstructor.setAccessible(true);
585                     return null;
586                 }
587             });
588             copiedObject = defaultConstructor.newInstance();
589             // Keep reference to avoid endless looping
590             /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
591             mapOfObjectsInprocess.put(object, copiedObject);
592             ++recursiveDepth;
593             for (final Field field : object.getClass().getDeclaredFields()) {
594                 if (!Modifier.isStatic(field.getModifiers())) {
595                     doPrivileged(new PrivilegedAction<Void>() {
596                         public Void run() {
597                             field.setAccessible(true);
598                             return null;
599                         }
600                     });
601                     final Object clonedField =
602                             cloneOrDeepCopyIfNotImmutable(field.get(object),
603                                                           mapOfObjectsInprocess,
604                                                           recursiveDepth,
605                                                           recursiveDepthTreshold,
606                                                           doCopyObject,
607                                                           doCopyEntities,
608                                                           false);
609                     field.set(copiedObject, clonedField);
610                 }
611             }
612         } catch (NoSuchMethodException e) {
613             // Keep until beta or something...
614             log.warn("Unable to deep copy object=" + object + " due to missing default constructor; just returning object itself... beware of state changes if object is mutable!");
615             //e.printStackTrace();
616             return object;
617 
618         } catch (IllegalAccessException e) {
619             // Keep until beta or something...
620             e.printStackTrace();
621             return object;
622 
623         } catch (InvocationTargetException e) {
624             // Keep until beta or something...
625             e.printStackTrace();
626             return object;
627 
628         } catch (InstantiationException e) {
629             // Keep until beta or something...
630             e.printStackTrace();
631             return object;
632 
633         } catch (Throwable t) {
634             // Keep until beta or something...
635             log.warn("Unable to deep copy object=" + object + ". Exception caught: " + t.getMessage());
636             t.printStackTrace();
637             return object;
638         }
639         return copiedObject;
640     }
641 
642 
643     /** @since 0.2.1 */
644     static <T> Boolean isAlreadyBeingProcessed(final T object, final Map<?, ?> mapOfObjectsBeingProcessed) {
645         return mapOfObjectsBeingProcessed.keySet().contains(object);
646     }
647 
648 
649     /** @since 0.2.1 */
650     static <T> Boolean isCloneableObject(final T object) {
651         return canCastFrom_To(object.getClass(), Cloneable.class);
652     }
653 
654 
655     /** @since 0.2.1 */
656     static <T> Boolean isImmutableObject(final T object) {
657         if (object.getClass().isArray()) { // Array classes have no fields...
658             return false;
659         }
660         if (object instanceof String ||
661             object instanceof Boolean ||
662             object instanceof Number ||
663             object instanceof Enum ||
664             object instanceof UUID) {
665             return true;
666         }
667         if (object.getClass().getDeclaredFields().length == 0) {
668             return true;
669         }
670         if (invokeBooleanMethod(object, "isImmutable")) {
671             return true;
672         }
673         if (invokeBooleanMethod(object, "isValueObject")) {
674             return true;
675         }
676         /* Redundant...
677         if (invokeBooleanMethod(object, "isEntity")) {
678             return false;
679         }
680         if (invokeBooleanMethod(object, "isEntityObject")) {
681             return false;
682         }
683         */
684         return false;
685     }
686 
687 
688     /**
689      * An object is regarded as an <i>entity</i> if and only if:
690      * <ul>
691      * <li/>the object is an instance of {@link AbstractEntity}
692      * </ul>
693      * The reason instances have to be of type {@link AbstractEntity}, and not just {@link Entity},
694      * is simply that for {@link Entity} objects there is no assurance that the semantics of <i>consistent/unchanging</i>
695      * <code>equals</code>/<code>hashCode</code> methods are addressed.
696      * For {@link AbstractEntity} objects on the other hand, there is.
697      *
698      * @return <code>true</code> if the given object is safely regarded by Domian as a real <i>entity</i> object
699      * @since 0.4
700      */
701     public static <T> Boolean isEntity(final T object) {
702         return object instanceof AbstractEntity; // || getEntityIdField(object) != null; Last expression is not accurate/semantically safe
703     }
704 
705 
706     /** @since 0.4.1 */
707     static boolean hasEntityIdField(final Object object) {
708         return getEntityIdField(object) != null;
709     }
710 
711 
712     /** @since 0.4 */
713     static <T> Field getEntityIdField(final T object) {
714         final Field idField = getFieldByName("entityId", object.getClass());
715         /* n/a as this method (for now) only is invoked on classes having field 'entityId' (AbstractEntity)
716         if (idField == null) {
717             idField = getFieldByName("uuid", object.getClass());
718         }
719         if (idField == null) {
720             idField = getFieldByName("uid", object.getClass());
721         }
722         if (idField == null) {
723             idField = getFieldByName("id", object.getClass());
724         }
725         */
726         return idField;
727     }
728 
729 
730     /**
731      * @return the cloned entity object
732      * @throws IllegalArgumentException if the entity object to copy is missing a proper entity id
733      * @throws SecurityException        if a security manager exists and if the caller does not have granted java.lang.reflect.ReflectPermission.suppressAccessChecks
734      * @since 0.4
735      */
736     static <T extends Entity> T copyEntity(final T entityObject,
737                                            final Map<T, T> mapOfObjectsBeingProcessed,
738                                            int recursiveDepth,
739                                            final int recursiveDepthTreshold,
740                                            final boolean doCopyObject) {
741         final Field entityIdField = getEntityIdField(entityObject);
742         if (entityIdField == null) {
743             throw new IllegalArgumentException("Unable to copy entity of type=" + entityObject.getClass().getName() + ". The class seems not to posess a proper entityId field.");
744         }
745 
746         try {
747             final T copiedEntityObject = cloneOrDeepCopyIfNotImmutable(entityObject,
748                                                                        mapOfObjectsBeingProcessed,
749                                                                        recursiveDepth,
750                                                                        recursiveDepthTreshold,
751                                                                        doCopyObject,
752                                                                        true,
753                                                                        true);
754 
755             mapOfObjectsBeingProcessed.put(entityObject, copiedEntityObject);
756 
757             doPrivileged(new PrivilegedAction<Void>() {
758                 public Void run() {
759                     entityIdField.setAccessible(true);
760                     return null;
761                 }
762             });
763             final Object entityId = entityIdField.get(entityObject);
764             entityIdField.set(copiedEntityObject, entityId);
765 
766             return copiedEntityObject;
767 
768         } catch (IllegalAccessException e) {
769             throw new IllegalArgumentException("Unable to copy entity object " + entityObject.getClass().getName(), e);
770         }
771     }
772 
773 
774     /**
775      * @return the cloned object, or <code>null</code> if shallow copying is known to take place...
776      * @since 0.2.1
777      */
778     @SuppressWarnings("unchecked")
779     static <T> T doCloneIfNotTheCloningIsJustShallowCopying(final T object) {
780         if (object instanceof Collection) { // Just shallow copying mostly...
781             return null;
782 
783         } else if (object instanceof Map) { // Just shallow copying mostly...
784             return null;
785 
786         } else if (object.getClass().isArray()) { // Don't go there...
787             return null;
788 
789         } else {
790             /* @SuppressWarnings("unchecked") -> TODO: check out the type mismatch implications here */
791             return (T) invokeMethod(object, "clone");
792         }
793     }
794 
795 
796     /**
797      * @param type the type
798      * @return the underlying class for the given type, or <code>null</code> if the type is a variable type
799      * @see <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=208860">Reflecting generics, by Ian Robertson</a>
800      * @since 0.4
801      */
802     public static Class<?> getClass(final Type type) {
803         if (type instanceof Class) {
804             return (Class) type;
805 
806         } else if (type instanceof ParameterizedType) {
807             return getClass(((ParameterizedType) type).getRawType());
808 
809         } else if (type instanceof GenericArrayType) {
810             final Type componentType = ((GenericArrayType) type).getGenericComponentType();
811             final Class<?> componentClass = getClass(componentType);
812             if (componentClass != null) {
813                 return Array.newInstance(componentClass, 0).getClass();
814             } else {
815                 return null;
816             }
817 
818         } else {
819             return null;
820         }
821     }
822 
823 
824     /**
825      * Gets the actual type arguments a subclass has used to extend a generic base class.
826      *
827      * @param baseClass the base class
828      * @param subclass  the subclass
829      * @return a list of the raw classes for the actual type arguments.
830      * @see <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=208860">Reflecting generics, by Ian Robertson</a>
831      * @since 0.4
832      */
833     public static <T> List<Class<?>> getTypeArguments(final Class<T> baseClass, final Class<? extends T> subclass) {
834         final Map<Type, Type> resolvedTypes = new HashMap<Type, Type>();
835         Type type = subclass;
836         /* Start walking up the inheritance hierarchy until we hit baseClass */
837         while (!getClass(type).equals(baseClass)) {
838             if (type instanceof Class) {
839                 /* There is no useful information for us in raw types, so just keep going */
840                 type = ((Class) type).getGenericSuperclass();
841 
842             } else {
843                 final ParameterizedType parameterizedType = (ParameterizedType) type;
844                 final Class<?> rawType = (Class) parameterizedType.getRawType();
845 
846                 final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
847                 final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
848                 for (int i = 0; i < actualTypeArguments.length; ++i) {
849                     resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
850                 }
851 
852                 if (!rawType.equals(baseClass)) {
853                     type = rawType.getGenericSuperclass();
854                 }
855             }
856         }
857         /* Finally, for each actual type argument provided to baseClass, determine (if possible) the raw class for that type argument */
858         final Type[] actualTypeArguments;
859         if (type instanceof Class) {
860             actualTypeArguments = ((Class) type).getTypeParameters();
861         } else {
862             actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
863         }
864 
865         final List<Class<?>> typeArgumentsAsClasses = new ArrayList<Class<?>>();
866         /* Resolve types by chasing down type variables. */
867         for (Type baseType : actualTypeArguments) {
868             while (resolvedTypes.containsKey(baseType)) {
869                 baseType = resolvedTypes.get(baseType);
870             }
871             typeArgumentsAsClasses.add(getClass(baseType));
872         }
873 
874         return typeArgumentsAsClasses;
875     }
876 }