/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package Framework;
import java.awt.Component;
import java.awt.Image;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTree;
import javax.swing.MenuElement;
import javax.swing.tree.DefaultMutableTreeNode;
import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import DisplayProject.CloneableComponent;
import DisplayProject.CloneableComponentWithProperties;
import DisplayProject.DisplayNode;
import DisplayProject.GridCell;
import DisplayProject.TreeViewModel;
import DisplayProject.UIutils;
import DisplayProject.actions.AppData;
import DisplayProject.actions.Column;
import DisplayProject.actions.FieldWidgetGravity;
import DisplayProject.actions.HeightPolicy;
import DisplayProject.actions.HelpTopic;
import DisplayProject.actions.Row;
import DisplayProject.actions.StatusText;
import DisplayProject.actions.WidgetState;
import DisplayProject.actions.WidthPolicy;
import DisplayProject.binding.beans.Observable;
import DisplayProject.controls.MenuList;
import DisplayProject.controls.PictureGraphic;
import DisplayProject.controls.TextGraphic;
import DisplayProject.events.AboutMenuListener;
import DisplayProject.factory.MenuFactory;
import DisplayProject.printing.PrintActionListener;
import DisplayProject.table.ArrayFieldCellHelper;
/**
* This Helper class implements deep and shallow cloning of objects
* <p>
* TF:9/12/07:Refactored this to use generics and remove warnings
* @author tim
*
*/
public class CloneHelper {
private static Logger _log = Logger.getLogger(CloneHelper.class);
/** A cache of all the classes that have been cloned and their fields. */
private static final Map<Class<?>, List<Field>> fieldCache = new HashMap<Class<?>, List<Field>>();
private CloneHelper() {
super();
}
/*
* Return a list of all the fields that can be used for duplicating.
*/
private static synchronized List<Field> getFields(Class<?> pClass) {
// TF:Mar 12, 2010:Cached the class fields for additional performance
List<Field> results = fieldCache.get(pClass);
if (results == null) {
// Get the declared fields
results = new ArrayList<Field>();
Field[] fields = pClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
// Do not return static or final fields (but do return transient fields, for arraylists!)
int modifiers = fields[i].getModifiers();
if (!(Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) || Modifier.isTransient(modifiers)) {
results.add(fields[i]);
}
}
// Now add in the superclass fields
Class<?> superclass = pClass.getSuperclass();
if (superclass != null) {
List<Field> superclassFields = getFields(superclass);
results.addAll(superclassFields);
}
fieldCache.put(pClass, results);
}
return results;
}
/**
* Perform a shallow clone of the object. This method can fail (and will
* return null) for several reasons, including: 1) There is no zero-argument
* constructor 2) The fields cannot be queried.
*
* If there is a public clone() method declared on the class, this method
* will be used. Otherwise, the fields will be copied field-by-field,
* excluding static and transient fields.
*
* @param pToBeCloned
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T shallowClone(T pToBeCloned) {
if (pToBeCloned == null) {
return null;
} else {
// Special case, see if this is an array so we can clone it directly
if (pToBeCloned instanceof DynamicArray) {
return (T)((DynamicArray)pToBeCloned).clone();
}
// See if the object declares a "Clone()" method
Class<?> aClass = pToBeCloned.getClass();
try {
Method aCloneMethod = aClass.getMethod("clone", (Class[]) null); //$NON-NLS-1$
// Yes, it has the method declared on this class, invoke it.
return (T)aCloneMethod.invoke(pToBeCloned, (Object[]) null);
} catch (Exception e) {
// Either the method doesn't exist or we couldn't invoke it.
// Try copying the object field-by-field (including superclass
// fields).
try {
T newObject = (T)aClass.newInstance();
List<Field> fields = CloneHelper.getFields(aClass);
for (Field thisField : fields) {
thisField.setAccessible(true);
//thisField.set(newObject, thisField.get(pToBeCloned));
CloneHelper.setFieldValue(thisField, newObject, thisField.get(pToBeCloned));
}
return newObject;
} catch (Exception e1) {
// We cannot clone this object, for example because it
// doesn't
// have a 0-argument constructor. Report the error and fail.
_log.error(Messages.getString("CloneHelper.1") //$NON-NLS-1$
+ aClass.getName()
+ Messages.getString("CloneHelper.2"), e1); //$NON-NLS-1$
return null;
}
}
}
}
/**
* Utility for making deep copies (vs. clone()'s shallow copies) of objects.
* Objects are first serialized and then deserialized. Error checking is
* fairly minimal in this implementation. If an object is encountered that
* cannot be serialized (or that references an object that cannot be
* serialized) an error is printed to System.err and null is returned.
*/
public static <T> T deepClone(T orig) {
// CraigM:06/11/2008 - Pass in an IdentityHashMap
// return deepClone(orig, new IdentityHashMap<Object, Object>(500));
return clone2(orig, true);
}
public static <T> T deepClone(T orig, Map<Object, Object> pMap) {
return clone2(orig, true, pMap);
}
// @SuppressWarnings("unchecked")
// public static <T> T deepClone(T orig, Map<Object, Object> pMap) {
// Object obj = null;
// if (orig == null) {
// return null;
// } else if (orig instanceof Component) {
// return (T)cloneComponent((Component)orig, true, pMap);
// } else
// try {
// if (orig instanceof DynamicArray) {
// obj = ((DynamicArray) orig).clone(true);
// } else {
// if (orig instanceof JMenu) {
// obj = MenuFactory.newMenu();
// } else {
// obj = orig.getClass().newInstance();
// }
// obj = duplicateIntoTarget(orig, obj, true, pMap, true);
// }
// } catch (InstantiationException e) {
// _log.error(e);
//// Do not throw exception - was trying to clone ExtendedPropertyChangeSupport
//// throw new UsageException("Could not clone class: "+ orig.getClass().getName(), e);
// } catch (IllegalAccessException e) {
// _log.error(e);
// UsageException errorVar = new UsageException("Could not clone class: "+ orig.getClass().getName(), e);
// ErrorMgr.addError(errorVar);
// throw errorVar;
// }
// return (T)obj;
// }
@SuppressWarnings("unchecked")
public static <T> T serialDeepClone(T orig) {
Object obj = null;
if (orig == null) {
return null;
}
try {
// Write the object out to a byte array
FastByteArrayOutputStream fbos = new FastByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(fbos);
out.writeObject(orig);
out.flush();
out.close();
// Retrieve an input stream from the byte array and read
// a copy of the object back in.
ObjectInputStream in = new ObjectInputStream(fbos.getInputStream());
obj = in.readObject();
} catch (IOException e) {
_log.error(e);
UsageException errorVar = new UsageException("Could not clone class: "+ orig.getClass().getName(), e);
ErrorMgr.addError(errorVar);
throw errorVar;
} catch (ClassNotFoundException cnfe) {
_log.error(cnfe);
UsageException errorVar = new UsageException("Could not clone class: "+ orig.getClass().getName(), cnfe);
ErrorMgr.addError(errorVar);
throw errorVar;
}
return (T)obj;
}
/**
* Convenience method to clone an object either deep or shallow based on the parameter
* @param pObject - the object to clone
* @param pDeep - whether the clone should be deep or shallow
* @return the cloned object
* @see CloneHelper#deepClone(Object)
* @see CloneHelper#shallowClone(Object)
*/
public static <T> T clone(T pObject, boolean pDeep) {
// if (pDeep) {
// return CloneHelper.clone2(pObject);
// } else {
// return CloneHelper.shallowClone(pObject);
// }
return clone2(pObject, pDeep);
}
/**
* Copy the fields of one object ontop on another object of the same type. This
* is done on a field-by-field basis.
* @param pSource - The object to duplicate
* @param pTarget - a reference to the location to duplicate the information to.
* @param pDeep - whether to recurse into child objects
* @return
*/
public static Object duplicateIntoTarget(Object pSource, Object pTarget, boolean pDeep) {
// TF:25/02/2009:Changed to use an identity hash map instead of a normal hash map
return CloneHelper.duplicateIntoTarget(pSource, pTarget, pDeep, new IdentityHashMap<Object, Object>(1000), false);
}
/**
* Try to use a setter. If no setter exists, then just do a straight set on the field.
*
* We need to use the setter, as if we don't, any data binding will not work.
*
* Eg: If a window passes its bound object to a server, which updates one of the values, when we
* return from the server call, we will come in here updating all the data. We still need the
* data binding to work, and to reflect the change back on the window.
*
* CraigM: 08/04/2008.
*
* @param field the field we are setting
* @param obj the object we are setting the value on
* @param value the value to set in the field on the object
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
private static void setFieldValue(Field field, Object obj, Object value, boolean changed) throws IllegalArgumentException, IllegalAccessException {
// AD:04/10/2008: CMA-31 Recoded method to ensure object binding is updated where necessary.
// Need to set off binding if the field is changed
field.set(obj, value);
if (changed) {
// Make sure that if the parent obj is Observable then fire a property change
if (obj instanceof Observable) {
try {
Field listenerField = obj.getClass().getField("qq_Listeners");
PropertyChangeSupport propertyChangeSupport = (PropertyChangeSupport)listenerField.get(obj);
String propertyName = FrameworkUtils.getPropertyName(field.getName());
if (propertyChangeSupport != null && propertyChangeSupport.hasListeners(propertyName)) {
propertyChangeSupport.firePropertyChange(propertyName, null, value);
}
} catch (Exception e) {
// Ignore the exception if the qq_Listeners is not found
}
}
// If the value is a DataValue then make sure it reports a change
if (value instanceof DataValue) {
((DataValue)value).reportChange();
}
}
}
private static void setFieldValue(Field field, Object obj, Object value) throws IllegalArgumentException, IllegalAccessException {
setFieldValue(field, obj, value, false);
}
@SuppressWarnings("unchecked")
private static Object duplicateIntoTarget(Object pSource, Object pTarget, boolean pDeep, Map<Object, Object> pMap, boolean pFromClone) {
Object aTarget;
if (pMap == null) {
// TF:18/07/2008:We MUST use an IdentityHashMap to ensure the reference equality (==) operator is used when
// comparing elements, otherwise inserting arrays into the map will be very problematic due to AbstractList
// overriding the hashcode and equals methods
pMap = new IdentityHashMap<Object, Object>(1000);
}
if (pSource == null) {
return null;
}
else if (pMap.containsKey(pSource)) {
// Look up this key to prevent infinite recursion on object graphs
return pMap.get(pSource);
}
/*
* Peters kludge for java.sql.TimeStamp (probably taken care of now in the date part...)
*/
else if (pSource instanceof java.sql.Timestamp) {
java.sql.Timestamp src = (java.sql.Timestamp) pSource;
return src.clone();
}
else if (pSource instanceof Proxy) {
// TF:10/12/08:
return pSource;
}
/*
* java.util.timestamp contains nothing but transient fields, so we need to do something tricky.
*/
else if (pSource instanceof java.util.Date) {
java.util.Date src = (java.util.Date) pSource;
return src.clone();
}
// TF:26/02/2009:Removed these lines as they stop classes containing ImageNullables from cloning properly
// else if (pSource instanceof ImageData) { // PM:28/07/2008:
//
// return new ImageData((ImageData)pSource);
// }
else if (pSource instanceof ByteVector) {
return new ByteVector((ByteVector)pSource);
}
else if (pSource instanceof PropertyChangeSupport) {
return pSource;
} else if (pSource instanceof HashTable) {
HashTable src = (HashTable) pSource;
return src.clone(true);
} else if (pSource instanceof StringBuffer) {
// Treat string buffers as a special case because of their underlying reliance on char arrays
return new StringBuffer(((StringBuffer) pSource).toString());
} else if (pSource instanceof StringBuilder) {
// Treat string Builder as a special case because of their underlying reliance on char arrays
return new StringBuilder(((StringBuilder) pSource).toString());
} else if (pSource instanceof SimpleDateFormat) {
// CraigM:13/12/2008 - SimpleDateFormat doesn't like being cloned
SimpleDateFormat sdf = new SimpleDateFormat(((SimpleDateFormat)pSource).toPattern());
sdf.setLenient(((SimpleDateFormat)pSource).isLenient());
sdf.setTimeZone(((SimpleDateFormat)pSource).getTimeZone());
return sdf;
} else if (pSource.getClass().equals(TextData.class)) {
// Treat TextData as a special case because of its property bind capability
// TF:6/7/07: Fixed a problem where the target could be null
// TF:12/11/07:Changed this to be equals, not instanceof, as otherwise domain classes and
// textNullable classes don't work properly. We need to do this for TextData so that if the
// textdata is bound to a control, the control is not duplicated as well.
// TF:19/03/2010:This has been changed to a MappedTextData. Added it here because it's cloned
// so frequently and it's faster doing it this way
if (pTarget == null) {
aTarget = new TextData();
} else {
aTarget = pTarget;
}
((TextData) aTarget).setValue((TextData) pSource);
((TextData) aTarget).setOffset(((TextData) pSource).getOffset());
return aTarget;
}
else if (pSource.getClass().equals(MappedTextData.class)) {
if (pTarget == null) {
aTarget = new MappedTextData();
} else {
aTarget = pTarget;
}
((TextData) aTarget).setValue((TextData) pSource);
((TextData) aTarget).setOffset(((TextData) pSource).getOffset());
return aTarget;
}
// else if (pSource instanceof JMenu) {
// if (pTarget == null)
// pTarget = MenuFactory.newMenu();
// ((JMenu) pTarget).setName(((JMenu) pSource).getName());
// ((JMenu) pTarget).setText(((JMenu) pSource).getText());
// ((JMenu) pTarget).setMnemonic(((JMenu) pSource).getMnemonic());
// return pTarget;
// }
else if (pSource instanceof Component) {
// TF:8/11/07:Made sure we can clone components correctly
return cloneComponent((Component)pSource, pDeep, pMap);
} else {
Class<?> sourceClass = pSource.getClass();
if (sourceClass.isPrimitive()
|| sourceClass.equals(String.class)
|| sourceClass.equals(Class.class)
|| sourceClass.equals(BigDecimal.class)
|| sourceClass.equals(BigInteger.class)
|| sourceClass.equals(Integer.class)
|| sourceClass.equals(Double.class)
|| sourceClass.equals(Boolean.class)
|| sourceClass.equals(Float.class)
|| sourceClass.equals(Byte.class)
|| sourceClass.equals(Integer.TYPE)
|| sourceClass.equals(Double.TYPE)
|| sourceClass.equals(Boolean.TYPE)
|| sourceClass.equals(Float.TYPE)
|| sourceClass.equals(Byte.TYPE)) {
// These classes are immutatble, so all we can do is return their value.
return pSource;
}
else if (pTarget == null || pTarget.getClass() != pSource.getClass()) {
try {
aTarget = pSource.getClass().newInstance();
} catch (Exception e) {
// _log.debug(e.getMessage(), e);
return pSource;
}
} else {
aTarget = pTarget;
}
// Add this target into our infinite-recursion preventing map...
pMap.put(pSource, aTarget);
// Now we have our target object, copy the objects into it.
List<Field> fields = CloneHelper.getFields(sourceClass);
for (Field thisField : fields) {
// NB: We must copy transient fields also, otherwise arraylists don't copy
// properly as their data is stored as transient!
thisField.setAccessible(true);
Class<?> fieldType = thisField.getType();
Object aSourceObject = null;
Object aTargetObject = null;
// TF:25/9/07: DisplayNodes have a userObject which is set by the runtime system
// to the control to which it is mapped. We do NOT want to clone this as it is
// an implementation consequence, not something the user desired to attach.
if (pSource instanceof DisplayNode && thisField.getName().equals("userObject")) {
continue;
}
// We don't want to change the existing property change listeners as, if we did, the data binding wouldn't work. CraigM: 08/04/2008.
if (PropertyChangeSupport.class.isAssignableFrom(fieldType)) {
continue;
}
try {
// For simple types (including String), set the reference.
// TF:10/12/08:Added the check for proxies -- a proxy cannot be cloned, it must be set explicitly
// TF:26/02/2009:DET-77:Added a test for Images. Images should always be wrappered in an imageData subclass
// and used as such, and therefore should be immutable as the user cannot get a reference to one. If this
// isn't the case, we will need to clone the image through serialisation / deserialisation
if (fieldType.isPrimitive()
|| fieldType.equals(String.class)
|| fieldType.equals(Class.class)
|| Proxy.isProxyClass(fieldType)
|| Image.class.isAssignableFrom(fieldType)) {
// thisField.set(aTarget, thisField.get(pSource));
//PM:26/11/2008:added the changed parameter set to true to fix JCT-623
//CloneHelper.setFieldValue(thisField, aTarget, thisField.get(pSource));
CloneHelper.setFieldValue(thisField, aTarget, thisField.get(pSource), true);
}
// For a shallow copy, or a field with the CloneReferenceOnly annotation, just copy over the object reference. CraigM:26/06/2008.
else if (!pDeep || thisField.getAnnotation(CloneReferenceOnly.class) != null) {
// If they did set the annotation class, check that they didn't ask us to null the value.
// AD:25/09/08 - Still need to set the field value even if there is no CloneReferenceOnly Annotation
if ((thisField.getAnnotation(CloneReferenceOnly.class) == null) || (thisField.getAnnotation(CloneReferenceOnly.class).nullReference() == false)) {
CloneHelper.setFieldValue(thisField, aTarget, thisField.get(pSource));
}
}
else {
// Deep clone, non-primitive type
if (fieldType.isArray()) {
// Instantiate and copy the array.
Object anArray = thisField.get(pSource);
if (anArray != null) {
Class<?> compType = fieldType.getComponentType();
int length = Array.getLength(anArray);
Object newArray = Array.newInstance(compType, length);
Object existingArray = thisField.get(aTarget);
int existingLength = 0;
if (existingArray != null) {
existingLength = Array.getLength(existingArray);
}
for (int i = 0; i < length; i++) {
Object sourceElement = Array.get(anArray, i);
Object existingElement = (i < existingLength) ? Array.get(existingArray, i) : null;
Array.set(newArray, i, CloneHelper.duplicateIntoTarget(sourceElement, existingElement, true, pMap, pFromClone));
}
//thisField.set(aTarget, newArray);
CloneHelper.setFieldValue(thisField, aTarget, newArray);
}
else {
//thisField.set(aTarget, null);
CloneHelper.setFieldValue(thisField, aTarget, null);
}
} else {
aSourceObject = thisField.get(pSource);
aTargetObject = thisField.get(aTarget);
if ((aSourceObject == null) && (aTargetObject != null))
//thisField.set(aTarget, null);
CloneHelper.setFieldValue(thisField, aTarget, null);
else {
if (aSourceObject instanceof Collection) {
Collection ds = (Collection) aSourceObject;
Collection dt = (Collection) aTargetObject;
if (ds != dt) {
// TF:18/07/2008:If the map already contains this collection, we do not instantiate it again, instead
// we use the existing one. This prevents a deep clone which has an array referenced multiple times
// from returning 2 discrete arrays with the same elements referenced inside them.
if (pMap.containsKey(ds)) {
// Look up this key to prevent infinite recursion on object graphs
dt = (Collection)pMap.get(ds);
CloneHelper.setFieldValue(thisField, aTarget, dt);
}
else {
if (dt == null) {
dt = ds.getClass().newInstance();
//thisField.set(aTarget, dt);
CloneHelper.setFieldValue(thisField, aTarget, dt);
}
else {
dt.clear();
}
// TF:18/07/2008:Add the new element into the map
pMap.put(ds, dt);
for (Iterator iter = ds.iterator(); iter.hasNext();) {
// TF: Must pass the map back into the clone object, otherwise have a high risk of
// mutual recursion leading to stack overflow.
dt.add(deepClone(iter.next(), pMap));
}
}
}
} else {
boolean changed;
if (pFromClone) {
// AD:21/11/2008: if it is from a clone we know it can not have changed
changed = false;
} else {
// AD:04/10/2008: CMA-31 Check if the source and Target are different before duplicating into Target,
// so that we can trigger binding if they have changed in setFieldValue
changed = !((aSourceObject == null && aTargetObject == null) || (aSourceObject != null && aSourceObject.equals(aTargetObject)));
}
Object o = CloneHelper.duplicateIntoTarget(aSourceObject, aTargetObject, true, pMap, pFromClone);
//thisField.set(aTarget, o);
CloneHelper.setFieldValue(thisField, aTarget, o, changed);
}
}
}
}
}
catch (Exception e) {
_log.error(Messages.getString("CloneHelper.3") + thisField.getName() //$NON-NLS-1$
+ Messages.getString("CloneHelper.4") + sourceClass.getName() + Messages.getString("CloneHelper.5"), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
return aTarget;
}
/**
* Clone the scroll pane and its contents.
* @param source
* @return
*
* Written by Craig Mitchell
* @since 11/02/2008
*/
public static JScrollPane cloneJScrollPane(JScrollPane source) {
JComponent child = (JComponent)source.getViewport().getView();
JComponent clonedChild = clone(child, true);
JScrollPane target = new JScrollPane();
target.setSize(source.getSize());
target.setMinimumSize(source.getMinimumSize());
target.setPreferredSize(source.getPreferredSize());
target.setVerticalScrollBarPolicy(source.getVerticalScrollBarPolicy());
target.setHorizontalScrollBarPolicy(source.getHorizontalScrollBarPolicy());
if (source.getRowHeader() != null)
target.setRowHeaderView(clone((Component)source.getRowHeader().getView(), true));
target.setViewportView(clonedChild);
// We need to clone the childs properties again as when we cloned them the first time, some properties
// could not be set as they depended on the parent scroll pane being set. For example, the widget state.
CloneHelper.cloneClientProperties(child, clonedChild);
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JLabel cloneJLabel(JLabel source) {
JLabel target = new JLabel(source.getText());
CloneHelper.cloneComponent(source, target, new String[] {"parent", "icon"});
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JScrollBar cloneJScrollBar(JScrollBar source) {
JScrollBar target = new JScrollBar(source.getOrientation(), source.getValue(),
source.getVisibleAmount(), source.getMinimum(),
source.getMaximum());
target.setBlockIncrement(source.getBlockIncrement());
target.setSize(source.getSize());
target.setMinimumSize(source.getMinimumSize());
target.setPreferredSize(source.getPreferredSize());
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JTree cloneJTree(JTree source) {
DisplayNode root = clone((DisplayNode) source.getModel().getRoot(), true);
TreeViewModel dtm = new TreeViewModel(root);
JTree target = new JTree(dtm);
CloneHelper.cloneComponent(source, target, new String[] {"UI", // class javax.swing.plaf.TreeUI
"UIClassID", // class java.lang.String
"accessibleContext", // class javax.accessibility.AccessibleContext
"actionMap", // class javax.swing.ActionMap
"alignmentX", // float
"alignmentY", // float
"ancestorListeners", // class [Ljavax.swing.event.AncestorListener;
"anchorSelectionPath", // class javax.swing.tree.TreePath
"autoscrolls", // boolean
//"background", // class java.awt.Color
"border", // interface javax.swing.border.Border
"cellEditor", // interface javax.swing.tree.TreeCellEditor
"cellRenderer", // interface javax.swing.tree.TreeCellRenderer
"component", // null
"componentCount", // int
"componentPopupMenu", // class javax.swing.JPopupMenu
"components", // class [Ljava.awt.Component;
"containerListeners", // class [Ljava.awt.event.ContainerListener;
"debugGraphicsOptions", // int
"doubleBuffered", // boolean
//"dragEnabled", // boolean
//"editable", // boolean
"editing", // boolean
"editingPath", // class javax.swing.tree.TreePath
//"enabled", // boolean
"expandsSelectedPaths", // boolean
//"fixedRowHeight", // boolean
"focusCycleRoot", // boolean
"focusTraversalKeys", // null
"focusTraversalPolicy", // class java.awt.FocusTraversalPolicy
"focusTraversalPolicyProvider", // boolean
"focusTraversalPolicySet", // boolean
//"focusable", // boolean
//"font", // class java.awt.Font
//"foreground", // class java.awt.Color
"graphics", // class java.awt.Graphics
//"height", // int
"inheritsPopupMenu", // boolean
"inputMap", // null
"inputVerifier", // class javax.swing.InputVerifier
//"insets", // class java.awt.Insets
"invokesStopCellEditing", // boolean
"largeModel", // boolean
"lastSelectedPathComponent", // class java.lang.Object
"layout", // interface java.awt.LayoutManager
"leadSelectionPath", // class javax.swing.tree.TreePath
"leadSelectionRow", // int
"managingFocus", // boolean
"maxSelectionRow", // int
//"maximumSize", // class java.awt.Dimension
"minSelectionRow", // int
//"minimumSize", // class java.awt.Dimension
"model", // interface javax.swing.tree.TreeModel
// "name", // class java.lang.String
"nextFocusableComponent", // class java.awt.Component
//"opaque", // boolean
//"optimizedDrawingEnabled", // boolean
"paintingTile", // boolean
"pathForRow", // null
"preferredScrollableViewportSize", // class java.awt.Dimension
"preferredSize", // class java.awt.Dimension
"registeredKeyStrokes", // class [Ljavax.swing.KeyStroke;
"requestFocusEnabled", // boolean
"rootPane", // class javax.swing.JRootPane
//"rootVisible", // boolean
//"rowBounds", // null
//"rowCount", // int
//"rowHeight", // int
"scrollableTracksViewportHeight", // boolean
"scrollableTracksViewportWidth", // boolean
"scrollsOnExpand", // boolean
"selectionCount", // int
"selectionEmpty", // boolean
"selectionInterval", // null
"selectionModel", // interface javax.swing.tree.TreeSelectionModel
"selectionPath", // class javax.swing.tree.TreePath
"selectionPaths", // class [Ljavax.swing.tree.TreePath;
"selectionRow", // int
"selectionRows", // class [I
//"showsRootHandles", // boolean
"toggleClickCount", // int
//"toolTipText", // class java.lang.String
"topLevelAncestor", // class java.awt.Container
"transferHandler", // class javax.swing.TransferHandler
"treeExpansionListeners", // class [Ljavax.swing.event.TreeExpansionListener;
"treeSelectionListeners", // class [Ljavax.swing.event.TreeSelectionListener;
"treeWillExpandListeners", // class [Ljavax.swing.event.TreeWillExpandListener;
"validateRoot", // boolean
"verifyInputWhenFocusTarget", // boolean
"vetoableChangeListeners", // class [Ljava.beans.VetoableChangeListener;
//"visible", // boolean
"visibleRect", // class java.awt.Rectangle
//"visibleRowCount", // int
//"width", // int
//"x", // int
//"y" // int
});
// for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(JTree.class)){
// System.out.println("\""+pd.getName()+"\", // "+pd.getPropertyType());
// }
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JMenu cloneMenu(JMenu source, boolean deep) {
JMenu target = MenuFactory.newMenu();
target.setName(source.getName());
target.setText(source.getText());
target.setMnemonic(source.getMnemonic());
for (Component entry : source.getMenuComponents()){
if (deep){
target.add(cloneComponent(entry, deep));
} else
target.add(entry);
}
CloneHelper.cloneClientProperties(source, target);
return target;
}
/**
* Clone a popup menu and its children. CraigM:19/12/2008.
* @param source
* @param deep
* @return
*/
public static JPopupMenu cloneMenu(JPopupMenu source, boolean deep) {
JPopupMenu target = new JPopupMenu();
target.setName(source.getName());
for (MenuElement entry : source.getSubElements()){
if (entry instanceof JMenuItem) {
if (deep) {
target.add(cloneComponent((JMenuItem)entry, deep));
}
else {
target.add((JMenu)entry);
}
}
else if (entry instanceof JSeparator) {
if (deep) {
target.add(cloneComponent((JSeparator)entry, deep));
}
else {
target.add((JSeparator)entry);
}
}
else {
throw new UsageException("Cannot clone a PopupMenu with children other then JMenuItem or JSeparator");
}
}
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JMenuItem cloneJMenuItem(JMenuItem source, boolean deep) {
JMenuItem target = new JMenuItem(source.getText());
target.setName(source.getName());
target.setText(source.getText());
target.setMnemonic(source.getMnemonic());
target.setAccelerator(source.getAccelerator());
ActionListener[] listeners = source.getActionListeners();
for (ActionListener listener : listeners){
if (listener instanceof PrintActionListener){
target.addActionListener(new PrintActionListener(((PrintActionListener)listener).getWindow()));
} else
if (listener instanceof AboutMenuListener){
target.addActionListener(new AboutMenuListener());
}
}
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JCheckBoxMenuItem cloneJCheckBoxMenuItem(JCheckBoxMenuItem source, boolean deep) {
JCheckBoxMenuItem target = new JCheckBoxMenuItem(source.getText());
target.setName(source.getName());
target.setText(source.getText());
target.setMnemonic(source.getMnemonic());
target.setAccelerator(source.getAccelerator());
target.setSelected(source.isSelected());
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static JRadioButtonMenuItem cloneJRadioButonenuItem(JRadioButtonMenuItem source, boolean deep) {
JRadioButtonMenuItem target = new JRadioButtonMenuItem(source.getText());
target.setName(source.getName());
target.setText(source.getText());
target.setMnemonic(source.getMnemonic());
target.setAccelerator(source.getAccelerator());
CloneHelper.cloneClientProperties(source, target);
return target;
}
// JSeparators are now MenuSeparators. CraigM: 29/01/2008.
public static JSeparator cloneJSeparator(JSeparator source, boolean deep) {
// TF:30/08/2008:Changed this to handle subclasses of JSeparator such as MenuSeparator
JSeparator target;
try {
target = source.getClass().newInstance();
}
catch (Exception e) {
// Nothing we can do except return a new JSeparator
target = new JSeparator();
}
target.setName(source.getName());
CloneHelper.cloneClientProperties(source, target);
return target;
}
public static void cloneClientProperties(JComponent source, JComponent target){
Row.set(target, Row.get(source));
Column.set(target, Column.get(source));
FieldWidgetGravity.set(target, FieldWidgetGravity.get(source));
WidthPolicy.set(target, WidthPolicy.get(source));
HeightPolicy.set(target, HeightPolicy.get(source));
AppData.set(target, AppData.get(source));
HelpTopic.set(target, HelpTopic.get(source));
StatusText.set(target, StatusText.get(source));
// TF:24/9/07: We also need to clone the widget state
WidgetState.set(target, WidgetState.get(source));
}
/**
* @param source
* @param deep
* @return
* @since 24/12/2007
* Written by Craig Mitchell
*/
public static JFrame cloneJFrame(JFrame source, boolean deep) {
JFrame _Result = (JFrame)BeanUtils.instantiateClass(source.getClass());
// THIS DOESN'T WORK CORRECTLY YET
// // Remove the default widgets
// _Result.getContentPane().removeAll();
//
// // Copy over the existing widgets
// JPanel originalForm = UIutils.getForm(source);
// JPanel clonedForm = CloneHelper.clone(originalForm, deep);
// _Result.add(clonedForm);
return _Result;
}
public static Component cloneComponent(Component orig, boolean deep){
// TF:25/02/2009:Changed to use an IdentityHashMap instead of a normal HashMap
return cloneComponent(orig, deep, new IdentityHashMap<Object, Object>(100));
}
private static Component cloneComponent(Component orig, boolean deep, Map<Object, Object> visitedObjects) {
Component result;
if (orig instanceof JFrame) {
result = cloneJFrame((JFrame)orig, deep);
} else if (orig instanceof JScrollPane) {
result = cloneJScrollPane((JScrollPane) orig);
} else if (orig instanceof JScrollBar) {
result = cloneJScrollBar((JScrollBar) orig);
} else if (orig instanceof JTree) {
result = cloneJTree((JTree) orig);
} else if (orig instanceof JMenu) {
result = cloneMenu((JMenu) orig, deep);
} else if (orig instanceof JMenuItem) {
result = cloneJMenuItem((JMenuItem) orig, deep);
// JSeparators are now MenuSeparators. CraigM: 29/01/2008.
} else if (orig instanceof JSeparator) {
result = cloneJSeparator((JSeparator) orig, deep);
} else if (orig instanceof JCheckBoxMenuItem) {
result = cloneJCheckBoxMenuItem((JCheckBoxMenuItem) orig, deep);
} else if (orig instanceof MenuList) {
result = (MenuList)((MenuList)orig).clone(deep);
} else if (orig instanceof JPopupMenu) { // CraigM:19/12/2008.
result = cloneMenu((JPopupMenu) orig, deep);
} else if (orig instanceof JRadioButtonMenuItem) {
result = cloneJRadioButonenuItem((JRadioButtonMenuItem) orig, deep);
} else if (orig instanceof JLabel
&& (!(orig instanceof TextGraphic || orig instanceof PictureGraphic))) {
result = cloneJLabel((JLabel) orig);
} else if (orig instanceof CloneableComponentWithProperties) {
// TF:15/10/2008:Performance optimisation with new interface
result = ((CloneableComponentWithProperties)orig).cloneComponent(visitedObjects);
} else if (orig instanceof CloneableComponent) {
// TF:15/10/2008:Performance optimisation with new interface
result = ((CloneableComponent)orig).cloneComponent();
} else {
try {
Method cloneMethod = BeanUtils
.findDeclaredMethodWithMinimalParameters(orig
.getClass(), "cloneComponent");
Object comp = cloneMethod.invoke(orig, (Object[]) null);
if (orig instanceof JComponent)
CloneHelper.cloneClientProperties((JComponent)orig, (JComponent)comp);
result = (Component)comp;
} catch (NullPointerException e) {
_log.error(e);
throw new UsageException(
"Cannot clone a "
+ orig.getClass().getName()
+ " because it is a widget and these are not able to be cloned in Java. The description of the object is: "
+ orig.toString(), e);
} catch (InvocationTargetException e) {
_log.error(e);
throw new UsageException(
"Cannot clone a "
+ orig.getClass().getName()
+ " because it is a widget and these are not able to be cloned in Java. The description of the object is: "
+ orig.toString(), e);
} catch (UsageException e) {
_log.error(e);
throw new UsageException(
"Cannot clone a "
+ orig.getClass().getName()
+ " because it is a widget and these are not able to be cloned in Java. The description of the object is: "
+ orig.toString(), e);
} catch (IllegalAccessException e) {
_log.error(e);
throw new UsageException(
"Cannot clone a "
+ orig.getClass().getName()
+ " because it is a widget and these are not able to be cloned in Java. The description of the object is: "
+ orig.toString(), e);
}
}
// TF:15/10/2008:We need to clone various things if we're inside an array, such as binding manager components
if (orig instanceof JComponent && result instanceof JComponent) {
ArrayFieldCellHelper.cloneComponentArrayParts((JComponent)orig, (JComponent)result);
}
return result;
}
/**
* Copy the widget properties and set the sizes correctly.
*
* @param source
* @param target
* @param ignoreProperties
* @throws BeansException
*
* Written by Craig Mitchell
* @since 18/02/2008
*/
public static void cloneComponent(Component source, Component target, String[] ignoreProperties) throws BeansException {
BeanUtils.copyProperties(source, target, ignoreProperties);
// TF:22/10/2008:Added in a processGUIActions, otherwise things like Width/HeightPolicies will not get
// processed prior to executing this method, which can change the sizes we're about to read.
UIutils.processGUIActions();
target.setPreferredSize(source.isPreferredSizeSet() ? source.getPreferredSize() : null);
target.setMinimumSize(source.isMinimumSizeSet() ? source.getMinimumSize() : null);
target.setMaximumSize(source.isMaximumSizeSet() ? source.getMaximumSize() : null);
if (source instanceof JComponent) {
GridCell orig = GridCell.getExisting((JComponent)source);
if (orig != null) {
GridCell cell = GridCell.get((JComponent)target);
cell.setWidthPolicy(orig.getWidthPolicy());
cell.setHeightPolicy(orig.getHeightPolicy());
}
// TF:03/11/2008:We need to clone specific client properties too, otherwise some things won't work.
ArrayFieldCellHelper.cloneComponentArrayParts((JComponent)source, (JComponent)target);
}
}
private abstract static class ClassCloner<T> {
/**
* Clone an object deeply.
* @param orig - This parameter will never be null
* @param visitedObjects - This parameter will never be null, and visitedObjects.get(orig) == null will always be true
* @return
*/
public abstract T clone(T orig, boolean pDeep, Map<Object, Object> visitedObjects);
}
/** Make a referenced copy to the passed object */
private static class Copier<T> extends ClassCloner<T> {
@Override
public T clone(T orig, boolean pDeep, Map<Object, Object> visitedObjects) {return orig;}
}
/** Null out the reference in the returned object */
private static class Nuller<T> extends ClassCloner<T> {
@Override
public T clone(T orig, boolean pDeep, Map<Object, Object> visitedObjects) {return null;}
}
private static class DynamicArrayCloner extends ClassCloner<DynamicArray<?>> {
@SuppressWarnings("unchecked")
@Override
public DynamicArray<?> clone(DynamicArray<?> orig, boolean pDeep, Map<Object, Object> visitedObjects) {
DynamicArray newInstance = null;
try {
newInstance = orig.getClass().newInstance();
} catch(InstantiationException instantiationException) {
handleException(orig, instantiationException);
} catch(IllegalAccessException illegalAccessException) {
handleException(orig, illegalAccessException);
}
newInstance.setDefaultClass(orig.getDefaultClass());
visitedObjects.put(orig, newInstance);
if (pDeep) {
for(int i = 0; i < orig.size(); i ++) {
Object item = orig.get(i);
newInstance.add(CloneHelper.deepClone(item, visitedObjects));
}
} else {
newInstance.addAll(orig);
}
return (DynamicArray<?>)newInstance;
}
}
private static class DateCloner extends ClassCloner<Date> {
@Override
public Date clone(Date orig, boolean pDeep, Map<Object, Object> visitedObjects) {
return (Date)orig.clone();
}
}
private static class TimestampCloner extends ClassCloner<Timestamp> {
@Override
public Timestamp clone(Timestamp orig, boolean pDeep, Map<Object, Object> visitedObjects) {
return (Timestamp)orig.clone();
}
}
private static class HashTableCloner extends ClassCloner<HashTable> {
@Override
public HashTable clone(HashTable orig, boolean pDeep, Map<Object, Object> visitedObjects) {
return (HashTable)orig.clone();
}
}
private static class StringBufferCloner extends ClassCloner<StringBuffer> {
@Override
public StringBuffer clone(StringBuffer orig, boolean pDeep, Map<Object, Object> visitedObjects) {
return new StringBuffer(orig);
}
}
private static class StringBuilderCloner extends ClassCloner<StringBuilder> {
@Override
public StringBuilder clone(StringBuilder orig, boolean pDeep, Map<Object, Object> visitedObjects) {
return new StringBuilder(orig);
}
}
private static class TextDataCloner extends ClassCloner<TextData> {
@Override
public TextData clone(TextData orig, boolean pDeep, Map<Object, Object> visitedObjects) {
// We need to just copy the text data values and not the bound property
TextData result = new TextData();
result.setValue(orig);
result.setIsNull(orig.isNull());
return result;
}
}
private static class NumericFormatCloner extends ClassCloner<NumericFormat> {
@Override
public NumericFormat clone(NumericFormat orig, boolean pDeep, Map<Object, Object> visitedObjects) {
// We need to just copy the text data values and not the bound property
NumericFormat result = new NumericFormat(orig.getTemplate());
result.setAllowsInvalid(orig.getAllowsInvalid());
result.setCommitsOnValidEdit(orig.getCommitsOnValidEdit());
result.setInvalidCharacters(orig.getInvalidCharacters());
try {
result.setMask(orig.getMask());
} catch (ParseException e) {
// This should never happen
e.printStackTrace();
}
result.setOverwriteMode(orig.getOverwriteMode());
result.setPlaceholder(orig.getPlaceholder());
result.setPlaceholderCharacter(orig.getPlaceholderCharacter());
result.setValueClass(orig.getValueClass());
result.setValueContainsLiteralCharacters(orig.getValueContainsLiteralCharacters());
return result;
}
}
private static class TextFormatCloner extends ClassCloner<TextFormat> {
@Override
public TextFormat clone(TextFormat orig, boolean pDeep, Map<Object, Object> visitedObjects) {
// We need to just copy the text data values and not the bound property
TextFormat result = new TextFormat(orig.getTemplate(), TextFormat.qq_Resolver.cTEMPLATE);
result.setAllowsInvalid(orig.getAllowsInvalid());
result.setCommitsOnValidEdit(orig.getCommitsOnValidEdit());
result.setInvalidCharacters(orig.getInvalidCharacters());
try {
result.setMask(orig.getMask());
} catch (ParseException e) {
// This should never happen
e.printStackTrace();
}
result.setOverwriteMode(orig.getOverwriteMode());
result.setPlaceholder(orig.getPlaceholder());
result.setPlaceholderCharacter(orig.getPlaceholderCharacter());
result.setValueClass(orig.getValueClass());
result.setValueContainsLiteralCharacters(orig.getValueContainsLiteralCharacters());
return result;
}
}
private static class DateFormatCloner extends ClassCloner<DateFormat> {
@Override
public DateFormat clone(DateFormat orig, boolean pDeep, Map<Object, Object> visitedObjects) {
// We need to just copy the text data values and not the bound property
DateFormat result = new DateFormat(orig.getTemplate());
result.setAllowsInvalid(orig.getAllowsInvalid());
result.setCommitsOnValidEdit(orig.getCommitsOnValidEdit());
result.setInvalidCharacters(orig.getInvalidCharacters());
try {
result.setMask(orig.getMask());
} catch (ParseException e) {
// This should never happen
e.printStackTrace();
}
result.setOverwriteMode(orig.getOverwriteMode());
result.setPlaceholder(orig.getPlaceholder());
result.setPlaceholderCharacter(orig.getPlaceholderCharacter());
result.setValueClass(orig.getValueClass());
result.setValueContainsLiteralCharacters(orig.getValueContainsLiteralCharacters());
return result;
}
}
private static class SimpleDateFormatCloner extends ClassCloner<SimpleDateFormat> {
@Override
public SimpleDateFormat clone(SimpleDateFormat orig, boolean pDeep, Map<Object, Object> visitedObjects) {
SimpleDateFormat sdf = new SimpleDateFormat(orig.toPattern());
sdf.setLenient(orig.isLenient());
sdf.setTimeZone(orig.getTimeZone());
return sdf;
}
}
private static class ImageIconCloner extends ClassCloner<ImageIcon> {
@Override
public ImageIcon clone(ImageIcon orig, boolean pDeep, Map<Object, Object> visitedObjects) {
ImageIcon icon;
if (orig.getImage() == null) {
icon = new ImageIcon();
}
else {
icon = new ImageIcon(orig.getImage());
}
icon.setDescription(orig.getDescription());
return icon;
}
}
private static class ComponentCloner extends ClassCloner<Component> {
@Override
public Component clone(Component orig, boolean pDeep, Map<Object, Object> visitedObjects) {
return cloneComponent(orig, true, visitedObjects);
}
}
private static class DefaultCloner<T> extends ClassCloner<T> {
private Method cloneMethod;
private List<Field> fieldsForShallowCopy;
private List<Field> fieldsForDeepCopy;
private List<Field> fieldsForNullling;
private boolean isProxy;
public DefaultCloner(Class<?> clazz) {
if (Proxy.isProxyClass(clazz)) {
isProxy = true;
return;
}
// First, see if we're cloneable, then look for a clone method.
if (Cloneable.class.isAssignableFrom(clazz)) {
// Go up the class hierarchy looking for clone() method. If it exists on anything other than Object.class
// we cannot use it because it may not do field-level copies.
Class<?> aClass = clazz;
while (aClass != null) {
try {
Method aMethod = aClass.getDeclaredMethod("clone");
if (aClass.equals(Object.class)) {
cloneMethod = aMethod;
cloneMethod.setAccessible(true);
}
break;
}
catch (NoSuchMethodException e) {
aClass = aClass.getSuperclass();
}
}
}
else {
System.out.println("Class " + clazz + " is not cloneable");
}
List<Field> allFields = getFields(clazz);
fieldsForDeepCopy = new ArrayList<Field>();
fieldsForShallowCopy = new ArrayList<Field>();
fieldsForNullling = new ArrayList<Field>();
for (Field f : allFields) {
f.setAccessible(true);
// TF:25/9/07: DisplayNodes have a userObject which is set by the runtime system
// to the control to which it is mapped. We do NOT want to clone this as it is
// an implementation consequence, not something the user desired to attach. As its
// on the superclass we cannot simply annotate it as we don't own the class
if (DefaultMutableTreeNode.class.isAssignableFrom(clazz) && f.getName().equals("userObject")) {
fieldsForNullling.add(f);
}
else {
// If we're primitive or marked with the annotation, add us to the shallow copy list
// TF:12/05/2010:Or if we're a String, which is immutable
if (f.getType().isPrimitive() || f.getAnnotation(CloneReferenceOnly.class) != null || f.getType().equals(String.class)) {
// Unless we're annotated and to be nulled
if (f.getAnnotation(CloneReferenceOnly.class) != null && f.getAnnotation(CloneReferenceOnly.class).nullReference()) {
fieldsForNullling.add(f);
}
else {
fieldsForShallowCopy.add(f);
}
}
else {
fieldsForDeepCopy.add(f);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public T clone(T orig, boolean pDeep, Map<Object, Object> visitedObjects) {
// Temp debugging
// Exception exception = new Exception();
// exception.fillInStackTrace();
// if (exception.getStackTrace().length > 800) {
// System.out.println("Possible infinite recusion!");
// }
if (isProxy) {
// Just copy the reference
return orig;
}
// First clone the object shallowly, using the method if possible or the fields if not
T result = null;
if (cloneMethod != null) {
try {
result = (T)cloneMethod.invoke(orig);
}
catch (Exception e) {
// Nothing to do, fall through to the shallow cloning via field code
}
}
try {
if (result == null) {
// Shallow clone via fields
result = (T)orig.getClass().newInstance();
for (Field f : fieldsForShallowCopy) {
f.set(result, f.get(orig));
}
// TF:12/05/2010:If we're shallow cloning manually, we also need
// to take the deep cloned fields and copy them across
if (!pDeep) {
for (Field f : fieldsForDeepCopy) {
f.set(result, f.get(orig));
}
}
}
// Put yourself in the map so that others pointing to me find me already cloned
visitedObjects.put(orig, result);
// Now deep clone the fields
if (pDeep) {
for (Field f : fieldsForDeepCopy) {
Class<?> fieldType = f.getType();
if (fieldType.isArray()) {
// Instantiate and copy the array.
Object anArray = f.get(orig);
if (anArray != null) {
Class<?> compType = fieldType.getComponentType();
int length = Array.getLength(anArray);
Object newArray = Array.newInstance(compType, length);
for (int i = 0; i < length; i++) {
Object sourceElement = Array.get(anArray, i);
Array.set(newArray, i, clone2(sourceElement, pDeep, visitedObjects));
}
f.set(result, newArray);
}
else {
f.set(result, null);
}
}
else {
f.set(result, clone2(f.get(orig), pDeep, visitedObjects));
}
}
}
// And null out the appropriate fields
for (Field f : fieldsForNullling) {
f.set(result, null);
}
}
catch (InstantiationException ie) {
handleException(orig, ie);
}
catch (IllegalAccessException iae) {
handleException(orig, iae);
}
return result;
}
}
private static void handleException(Object orig, Exception e) {
_log.error("Error cloning object of type "+orig.getClass(), e);
UsageException errorVar = new UsageException("Could not clone class: " + orig.getClass().getName(), e);
ErrorMgr.addError(errorVar);
throw errorVar;
}
/** The superClassCloners will clone any instance of their class, or any subclasses of their class */
private static Map<Class<?>, ClassCloner<?>> superClassCloners = new HashMap<Class<?>, ClassCloner<?>>(10);
/** The cloners will clone only instances of their class, not subclasses of their classes */
private static Map<Class<?>, ClassCloner<?>> cloners = new HashMap<Class<?>, ClassCloner<?>>(100);
static {
cloners.put(String.class, new Copier<String>());
cloners.put(Class.class, new Copier<Class<?>>());
cloners.put(BigDecimal.class, new Copier<BigDecimal>());
cloners.put(BigInteger.class, new Copier<BigInteger>());
cloners.put(Integer.class, new Copier<Integer>());
cloners.put(Double.class, new Copier<Double>());
cloners.put(Boolean.class, new Copier<Boolean>());
cloners.put(Float.class, new Copier<Float>());
cloners.put(Byte.class, new Copier<Byte>());
cloners.put(Proxy.class, new Copier<Proxy>());
// Deliberately clone mapped text datas as just plain textdatas
cloners.put(MappedTextData.class, new TextDataCloner());
cloners.put(TextData.class, new TextDataCloner());
cloners.put(NumericFormat.class, new NumericFormatCloner());
cloners.put(TextFormat.class, new TextFormatCloner());
cloners.put(DateFormat.class, new DateFormatCloner());
superClassCloners.put(PropertyChangeSupport.class, new Nuller<PropertyChangeSupport>());
superClassCloners.put(Date.class, new DateCloner());
superClassCloners.put(Timestamp.class, new TimestampCloner());
superClassCloners.put(HashTable.class, new HashTableCloner());
superClassCloners.put(StringBuffer.class, new StringBufferCloner());
superClassCloners.put(StringBuilder.class, new StringBuilderCloner());
superClassCloners.put(SimpleDateFormat.class, new SimpleDateFormatCloner());
superClassCloners.put(DynamicArray.class, new DynamicArrayCloner());
superClassCloners.put(Component.class, new ComponentCloner());
superClassCloners.put(Image.class, new Copier<Image>()); // Images just keep their references
superClassCloners.put(ImageIcon.class, new ImageIconCloner()); // Images just keep their references
}
public static <T> T clone2(T o, boolean pDeep) {
return clone2(o, pDeep, new IdentityHashMap<Object, Object>());
}
@SuppressWarnings("unchecked")
private static <T> T clone2(T orig, boolean pDeep, Map<Object, Object> nodesVisited) {
if (orig == null) {
return null;
}
else {
// Simple case, have we visited this node and if so just return the existing one?
Object duplicate = nodesVisited.get(orig);
if (duplicate != null) {
return (T)duplicate;
}
T result = null;
// Nope, look it up in the map of cloners
ClassCloner<T> aCloner;
synchronized (cloners) {
aCloner = (ClassCloner<T>)cloners.get(orig.getClass());
}
if (aCloner == null) {
// No known cloner for this class, see if there's a superclass cloner
Class<?> superClass = orig.getClass();
while (superClass != null) {
aCloner = (ClassCloner<T>)superClassCloners.get(superClass);
if (aCloner != null) {
// Don't forget to remember this cloner for this class for next time!
synchronized (cloners) {
cloners.put(orig.getClass(), aCloner);
}
break;
}
superClass = superClass.getSuperclass();
}
}
if (aCloner == null) {
// No cloners are available for this class, create a default cloner and use it
aCloner = new DefaultCloner<T>(orig.getClass());
synchronized (cloners) {
cloners.put(orig.getClass(), aCloner);
}
}
result = aCloner.clone(orig, pDeep, nodesVisited);
nodesVisited.put(orig, result);
return result;
}
}
}