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    }