001/**
002 * Copyright 2005-2018 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 java.lang.annotation.Annotation;
019import java.lang.reflect.Array;
020import java.lang.reflect.Field;
021import java.lang.reflect.Modifier;
022import java.lang.reflect.ParameterizedType;
023import java.lang.reflect.Type;
024import java.lang.reflect.TypeVariable;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.IdentityHashMap;
030import java.util.LinkedList;
031import java.util.List;
032import java.util.Map;
033import java.util.Map.Entry;
034import java.util.Queue;
035import java.util.WeakHashMap;
036
037import org.kuali.rice.core.api.config.property.Config;
038import org.kuali.rice.core.api.config.property.ConfigContext;
039import org.kuali.rice.krad.datadictionary.Copyable;
040import org.kuali.rice.krad.uif.component.DelayedCopy;
041import org.kuali.rice.krad.uif.component.ReferenceCopy;
042import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
043import org.kuali.rice.krad.util.KRADConstants;
044
045/**
046 * Provides a lightweight "hands-free" copy implementation to replace the need for copyProperties()
047 * in building {@link LifecycleElement} implementations.
048 *
049 * @author Kuali Rice Team (rice.collab@kuali.org)
050 */
051public final class CopyUtils {
052
053    private static Boolean delay;
054
055    /**
056     * Determine whether or not to use a delayed copy proxy.
057     *
058     * <p>
059     * When true, deep copy operations will be truncated where a copyable represented by an
060     * interfaces is specified by the field, array, list or map involved indicated. Rather than copy
061     * the object directly, a proxy wrapping the original will be placed, which when used will
062     * invoke the copy operation.
063     * </p>
064     *
065     * <p>
066     * This value is controlled by the parameter &quot;krad.uif.copyable.delay&quot;. By default,
067     * full deep copy will be used.
068     * </p>
069     *
070     * @return True if deep copy will be truncated with a delayed copy proxy, false for full deep
071     *         copy.
072     */
073    public static boolean isDelay() {
074        if (delay == null) {
075            boolean defaultDelay = false;
076            Config config = ConfigContext.getCurrentContextConfig();
077            delay = config == null ? defaultDelay : config.getBooleanProperty(
078                    KRADConstants.ConfigParameters.KRAD_COPY_DELAY, defaultDelay);
079        }
080
081        return delay;
082    }
083
084    /**
085     * Mix-in copy implementation for objects that implement the {@link Copyable} interface}
086     *
087     * @param <T> copyable type
088     * @param obj The object to copy.
089     * @return A deep copy of the object.
090     */
091    @SuppressWarnings("unchecked")
092    public static <T> T copy(Copyable obj) {
093        if (obj == null) {
094            return null;
095        }
096
097        String cid = null;
098        if (ViewLifecycle.isTrace()) {
099            StackTraceElement[] trace = Thread.currentThread().getStackTrace();
100            int i = 3;
101            while (ComponentUtils.class.getName().equals(trace[i].getClassName())) {
102                i++;
103            }
104            StackTraceElement caller = trace[i];
105            cid = obj.getClass().getSimpleName() + ":" + caller.getClassName()
106                    + ":" + caller.getMethodName() + ":" + caller.getLineNumber();
107            ProcessLogger.ntrace("deep-copy:", ":" + cid, 1000L, 500L);
108        }
109
110        return (T) getDeepCopy(obj);
111    }
112
113    /**
114     * Determine if deep copying is available for a type.
115     *
116     * @param type The type to check.
117     * @return True if {@link #getDeepCopy(Object)} may be expected to follow references to this
118     *         type. False if the type should not be deeply copied.
119     */
120    public static boolean isCopyAvailable(Class<?> type) {
121        return type != null
122                && (Copyable.class.isAssignableFrom(type)
123                        || ArrayList.class.isAssignableFrom(type)
124                        || LinkedList.class.isAssignableFrom(type)
125                        || HashMap.class.isAssignableFrom(type)
126                        || HashSet.class.isAssignableFrom(type)
127                        || type.isArray());
128    }
129
130    /**
131     * Get a shallow copy (clone) of an object.
132     *
133     * <p>
134     * This method simplifies access to the clone() method.
135     * </p>
136     *
137     * @param <T> copyable type
138     * @param obj The object to clone.
139     * @return A shallow copy of obj, or null if obj is null.
140     *
141     * @throws CloneNotSupportedException If copying is not available on the object, or if thrown by
142     *         clone() itself. When isShallowCopyAvailable() returns true, then this exception is
143     *         not expected and may be considered an internal error.
144     */
145    @SuppressWarnings("unchecked")
146    public static <T> T getShallowCopy(T obj) throws CloneNotSupportedException {
147        if (obj == null) {
148            return null;
149        }
150
151        if (ViewLifecycle.isTrace()) {
152            ProcessLogger.ntrace("clone:", ":" + obj.getClass().getSimpleName(), 1000);
153        }
154
155        synchronized (obj) {
156            if (obj instanceof Copyable) {
157                return (T) ((Copyable) obj).clone();
158            }
159
160            if (obj instanceof Object[]) {
161                return (T) ((Object[]) obj).clone();
162            }
163
164            // synchronized on collections/maps below here is to avoid
165            // concurrent modification
166            if (obj instanceof ArrayList) {
167                return (T) ((ArrayList<?>) obj).clone();
168            }
169
170            if (obj instanceof LinkedList) {
171                return (T) ((LinkedList<?>) obj).clone();
172            }
173
174            if (obj instanceof HashSet) {
175                return (T) ((HashSet<?>) obj).clone();
176            }
177
178            if (obj instanceof HashMap) {
179                return (T) ((HashMap<?, ?>) obj).clone();
180            }
181
182            throw new CloneNotSupportedException(
183                    "Not a supported copyable type.  This condition should not be reached. " + obj.getClass() + " "
184                            + obj);
185        }
186    }
187
188    /**
189     * Helper for {@link #getDeepCopy(Object)} for detecting whether or not to queue deep references
190     * from the current node.
191     */
192    private static boolean isDeep(CopyReference<?> ref, Object source) {
193        if (!(ref instanceof FieldReference)) {
194            return true;
195        }
196
197        FieldReference<?> fieldRef = (FieldReference<?>) ref;
198        Field field = fieldRef.field;
199
200        if (field.isAnnotationPresent(ReferenceCopy.class)) {
201            return false;
202        }
203
204        if (!(source instanceof Copyable) && ((source instanceof Map) || (source instanceof List))) {
205            Class<?> collectionType = getMetadata(fieldRef.source.getClass()).collectionTypeByField.get(field);
206
207            if (!Object.class.equals(collectionType) && !isCopyAvailable(collectionType)) {
208                return false;
209            }
210        }
211
212        return true;
213    }
214
215    /**
216     * Helper for {@link #getDeepCopy(Object)} to detect whether or not to copy the current node or
217     * to keep the cloned reference.
218     */
219    private static boolean isCopy(CopyReference<?> ref) {
220        if (!(ref instanceof FieldReference)) {
221            return true;
222        }
223
224        FieldReference<?> fieldRef = (FieldReference<?>) ref;
225        Field field = fieldRef.field;
226        ReferenceCopy refCopy = (ReferenceCopy) field.getAnnotation(ReferenceCopy.class);
227
228        return refCopy == null || refCopy.newCollectionInstance();
229    }
230
231    /**
232     * Unwrap an object from any wrapper class or proxy it may be decorated with related to the copy
233     * process.
234     *
235     * <p>
236     * This method is a public utility passthrough for
237     * {@link DelayedCopyableHandler#getDelayedCopy(Copyable)}.
238     * </p>
239     *
240     * @param obj an object.
241     * @return The non-proxied bean represented by source, copied if needed. When source is not
242     *         copyable, or not proxied, it is returned as-is.
243     */
244    public static <T> T unwrap(T obj) {
245        return DelayedCopyableHandler.unwrap(obj);
246    }
247
248    /**
249     * Get a deep copy of an object using cloning.
250     *
251     * @param <T> copyable type
252     * @param obj The object to get a deep copy of.
253     * @return A deep copy of the object.
254     */
255    @SuppressWarnings("unchecked")
256    public static <T> T getDeepCopy(T obj) {
257        CopyState copyState = RecycleUtils.getRecycledInstance(CopyState.class);
258        if (copyState == null) {
259            copyState = new CopyState();
260        }
261
262        obj = unwrap(obj);
263
264        SimpleReference<?> topReference = getSimpleReference(obj);
265        try {
266            copyState.queue.offer(topReference);
267
268            while (!copyState.queue.isEmpty()) {
269                CopyReference<?> toCopy = copyState.queue.poll();
270                Object source = toCopy.get();
271
272                if (source == null || !isCopyAvailable(source.getClass()) || !isCopy(toCopy)) {
273                    continue;
274                }
275
276                if (source instanceof Copyable) {
277                    source = unwrap(source);
278                }
279
280                if (ViewLifecycle.isTrace()) {
281                    ProcessLogger.ntrace("deep-copy:", ":" + toCopy.getPath(), 10000, 1000);
282                }
283
284                toCopy.set(copyState.getTarget(source, isDeep(toCopy, source), toCopy));
285
286                if (toCopy != topReference) {
287                    recycle(toCopy);
288                }
289            }
290
291            return (T) topReference.get();
292        } finally {
293            recycle(topReference);
294            copyState.recycle();
295        }
296    }
297
298    /**
299     * Retrieves all field names for the given class that have the given annotation
300     *
301     * @param clazz class to find field annotations for
302     * @param annotationClass class for annotation to find
303     * @return map containing the field name that has the annotation as a key and the annotation
304     *         instance as a value
305     */
306    public static Map<String, Annotation> getFieldsWithAnnotation(Class<?> clazz,
307            Class<? extends Annotation> annotationClass) {
308        if (clazz == null) {
309            return Collections.<String, Annotation> emptyMap();
310        }
311        Map<String, Annotation> rv = getMetadata(clazz).annotatedFieldsByAnnotationType.get(annotationClass);
312        return rv == null ? Collections.<String, Annotation> emptyMap() : rv;
313    }
314
315    /**
316     * Determines whether the field of the given class has the given annotation specified
317     *
318     * @param clazz class containing the field to check
319     * @param fieldName name of the field to check
320     * @param annotationClass class for the annotation to look for
321     * @return true if the named field has the given annotation, false if not
322     */
323    public static boolean fieldHasAnnotation(Class<?> clazz, String fieldName,
324            Class<? extends Annotation> annotationClass) {
325        return getFieldAnnotation(clazz, fieldName, annotationClass) != null;
326    }
327
328    /**
329     * Returns annotation of the given type for the given field (if present)
330     *
331     * @param clazz class containing the field to check
332     * @param fieldName name of the field to check
333     * @param annotationClass class for the annotation to look for
334     */
335    public static Annotation getFieldAnnotation(Class<?> clazz, String fieldName,
336            Class<? extends Annotation> annotationClass) {
337        Map<String, Annotation> annotationsByField = getFieldsWithAnnotation(clazz, annotationClass);
338        return annotationsByField == null ? null : annotationsByField.get(fieldName);
339    }
340
341    /**
342     * Holds copy state for use with {@link #getDeepCopy(Object)}.
343     */
344    private static class CopyState {
345
346        private final Queue<CopyReference<?>> queue = new LinkedList<CopyReference<?>>();
347        private final Map<Object, Object> cache = new IdentityHashMap<Object, Object>();
348
349        /**
350         * Get a shallow copy of the source object appropriate for the current copy operation.
351         *
352         * @param source The original source object to copy.
353         * @return A shallow copy of the source object.
354         */
355        private Object getTarget(Object source, boolean queueDeepReferences, CopyReference<?> ref) {
356            boolean useCache = source != Collections.EMPTY_LIST && source != Collections.EMPTY_MAP;
357
358            Object target = useCache ? cache.get(source) : null;
359
360            if (target == null) {
361                Class<?> targetClass = ref.getTargetClass();
362
363                if (Copyable.class.isAssignableFrom(targetClass) && targetClass.isInterface()
364                        && ref.isDelayAvailable() && isDelay()) {
365                    target = DelayedCopyableHandler.getDelayedCopy((Copyable) source);
366                } else {
367
368                    try {
369                        target = getShallowCopy(source);
370                    } catch (CloneNotSupportedException e) {
371                        throw new IllegalStateException("Unexpected cloning error during shallow copy", e);
372                    }
373
374                    if (queueDeepReferences) {
375                        queueDeepCopyReferences(source, target, ref);
376                    }
377                }
378
379                if (useCache) {
380                    cache.put(source, target);
381                }
382            }
383
384            return target;
385        }
386
387        /**
388         * Queues references for deep copy after performing the shallow copy.
389         *
390         * @param source The original source object at the current node.
391         * @param target A shallow copy of the source object, to be analyzed for deep copy.
392         */
393        private void queueDeepCopyReferences(Object source, Object target, CopyReference<?> ref) {
394            Class<?> type = source.getClass();
395            Class<?> targetClass = ref.getTargetClass();
396            if (!isCopyAvailable(type)) {
397                return;
398            }
399
400            // Don't queue references if the source has already been seen
401            if (cache.containsKey(source)) {
402                return;
403            } else if (target == null) {
404                cache.put(source, source);
405            }
406
407            if (Copyable.class.isAssignableFrom(type)) {
408                for (Field field : getMetadata(type).cloneFields) {
409                    queue.offer(getFieldReference(source, target, field, ref));
410                }
411
412                // Used fields for deep copying, even if List or Map is implemented.
413                // The wrapped list/map should be picked up as a field during deep copy.
414                return;
415            }
416
417            if (List.class.isAssignableFrom(targetClass)) {
418                List<?> sourceList = (List<?>) source;
419                List<?> targetList = (List<?>) target;
420                Type componentType = ObjectPropertyUtils.getComponentType(ref.getType());
421
422                if (componentType instanceof TypeVariable<?>) {
423                    TypeVariable<?> tvar = (TypeVariable<?>) componentType;
424                    if (ref.getTypeVariables().containsKey(tvar.getName())) {
425                        componentType = ref.getTypeVariables().get(tvar.getName());
426                    }
427                }
428
429                Class<?> componentClass = ObjectPropertyUtils.getUpperBound(componentType);
430
431                for (int i = 0; i < sourceList.size(); i++) {
432                    queue.offer(getListReference(sourceList, targetList, i, componentClass, componentType, ref));
433                }
434            }
435
436            if (Map.class.isAssignableFrom(targetClass)) {
437                Map<?, ?> sourceMap = (Map<?, ?>) source;
438                Map<?, ?> targetMap = (Map<?, ?>) target;
439                Type componentType = ObjectPropertyUtils.getComponentType(ref.getType());
440                Class<?> componentClass = ObjectPropertyUtils.getUpperBound(componentType);
441
442                for (Map.Entry<?, ?> sourceEntry : sourceMap.entrySet()) {
443                    queue.offer(getMapReference(sourceEntry, targetMap, componentClass, componentType, ref));
444                }
445            }
446
447            if (targetClass.isArray()) {
448                for (int i = 0; i < Array.getLength(source); i++) {
449                    queue.offer(getArrayReference(source, target, i, ref));
450                }
451            }
452        }
453
454        /**
455         * Clear queue and cache, and recycle this state object.
456         */
457        private void recycle() {
458            queue.clear();
459            cache.clear();
460            RecycleUtils.recycle(this);
461        }
462    }
463
464    /**
465     * Represents a abstract reference to a targeted value for use during deep copying.
466     */
467    private interface CopyReference<T> {
468
469        /**
470         * Gets the type this reference refers to.
471         *
472         * @return the class referred to
473         */
474        Class<T> getTargetClass();
475
476        /**
477         * This method ...
478         *
479         * @return
480         */
481        String getPath();
482
483        /**
484         * Determines whether or not a delayed copy proxy should be considered on this reference.
485         *
486         * @return True if a delayed copy proxy may be used with this reference, false to always
487         *         perform deep copy.
488         */
489        boolean isDelayAvailable();
490
491        /**
492         * Gets the generic type this reference refers to.
493         *
494         * @return the generic type referred to
495         */
496        Type getType();
497
498        /**
499         * Gets the type variable mapping.
500         *
501         * @return the type variable mapping.
502         */
503        Map<String, Type> getTypeVariables();
504
505        /**
506         * Retrieve the targeted value for populating the reference.
507         *
508         * <p>
509         * This value returned by this method will typically come from a source object, then after
510         * copy operations have been performed {@link #set(Object)} will be called to populate the
511         * target value on the destination object.
512         * </p>
513         *
514         * @return The targeted value for populating the reference.
515         */
516        T get();
517
518        /**
519         * Modify the value targeted by the reference.
520         *
521         * <p>
522         * This value passed to this method will have typically come {@link #get()}. After copy
523         * operations have been performed, this method will be called to populate the target value
524         * on the destination object.
525         * </p>
526         *
527         * @param value The value to modify the reference as.
528         */
529        void set(Object value);
530
531        /**
532         * Clean the reference for recycling.
533         */
534        void clean();
535    }
536
537    /**
538     * Recycle a copy reference for later use, once the copy has been performed.
539     */
540    private static <T> void recycle(CopyReference<T> ref) {
541        ref.clean();
542        RecycleUtils.recycle(ref);
543    }
544
545    /**
546     * Simple copy reference for holding top-level value to be later inspected and returned. Values
547     * held by this class will be modified in place.
548     */
549    private static class SimpleReference<T> implements CopyReference<T> {
550
551        private T value;
552        private Class<T> targetClass;
553
554        /**
555         * Gets the target class.
556         *
557         * @return target class
558         */
559        public Class<T> getTargetClass() {
560            return this.targetClass;
561        }
562
563        /**
564         * {@inheritDoc}
565         */
566        @Override
567        public boolean isDelayAvailable() {
568            return false;
569        }
570
571        /**
572         * Gets the target class.
573         *
574         * @return target class
575         */
576        @Override
577        public Type getType() {
578            return this.targetClass;
579        }
580
581        /**
582         * {@inheritDoc}
583         */
584        @Override
585        public Map<String, Type> getTypeVariables() {
586            return Collections.emptyMap();
587        }
588
589        /**
590         * Gets the value.
591         *
592         * @return value
593         */
594        @Override
595        public T get() {
596            return value;
597        }
598
599        /**
600         * Sets the a value.
601         *
602         * @param value The value to set.
603         */
604        @Override
605        public void set(Object value) {
606            this.value = targetClass.cast(value);
607        }
608
609        /**
610         * @return the path
611         */
612        public String getPath() {
613            return null;
614        }
615
616        /**
617         * {@inheritDoc}
618         */
619        @Override
620        public void clean() {
621            this.value = null;
622            this.targetClass = null;
623        }
624    }
625
626    /**
627     * Get a simple reference for temporary use while deep cloning.
628     *
629     * <p>
630     * Call {@link #recycle(CopyReference)} when done working with the reference.
631     * </p>
632     *
633     * @param value The initial object to refer to.
634     * @return A simple reference for temporary use while deep cloning.
635     */
636    @SuppressWarnings("unchecked")
637    private static SimpleReference<?> getSimpleReference(Object value) {
638        SimpleReference<Object> ref = RecycleUtils.getRecycledInstance(SimpleReference.class);
639
640        if (ref == null) {
641            ref = new SimpleReference<Object>();
642        }
643
644        ref.targetClass = (Class<Object>) value.getClass();
645        ref.value = value;
646
647        return ref;
648    }
649
650    /**
651     * Reference implementation for a field on an object.
652     */
653    private static class FieldReference<T> implements CopyReference<T> {
654
655        private Object source;
656        private Object target;
657        private Field field;
658        private boolean delayAvailable;
659        private Map<String, Type> typeVariables = new HashMap<String, Type>();
660        private String path;
661
662        /**
663         * Gets the type of the field.
664         *
665         * {@inheritDoc}
666         */
667        @SuppressWarnings("unchecked")
668        @Override
669        public Class<T> getTargetClass() {
670            return (Class<T>) field.getType();
671        }
672
673        /**
674         * {@inheritDoc}
675         */
676        @Override
677        public boolean isDelayAvailable() {
678            return delayAvailable;
679        }
680
681        /**
682         * Gets the generic type of this field.
683         *
684         * {@inheritDoc}
685         */
686        @Override
687        public Type getType() {
688            return field.getGenericType();
689        }
690
691        /**
692         * {@inheritDoc}
693         */
694        @Override
695        public Map<String, Type> getTypeVariables() {
696            return typeVariables;
697        }
698
699        /**
700         * Get a value from the field on the source object.
701         *
702         * @return The value referred to by the field on the source object.
703         */
704        @SuppressWarnings("unchecked")
705        @Override
706        public T get() {
707            try {
708                ReferenceCopy ref = field.getAnnotation(ReferenceCopy.class);
709                if (ref != null && ref.referenceTransient()) {
710                    return null;
711                }
712
713                return (T) field.get(source);
714            } catch (IllegalAccessException e) {
715                throw new IllegalStateException("Access error attempting to get from " + field, e);
716            }
717        }
718
719        /**
720         * Set a value for the field on the target object.
721         *
722         * @param value The value to set for the field on the target object.
723         */
724        @Override
725        public void set(Object value) {
726            try {
727                field.set(target, value);
728            } catch (IllegalAccessException e) {
729                throw new IllegalStateException("Access error attempting to set " + field, e);
730            }
731        }
732
733        /**
734         * @return the path
735         */
736        public String getPath() {
737            return this.path;
738        }
739
740        /**
741         * {@inheritDoc}
742         */
743        @Override
744        public void clean() {
745            source = null;
746            target = null;
747            field = null;
748            delayAvailable = false;
749            path = null;
750            typeVariables.clear();
751        }
752    }
753
754    /**
755     * Get a field reference for temporary use while deep cloning.
756     *
757     * <p>
758     * Call {@link #recycle(CopyReference)} when done working with the reference.
759     * </p>
760     *
761     * @param source The source object.
762     * @param target The target object.
763     * @param field The field to use as the reference target.
764     * @return A field reference for temporary use while deep cloning.
765     */
766    private static <T> FieldReference<T> getFieldReference(Object source, Object target, Field field,
767            CopyReference<T> pref) {
768        @SuppressWarnings("unchecked")
769        FieldReference<T> ref = RecycleUtils.getRecycledInstance(FieldReference.class);
770
771        if (ref == null) {
772            ref = new FieldReference<T>();
773        }
774
775        ref.source = source;
776        ref.target = target;
777        ref.field = field;
778
779        DelayedCopy delayedCopy = field.getAnnotation(DelayedCopy.class);
780        ref.delayAvailable = delayedCopy != null && (!delayedCopy.inherit() || pref.isDelayAvailable());
781
782        Map<String, Type> pTypeVars = pref.getTypeVariables();
783
784        if (pTypeVars != null && source != null) {
785            Class<?> sourceType = source.getClass();
786            Class<?> targetClass = pref.getTargetClass();
787            Type targetType = ObjectPropertyUtils.findGenericType(sourceType, targetClass);
788            if (targetType instanceof ParameterizedType) {
789                ParameterizedType parameterizedTargetType = (ParameterizedType) targetType;
790                Type[] params = parameterizedTargetType.getActualTypeArguments();
791                for (int j = 0; j < params.length; j++) {
792                    if (params[j] instanceof TypeVariable<?>) {
793                        Type pType = pTypeVars.get(targetClass.getTypeParameters()[j].getName());
794                        ref.typeVariables.put(((TypeVariable<?>) params[j]).getName(), pType);
795                    }
796                }
797            }
798        }
799
800        Class<?> rawType = field.getType();
801        Type genericType = field.getGenericType();
802        if (genericType instanceof ParameterizedType) {
803            ParameterizedType parameterizedType = (ParameterizedType) genericType;
804            TypeVariable<?>[] typeParams = rawType.getTypeParameters();
805            Type[] params = parameterizedType.getActualTypeArguments();
806            assert params.length == typeParams.length;
807            for (int i = 0; i < params.length; i++) {
808                Type paramType = params[i];
809                if (paramType instanceof TypeVariable<?>) {
810                    Type fType = ref.typeVariables.get(((TypeVariable<?>) paramType).getName());
811                    if (fType != null) {
812                        paramType = fType;
813                    }
814                }
815                ref.typeVariables.put(typeParams[i].getName(), paramType);
816            }
817        }
818        return ref;
819    }
820
821    /**
822     * Reference implementation for an entry in an array.
823     */
824    private static class ArrayReference<T> implements CopyReference<T> {
825
826        private Object source;
827        private Object target;
828        private int index = -1;
829        private boolean delayAvailable;
830        private String path;
831        private Map<String, Type> typeVariables = new HashMap<String, Type>();
832
833        /**
834         * Gets the component type of the array.
835         *
836         * @return component type
837         */
838        @SuppressWarnings("unchecked")
839        @Override
840        public Class<T> getTargetClass() {
841            return (Class<T>) source.getClass().getComponentType();
842        }
843
844        /**
845         * {@inheritDoc}
846         */
847        @Override
848        public boolean isDelayAvailable() {
849            return delayAvailable;
850        }
851
852        /**
853         * Gets the component type of the array.
854         *
855         * @return component type
856         */
857        @Override
858        public Type getType() {
859            return source.getClass().getComponentType();
860        }
861
862        /**
863         * {@inheritDoc}
864         */
865        public Map<String, Type> getTypeVariables() {
866            return this.typeVariables;
867        }
868
869        /**
870         * Get the value of the indicated entry in the source array.
871         *
872         * @return The value of the indicated entry in the source array.
873         */
874        @SuppressWarnings("unchecked")
875        @Override
876        public T get() {
877            return (T) Array.get(source, index);
878        }
879
880        /**
881         * Modify the value of the indicated entry in the target array.
882         *
883         * @param value The value to set on the indicated entry in the target array.
884         */
885        @Override
886        public void set(Object value) {
887            Array.set(target, index, value);
888        }
889
890        /**
891         * @return the path
892         */
893        public String getPath() {
894            return this.path;
895        }
896
897        @Override
898        public void clean() {
899            source = null;
900            target = null;
901            index = -1;
902            delayAvailable = false;
903            path = null;
904            typeVariables.clear();
905        }
906    }
907
908    /**
909     * Get an array reference for temporary use while deep cloning.
910     *
911     * <p>
912     * Call {@link #recycle(CopyReference)} when done working with the reference.
913     * </p>
914     *
915     * @param source The source array.
916     * @param target The target array.
917     * @param index The array index.
918     * @return An array reference for temporary use while deep cloning.
919     */
920    private static <T> ArrayReference<T> getArrayReference(
921            Object source, Object target, int index, CopyReference<?> pref) {
922        @SuppressWarnings("unchecked")
923        ArrayReference<T> ref = RecycleUtils.getRecycledInstance(ArrayReference.class);
924
925        if (ref == null) {
926            ref = new ArrayReference<T>();
927        }
928
929        ref.source = source;
930        ref.target = target;
931        ref.index = index;
932        ref.delayAvailable = pref.isDelayAvailable();
933        ref.typeVariables.putAll(pref.getTypeVariables());
934        return ref;
935    }
936
937    /**
938     * Reference implementation for an item in a list.
939     */
940    private static class ListReference<T> implements CopyReference<T> {
941
942        private Class<T> targetClass;
943        private Type type;
944        private List<T> source;
945        private List<T> target;
946        private int index = -1;
947        private boolean delayAvailable;
948        private String path;
949        private Map<String, Type> typeVariables = new HashMap<String, Type>();
950
951        /**
952         * Gets the item class for the list.
953         *
954         * @return item class
955         */
956        @Override
957        public Class<T> getTargetClass() {
958            return targetClass;
959        }
960
961        /**
962         * {@inheritDoc}
963         */
964        public boolean isDelayAvailable() {
965            return this.delayAvailable;
966        }
967
968        /**
969         * Gets the generic item type for the list.
970         *
971         * @return generic item type
972         */
973        @Override
974        public Type getType() {
975            return type;
976        }
977
978        /**
979         * {@inheritDoc}
980         */
981        public Map<String, Type> getTypeVariables() {
982            return this.typeVariables;
983        }
984
985        /**
986         * Get the value of the indicated item in the source array.
987         *
988         * @return The value of the indicated item in the source array.
989         */
990        @Override
991        public T get() {
992            return targetClass.cast(source.get(index));
993        }
994
995        /**
996         * Modify the list item.
997         *
998         * @param value The value to modify the list item as.
999         */
1000        @Override
1001        public void set(Object value) {
1002            target.set(index, targetClass.cast(value));
1003        }
1004
1005        /**
1006         * @return the path
1007         */
1008        public String getPath() {
1009            return this.path;
1010        }
1011
1012        /**
1013         * {@inheritDoc}
1014         */
1015        @Override
1016        public void clean() {
1017            targetClass = null;
1018            type = null;
1019            source = null;
1020            target = null;
1021            index = -1;
1022            delayAvailable = false;
1023            typeVariables.clear();
1024        }
1025    }
1026
1027    /**
1028     * Get a list reference for temporary use while deep cloning.
1029     *
1030     * <p>
1031     * Call {@link #recycle(CopyReference)} when done working with the reference.
1032     * </p>
1033     *
1034     * @param source The source list.
1035     * @param target The target list.
1036     * @param index The index of the list item.
1037     * @return A list reference for temporary use while deep cloning.
1038     */
1039    @SuppressWarnings("unchecked")
1040    private static ListReference<?> getListReference(List<?> source, List<?> target, int index,
1041            Class<?> targetClass, Type type, CopyReference<?> pref) {
1042        ListReference<Object> ref = RecycleUtils.getRecycledInstance(ListReference.class);
1043
1044        if (ref == null) {
1045            ref = new ListReference<Object>();
1046        }
1047
1048        ref.source = (List<Object>) source;
1049        ref.target = (List<Object>) target;
1050        ref.index = index;
1051        ref.targetClass = (Class<Object>) targetClass;
1052        ref.type = type;
1053        ref.delayAvailable = pref.isDelayAvailable();
1054        ref.typeVariables.putAll(pref.getTypeVariables());
1055
1056        if (pref == null || pref.getPath() == null) {
1057            ref.path = "[" + index + ']';
1058        } else {
1059            ref.path = pref.getPath() + '[' + index + ']';
1060        }
1061
1062        return ref;
1063    }
1064
1065    /**
1066     * Reference implementation for an entry in a map.
1067     */
1068    private static class MapReference<T> implements CopyReference<T> {
1069
1070        private Class<T> targetClass;
1071        private Type type;
1072        private Map.Entry<Object, T> sourceEntry;
1073        private Map<Object, T> target;
1074        private boolean delayAvailable;
1075        private String path;
1076        private Map<String, Type> typeVariables = new HashMap<String, Type>();
1077
1078        /**
1079         * Gets the value class for the map.
1080         *
1081         * @return value class
1082         */
1083        @Override
1084        public Class<T> getTargetClass() {
1085            return targetClass;
1086        }
1087
1088        /**
1089         * @return the delayAvailable
1090         */
1091        public boolean isDelayAvailable() {
1092            return this.delayAvailable;
1093        }
1094
1095        /**
1096         * Gets the generic value type for the map.
1097         *
1098         * @return generic value type
1099         */
1100        @Override
1101        public Type getType() {
1102            return type;
1103        }
1104
1105        /**
1106         * {@inheritDoc}
1107         */
1108        public Map<String, Type> getTypeVariables() {
1109            return this.typeVariables;
1110        }
1111
1112        /**
1113         * Get the value of the map entry.
1114         *
1115         * @return The value of the map entry.
1116         */
1117        @Override
1118        public T get() {
1119            return sourceEntry.getValue();
1120        }
1121
1122        /**
1123         * Modify the map entry.
1124         *
1125         * @param value The value to modify the map entry with.
1126         */
1127        @Override
1128        public void set(Object value) {
1129            target.put(sourceEntry.getKey(), targetClass.cast(value));
1130        }
1131
1132        /**
1133         * @return the path
1134         */
1135        public String getPath() {
1136            return this.path;
1137        }
1138
1139        /**
1140         * {@inheritDoc}
1141         */
1142        @Override
1143        public void clean() {
1144            targetClass = null;
1145            type = null;
1146            sourceEntry = null;
1147            target = null;
1148            delayAvailable = false;
1149            typeVariables.clear();
1150        }
1151    }
1152
1153    /**
1154     * Get a map reference for temporary use while deep cloning.
1155     *
1156     * <p>
1157     * Call {@link #recycle(CopyReference)} when done working with the reference.
1158     * </p>
1159     *
1160     * @param sourceEntry The source entry.
1161     * @param target The target map.
1162     * @return A map reference for temporary use while deep cloning.
1163     */
1164    @SuppressWarnings("unchecked")
1165    private static MapReference<?> getMapReference(Map.Entry<?, ?> sourceEntry, Map<?, ?> target,
1166            Class<?> targetClass, Type type, CopyReference<?> pref) {
1167        MapReference<Object> ref = RecycleUtils.getRecycledInstance(MapReference.class);
1168
1169        if (ref == null) {
1170            ref = new MapReference<Object>();
1171        }
1172
1173        ref.sourceEntry = (Map.Entry<Object, Object>) sourceEntry;
1174        ref.target = (Map<Object, Object>) target;
1175        ref.targetClass = (Class<Object>) targetClass;
1176        ref.type = type;
1177        ref.delayAvailable = pref.isDelayAvailable();
1178        ref.typeVariables.putAll(pref.getTypeVariables());
1179
1180        if (pref == null || pref.getPath() == null) {
1181            ref.path = "[" + sourceEntry.getKey() + ']';
1182        } else {
1183            ref.path = pref.getPath() + '[' + sourceEntry.getKey() + ']';
1184        }
1185
1186        return ref;
1187    }
1188
1189    /**
1190     * Internal field cache meta-data node, for reducing field lookup overhead.
1191     *
1192     * @author Kuali Rice Team (rice.collab@kuali.org)
1193     */
1194    private static class ClassMetadata {
1195
1196        /**
1197         * All fields on the class that should have a shallow copy performed during a deep copy
1198         * operation.
1199         */
1200        private final List<Field> cloneFields;
1201
1202        /**
1203         * Mapping from field to generic collection type, for Map and List fields that should be
1204         * deep copied.
1205         */
1206        private final Map<Field, Class<?>> collectionTypeByField;
1207
1208        /**
1209         * Mapping from annotation type to field name to annotation mapping.
1210         */
1211        private final Map<Class<?>, Map<String, Annotation>> annotatedFieldsByAnnotationType;
1212
1213        /**
1214         * Create a new field reference for a target class.
1215         *
1216         * @param targetClass The class to inspect for meta-data.
1217         */
1218        private ClassMetadata(Class<?> targetClass) {
1219            // Create mutable collections for building meta-data indexes.
1220            List<Field> cloneList = new ArrayList<Field>();
1221            Map<Field, Class<?>> collectionTypeMap = new HashMap<Field, Class<?>>();
1222            Map<Class<?>, Map<String, Annotation>> annotationMap = new HashMap<Class<?>, Map<String, Annotation>>();
1223
1224            Class<?> currentClass = targetClass;
1225            while (currentClass != Object.class && currentClass != null) {
1226
1227                for (Field currentField : currentClass.getDeclaredFields()) {
1228                    if ((currentField.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
1229                        continue;
1230                    }
1231
1232                    Annotation[] annotations = currentField.getAnnotations();
1233                    if (annotations != null) {
1234                        for (Annotation annotation : annotations) {
1235                            Class<?> annotationType = annotation.annotationType();
1236                            Map<String, Annotation> amap = annotationMap.get(annotationType);
1237
1238                            if (amap == null) {
1239                                amap = new HashMap<String, Annotation>();
1240                                annotationMap.put(annotationType, amap);
1241                            }
1242
1243                            amap.put(currentField.getName(), annotation);
1244                        }
1245                    }
1246
1247                    Class<?> type = currentField.getType();
1248
1249                    boolean isList = List.class.isAssignableFrom(type);
1250                    boolean isMap = Map.class.isAssignableFrom(type);
1251                    if (isList || isMap || isCopyAvailable(type)) {
1252                        currentField.setAccessible(true);
1253                        cloneList.add(currentField);
1254                    }
1255
1256                    if (!isList && !isMap) {
1257                        continue;
1258                    }
1259
1260                    Class<?> collectionType = ObjectPropertyUtils.getUpperBound(
1261                            ObjectPropertyUtils.getComponentType(currentField.getGenericType()));
1262
1263                    if (collectionType.equals(Object.class) || isCopyAvailable(collectionType)) {
1264                        collectionTypeMap.put(currentField, collectionType);
1265                    }
1266                }
1267
1268                currentClass = currentClass.getSuperclass();
1269            }
1270
1271            // Seal index collections to prevent external modification.
1272            cloneFields = Collections.unmodifiableList(cloneList);
1273            collectionTypeByField = Collections.unmodifiableMap(collectionTypeMap);
1274
1275            for (Entry<Class<?>, Map<String, Annotation>> aentry : annotationMap.entrySet()) {
1276                aentry.setValue(Collections.unmodifiableMap(aentry.getValue()));
1277            }
1278            annotatedFieldsByAnnotationType = Collections.unmodifiableMap(annotationMap);
1279        }
1280    }
1281
1282    /**
1283     * Static cache for reducing annotated field lookup overhead.
1284     */
1285    private static final Map<Class<?>, ClassMetadata> CLASS_META_CACHE =
1286            Collections.synchronizedMap(new WeakHashMap<Class<?>, ClassMetadata>());
1287
1288    /**
1289     * Get copy metadata for a class.
1290     *
1291     * @param targetClass The class.
1292     * @return Copy metadata for the class.
1293     */
1294    private static final ClassMetadata getMetadata(Class<?> targetClass) {
1295        ClassMetadata metadata = CLASS_META_CACHE.get(targetClass);
1296
1297        if (metadata == null) {
1298            CLASS_META_CACHE.put(targetClass, metadata = new ClassMetadata(targetClass));
1299        }
1300
1301        return metadata;
1302    }
1303}