/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2001 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.foundation.internal; import net.wotonomy.foundation.*; import java.io.*; import java.util.*; //collections /** * Duplicator makes use of Introspector to duplicate objects, either by shallow * copy, deep copy, or by copying properties from one object to apply to another * object. You may find this class useful because java.lang.Object.clone() only * supports shallow copying. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 895 $ */ public class Duplicator { /** * Used to represent null values for properties in the maps returned by * readProperties and cloneProperties and in the parameter to writeProperties. * This actually references the NSNull instance. */ public static final Object NULL = NSNull.nullValue(); private static NSSelector clone = new NSSelector("clone"); /** * Returns a list of properties for the specified class that are both readable * and writable. */ static public List editablePropertiesForObject(Object anObject) { List readProperties = new ArrayList<>(); String[] read = Introspector.getReadPropertiesForObject(anObject); for (int i = 0; i < read.length; i++) { readProperties.add(read[i]); } List properties = new ArrayList<>(); String[] write = Introspector.getWritePropertiesForObject(anObject); for (int i = 0; i < write.length; i++) { properties.add(write[i]); } // only use properties on both lists: read/write properties.retainAll(readProperties); return properties; } /** * Returns a Map containing only the mutable properties for the specified object * and their values. Any null values for properties will be represented with the * NULL object. */ static public Map readPropertiesForObject(Object anObject) { NSMutableDictionary result = new NSMutableDictionary<>(); String key; Object value; Iterator it = editablePropertiesForObject(anObject).iterator(); while (it.hasNext()) { key = it.next().toString(); value = Introspector.get(anObject, key); if (value == null) value = NULL; result.setObjectForKey(value, key); } return result; } /** * Returns a Map containing only the mutable properties for the specified object * and deep clones of their values. Nulls are represented by the NULL object. */ static public Map clonePropertiesForObject(Object anObject) { String key; Object value; Map result = readPropertiesForObject(anObject); Iterator it = result.keySet().iterator(); while (it.hasNext()) { key = it.next(); value = result.get(key); value = deepClone(value); result.put(key, value); } return result; } /** * Applies the map of properties and values to the specified object. Null values * for properties must be represented by the NULL object. */ static public void writePropertiesForObject(Map aMap, Object anObject) { String key; Object value; Iterator it = aMap.keySet().iterator(); while (it.hasNext()) { key = it.next().toString(); value = aMap.get(key); if (NULL.equals(value)) value = null; Introspector.set(anObject, key, value); } } /** * Creates a new copy of the specified object. This implementation tries to call * clone(), and failing that, calls newInstance and then calls copy() to * transfer the values. * * @throws WotonomyException if any operation fails. */ static public Object clone(Object aSource) { Object result = null; if (clone.implementedByObject(aSource)) { try { result = clone.invoke(aSource); return result; } catch (Exception exc) { // fall back on newInstance() } } Class c = aSource.getClass(); try { result = c.getDeclaredConstructor().newInstance(); } catch (Exception exc) { throw new WotonomyException(exc); } return copy(aSource, result); } /** * Creates a deep copy of the specified object. Every object in this objects * graph will be duplicated with new instances. * * @throws WotonomyException if any operation fails. */ static public Object deepClone(Object aSource) { // the only known way to deep copy in // java without native code is serialization try { ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); ObjectOutputStream objectOutput = new ObjectOutputStream(byteOutput); objectOutput.writeObject(aSource); objectOutput.flush(); objectOutput.close(); ByteArrayInputStream byteInput = new ByteArrayInputStream(byteOutput.toByteArray()); ObjectInputStream objectInput = new ObjectInputStream(byteInput); return objectInput.readObject(); } catch (Exception exc) { throw new WotonomyException("Error cloning object: " + aSource, exc); } } /** * Copies values from one object to another. Returns the destination object. * * @throws WotonomyException if any operation fails. */ static public Object copy(Object aSource, Object aDestination) { try { writePropertiesForObject(readPropertiesForObject(aSource), aDestination); } catch (RuntimeException exc) { throw new WotonomyException(exc); } return aDestination; } /** * Deeply clones the values from one object and applies them to another object. * Returns the destination object. * * @throws WotonomyException if any operation fails. */ static public Object deepCopy(Object aSource, Object aDestination) { try { writePropertiesForObject(clonePropertiesForObject(aSource), aDestination); } catch (RuntimeException exc) { throw new WotonomyException(exc); } return aDestination; } } /* * $Log$ Revision 1.1 2006/02/16 16:52:12 cgruber Add cvsignore crap to find off * checking in binary crap. * * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.11 2001/08/22 19:24:26 mpowers Providing a more helpful error * message for cloning exceptions. * * Revision 1.10 2001/03/29 03:30:36 mpowers Refactored duplicator a bit. * Disabled MissingPropertyExceptions for now. * * Revision 1.9 2001/03/28 14:11:23 mpowers Removed debugging printlns. * * Revision 1.8 2001/03/27 23:25:48 mpowers Basically reverting to the previous * version. * * Revision 1.7 2001/03/06 23:18:13 mpowers Clarified some comments. * * Revision 1.6 2001/03/01 20:36:35 mpowers Better error handling and better * handling of nulls. * * Revision 1.5 2001/02/27 21:43:40 mpowers Removed NullMarker class in favor of * NSNull. * * Revision 1.4 2001/02/26 22:41:51 mpowers Implemented null placeholder * classes. Duplicator now uses NSNull. No longer catching base exception class. * * Revision 1.3 2001/02/23 21:07:46 mpowers Documented the NULL object. * * Revision 1.1 2001/02/16 22:51:29 mpowers Now deep-cloning objects passed * between editing contexts. * * */