KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > ojb > otm > copy > ReflectiveObjectCopyStrategy


1 package org.apache.ojb.otm.copy;
2
3 /* Copyright 2003-2005 The Apache Software Foundation
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 import java.lang.reflect.Array JavaDoc;
19 import java.lang.reflect.Constructor JavaDoc;
20 import java.lang.reflect.Field JavaDoc;
21 import java.lang.reflect.Modifier JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.Set JavaDoc;
26
27 import org.apache.ojb.broker.PersistenceBroker;
28 import org.apache.ojb.broker.util.IdentityMapFactory;
29
30 /**
31  * User: matthew.baird
32  * Date: Jul 7, 2003
33  * Time: 3:05:22 PM
34  */

35 public final class ReflectiveObjectCopyStrategy implements ObjectCopyStrategy
36 {
37     private static final Set JavaDoc FINAL_IMMUTABLE_CLASSES;
38     private static final Object JavaDoc[] EMPTY_OBJECT_ARRAY = new Object JavaDoc[0];
39     private static final Class JavaDoc[] EMPTY_CLASS_ARRAY = new Class JavaDoc[0];
40     private static final SerializeObjectCopyStrategy _serialize = new SerializeObjectCopyStrategy();
41
42     static
43     {
44         FINAL_IMMUTABLE_CLASSES = new HashSet JavaDoc(17);
45         FINAL_IMMUTABLE_CLASSES.add(String JavaDoc.class);
46         FINAL_IMMUTABLE_CLASSES.add(Byte JavaDoc.class);
47         FINAL_IMMUTABLE_CLASSES.add(Short JavaDoc.class);
48         FINAL_IMMUTABLE_CLASSES.add(Integer JavaDoc.class);
49         FINAL_IMMUTABLE_CLASSES.add(Long JavaDoc.class);
50         FINAL_IMMUTABLE_CLASSES.add(Float JavaDoc.class);
51         FINAL_IMMUTABLE_CLASSES.add(Double JavaDoc.class);
52         FINAL_IMMUTABLE_CLASSES.add(Character JavaDoc.class);
53         FINAL_IMMUTABLE_CLASSES.add(Boolean JavaDoc.class);
54     }
55
56     /**
57      * makes a deep clone of the object, using reflection.
58      * @param toCopy the object you want to copy
59      * @return
60      */

61     public final Object JavaDoc copy(final Object JavaDoc toCopy, PersistenceBroker broker)
62     {
63         return clone(toCopy, IdentityMapFactory.getIdentityMap(), new HashMap JavaDoc());
64     }
65
66     /*
67      * class used to cache class metadata info
68      */

69     private static final class ClassMetadata
70     {
71         Constructor JavaDoc m_noArgConstructor;
72         Field JavaDoc[] m_declaredFields;
73         boolean m_noArgConstructorAccessible;
74         boolean m_fieldsAccessible;
75         boolean m_hasNoArgConstructor = true;
76     }
77
78     private static Object JavaDoc clone(final Object JavaDoc toCopy, final Map JavaDoc objMap, final Map JavaDoc metadataMap)
79     {
80         /**
81          * first, check to make sure we aren't recursing to some object that we've already copied.
82          * if the toCopy is in the objMap, just return it.
83          */

84         if (objMap.containsKey(toCopy)) return objMap.get(toCopy);
85         final Class JavaDoc objClass = toCopy.getClass();
86         final Object JavaDoc 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 JavaDoc handleObjectWithNoArgsConstructor(final Map JavaDoc metadataMap, final Class JavaDoc objClass, final Map JavaDoc objMap, final Object JavaDoc toCopy)
104     {
105         Object JavaDoc 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 JavaDoc 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 JavaDoc e)
123                 {
124                     metadata.m_hasNoArgConstructor = false;
125     // throw new ObjectCopyException("class [" + objClass.getName() + "] has no noArg constructor: " + e.toString(), e);
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 JavaDoc 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                  * create the return value via the default no argument constructor
147                  */

148                 retval = noArg.newInstance(EMPTY_OBJECT_ARRAY);
149                 objMap.put(toCopy, retval);
150             }
151             catch (Exception JavaDoc e)
152             {
153                 throw new ObjectCopyException("cannot instantiate class [" + objClass.getName() + "] using noArg constructor: " + e.toString(), e);
154             }
155             for (Class JavaDoc c = objClass; c != Object JavaDoc.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 JavaDoc metadataMap, final Class JavaDoc c, final Object JavaDoc obj, final Object JavaDoc retval, final Map JavaDoc 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 JavaDoc[] 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 JavaDoc handleArray(final Object JavaDoc obj, final Map JavaDoc objMap, final Class JavaDoc objClass, final Map JavaDoc metadataMap)
187     {
188         final Object JavaDoc retval;
189         final int arrayLength = Array.getLength(obj);
190         /**
191          * immutable
192          */

193         if (arrayLength == 0)
194         {
195             objMap.put(obj, obj);
196             retval = obj;
197         }
198         else
199         {
200             final Class JavaDoc componentType = objClass.getComponentType();
201             /**
202              * even though arrays implicitly have a public clone(), it
203              * cannot be invoked reflectively, so need to do copy construction
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                      * recursively clone each array slot:
217                      */

218                     final Object JavaDoc slot = Array.get(obj, i);
219                     if (slot != null)
220                     {
221                         final Object JavaDoc slotClone = clone(slot, objMap, metadataMap);
222                         Array.set(retval, i, slotClone);
223                     }
224                 }
225             }
226         }
227         return retval;
228     }
229
230     /**
231      * copy all fields from the "from" object to the "to" object.
232      *
233      * @param from source object
234      * @param to from's clone
235      * @param fields fields to be populated
236      * @param accessible 'true' if all 'fields' have been made accessible during
237      * traversal
238      */

239     private static void setFields(final Object JavaDoc from, final Object JavaDoc to,
240                                   final Field JavaDoc[] fields, final boolean accessible,
241                                   final Map JavaDoc objMap, final Map JavaDoc metadataMap)
242     {
243         for (int f = 0, fieldsLength = fields.length; f < fieldsLength; ++f)
244         {
245             final Field JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc field, final Object JavaDoc src, final Object JavaDoc dest, final Map JavaDoc objMap, final Map JavaDoc metadataMap) throws IllegalAccessException JavaDoc
273     {
274         Object JavaDoc value = field.get(src);
275         if (value == null)
276         {
277             /**
278              * null is a valid type, ie the object may initialize this field to a different value,
279              * so we must explicitely set all null fields.
280              */

281             field.set(dest, null);
282         }
283         else
284         {
285             final Class JavaDoc valueType = value.getClass();
286             if (!valueType.isPrimitive() && !FINAL_IMMUTABLE_CLASSES.contains(valueType))
287             {
288                 /**
289                  * recursively call clone on value as it could be an object reference, an array,
290                  * or some mutable type
291                  */

292                 value = clone(value, objMap, metadataMap);
293             }
294             field.set(dest, value);
295         }
296     }
297 }
298
Popular Tags