View Javadoc

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;
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   * 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 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  	 * 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 copy(final Object toCopy, PersistenceBroker broker)
62  	{
63  		return clone(toCopy, IdentityMapFactory.getIdentityMap(), new HashMap());
64  	}
65  
66  	/*
67  	 * class used to cache class metadata info
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  		 * 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 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 	//				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 }