View Javadoc

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