|
|
/*
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.util;
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: mpowers $
* @version $Revision: 1.11 $
*/
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 )
{
Object key, 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.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: Duplicator.java,v $
* 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.
*
*
*/
|