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}