1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
/*
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<String> editablePropertiesForObject(Object anObject) {
List<String> readProperties = new ArrayList<>();
String[] read = Introspector.getReadPropertiesForObject(anObject);
for (int i = 0; i < read.length; i++) {
readProperties.add(read[i]);
}
List<String> 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<String, Object> readPropertiesForObject(Object anObject) {
NSMutableDictionary<String, Object> result = new NSMutableDictionary<>();
String key;
Object value;
Iterator<String> 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<String, Object> clonePropertiesForObject(Object anObject) {
String key;
Object value;
Map<String, Object> result = readPropertiesForObject(anObject);
Iterator<String> 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<String, Object> aMap, Object anObject) {
String key;
Object value;
Iterator<String> 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.
*
*
*/
|