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 }