1 package org.apache.ojb.otm.copy;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import java.lang.reflect.Array;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.Modifier;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.ojb.broker.PersistenceBroker;
28 import org.apache.ojb.broker.util.IdentityMapFactory;
29
30
31
32
33
34
35 public final class ReflectiveObjectCopyStrategy implements ObjectCopyStrategy
36 {
37 private static final Set FINAL_IMMUTABLE_CLASSES;
38 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
39 private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
40 private static final SerializeObjectCopyStrategy _serialize = new SerializeObjectCopyStrategy();
41
42 static
43 {
44 FINAL_IMMUTABLE_CLASSES = new HashSet(17);
45 FINAL_IMMUTABLE_CLASSES.add(String.class);
46 FINAL_IMMUTABLE_CLASSES.add(Byte.class);
47 FINAL_IMMUTABLE_CLASSES.add(Short.class);
48 FINAL_IMMUTABLE_CLASSES.add(Integer.class);
49 FINAL_IMMUTABLE_CLASSES.add(Long.class);
50 FINAL_IMMUTABLE_CLASSES.add(Float.class);
51 FINAL_IMMUTABLE_CLASSES.add(Double.class);
52 FINAL_IMMUTABLE_CLASSES.add(Character.class);
53 FINAL_IMMUTABLE_CLASSES.add(Boolean.class);
54 }
55
56
57
58
59
60
61 public final Object copy(final Object toCopy, PersistenceBroker broker)
62 {
63 return clone(toCopy, IdentityMapFactory.getIdentityMap(), new HashMap());
64 }
65
66
67
68
69 private static final class ClassMetadata
70 {
71 Constructor m_noArgConstructor;
72 Field[] m_declaredFields;
73 boolean m_noArgConstructorAccessible;
74 boolean m_fieldsAccessible;
75 boolean m_hasNoArgConstructor = true;
76 }
77
78 private static Object clone(final Object toCopy, final Map objMap, final Map metadataMap)
79 {
80
81
82
83
84 if (objMap.containsKey(toCopy)) return objMap.get(toCopy);
85 final Class objClass = toCopy.getClass();
86 final Object retval;
87 if (objClass.isArray())
88 {
89 retval = handleArray(toCopy, objMap, objClass, metadataMap);
90 }
91 else if (FINAL_IMMUTABLE_CLASSES.contains(objClass))
92 {
93 objMap.put(toCopy, toCopy);
94 retval = toCopy;
95 }
96 else
97 {
98 retval = handleObjectWithNoArgsConstructor(metadataMap, objClass, objMap, toCopy);
99 }
100 return retval;
101 }
102
103 private static Object handleObjectWithNoArgsConstructor(final Map metadataMap, final Class objClass, final Map objMap, final Object toCopy)
104 {
105 Object retval = null;
106 ClassMetadata metadata = (ClassMetadata) metadataMap.get(objClass);
107 if (metadata == null)
108 {
109 metadata = new ClassMetadata();
110 metadataMap.put(objClass, metadata);
111 }
112 Constructor noArg = metadata.m_noArgConstructor;
113 if (metadata.m_hasNoArgConstructor)
114 {
115 if (noArg == null)
116 {
117 try
118 {
119 noArg = objClass.getDeclaredConstructor(EMPTY_CLASS_ARRAY);
120 metadata.m_noArgConstructor = noArg;
121 }
122 catch (Exception e)
123 {
124 metadata.m_hasNoArgConstructor = false;
125
126 }
127 }
128 }
129 if (metadata.m_hasNoArgConstructor)
130 {
131 if (!metadata.m_noArgConstructorAccessible && (Modifier.PUBLIC & noArg.getModifiers()) == 0)
132 {
133 try
134 {
135 noArg.setAccessible(true);
136 }
137 catch (SecurityException e)
138 {
139 throw new ObjectCopyException("cannot access noArg constructor [" + noArg + "] of class [" + objClass.getName() + "]: " + e.toString(), e);
140 }
141 metadata.m_noArgConstructorAccessible = true;
142 }
143 try
144 {
145
146
147
148 retval = noArg.newInstance(EMPTY_OBJECT_ARRAY);
149 objMap.put(toCopy, retval);
150 }
151 catch (Exception e)
152 {
153 throw new ObjectCopyException("cannot instantiate class [" + objClass.getName() + "] using noArg constructor: " + e.toString(), e);
154 }
155 for (Class c = objClass; c != Object.class; c = c.getSuperclass())
156 {
157 copyClass(metadataMap, c, toCopy, retval, objMap);
158 }
159 }
160 else
161 {
162 retval = _serialize.copy(toCopy, null);
163 }
164 return retval;
165 }
166
167 private static void copyClass(final Map metadataMap, final Class c, final Object obj, final Object retval, final Map objMap)
168 {
169 ClassMetadata metadata;
170 metadata = (ClassMetadata) metadataMap.get(c);
171 if (metadata == null)
172 {
173 metadata = new ClassMetadata();
174 metadataMap.put(c, metadata);
175 }
176 Field[] declaredFields = metadata.m_declaredFields;
177 if (declaredFields == null)
178 {
179 declaredFields = c.getDeclaredFields();
180 metadata.m_declaredFields = declaredFields;
181 }
182 setFields(obj, retval, declaredFields, metadata.m_fieldsAccessible, objMap, metadataMap);
183 metadata.m_fieldsAccessible = true;
184 }
185
186 private static Object handleArray(final Object obj, final Map objMap, final Class objClass, final Map metadataMap)
187 {
188 final Object retval;
189 final int arrayLength = Array.getLength(obj);
190
191
192
193 if (arrayLength == 0)
194 {
195 objMap.put(obj, obj);
196 retval = obj;
197 }
198 else
199 {
200 final Class componentType = objClass.getComponentType();
201
202
203
204
205 retval = Array.newInstance(componentType, arrayLength);
206 objMap.put(obj, retval);
207 if (componentType.isPrimitive() || FINAL_IMMUTABLE_CLASSES.contains(componentType))
208 {
209 System.arraycopy(obj, 0, retval, 0, arrayLength);
210 }
211 else
212 {
213 for (int i = 0; i < arrayLength; ++i)
214 {
215
216
217
218 final Object slot = Array.get(obj, i);
219 if (slot != null)
220 {
221 final Object slotClone = clone(slot, objMap, metadataMap);
222 Array.set(retval, i, slotClone);
223 }
224 }
225 }
226 }
227 return retval;
228 }
229
230
231
232
233
234
235
236
237
238
239 private static void setFields(final Object from, final Object to,
240 final Field[] fields, final boolean accessible,
241 final Map objMap, final Map metadataMap)
242 {
243 for (int f = 0, fieldsLength = fields.length; f < fieldsLength; ++f)
244 {
245 final Field field = fields[f];
246 final int modifiers = field.getModifiers();
247 if ((Modifier.STATIC & modifiers) != 0) continue;
248 if ((Modifier.FINAL & modifiers) != 0)
249 throw new ObjectCopyException("cannot set final field [" + field.getName() + "] of class [" + from.getClass().getName() + "]");
250 if (!accessible && ((Modifier.PUBLIC & modifiers) == 0))
251 {
252 try
253 {
254 field.setAccessible(true);
255 }
256 catch (SecurityException e)
257 {
258 throw new ObjectCopyException("cannot access field [" + field.getName() + "] of class [" + from.getClass().getName() + "]: " + e.toString(), e);
259 }
260 }
261 try
262 {
263 cloneAndSetFieldValue(field, from, to, objMap, metadataMap);
264 }
265 catch (Exception e)
266 {
267 throw new ObjectCopyException("cannot set field [" + field.getName() + "] of class [" + from.getClass().getName() + "]: " + e.toString(), e);
268 }
269 }
270 }
271
272 private static void cloneAndSetFieldValue(final Field field, final Object src, final Object dest, final Map objMap, final Map metadataMap) throws IllegalAccessException
273 {
274 Object value = field.get(src);
275 if (value == null)
276 {
277
278
279
280
281 field.set(dest, null);
282 }
283 else
284 {
285 final Class valueType = value.getClass();
286 if (!valueType.isPrimitive() && !FINAL_IMMUTABLE_CLASSES.contains(valueType))
287 {
288
289
290
291
292 value = clone(value, objMap, metadataMap);
293 }
294 field.set(dest, value);
295 }
296 }
297 }