001 package org.apache.ojb.otm.copy;
002
003 /* Copyright 2003-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.lang.reflect.Array;
019 import java.lang.reflect.Constructor;
020 import java.lang.reflect.Field;
021 import java.lang.reflect.Modifier;
022 import java.util.HashMap;
023 import java.util.HashSet;
024 import java.util.Map;
025 import java.util.Set;
026
027 import org.apache.ojb.broker.PersistenceBroker;
028 import org.apache.ojb.broker.util.IdentityMapFactory;
029
030 /**
031 * User: matthew.baird
032 * Date: Jul 7, 2003
033 * Time: 3:05:22 PM
034 */
035 public final class ReflectiveObjectCopyStrategy implements ObjectCopyStrategy
036 {
037 private static final Set FINAL_IMMUTABLE_CLASSES;
038 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
039 private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
040 private static final SerializeObjectCopyStrategy _serialize = new SerializeObjectCopyStrategy();
041
042 static
043 {
044 FINAL_IMMUTABLE_CLASSES = new HashSet(17);
045 FINAL_IMMUTABLE_CLASSES.add(String.class);
046 FINAL_IMMUTABLE_CLASSES.add(Byte.class);
047 FINAL_IMMUTABLE_CLASSES.add(Short.class);
048 FINAL_IMMUTABLE_CLASSES.add(Integer.class);
049 FINAL_IMMUTABLE_CLASSES.add(Long.class);
050 FINAL_IMMUTABLE_CLASSES.add(Float.class);
051 FINAL_IMMUTABLE_CLASSES.add(Double.class);
052 FINAL_IMMUTABLE_CLASSES.add(Character.class);
053 FINAL_IMMUTABLE_CLASSES.add(Boolean.class);
054 }
055
056 /**
057 * makes a deep clone of the object, using reflection.
058 * @param toCopy the object you want to copy
059 * @return
060 */
061 public final Object copy(final Object toCopy, PersistenceBroker broker)
062 {
063 return clone(toCopy, IdentityMapFactory.getIdentityMap(), new HashMap());
064 }
065
066 /*
067 * class used to cache class metadata info
068 */
069 private static final class ClassMetadata
070 {
071 Constructor m_noArgConstructor;
072 Field[] m_declaredFields;
073 boolean m_noArgConstructorAccessible;
074 boolean m_fieldsAccessible;
075 boolean m_hasNoArgConstructor = true;
076 }
077
078 private static Object clone(final Object toCopy, final Map objMap, final Map metadataMap)
079 {
080 /**
081 * first, check to make sure we aren't recursing to some object that we've already copied.
082 * if the toCopy is in the objMap, just return it.
083 */
084 if (objMap.containsKey(toCopy)) return objMap.get(toCopy);
085 final Class objClass = toCopy.getClass();
086 final Object retval;
087 if (objClass.isArray())
088 {
089 retval = handleArray(toCopy, objMap, objClass, metadataMap);
090 }
091 else if (FINAL_IMMUTABLE_CLASSES.contains(objClass))
092 {
093 objMap.put(toCopy, toCopy);
094 retval = toCopy;
095 }
096 else
097 {
098 retval = handleObjectWithNoArgsConstructor(metadataMap, objClass, objMap, toCopy);
099 }
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 // 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 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 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 * immutable
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 * 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 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 * 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 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 * 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 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 }