Coverage Report - org.apache.ojb.otm.copy.ReflectiveObjectCopyStrategy
 
Classes in this File Line Coverage Branch Coverage Complexity
ReflectiveObjectCopyStrategy
N/A
N/A
6.143
ReflectiveObjectCopyStrategy$1
N/A
N/A
6.143
ReflectiveObjectCopyStrategy$ClassMetadata
N/A
N/A
6.143
 
 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  
 }