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.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         else if (original instanceof Enum) {
109             return original;
110         }
111 
112         // To our understanding, this is a mutable object, so clone it
113         Class<?> c = original.getClass();
114         Field[] fields = getFields(c, false);
115         try {
116             Object copy = instantiate(original);
117 
118             // Put into cache
119             cache.put(original, copy);
120 
121             // iterate through and copy fields
122             for (Field f : fields) {
123                 Object object = f.get(original);
124 
125                 boolean referenceCopy = false;
126                 boolean referenceCollectionCopy = false;
127                 ReferenceCopy copyAnnotation = f.getAnnotation(ReferenceCopy.class);
128                 if (copyAnnotation != null) {
129                     referenceCopy = true;
130                     referenceCollectionCopy = copyAnnotation.newCollectionInstance();
131                 }
132 
133                 if (!referenceCopy || referenceCollectionCopy) {
134                     object = CloneUtils.deepCloneReflectionInternal(object, cache, referenceCollectionCopy);
135                 }
136                 f.set(copy, object);
137             }
138 
139             return copy;
140         }
141         catch (Throwable t) {
142             LOG.warn("Exception during clone (returning original): " + t.getMessage());
143             return original;
144         }
145     }
146 
147     @SuppressWarnings("unchecked")
148     protected static Object deepCloneMap(Object original, Map<Object, Object> cache, boolean referenceCollectionCopy)
149             throws Exception {
150         // Instantiate a new instance
151         Map<Object, Object> clone = (Map<Object, Object>) instantiate(original);
152 
153         // Populate data
154         for (Entry<Object, Object> entry : ((Map<Object, Object>) original).entrySet()) {
155             if (referenceCollectionCopy) {
156                 clone.put(entry.getKey(), entry.getValue());
157             }
158             else {
159                 clone.put(deepCloneReflectionInternal(entry.getKey(), cache, false),
160                         deepCloneReflectionInternal(entry.getValue(), cache, false));
161             }
162         }
163 
164         return clone;
165     }
166 
167     @SuppressWarnings("unchecked")
168     protected static Object deepCloneList(Object original, Map<Object, Object> cache, boolean referenceCollectionCopy)
169             throws Exception {
170         // Instantiate a new instance
171         List<Object> clone = (List<Object>) instantiate(original);
172 
173         // Populate data
174         for (Iterator<Object> iterator = ((List<Object>) original).iterator(); iterator.hasNext();) {
175             Object object = iterator.next();
176             if (referenceCollectionCopy) {
177                 clone.add(object);
178             }
179             else {
180                 clone.add(deepCloneReflectionInternal(object, cache, false));
181             }
182         }
183 
184         return clone;
185     }
186 
187     /**
188      * Retrieves all field names for the given class that have the given annotation
189      *
190      * @param clazz - class to find field annotations for
191      * @param annotationClass - class for annotation to find
192      * @return Map<String, Annotation> map containing the field name that has the annotation as a key and the
193      *         annotation instance as a value
194      */
195     public static Map<String, Annotation> getFieldsWithAnnotation(Class<?> clazz,
196             Class<? extends Annotation> annotationClass) {
197         Map<String, Annotation> annotationFields = new HashMap<String, Annotation>();
198 
199         Field[] fields = getFields(clazz, false);
200         for (Field f : fields) {
201             Annotation fieldAnnotation = f.getAnnotation(annotationClass);
202             if (fieldAnnotation != null) {
203                 annotationFields.put(f.getName(), fieldAnnotation);
204             }
205         }
206 
207         return annotationFields;
208     }
209 
210     /**
211      * Determines whether the property of the given class has the given annotation specified
212      *
213      * @param clazz - class containing the property to check
214      * @param propertyName - name of the property to check
215      * @param annotationClass - class for the annotation to look for
216      * @return boolean true if the field associated with the property name has the given annotation, false if not
217      */
218     public static boolean fieldHasAnnotation(Class<?> clazz, String propertyName,
219             Class<? extends Annotation> annotationClass) {
220         Field[] fields = getFields(clazz, false);
221         for (int i = 0; i < fields.length; i++) {
222             Field field = fields[i];
223             if (field.getName().equals(propertyName)) {
224                 Annotation fieldAnnotation = field.getAnnotation(annotationClass);
225                 if (fieldAnnotation != null) {
226                     return true;
227                 } else {
228                     return false;
229                 }
230             }
231         }
232 
233         return false;
234     }
235 
236     protected static final Object instantiate(Object original) throws InstantiationException, IllegalAccessException {
237         return original.getClass().newInstance();
238     }
239 
240     public static Field[] getFields(Object object, boolean includeStatic) {
241         return getFields(object, includeStatic, true);
242     }
243 
244     public static Field[] getFields(Object object, boolean includeStatic, boolean includeTransient) {
245         Class<?> c = object.getClass();
246         return getFields(c, includeStatic, includeTransient);
247     }
248 
249     public static Field[] getFields(Class<?> c, boolean includeStatic) {
250         return getFields(c, includeStatic, true);
251     }
252 
253     public static Field[] getFields(Class<?> c, boolean includeStatic, boolean includeTransient) {
254         String cacheKey = c.getCanonicalName() + ":" + includeStatic;
255         Field[] array = fieldCache.get(cacheKey);
256 
257         if (array == null) {
258             ArrayList<Field> fields = new ArrayList<Field>();
259 
260             List<Class<?>> classes = getClassHierarchy(c, false);
261 
262             // Reverse order so we make sure we maintain consistent order
263             Collections.reverse(classes);
264 
265             for (Class<?> clazz : classes) {
266                 Field[] allFields = clazz.getDeclaredFields();
267                 for (Field f : allFields) {
268                     if ((!includeTransient) && ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT)) {
269                         continue;
270                     }
271                     else if (f.isSynthetic()) {
272                         // Synthetic fields are bad!!!
273                         continue;
274                     }
275                     boolean isStatic = (f.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
276                     if ((isStatic) && (!includeStatic)) {
277                         continue;
278                     }
279                     if (f.getName().equalsIgnoreCase("serialVersionUID")) {
280                         continue;
281                     }
282                     f.setAccessible(true);
283                     fields.add(f);
284                 }
285             }
286 
287             array = fields.toArray(new Field[fields.size()]);
288             fieldCache.put(cacheKey, array);
289         }
290         return array;
291     }
292 
293     protected static final Field internalField(Object object, String fieldName) {
294         if (object == null) {
295             System.out.println("Internal Field: " + object + ", " + fieldName);
296             return null;
297         }
298 
299         String key = object.getClass().getCanonicalName() + "." + fieldName;
300         Field field = internalFields.get(key);
301         if (field == null) {
302             Field[] fields = getFields(object.getClass(), false);
303 
304             for (Field f : fields) {
305                 String name = f.getName();
306                 if (name.equals(fieldName)) {
307                     field = f;
308                     internalFields.put(key, field);
309                     break;
310                 }
311             }
312         }
313 
314         return field;
315     }
316 
317     protected static List<Class<?>> getClassHierarchy(Class<?> c, boolean includeInterfaces) {
318         List<Class<?>> classes = new ArrayList<Class<?>>();
319         while (c != Object.class) {
320             classes.add(c);
321             if (includeInterfaces) {
322                 Class<?>[] interfaces = c.getInterfaces();
323                 for (Class<?> i : interfaces) {
324                     classes.add(i);
325                 }
326             }
327             c = c.getSuperclass();
328             if (c == null) {
329                 break;
330             }
331         }
332 
333         return classes;
334     }
335 
336 }