View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation Licensed under the Educational Community
3    * License, Version 1.0 (the "License"); you may not use this file except in
4    * compliance with the License. You may obtain a copy of the License at
5    * http://www.opensource.org/licenses/ecl1.php Unless required by applicable law
6    * or agreed to in writing, software distributed under the License is
7    * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8    * KIND, either express or implied. See the License for the specific language
9    * governing permissions and limitations under the License.
10   */
11  package org.kuali.rice.krad.uif.util;
12  
13  import org.kuali.rice.krad.uif.core.ReferenceCopy;
14  
15  import java.lang.reflect.Field;
16  import java.lang.reflect.Modifier;
17  import java.util.ArrayList;
18  import java.util.Collections;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Map.Entry;
24  import java.util.concurrent.atomic.AtomicInteger;
25  import java.util.concurrent.atomic.AtomicLong;
26  
27  /**
28   * Utility class for copying objects using reflection. Modified from the jCommon
29   * library: http://www.matthicks.com/2008/05/fastest-deep-cloning.html
30   * 
31   * @author Matt Hicks
32   * @author Kuali Rice Team (rice.collab@kuali.org)
33   */
34  public class CloneUtils {
35      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CloneUtils.class);
36  
37      private static final Map<String, Field[]> fieldCache = new HashMap<String, Field[]>();
38      private static final Map<String, Field> internalFields = new HashMap<String, Field>();
39  
40      public static final <O> O deepClone(O original) throws Exception {
41          return deepCloneReflection(original);
42      }
43  
44      @SuppressWarnings("unchecked")
45      public static final <O> O deepCloneReflection(O original) throws Exception {
46          return (O) deepCloneReflectionInternal(original, new HashMap<Object, Object>(), false);
47      }
48  
49      protected static final Object deepCloneReflectionInternal(Object original, Map<Object, Object> cache,
50              boolean referenceCollectionCopy) throws Exception {
51          if (original == null) { // No need to clone nulls
52              return original;
53          }
54          else if (cache.containsKey(original)) {
55              return cache.get(original);
56          }
57  
58          // Deep clone
59          Object clone = null;
60          if (List.class.isAssignableFrom(original.getClass())) {
61              clone = deepCloneList(original, cache, referenceCollectionCopy);
62          }
63          else if (Map.class.isAssignableFrom(original.getClass())) {
64              clone = deepCloneMap(original, cache, referenceCollectionCopy);
65          }
66          else {
67              clone = deepCloneObject(original, cache);
68          }
69  
70          return clone;
71      }
72  
73      protected static Object deepCloneObject(Object original, Map<Object, Object> cache) throws Exception {
74          if (original instanceof Number) { // Numbers are immutable
75              if (original instanceof AtomicInteger) {
76                  // AtomicIntegers are mutable
77              }
78              else if (original instanceof AtomicLong) {
79                  // AtomLongs are mutable
80              }
81              else {
82                  return original;
83              }
84          }
85          else if (original instanceof String) { // Strings are immutable
86              return original;
87          }
88          else if (original instanceof Character) { // Characters are immutable
89              return original;
90          }
91          else if (original instanceof Class) { // Classes are immutable
92              return original;
93          }
94          else if (original instanceof Boolean) {
95              return new Boolean(((Boolean) original).booleanValue());
96          }
97  
98          // To our understanding, this is a mutable object, so clone it
99          Class<?> c = original.getClass();
100         Field[] fields = getFields(c, false);
101         try {
102             Object copy = instantiate(original);
103 
104             // Put into cache
105             cache.put(original, copy);
106 
107             // iterate through and copy fields
108             for (Field f : fields) {
109                 Object object = f.get(original);
110 
111                 boolean referenceCopy = false;
112                 boolean referenceCollectionCopy = false;
113                 ReferenceCopy copyAnnotation = f.getAnnotation(ReferenceCopy.class);
114                 if (copyAnnotation != null) {
115                     referenceCopy = true;
116                     referenceCollectionCopy = copyAnnotation.newCollectionInstance();
117                 }
118 
119                 if (!referenceCopy || referenceCollectionCopy) {
120                     object = CloneUtils.deepCloneReflectionInternal(object, cache, referenceCollectionCopy);
121                 }
122                 f.set(copy, object);
123             }
124 
125             return copy;
126         }
127         catch (Throwable t) {
128             LOG.warn("Exception during clone (returning original): " + t.getMessage());
129             return original;
130         }
131     }
132 
133     @SuppressWarnings("unchecked")
134     protected static Object deepCloneMap(Object original, Map<Object, Object> cache, boolean referenceCollectionCopy)
135             throws Exception {
136         // Instantiate a new instance
137         Map<Object, Object> clone = (Map<Object, Object>) instantiate(original);
138 
139         // Populate data
140         for (Entry<Object, Object> entry : ((Map<Object, Object>) original).entrySet()) {
141             if (referenceCollectionCopy) {
142                 clone.put(entry.getKey(), entry.getValue());
143             }
144             else {
145                 clone.put(deepCloneReflectionInternal(entry.getKey(), cache, false),
146                         deepCloneReflectionInternal(entry.getValue(), cache, false));
147             }
148         }
149 
150         return clone;
151     }
152 
153     @SuppressWarnings("unchecked")
154     protected static Object deepCloneList(Object original, Map<Object, Object> cache, boolean referenceCollectionCopy)
155             throws Exception {
156         // Instantiate a new instance
157         List<Object> clone = (List<Object>) instantiate(original);
158 
159         // Populate data
160         for (Iterator<Object> iterator = ((List<Object>) original).iterator(); iterator.hasNext();) {
161             Object object = iterator.next();
162             if (referenceCollectionCopy) {
163                 clone.add(object);
164             }
165             else {
166                 clone.add(deepCloneReflectionInternal(object, cache, false));
167             }
168         }
169 
170         return clone;
171     }
172 
173     protected static final Object instantiate(Object original) throws InstantiationException, IllegalAccessException {
174         return original.getClass().newInstance();
175     }
176 
177     public static Field[] getFields(Object object, boolean includeStatic) {
178         return getFields(object, includeStatic, true);
179     }
180 
181     public static Field[] getFields(Object object, boolean includeStatic, boolean includeTransient) {
182         Class<?> c = object.getClass();
183         return getFields(c, includeStatic, includeTransient);
184     }
185 
186     public static Field[] getFields(Class<?> c, boolean includeStatic) {
187         return getFields(c, includeStatic, true);
188     }
189 
190     public static Field[] getFields(Class<?> c, boolean includeStatic, boolean includeTransient) {
191         String cacheKey = c.getCanonicalName() + ":" + includeStatic;
192         Field[] array = fieldCache.get(cacheKey);
193 
194         if (array == null) {
195             ArrayList<Field> fields = new ArrayList<Field>();
196 
197             List<Class<?>> classes = getClassHierarchy(c, false);
198 
199             // Reverse order so we make sure we maintain consistent order
200             Collections.reverse(classes);
201 
202             for (Class<?> clazz : classes) {
203                 Field[] allFields = clazz.getDeclaredFields();
204                 for (Field f : allFields) {
205                     if ((!includeTransient) && ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT)) {
206                         continue;
207                     }
208                     else if (f.isSynthetic()) {
209                         // Synthetic fields are bad!!!
210                         continue;
211                     }
212                     boolean isStatic = (f.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
213                     if ((isStatic) && (!includeStatic)) {
214                         continue;
215                     }
216                     if (f.getName().equalsIgnoreCase("serialVersionUID")) {
217                         continue;
218                     }
219                     f.setAccessible(true);
220                     fields.add(f);
221                 }
222             }
223 
224             array = fields.toArray(new Field[fields.size()]);
225             fieldCache.put(cacheKey, array);
226         }
227         return array;
228     }
229 
230     protected static final Field internalField(Object object, String fieldName) {
231         if (object == null) {
232             System.out.println("Internal Field: " + object + ", " + fieldName);
233             return null;
234         }
235 
236         String key = object.getClass().getCanonicalName() + "." + fieldName;
237         Field field = internalFields.get(key);
238         if (field == null) {
239             Field[] fields = getFields(object.getClass(), false);
240 
241             for (Field f : fields) {
242                 String name = f.getName();
243                 if (name.equals(fieldName)) {
244                     field = f;
245                     internalFields.put(key, field);
246                     break;
247                 }
248             }
249         }
250 
251         return field;
252     }
253 
254     protected static List<Class<?>> getClassHierarchy(Class<?> c, boolean includeInterfaces) {
255         List<Class<?>> classes = new ArrayList<Class<?>>();
256         while (c != Object.class) {
257             classes.add(c);
258             if (includeInterfaces) {
259                 Class<?>[] interfaces = c.getInterfaces();
260                 for (Class<?> i : interfaces) {
261                     classes.add(i);
262                 }
263             }
264             c = c.getSuperclass();
265             if (c == null) {
266                 break;
267             }
268         }
269 
270         return classes;
271     }
272 
273 }