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 "krad.uif.copyable.delay". 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}