View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2001 Michael Powers
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.foundation.internal;
20  
21  import net.wotonomy.foundation.*;
22  import net.wotonomy.foundation.internal.Introspector;
23  import net.wotonomy.foundation.internal.WotonomyException;
24  
25  import java.io.*;
26  import java.util.*; //collections
27  
28  /***
29  * Duplicator makes use of Introspector to duplicate objects,
30  * either by shallow copy, deep copy, or by copying properties
31  * from one object to apply to another object.  You may find this
32  * class useful because java.lang.Object.clone() only supports
33  * shallow copying.
34  *
35  * @author michael@mpowers.net
36  * @author $Author: cgruber $
37  * @version $Revision: 895 $
38  */
39  
40  public class Duplicator
41  {
42      /***
43      * Used to represent null values for properties in the
44      * maps returned by readProperties and cloneProperties
45      * and in the parameter to writeProperties.
46      * This actually references the NSNull instance.
47      */
48      public static final Object NULL = NSNull.nullValue();
49      private static NSSelector clone = new NSSelector( "clone" );
50  
51      /***
52      * Returns a list of properties for the specified class
53      * that are both readable and writable.
54      */
55      static public List editablePropertiesForObject( 
56          Object anObject )
57      {
58          List readProperties = new ArrayList();
59          String[] read = Introspector.getReadPropertiesForObject( anObject );
60          for ( int i = 0; i < read.length; i++ )
61          {
62              readProperties.add( read[i] );
63          }
64  
65          List properties = new ArrayList();
66          String[] write = Introspector.getWritePropertiesForObject( anObject );
67          for ( int i = 0; i < write.length; i++ )
68          {
69              properties.add( write[i] );
70          }
71          
72          // only use properties on both lists: read/write
73          properties.retainAll( readProperties );
74  
75          return properties;
76      }
77      
78      /***
79      * Returns a Map containing only the mutable properties 
80      * for the specified object and their values.
81      * Any null values for properties will be represented with
82      * the NULL object.
83      */
84      static public Map readPropertiesForObject( 
85          Object anObject )
86      {
87          NSMutableDictionary result = new NSMutableDictionary();
88          
89          String key;
90          Object value;
91          Iterator it = editablePropertiesForObject( anObject ).iterator();
92          while ( it.hasNext() )
93          {
94              key = it.next().toString();
95              value = Introspector.get( anObject, key );
96              if ( value == null ) value = NULL;
97              result.setObjectForKey( value, key );
98          }
99          return result;        
100     }
101     
102     /***
103     * Returns a Map containing only the mutable properties 
104     * for the specified object and deep clones of their values.
105     * Nulls are represented by the NULL object.
106     */
107     static public Map clonePropertiesForObject( 
108         Object anObject )
109     {
110         Object key, value;
111         Map result = readPropertiesForObject( anObject );
112         Iterator it = result.keySet().iterator();
113         while ( it.hasNext() )
114         {
115             key = it.next(); 
116             value = result.get( key );            
117             value = deepClone( value );
118             result.put( key, value );
119         }
120         return result;
121     }
122     
123     /***
124     * Applies the map of properties and values to the
125     * specified object.  Null values for properties must
126     * be represented by the NULL object.
127     */
128     static public void writePropertiesForObject( 
129         Map aMap, Object anObject )
130     {
131         String key;
132         Object value;
133         Iterator it = aMap.keySet().iterator();
134         while ( it.hasNext() )
135         {
136             key = it.next().toString();
137             value = aMap.get( key );
138             if ( NULL.equals( value ) ) value = null;
139             Introspector.set( anObject, key, value );
140         }
141     }    
142     
143     /***
144     * Creates a new copy of the specified object.
145     * This implementation tries to call clone(),
146     * and failing that, calls newInstance
147     * and then calls copy() to transfer the values.
148     * @throws WotonomyException if any operation fails.
149     */ 
150     static public Object clone( 
151         Object aSource )
152     {
153         Object result = null;
154         if ( clone.implementedByObject( aSource ) )
155         {
156             try 
157             {
158                 result = clone.invoke( aSource );
159                 return result;
160             }
161             catch ( Exception exc )
162             {
163                 // fall back on newInstance()
164             }
165         }
166         
167         Class c = aSource.getClass();
168         try 
169         {
170             result = c.newInstance();
171         }
172         catch ( Exception exc )
173         {
174             throw new WotonomyException( exc );
175         }
176         return copy( aSource, result );        
177     }
178 
179     /***
180     * Creates a deep copy of the specified object.
181     * Every object in this objects graph will be
182     * duplicated with new instances.
183     * @throws WotonomyException if any operation fails.
184     */ 
185     static public Object deepClone(
186         Object aSource )
187     {
188         // the only known way to deep copy in
189         // java without native code is serialization
190         
191         try
192         {
193             ByteArrayOutputStream byteOutput =
194                 new ByteArrayOutputStream();
195             ObjectOutputStream objectOutput =
196                 new ObjectOutputStream( byteOutput );
197                 
198             objectOutput.writeObject( aSource );
199             objectOutput.flush();
200             objectOutput.close();
201             
202             ByteArrayInputStream byteInput =
203                 new ByteArrayInputStream( byteOutput.toByteArray() );
204             ObjectInputStream objectInput =
205                 new ObjectInputStream( byteInput );
206             return objectInput.readObject();
207         } 
208         catch ( Exception exc )
209         {
210             throw new WotonomyException( "Error cloning object: " + aSource, exc );
211         }
212     }
213 
214     /***
215     * Copies values from one object to another.
216     * Returns the destination object.
217     * @throws WotonomyException if any operation fails.
218     */ 
219     static public Object copy( 
220         Object aSource, Object aDestination )
221     {
222         try 
223         {
224             writePropertiesForObject(
225                 readPropertiesForObject( aSource ), aDestination );
226         }
227         catch ( RuntimeException exc )
228         {
229             throw new WotonomyException( exc );
230         }
231         return aDestination;        
232     }
233     
234     /***
235     * Deeply clones the values from one object and applies them
236     * to another object.
237     * Returns the destination object.
238     * @throws WotonomyException if any operation fails.
239     */ 
240     static public Object deepCopy( 
241         Object aSource, Object aDestination )
242     {
243         try 
244         {
245             writePropertiesForObject(
246                 clonePropertiesForObject( aSource ), aDestination );
247         }
248         catch ( RuntimeException exc )
249         {
250             throw new WotonomyException( exc );
251         }
252         return aDestination;        
253     }
254 }
255 
256 /*
257  * $Log$
258  * Revision 1.1  2006/02/16 16:52:12  cgruber
259  * Add cvsignore crap to find off checking in binary crap.
260  *
261  * Revision 1.1  2006/02/16 13:22:22  cgruber
262  * Check in all sources in eclipse-friendly maven-enabled packages.
263  *
264  * Revision 1.11  2001/08/22 19:24:26  mpowers
265  * Providing a more helpful error message for cloning exceptions.
266  *
267  * Revision 1.10  2001/03/29 03:30:36  mpowers
268  * Refactored duplicator a bit.
269  * Disabled MissingPropertyExceptions for now.
270  *
271  * Revision 1.9  2001/03/28 14:11:23  mpowers
272  * Removed debugging printlns.
273  *
274  * Revision 1.8  2001/03/27 23:25:48  mpowers
275  * Basically reverting to the previous version.
276  *
277  * Revision 1.7  2001/03/06 23:18:13  mpowers
278  * Clarified some comments.
279  *
280  * Revision 1.6  2001/03/01 20:36:35  mpowers
281  * Better error handling and better handling of nulls.
282  *
283  * Revision 1.5  2001/02/27 21:43:40  mpowers
284  * Removed NullMarker class in favor of NSNull.
285  *
286  * Revision 1.4  2001/02/26 22:41:51  mpowers
287  * Implemented null placeholder classes.
288  * Duplicator now uses NSNull.
289  * No longer catching base exception class.
290  *
291  * Revision 1.3  2001/02/23 21:07:46  mpowers
292  * Documented the NULL object.
293  *
294  * Revision 1.1  2001/02/16 22:51:29  mpowers
295  * Now deep-cloning objects passed between editing contexts.
296  *
297  *
298  */
299