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
036: ObjectCopyStrategy {
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: FINAL_IMMUTABLE_CLASSES = new HashSet(17);
044: FINAL_IMMUTABLE_CLASSES.add(String.class);
045: FINAL_IMMUTABLE_CLASSES.add(Byte.class);
046: FINAL_IMMUTABLE_CLASSES.add(Short.class);
047: FINAL_IMMUTABLE_CLASSES.add(Integer.class);
048: FINAL_IMMUTABLE_CLASSES.add(Long.class);
049: FINAL_IMMUTABLE_CLASSES.add(Float.class);
050: FINAL_IMMUTABLE_CLASSES.add(Double.class);
051: FINAL_IMMUTABLE_CLASSES.add(Character.class);
052: FINAL_IMMUTABLE_CLASSES.add(Boolean.class);
053: }
054:
055: /**
056: * makes a deep clone of the object, using reflection.
057: * @param toCopy the object you want to copy
058: * @return
059: */
060: public final Object copy(final Object toCopy,
061: PersistenceBroker broker) {
062: return clone(toCopy, IdentityMapFactory.getIdentityMap(),
063: new HashMap());
064: }
065:
066: /*
067: * class used to cache class metadata info
068: */
069: private static final class ClassMetadata {
070: Constructor m_noArgConstructor;
071: Field[] m_declaredFields;
072: boolean m_noArgConstructorAccessible;
073: boolean m_fieldsAccessible;
074: boolean m_hasNoArgConstructor = true;
075: }
076:
077: private static Object clone(final Object toCopy, final Map objMap,
078: final Map metadataMap) {
079: /**
080: * first, check to make sure we aren't recursing to some object that we've already copied.
081: * if the toCopy is in the objMap, just return it.
082: */
083: if (objMap.containsKey(toCopy))
084: return objMap.get(toCopy);
085: final Class objClass = toCopy.getClass();
086: final Object retval;
087: if (objClass.isArray()) {
088: retval = handleArray(toCopy, objMap, objClass, metadataMap);
089: } else if (FINAL_IMMUTABLE_CLASSES.contains(objClass)) {
090: objMap.put(toCopy, toCopy);
091: retval = toCopy;
092: } else {
093: retval = handleObjectWithNoArgsConstructor(metadataMap,
094: objClass, objMap, toCopy);
095: }
096: return retval;
097: }
098:
099: private static Object handleObjectWithNoArgsConstructor(
100: final Map metadataMap, final Class objClass,
101: final Map objMap, final Object toCopy) {
102: Object retval = null;
103: ClassMetadata metadata = (ClassMetadata) metadataMap
104: .get(objClass);
105: if (metadata == null) {
106: metadata = new ClassMetadata();
107: metadataMap.put(objClass, metadata);
108: }
109: Constructor noArg = metadata.m_noArgConstructor;
110: if (metadata.m_hasNoArgConstructor) {
111: if (noArg == null) {
112: try {
113: noArg = objClass
114: .getDeclaredConstructor(EMPTY_CLASS_ARRAY);
115: metadata.m_noArgConstructor = noArg;
116: } catch (Exception e) {
117: metadata.m_hasNoArgConstructor = false;
118: // throw new ObjectCopyException("class [" + objClass.getName() + "] has no noArg constructor: " + e.toString(), e);
119: }
120: }
121: }
122: if (metadata.m_hasNoArgConstructor) {
123: if (!metadata.m_noArgConstructorAccessible
124: && (Modifier.PUBLIC & noArg.getModifiers()) == 0) {
125: try {
126: noArg.setAccessible(true);
127: } catch (SecurityException e) {
128: throw new ObjectCopyException(
129: "cannot access noArg constructor [" + noArg
130: + "] of class ["
131: + objClass.getName() + "]: "
132: + e.toString(), e);
133: }
134: metadata.m_noArgConstructorAccessible = true;
135: }
136: try {
137: /**
138: * create the return value via the default no argument constructor
139: */
140: retval = noArg.newInstance(EMPTY_OBJECT_ARRAY);
141: objMap.put(toCopy, retval);
142: } catch (Exception e) {
143: throw new ObjectCopyException(
144: "cannot instantiate class ["
145: + objClass.getName()
146: + "] using noArg constructor: "
147: + e.toString(), e);
148: }
149: for (Class c = objClass; c != Object.class; c = c
150: .getSuperclass()) {
151: copyClass(metadataMap, c, toCopy, retval, objMap);
152: }
153: } else {
154: retval = _serialize.copy(toCopy, null);
155: }
156: return retval;
157: }
158:
159: private static void copyClass(final Map metadataMap, final Class c,
160: final Object obj, final Object retval, final Map objMap) {
161: ClassMetadata metadata;
162: metadata = (ClassMetadata) metadataMap.get(c);
163: if (metadata == null) {
164: metadata = new ClassMetadata();
165: metadataMap.put(c, metadata);
166: }
167: Field[] declaredFields = metadata.m_declaredFields;
168: if (declaredFields == null) {
169: declaredFields = c.getDeclaredFields();
170: metadata.m_declaredFields = declaredFields;
171: }
172: setFields(obj, retval, declaredFields,
173: metadata.m_fieldsAccessible, objMap, metadataMap);
174: metadata.m_fieldsAccessible = true;
175: }
176:
177: private static Object handleArray(final Object obj,
178: final Map objMap, final Class objClass,
179: final Map metadataMap) {
180: final Object retval;
181: final int arrayLength = Array.getLength(obj);
182: /**
183: * immutable
184: */
185: if (arrayLength == 0) {
186: objMap.put(obj, obj);
187: retval = obj;
188: } else {
189: final Class componentType = objClass.getComponentType();
190: /**
191: * even though arrays implicitly have a public clone(), it
192: * cannot be invoked reflectively, so need to do copy construction
193: */
194: retval = Array.newInstance(componentType, arrayLength);
195: objMap.put(obj, retval);
196: if (componentType.isPrimitive()
197: || FINAL_IMMUTABLE_CLASSES.contains(componentType)) {
198: System.arraycopy(obj, 0, retval, 0, arrayLength);
199: } else {
200: for (int i = 0; i < arrayLength; ++i) {
201: /**
202: * recursively clone each array slot:
203: */
204: final Object slot = Array.get(obj, i);
205: if (slot != null) {
206: final Object slotClone = clone(slot, objMap,
207: metadataMap);
208: Array.set(retval, i, slotClone);
209: }
210: }
211: }
212: }
213: return retval;
214: }
215:
216: /**
217: * copy all fields from the "from" object to the "to" object.
218: *
219: * @param from source object
220: * @param to from's clone
221: * @param fields fields to be populated
222: * @param accessible 'true' if all 'fields' have been made accessible during
223: * traversal
224: */
225: private static void setFields(final Object from, final Object to,
226: final Field[] fields, final boolean accessible,
227: final Map objMap, final Map metadataMap) {
228: for (int f = 0, fieldsLength = fields.length; f < fieldsLength; ++f) {
229: final Field field = fields[f];
230: final int modifiers = field.getModifiers();
231: if ((Modifier.STATIC & modifiers) != 0)
232: continue;
233: if ((Modifier.FINAL & modifiers) != 0)
234: throw new ObjectCopyException(
235: "cannot set final field [" + field.getName()
236: + "] of class ["
237: + from.getClass().getName() + "]");
238: if (!accessible && ((Modifier.PUBLIC & modifiers) == 0)) {
239: try {
240: field.setAccessible(true);
241: } catch (SecurityException e) {
242: throw new ObjectCopyException(
243: "cannot access field [" + field.getName()
244: + "] of class ["
245: + from.getClass().getName() + "]: "
246: + e.toString(), e);
247: }
248: }
249: try {
250: cloneAndSetFieldValue(field, from, to, objMap,
251: metadataMap);
252: } catch (Exception e) {
253: throw new ObjectCopyException("cannot set field ["
254: + field.getName() + "] of class ["
255: + from.getClass().getName() + "]: "
256: + e.toString(), e);
257: }
258: }
259: }
260:
261: private static void cloneAndSetFieldValue(final Field field,
262: final Object src, final Object dest, final Map objMap,
263: final Map metadataMap) throws IllegalAccessException {
264: Object value = field.get(src);
265: if (value == null) {
266: /**
267: * null is a valid type, ie the object may initialize this field to a different value,
268: * so we must explicitely set all null fields.
269: */
270: field.set(dest, null);
271: } else {
272: final Class valueType = value.getClass();
273: if (!valueType.isPrimitive()
274: && !FINAL_IMMUTABLE_CLASSES.contains(valueType)) {
275: /**
276: * recursively call clone on value as it could be an object reference, an array,
277: * or some mutable type
278: */
279: value = clone(value, objMap, metadataMap);
280: }
281: field.set(dest, value);
282: }
283: }
284: }
|