Coverage Report - org.kuali.rice.krad.uif.util.CloneUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
CloneUtils
0%
0/134
0%
0/84
4.733
 
 1  
 /**
 2  
  * Copyright 2005-2011 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  0
 public class CloneUtils {
 42  0
     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CloneUtils.class);
 43  
 
 44  0
     private static final Map<String, Field[]> fieldCache = new HashMap<String, Field[]>();
 45  0
     private static final Map<String, Field> internalFields = new HashMap<String, Field>();
 46  
 
 47  
     public static final <O> O deepClone(O original) {
 48  
         try {
 49  0
             return deepCloneReflection(original);
 50  0
         } catch (Exception e) {
 51  0
             throw new RiceRuntimeException(e);
 52  
         }
 53  
     }
 54  
 
 55  
     @SuppressWarnings("unchecked")
 56  
     public static final <O> O deepCloneReflection(O original) throws Exception {
 57  0
         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  0
         if (original == null) { // No need to clone nulls
 63  0
             return original;
 64  
         }
 65  0
         else if (cache.containsKey(original)) {
 66  0
             return cache.get(original);
 67  
         }
 68  
 
 69  
         // Deep clone
 70  0
         Object clone = null;
 71  0
         if (List.class.isAssignableFrom(original.getClass())) {
 72  0
             clone = deepCloneList(original, cache, referenceCollectionCopy);
 73  
         }
 74  0
         else if (Map.class.isAssignableFrom(original.getClass())) {
 75  0
             clone = deepCloneMap(original, cache, referenceCollectionCopy);
 76  
         }
 77  
         else {
 78  0
             clone = deepCloneObject(original, cache);
 79  
         }
 80  
 
 81  0
         return clone;
 82  
     }
 83  
 
 84  
     protected static Object deepCloneObject(Object original, Map<Object, Object> cache) throws Exception {
 85  0
         if (original instanceof Number) { // Numbers are immutable
 86  0
             if (original instanceof AtomicInteger) {
 87  
                 // AtomicIntegers are mutable
 88  
             }
 89  0
             else if (original instanceof AtomicLong) {
 90  
                 // AtomLongs are mutable
 91  
             }
 92  
             else {
 93  0
                 return original;
 94  
             }
 95  
         }
 96  0
         else if (original instanceof String) { // Strings are immutable
 97  0
             return original;
 98  
         }
 99  0
         else if (original instanceof Character) { // Characters are immutable
 100  0
             return original;
 101  
         }
 102  0
         else if (original instanceof Class) { // Classes are immutable
 103  0
             return original;
 104  
         }
 105  0
         else if (original instanceof Boolean) {
 106  0
             return new Boolean(((Boolean) original).booleanValue());
 107  
         }
 108  
 
 109  
         // To our understanding, this is a mutable object, so clone it
 110  0
         Class<?> c = original.getClass();
 111  0
         Field[] fields = getFields(c, false);
 112  
         try {
 113  0
             Object copy = instantiate(original);
 114  
 
 115  
             // Put into cache
 116  0
             cache.put(original, copy);
 117  
 
 118  
             // iterate through and copy fields
 119  0
             for (Field f : fields) {
 120  0
                 Object object = f.get(original);
 121  
 
 122  0
                 boolean referenceCopy = false;
 123  0
                 boolean referenceCollectionCopy = false;
 124  0
                 ReferenceCopy copyAnnotation = f.getAnnotation(ReferenceCopy.class);
 125  0
                 if (copyAnnotation != null) {
 126  0
                     referenceCopy = true;
 127  0
                     referenceCollectionCopy = copyAnnotation.newCollectionInstance();
 128  
                 }
 129  
 
 130  0
                 if (!referenceCopy || referenceCollectionCopy) {
 131  0
                     object = CloneUtils.deepCloneReflectionInternal(object, cache, referenceCollectionCopy);
 132  
                 }
 133  0
                 f.set(copy, object);
 134  
             }
 135  
 
 136  0
             return copy;
 137  
         }
 138  0
         catch (Throwable t) {
 139  0
             LOG.debug("Exception during clone (returning original): " + t.getMessage());
 140  0
             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  0
         Map<Object, Object> clone = (Map<Object, Object>) instantiate(original);
 149  
 
 150  
         // Populate data
 151  0
         for (Entry<Object, Object> entry : ((Map<Object, Object>) original).entrySet()) {
 152  0
             if (referenceCollectionCopy) {
 153  0
                 clone.put(entry.getKey(), entry.getValue());
 154  
             }
 155  
             else {
 156  0
                 clone.put(deepCloneReflectionInternal(entry.getKey(), cache, false),
 157  
                         deepCloneReflectionInternal(entry.getValue(), cache, false));
 158  
             }
 159  
         }
 160  
 
 161  0
         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  0
         List<Object> clone = (List<Object>) instantiate(original);
 169  
 
 170  
         // Populate data
 171  0
         for (Iterator<Object> iterator = ((List<Object>) original).iterator(); iterator.hasNext();) {
 172  0
             Object object = iterator.next();
 173  0
             if (referenceCollectionCopy) {
 174  0
                 clone.add(object);
 175  
             }
 176  
             else {
 177  0
                 clone.add(deepCloneReflectionInternal(object, cache, false));
 178  
             }
 179  0
         }
 180  
 
 181  0
         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  0
         Map<String, Annotation> annotationFields = new HashMap<String, Annotation>();
 195  
 
 196  0
         Field[] fields = getFields(clazz, false);
 197  0
         for (Field f : fields) {
 198  0
             Annotation fieldAnnotation = f.getAnnotation(annotationClass);
 199  0
             if (fieldAnnotation != null) {
 200  0
                 annotationFields.put(f.getName(), fieldAnnotation);
 201  
             }
 202  
         }
 203  
 
 204  0
         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  0
         Field[] fields = getFields(clazz, false);
 218  0
         for (int i = 0; i < fields.length; i++) {
 219  0
             Field field = fields[i];
 220  0
             if (field.getName().equals(propertyName)) {
 221  0
                 Annotation fieldAnnotation = field.getAnnotation(annotationClass);
 222  0
                 if (fieldAnnotation != null) {
 223  0
                     return true;
 224  
                 } else {
 225  0
                     return false;
 226  
                 }
 227  
             }
 228  
         }
 229  
 
 230  0
         return false;
 231  
     }
 232  
 
 233  
     protected static final Object instantiate(Object original) throws InstantiationException, IllegalAccessException {
 234  0
         return original.getClass().newInstance();
 235  
     }
 236  
 
 237  
     public static Field[] getFields(Object object, boolean includeStatic) {
 238  0
         return getFields(object, includeStatic, true);
 239  
     }
 240  
 
 241  
     public static Field[] getFields(Object object, boolean includeStatic, boolean includeTransient) {
 242  0
         Class<?> c = object.getClass();
 243  0
         return getFields(c, includeStatic, includeTransient);
 244  
     }
 245  
 
 246  
     public static Field[] getFields(Class<?> c, boolean includeStatic) {
 247  0
         return getFields(c, includeStatic, true);
 248  
     }
 249  
 
 250  
     public static Field[] getFields(Class<?> c, boolean includeStatic, boolean includeTransient) {
 251  0
         String cacheKey = c.getCanonicalName() + ":" + includeStatic;
 252  0
         Field[] array = fieldCache.get(cacheKey);
 253  
 
 254  0
         if (array == null) {
 255  0
             ArrayList<Field> fields = new ArrayList<Field>();
 256  
 
 257  0
             List<Class<?>> classes = getClassHierarchy(c, false);
 258  
 
 259  
             // Reverse order so we make sure we maintain consistent order
 260  0
             Collections.reverse(classes);
 261  
 
 262  0
             for (Class<?> clazz : classes) {
 263  0
                 Field[] allFields = clazz.getDeclaredFields();
 264  0
                 for (Field f : allFields) {
 265  0
                     if ((!includeTransient) && ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT)) {
 266  0
                         continue;
 267  
                     }
 268  0
                     else if (f.isSynthetic()) {
 269  
                         // Synthetic fields are bad!!!
 270  0
                         continue;
 271  
                     }
 272  0
                     boolean isStatic = (f.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
 273  0
                     if ((isStatic) && (!includeStatic)) {
 274  0
                         continue;
 275  
                     }
 276  0
                     if (f.getName().equalsIgnoreCase("serialVersionUID")) {
 277  0
                         continue;
 278  
                     }
 279  0
                     f.setAccessible(true);
 280  0
                     fields.add(f);
 281  
                 }
 282  0
             }
 283  
 
 284  0
             array = fields.toArray(new Field[fields.size()]);
 285  0
             fieldCache.put(cacheKey, array);
 286  
         }
 287  0
         return array;
 288  
     }
 289  
 
 290  
     protected static final Field internalField(Object object, String fieldName) {
 291  0
         if (object == null) {
 292  0
             System.out.println("Internal Field: " + object + ", " + fieldName);
 293  0
             return null;
 294  
         }
 295  
 
 296  0
         String key = object.getClass().getCanonicalName() + "." + fieldName;
 297  0
         Field field = internalFields.get(key);
 298  0
         if (field == null) {
 299  0
             Field[] fields = getFields(object.getClass(), false);
 300  
 
 301  0
             for (Field f : fields) {
 302  0
                 String name = f.getName();
 303  0
                 if (name.equals(fieldName)) {
 304  0
                     field = f;
 305  0
                     internalFields.put(key, field);
 306  0
                     break;
 307  
                 }
 308  
             }
 309  
         }
 310  
 
 311  0
         return field;
 312  
     }
 313  
 
 314  
     protected static List<Class<?>> getClassHierarchy(Class<?> c, boolean includeInterfaces) {
 315  0
         List<Class<?>> classes = new ArrayList<Class<?>>();
 316  0
         while (c != Object.class) {
 317  0
             classes.add(c);
 318  0
             if (includeInterfaces) {
 319  0
                 Class<?>[] interfaces = c.getInterfaces();
 320  0
                 for (Class<?> i : interfaces) {
 321  0
                     classes.add(i);
 322  
                 }
 323  
             }
 324  0
             c = c.getSuperclass();
 325  0
             if (c == null) {
 326  0
                 break;
 327  
             }
 328  
         }
 329  
 
 330  0
         return classes;
 331  
     }
 332  
 
 333  
 }