001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.util; 017 018import org.kuali.rice.core.api.exception.RiceRuntimeException; 019import org.kuali.rice.krad.uif.component.ReferenceCopy; 020 021import java.lang.annotation.Annotation; 022import java.lang.reflect.Field; 023import java.lang.reflect.Modifier; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.concurrent.atomic.AtomicInteger; 032import java.util.concurrent.atomic.AtomicLong; 033 034/** 035 * Utility class for copying objects using reflection. Modified from the jCommon 036 * library: http://www.matthicks.com/2008/05/fastest-deep-cloning.html 037 * 038 * @author Matt Hicks 039 * @author Kuali Rice Team (rice.collab@kuali.org) 040 */ 041public class CloneUtils { 042 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CloneUtils.class); 043 044 private static final Map<String, Field[]> fieldCache = new HashMap<String, Field[]>(); 045 private static final Map<String, Field> internalFields = new HashMap<String, Field>(); 046 047 public static final <O> O deepClone(O original) { 048 try { 049 return deepCloneReflection(original); 050 } catch (Exception e) { 051 throw new RiceRuntimeException(e); 052 } 053 } 054 055 @SuppressWarnings("unchecked") 056 public static final <O> O deepCloneReflection(O original) throws Exception { 057 return (O) deepCloneReflectionInternal(original, new HashMap<Object, Object>(), false); 058 } 059 060 protected static final Object deepCloneReflectionInternal(Object original, Map<Object, Object> cache, 061 boolean referenceCollectionCopy) throws Exception { 062 if (original == null) { // No need to clone nulls 063 return original; 064 } 065 else if (cache.containsKey(original)) { 066 return cache.get(original); 067 } 068 069 // Deep clone 070 Object clone = null; 071 if (List.class.isAssignableFrom(original.getClass())) { 072 clone = deepCloneList(original, cache, referenceCollectionCopy); 073 } 074 else if (Map.class.isAssignableFrom(original.getClass())) { 075 clone = deepCloneMap(original, cache, referenceCollectionCopy); 076 } 077 else { 078 clone = deepCloneObject(original, cache); 079 } 080 081 return clone; 082 } 083 084 protected static Object deepCloneObject(Object original, Map<Object, Object> cache) throws Exception { 085 if (original instanceof Number) { // Numbers are immutable 086 if (original instanceof AtomicInteger) { 087 // AtomicIntegers are mutable 088 } 089 else if (original instanceof AtomicLong) { 090 // AtomLongs are mutable 091 } 092 else { 093 return original; 094 } 095 } 096 else if (original instanceof String) { // Strings are immutable 097 return original; 098 } 099 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 109 // To our understanding, this is a mutable object, so clone it 110 Class<?> c = original.getClass(); 111 Field[] fields = getFields(c, false); 112 try { 113 Object copy = instantiate(original); 114 115 // Put into cache 116 cache.put(original, copy); 117 118 // iterate through and copy fields 119 for (Field f : fields) { 120 Object object = f.get(original); 121 122 boolean referenceCopy = false; 123 boolean referenceCollectionCopy = false; 124 ReferenceCopy copyAnnotation = f.getAnnotation(ReferenceCopy.class); 125 if (copyAnnotation != null) { 126 referenceCopy = true; 127 referenceCollectionCopy = copyAnnotation.newCollectionInstance(); 128 } 129 130 if (!referenceCopy || referenceCollectionCopy) { 131 object = CloneUtils.deepCloneReflectionInternal(object, cache, referenceCollectionCopy); 132 } 133 f.set(copy, object); 134 } 135 136 return copy; 137 } 138 catch (Throwable t) { 139 LOG.debug("Exception during clone (returning original): " + t.getMessage()); 140 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 Map<Object, Object> clone = (Map<Object, Object>) instantiate(original); 149 150 // Populate data 151 for (Entry<Object, Object> entry : ((Map<Object, Object>) original).entrySet()) { 152 if (referenceCollectionCopy) { 153 clone.put(entry.getKey(), entry.getValue()); 154 } 155 else { 156 clone.put(deepCloneReflectionInternal(entry.getKey(), cache, false), 157 deepCloneReflectionInternal(entry.getValue(), cache, false)); 158 } 159 } 160 161 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 List<Object> clone = (List<Object>) instantiate(original); 169 170 // Populate data 171 for (Iterator<Object> iterator = ((List<Object>) original).iterator(); iterator.hasNext();) { 172 Object object = iterator.next(); 173 if (referenceCollectionCopy) { 174 clone.add(object); 175 } 176 else { 177 clone.add(deepCloneReflectionInternal(object, cache, false)); 178 } 179 } 180 181 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 Map<String, Annotation> annotationFields = new HashMap<String, Annotation>(); 195 196 Field[] fields = getFields(clazz, false); 197 for (Field f : fields) { 198 Annotation fieldAnnotation = f.getAnnotation(annotationClass); 199 if (fieldAnnotation != null) { 200 annotationFields.put(f.getName(), fieldAnnotation); 201 } 202 } 203 204 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 Field[] fields = getFields(clazz, false); 218 for (int i = 0; i < fields.length; i++) { 219 Field field = fields[i]; 220 if (field.getName().equals(propertyName)) { 221 Annotation fieldAnnotation = field.getAnnotation(annotationClass); 222 if (fieldAnnotation != null) { 223 return true; 224 } else { 225 return false; 226 } 227 } 228 } 229 230 return false; 231 } 232 233 protected static final Object instantiate(Object original) throws InstantiationException, IllegalAccessException { 234 return original.getClass().newInstance(); 235 } 236 237 public static Field[] getFields(Object object, boolean includeStatic) { 238 return getFields(object, includeStatic, true); 239 } 240 241 public static Field[] getFields(Object object, boolean includeStatic, boolean includeTransient) { 242 Class<?> c = object.getClass(); 243 return getFields(c, includeStatic, includeTransient); 244 } 245 246 public static Field[] getFields(Class<?> c, boolean includeStatic) { 247 return getFields(c, includeStatic, true); 248 } 249 250 public static Field[] getFields(Class<?> c, boolean includeStatic, boolean includeTransient) { 251 String cacheKey = c.getCanonicalName() + ":" + includeStatic; 252 Field[] array = fieldCache.get(cacheKey); 253 254 if (array == null) { 255 ArrayList<Field> fields = new ArrayList<Field>(); 256 257 List<Class<?>> classes = getClassHierarchy(c, false); 258 259 // Reverse order so we make sure we maintain consistent order 260 Collections.reverse(classes); 261 262 for (Class<?> clazz : classes) { 263 Field[] allFields = clazz.getDeclaredFields(); 264 for (Field f : allFields) { 265 if ((!includeTransient) && ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT)) { 266 continue; 267 } 268 else if (f.isSynthetic()) { 269 // Synthetic fields are bad!!! 270 continue; 271 } 272 boolean isStatic = (f.getModifiers() & Modifier.STATIC) == Modifier.STATIC; 273 if ((isStatic) && (!includeStatic)) { 274 continue; 275 } 276 if (f.getName().equalsIgnoreCase("serialVersionUID")) { 277 continue; 278 } 279 f.setAccessible(true); 280 fields.add(f); 281 } 282 } 283 284 array = fields.toArray(new Field[fields.size()]); 285 fieldCache.put(cacheKey, array); 286 } 287 return array; 288 } 289 290 protected static final Field internalField(Object object, String fieldName) { 291 if (object == null) { 292 System.out.println("Internal Field: " + object + ", " + fieldName); 293 return null; 294 } 295 296 String key = object.getClass().getCanonicalName() + "." + fieldName; 297 Field field = internalFields.get(key); 298 if (field == null) { 299 Field[] fields = getFields(object.getClass(), false); 300 301 for (Field f : fields) { 302 String name = f.getName(); 303 if (name.equals(fieldName)) { 304 field = f; 305 internalFields.put(key, field); 306 break; 307 } 308 } 309 } 310 311 return field; 312 } 313 314 protected static List<Class<?>> getClassHierarchy(Class<?> c, boolean includeInterfaces) { 315 List<Class<?>> classes = new ArrayList<Class<?>>(); 316 while (c != Object.class) { 317 classes.add(c); 318 if (includeInterfaces) { 319 Class<?>[] interfaces = c.getInterfaces(); 320 for (Class<?> i : interfaces) { 321 classes.add(i); 322 } 323 } 324 c = c.getSuperclass(); 325 if (c == null) { 326 break; 327 } 328 } 329 330 return classes; 331 } 332 333}