Coverage Report - org.kuali.rice.kns.uif.util.CloneUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
CloneUtils
0%
0/116
0%
0/74
4.538
 
 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.kns.uif.util;
 12  
 
 13  
 import java.lang.reflect.Field;
 14  
 import java.lang.reflect.Modifier;
 15  
 import java.util.ArrayList;
 16  
 import java.util.Collections;
 17  
 import java.util.HashMap;
 18  
 import java.util.Iterator;
 19  
 import java.util.List;
 20  
 import java.util.Map;
 21  
 import java.util.Map.Entry;
 22  
 import java.util.concurrent.atomic.AtomicInteger;
 23  
 import java.util.concurrent.atomic.AtomicLong;
 24  
 
 25  
 import org.kuali.rice.kns.uif.core.ReferenceCopy;
 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  0
 public class CloneUtils {
 35  0
     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CloneUtils.class);
 36  
 
 37  0
     private static final Map<String, Field[]> fieldCache = new HashMap<String, Field[]>();
 38  0
     private static final Map<String, Field> internalFields = new HashMap<String, Field>();
 39  
 
 40  
     public static final <O> O deepClone(O original) throws Exception {
 41  0
         return deepCloneReflection(original);
 42  
     }
 43  
 
 44  
     @SuppressWarnings("unchecked")
 45  
     public static final <O> O deepCloneReflection(O original) throws Exception {
 46  0
         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  0
         if (original == null) { // No need to clone nulls
 52  0
             return original;
 53  
         }
 54  0
         else if (cache.containsKey(original)) {
 55  0
             return cache.get(original);
 56  
         }
 57  
 
 58  
         // Deep clone
 59  0
         Object clone = null;
 60  0
         if (List.class.isAssignableFrom(original.getClass())) {
 61  0
             clone = deepCloneList(original, cache, referenceCollectionCopy);
 62  
         }
 63  0
         else if (Map.class.isAssignableFrom(original.getClass())) {
 64  0
             clone = deepCloneMap(original, cache, referenceCollectionCopy);
 65  
         }
 66  
         else {
 67  0
             clone = deepCloneObject(original, cache);
 68  
         }
 69  
 
 70  0
         return clone;
 71  
     }
 72  
 
 73  
     protected static Object deepCloneObject(Object original, Map<Object, Object> cache) throws Exception {
 74  0
         if (original instanceof Number) { // Numbers are immutable
 75  0
             if (original instanceof AtomicInteger) {
 76  
                 // AtomicIntegers are mutable
 77  
             }
 78  0
             else if (original instanceof AtomicLong) {
 79  
                 // AtomLongs are mutable
 80  
             }
 81  
             else {
 82  0
                 return original;
 83  
             }
 84  
         }
 85  0
         else if (original instanceof String) { // Strings are immutable
 86  0
             return original;
 87  
         }
 88  0
         else if (original instanceof Character) { // Characters are immutable
 89  0
             return original;
 90  
         }
 91  0
         else if (original instanceof Class) { // Classes are immutable
 92  0
             return original;
 93  
         }
 94  0
         else if (original instanceof Boolean) {
 95  0
             return new Boolean(((Boolean) original).booleanValue());
 96  
         }
 97  
 
 98  
         // To our understanding, this is a mutable object, so clone it
 99  0
         Class<?> c = original.getClass();
 100  0
         Field[] fields = getFields(c, false);
 101  
         try {
 102  0
             Object copy = instantiate(original);
 103  
 
 104  
             // Put into cache
 105  0
             cache.put(original, copy);
 106  
 
 107  
             // iterate through and copy fields
 108  0
             for (Field f : fields) {
 109  0
                 Object object = f.get(original);
 110  
 
 111  0
                 boolean referenceCopy = false;
 112  0
                 boolean referenceCollectionCopy = false;
 113  0
                 ReferenceCopy copyAnnotation = f.getAnnotation(ReferenceCopy.class);
 114  0
                 if (copyAnnotation != null) {
 115  0
                     referenceCopy = true;
 116  0
                     referenceCollectionCopy = copyAnnotation.newCollectionInstance();
 117  
                 }
 118  
 
 119  0
                 if (!referenceCopy || referenceCollectionCopy) {
 120  0
                     object = CloneUtils.deepCloneReflectionInternal(object, cache, referenceCollectionCopy);
 121  
                 }
 122  0
                 f.set(copy, object);
 123  
             }
 124  
 
 125  0
             return copy;
 126  
         }
 127  0
         catch (Throwable t) {
 128  0
             LOG.warn("Exception during clone (returning original): " + t.getMessage());
 129  0
             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  0
         Map<Object, Object> clone = (Map<Object, Object>) instantiate(original);
 138  
 
 139  
         // Populate data
 140  0
         for (Entry<Object, Object> entry : ((Map<Object, Object>) original).entrySet()) {
 141  0
             if (referenceCollectionCopy) {
 142  0
                 clone.put(entry.getKey(), entry.getValue());
 143  
             }
 144  
             else {
 145  0
                 clone.put(deepCloneReflectionInternal(entry.getKey(), cache, false),
 146  
                         deepCloneReflectionInternal(entry.getValue(), cache, false));
 147  
             }
 148  
         }
 149  
 
 150  0
         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  0
         List<Object> clone = (List<Object>) instantiate(original);
 158  
 
 159  
         // Populate data
 160  0
         for (Iterator<Object> iterator = ((List<Object>) original).iterator(); iterator.hasNext();) {
 161  0
             Object object = iterator.next();
 162  0
             if (referenceCollectionCopy) {
 163  0
                 clone.add(object);
 164  
             }
 165  
             else {
 166  0
                 clone.add(deepCloneReflectionInternal(object, cache, false));
 167  
             }
 168  0
         }
 169  
 
 170  0
         return clone;
 171  
     }
 172  
 
 173  
     protected static final Object instantiate(Object original) throws InstantiationException, IllegalAccessException {
 174  0
         return original.getClass().newInstance();
 175  
     }
 176  
 
 177  
     public static Field[] getFields(Object object, boolean includeStatic) {
 178  0
         return getFields(object, includeStatic, true);
 179  
     }
 180  
 
 181  
     public static Field[] getFields(Object object, boolean includeStatic, boolean includeTransient) {
 182  0
         Class<?> c = object.getClass();
 183  0
         return getFields(c, includeStatic, includeTransient);
 184  
     }
 185  
 
 186  
     public static Field[] getFields(Class<?> c, boolean includeStatic) {
 187  0
         return getFields(c, includeStatic, true);
 188  
     }
 189  
 
 190  
     public static Field[] getFields(Class<?> c, boolean includeStatic, boolean includeTransient) {
 191  0
         String cacheKey = c.getCanonicalName() + ":" + includeStatic;
 192  0
         Field[] array = fieldCache.get(cacheKey);
 193  
 
 194  0
         if (array == null) {
 195  0
             ArrayList<Field> fields = new ArrayList<Field>();
 196  
 
 197  0
             List<Class<?>> classes = getClassHierarchy(c, false);
 198  
 
 199  
             // Reverse order so we make sure we maintain consistent order
 200  0
             Collections.reverse(classes);
 201  
 
 202  0
             for (Class<?> clazz : classes) {
 203  0
                 Field[] allFields = clazz.getDeclaredFields();
 204  0
                 for (Field f : allFields) {
 205  0
                     if ((!includeTransient) && ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT)) {
 206  0
                         continue;
 207  
                     }
 208  0
                     else if (f.isSynthetic()) {
 209  
                         // Synthetic fields are bad!!!
 210  0
                         continue;
 211  
                     }
 212  0
                     boolean isStatic = (f.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
 213  0
                     if ((isStatic) && (!includeStatic)) {
 214  0
                         continue;
 215  
                     }
 216  0
                     if (f.getName().equalsIgnoreCase("serialVersionUID")) {
 217  0
                         continue;
 218  
                     }
 219  0
                     f.setAccessible(true);
 220  0
                     fields.add(f);
 221  
                 }
 222  0
             }
 223  
 
 224  0
             array = fields.toArray(new Field[fields.size()]);
 225  0
             fieldCache.put(cacheKey, array);
 226  
         }
 227  0
         return array;
 228  
     }
 229  
 
 230  
     protected static final Field internalField(Object object, String fieldName) {
 231  0
         if (object == null) {
 232  0
             System.out.println("Internal Field: " + object + ", " + fieldName);
 233  0
             return null;
 234  
         }
 235  
 
 236  0
         String key = object.getClass().getCanonicalName() + "." + fieldName;
 237  0
         Field field = internalFields.get(key);
 238  0
         if (field == null) {
 239  0
             Field[] fields = getFields(object.getClass(), false);
 240  
 
 241  0
             for (Field f : fields) {
 242  0
                 String name = f.getName();
 243  0
                 if (name.equals(fieldName)) {
 244  0
                     field = f;
 245  0
                     internalFields.put(key, field);
 246  0
                     break;
 247  
                 }
 248  
             }
 249  
         }
 250  
 
 251  0
         return field;
 252  
     }
 253  
 
 254  
     protected static List<Class<?>> getClassHierarchy(Class<?> c, boolean includeInterfaces) {
 255  0
         List<Class<?>> classes = new ArrayList<Class<?>>();
 256  0
         while (c != Object.class) {
 257  0
             classes.add(c);
 258  0
             if (includeInterfaces) {
 259  0
                 Class<?>[] interfaces = c.getInterfaces();
 260  0
                 for (Class<?> i : interfaces) {
 261  0
                     classes.add(i);
 262  
                 }
 263  
             }
 264  0
             c = c.getSuperclass();
 265  0
             if (c == null) {
 266  0
                 break;
 267  
             }
 268  
         }
 269  
 
 270  0
         return classes;
 271  
     }
 272  
 
 273  
 }