View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 org.kuali.rice.krad.uif.util;
17  
18  import org.kuali.rice.core.api.exception.RiceRuntimeException;
19  import org.kuali.rice.krad.uif.component.ReferenceCopy;
20  
21  import java.lang.annotation.Annotation;
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  import java.util.concurrent.atomic.AtomicInteger;
32  import java.util.concurrent.atomic.AtomicLong;
33  
34  /**
35   * Utility class for copying objects using reflection. Modified from the jCommon
36   * library: http://www.matthicks.com/2008/05/fastest-deep-cloning.html
37   * 
38   * @author Matt Hicks
39   * @author Kuali Rice Team (rice.collab@kuali.org)
40   */
41  public class CloneUtils {
42      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CloneUtils.class);
43  
44      private static final Map<String, Field[]> fieldCache = new HashMap<String, Field[]>();
45      private static final Map<String, Field> internalFields = new HashMap<String, Field>();
46  
47      public static final <O> O deepClone(O original) {
48          try {
49              return deepCloneReflection(original);
50          } catch (Exception e) {
51              throw new RiceRuntimeException(e);
52          }
53      }
54  
55      @SuppressWarnings("unchecked")
56      public static final <O> O deepCloneReflection(O original) throws Exception {
57          return (O) deepCloneReflectionInternal(original, new HashMap<Object, Object>(), false);
58      }
59  
60      protected static final Object deepCloneReflectionInternal(Object original, Map<Object, Object> cache,
61              boolean referenceCollectionCopy) throws Exception {
62          if (original == null) { // No need to clone nulls
63              return original;
64          }
65          else if (cache.containsKey(original)) {
66              return cache.get(original);
67          }
68  
69          // Deep clone
70          Object clone = null;
71          if (List.class.isAssignableFrom(original.getClass())) {
72              clone = deepCloneList(original, cache, referenceCollectionCopy);
73          }
74          else if (Map.class.isAssignableFrom(original.getClass())) {
75              clone = deepCloneMap(original, cache, referenceCollectionCopy);
76          }
77          else {
78              clone = deepCloneObject(original, cache);
79          }
80  
81          return clone;
82      }
83  
84      protected static Object deepCloneObject(Object original, Map<Object, Object> cache) throws Exception {
85          if (original instanceof Number) { // Numbers are immutable
86              if (original instanceof AtomicInteger) {
87                  // AtomicIntegers are mutable
88              }
89              else if (original instanceof AtomicLong) {
90                  // AtomLongs are mutable
91              }
92              else {
93                  return original;
94              }
95          }
96          else if (original instanceof String) { // Strings are immutable
97              return original;
98          }
99          else if (original instanceof Character) { // Characters are immutable
100             return original;
101         }
102         else if (original instanceof Class) { // Classes are immutable
103             return original;
104         }
105         else if (original instanceof Boolean) {
106             return new Boolean(((Boolean) original).booleanValue());
107         }
108 
109         // To our understanding, this is a mutable object, so clone it
110         Class<?> c = original.getClass();
111         Field[] fields = getFields(c, false);
112         try {
113             Object copy = instantiate(original);
114 
115             // Put into cache
116             cache.put(original, copy);
117 
118             // iterate through and copy fields
119             for (Field f : fields) {
120                 Object object = f.get(original);
121 
122                 boolean referenceCopy = false;
123                 boolean referenceCollectionCopy = false;
124                 ReferenceCopy copyAnnotation = f.getAnnotation(ReferenceCopy.class);
125                 if (copyAnnotation != null) {
126                     referenceCopy = true;
127                     referenceCollectionCopy = copyAnnotation.newCollectionInstance();
128                 }
129 
130                 if (!referenceCopy || referenceCollectionCopy) {
131                     object = CloneUtils.deepCloneReflectionInternal(object, cache, referenceCollectionCopy);
132                 }
133                 f.set(copy, object);
134             }
135 
136             return copy;
137         }
138         catch (Throwable t) {
139             LOG.debug("Exception during clone (returning original): " + t.getMessage());
140             return original;
141         }
142     }
143 
144     @SuppressWarnings("unchecked")
145     protected static Object deepCloneMap(Object original, Map<Object, Object> cache, boolean referenceCollectionCopy)
146             throws Exception {
147         // Instantiate a new instance
148         Map<Object, Object> clone = (Map<Object, Object>) instantiate(original);
149 
150         // Populate data
151         for (Entry<Object, Object> entry : ((Map<Object, Object>) original).entrySet()) {
152             if (referenceCollectionCopy) {
153                 clone.put(entry.getKey(), entry.getValue());
154             }
155             else {
156                 clone.put(deepCloneReflectionInternal(entry.getKey(), cache, false),
157                         deepCloneReflectionInternal(entry.getValue(), cache, false));
158             }
159         }
160 
161         return clone;
162     }
163 
164     @SuppressWarnings("unchecked")
165     protected static Object deepCloneList(Object original, Map<Object, Object> cache, boolean referenceCollectionCopy)
166             throws Exception {
167         // Instantiate a new instance
168         List<Object> clone = (List<Object>) instantiate(original);
169 
170         // Populate data
171         for (Iterator<Object> iterator = ((List<Object>) original).iterator(); iterator.hasNext();) {
172             Object object = iterator.next();
173             if (referenceCollectionCopy) {
174                 clone.add(object);
175             }
176             else {
177                 clone.add(deepCloneReflectionInternal(object, cache, false));
178             }
179         }
180 
181         return clone;
182     }
183 
184     /**
185      * Retrieves all field names for the given class that have the given annotation
186      *
187      * @param clazz - class to find field annotations for
188      * @param annotationClass - class for annotation to find
189      * @return Map<String, Annotation> map containing the field name that has the annotation as a key and the
190      *         annotation instance as a value
191      */
192     public static Map<String, Annotation> getFieldsWithAnnotation(Class<?> clazz,
193             Class<? extends Annotation> annotationClass) {
194         Map<String, Annotation> annotationFields = new HashMap<String, Annotation>();
195 
196         Field[] fields = getFields(clazz, false);
197         for (Field f : fields) {
198             Annotation fieldAnnotation = f.getAnnotation(annotationClass);
199             if (fieldAnnotation != null) {
200                 annotationFields.put(f.getName(), fieldAnnotation);
201             }
202         }
203 
204         return annotationFields;
205     }
206 
207     /**
208      * Determines whether the property of the given class has the given annotation specified
209      *
210      * @param clazz - class containing the property to check
211      * @param propertyName - name of the property to check
212      * @param annotationClass - class for the annotation to look for
213      * @return boolean true if the field associated with the property name has the given annotation, false if not
214      */
215     public static boolean fieldHasAnnotation(Class<?> clazz, String propertyName,
216             Class<? extends Annotation> annotationClass) {
217         Field[] fields = getFields(clazz, false);
218         for (int i = 0; i < fields.length; i++) {
219             Field field = fields[i];
220             if (field.getName().equals(propertyName)) {
221                 Annotation fieldAnnotation = field.getAnnotation(annotationClass);
222                 if (fieldAnnotation != null) {
223                     return true;
224                 } else {
225                     return false;
226                 }
227             }
228         }
229 
230         return false;
231     }
232 
233     protected static final Object instantiate(Object original) throws InstantiationException, IllegalAccessException {
234         return original.getClass().newInstance();
235     }
236 
237     public static Field[] getFields(Object object, boolean includeStatic) {
238         return getFields(object, includeStatic, true);
239     }
240 
241     public static Field[] getFields(Object object, boolean includeStatic, boolean includeTransient) {
242         Class<?> c = object.getClass();
243         return getFields(c, includeStatic, includeTransient);
244     }
245 
246     public static Field[] getFields(Class<?> c, boolean includeStatic) {
247         return getFields(c, includeStatic, true);
248     }
249 
250     public static Field[] getFields(Class<?> c, boolean includeStatic, boolean includeTransient) {
251         String cacheKey = c.getCanonicalName() + ":" + includeStatic;
252         Field[] array = fieldCache.get(cacheKey);
253 
254         if (array == null) {
255             ArrayList<Field> fields = new ArrayList<Field>();
256 
257             List<Class<?>> classes = getClassHierarchy(c, false);
258 
259             // Reverse order so we make sure we maintain consistent order
260             Collections.reverse(classes);
261 
262             for (Class<?> clazz : classes) {
263                 Field[] allFields = clazz.getDeclaredFields();
264                 for (Field f : allFields) {
265                     if ((!includeTransient) && ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT)) {
266                         continue;
267                     }
268                     else if (f.isSynthetic()) {
269                         // Synthetic fields are bad!!!
270                         continue;
271                     }
272                     boolean isStatic = (f.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
273                     if ((isStatic) && (!includeStatic)) {
274                         continue;
275                     }
276                     if (f.getName().equalsIgnoreCase("serialVersionUID")) {
277                         continue;
278                     }
279                     f.setAccessible(true);
280                     fields.add(f);
281                 }
282             }
283 
284             array = fields.toArray(new Field[fields.size()]);
285             fieldCache.put(cacheKey, array);
286         }
287         return array;
288     }
289 
290     protected static final Field internalField(Object object, String fieldName) {
291         if (object == null) {
292             System.out.println("Internal Field: " + object + ", " + fieldName);
293             return null;
294         }
295 
296         String key = object.getClass().getCanonicalName() + "." + fieldName;
297         Field field = internalFields.get(key);
298         if (field == null) {
299             Field[] fields = getFields(object.getClass(), false);
300 
301             for (Field f : fields) {
302                 String name = f.getName();
303                 if (name.equals(fieldName)) {
304                     field = f;
305                     internalFields.put(key, field);
306                     break;
307                 }
308             }
309         }
310 
311         return field;
312     }
313 
314     protected static List<Class<?>> getClassHierarchy(Class<?> c, boolean includeInterfaces) {
315         List<Class<?>> classes = new ArrayList<Class<?>>();
316         while (c != Object.class) {
317             classes.add(c);
318             if (includeInterfaces) {
319                 Class<?>[] interfaces = c.getInterfaces();
320                 for (Class<?> i : interfaces) {
321                     classes.add(i);
322                 }
323             }
324             c = c.getSuperclass();
325             if (c == null) {
326                 break;
327             }
328         }
329 
330         return classes;
331     }
332 
333 }