Coverage Report - org.dozer.propertydescriptor.GetterSetterPropertyDescriptor
 
Classes in this File Line Coverage Branch Coverage Complexity
GetterSetterPropertyDescriptor
89%
141/158
86%
76/88
4.3
 
 1  
 /**
 2  
  * Copyright 2005-2013 Dozer Project
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.dozer.propertydescriptor;
 17  
 
 18  
 import org.dozer.MappingException;
 19  
 import org.dozer.factory.BeanCreationDirective;
 20  
 import org.dozer.factory.DestBeanCreator;
 21  
 import org.dozer.fieldmap.FieldMap;
 22  
 import org.dozer.fieldmap.HintContainer;
 23  
 import org.dozer.util.BridgedMethodFinder;
 24  
 import org.dozer.util.CollectionUtils;
 25  
 import org.dozer.util.MappingUtils;
 26  
 import org.dozer.util.ReflectionUtils;
 27  
 import org.dozer.util.TypeResolver;
 28  
 import org.slf4j.Logger;
 29  
 import org.slf4j.LoggerFactory;
 30  
 
 31  
 import java.beans.PropertyDescriptor;
 32  
 import java.lang.reflect.Array;
 33  
 import java.lang.reflect.Method;
 34  
 import java.util.Collection;
 35  
 
 36  
 
 37  
 /**
 38  
  * Internal class used to read and write values for fields that have a getter and setter method. This class encapsulates
 39  
  * underlying dozer specific logic such as index mapping and deep mapping for reading and writing field values. Only
 40  
  * intended for internal use.
 41  
  *
 42  
  * @author garsombke.franz
 43  
  * @author tierney.matt
 44  
  * @author dmitry.buzdin
 45  
  */
 46  
 public abstract class GetterSetterPropertyDescriptor extends AbstractPropertyDescriptor {
 47  
 
 48  59387
   private final Logger log = LoggerFactory.getLogger(GetterSetterPropertyDescriptor.class);
 49  
 
 50  
   private Class<?> propertyType;
 51  
 
 52  
   public GetterSetterPropertyDescriptor(Class<?> clazz, String fieldName, boolean isIndexed, int index,
 53  
                                         HintContainer srcDeepIndexHintContainer, HintContainer destDeepIndexHintContainer) {
 54  59387
     super(clazz, fieldName, isIndexed, index, srcDeepIndexHintContainer, destDeepIndexHintContainer);
 55  59387
   }
 56  
 
 57  
   protected abstract Method getWriteMethod() throws NoSuchMethodException;
 58  
 
 59  
   protected abstract Method getReadMethod() throws NoSuchMethodException;
 60  
 
 61  
   protected abstract String getSetMethodName() throws NoSuchMethodException;
 62  
 
 63  
   protected abstract boolean isCustomSetMethod();
 64  
 
 65  
   public Class<?> getWriteMethodPropertyType() {
 66  
     try {
 67  52
       return getWriteMethod().getParameterTypes()[0];
 68  0
     } catch (Exception e) {
 69  0
       MappingUtils.throwMappingException(e);
 70  0
       return null;
 71  
     }
 72  
   }
 73  
 
 74  
   public Class<?> getPropertyType() {
 75  429028
     if (propertyType == null) {
 76  56701
       propertyType = determinePropertyType();
 77  
     }
 78  429024
     return propertyType;
 79  
   }
 80  
 
 81  
   public Object getPropertyValue(Object bean) {
 82  
     Object result;
 83  269964
     if (MappingUtils.isDeepMapping(fieldName)) {
 84  411
       result = getDeepSrcFieldValue(bean);
 85  
     } else {
 86  269553
       result = invokeReadMethod(bean);
 87  269543
       if (isIndexed) {
 88  50
         result = MappingUtils.getIndexedValue(result, index);
 89  
       }
 90  
     }
 91  269942
     return result;
 92  
   }
 93  
 
 94  
   public void setPropertyValue(Object bean, Object value, FieldMap fieldMap) {
 95  179566
     if (MappingUtils.isDeepMapping(fieldName)) {
 96  131362
       writeDeepDestinationValue(bean, value, fieldMap);
 97  
     } else {
 98  48204
       if (!getPropertyType().isPrimitive() || value != null) {
 99  
         //First check if value is indexed. If it's null, then the new array will be created
 100  48140
         if (isIndexed) {
 101  32
           writeIndexedValue(bean, value);
 102  
         } else {
 103  
           // Check if dest value is already set and is equal to src value. If true, no need to rewrite the dest value
 104  
           try {
 105  48108
             if (getPropertyValue(bean) == value && !isIndexed) {
 106  3950
               return;
 107  
             }
 108  2
           } catch (Exception e) {
 109  
             // if we failed to read the value, assume we must write, and continue...
 110  44156
           }
 111  44158
           invokeWriteMethod(bean, value);
 112  
         }
 113  
       }
 114  
     }
 115  175612
   }
 116  
 
 117  
   private Object getDeepSrcFieldValue(Object srcObj) {
 118  
     // follow deep field hierarchy. If any values are null along the way, then return null
 119  411
     Object parentObj = srcObj;
 120  411
     Object hierarchyValue = parentObj;
 121  411
     DeepHierarchyElement[] hierarchy = getDeepFieldHierarchy(srcObj, srcDeepIndexHintContainer);
 122  399
     int size = hierarchy.length;
 123  1025
     for (int i = 0; i < size; i++) {
 124  784
       DeepHierarchyElement hierarchyElement = hierarchy[i];
 125  784
       PropertyDescriptor pd = hierarchyElement.getPropDescriptor();
 126  
       // If any fields in the deep hierarchy are indexed, get actual value within the collection at the specified index
 127  784
       if (hierarchyElement.getIndex() > -1) {
 128  32
         hierarchyValue = MappingUtils.getIndexedValue(ReflectionUtils.invoke(pd.getReadMethod(), hierarchyValue, null),
 129  
                 hierarchyElement.getIndex());
 130  
       } else {
 131  752
         hierarchyValue = ReflectionUtils.invoke(pd.getReadMethod(), parentObj, null);
 132  
       }
 133  784
       parentObj = hierarchyValue;
 134  784
       if (hierarchyValue == null) {
 135  158
         break;
 136  
       }
 137  
     }
 138  
 
 139  
     // If dest field is indexed, get actual value within the collection at the specified index
 140  399
     if (isIndexed) {
 141  20
       hierarchyValue = MappingUtils.getIndexedValue(hierarchyValue, index);
 142  
     }
 143  
 
 144  399
     return hierarchyValue;
 145  
   }
 146  
 
 147  
   protected void writeDeepDestinationValue(Object destObj, Object destFieldValue, FieldMap fieldMap) {
 148  
     // follow deep field hierarchy. If any values are null along the way, then create a new instance
 149  131362
     DeepHierarchyElement[] hierarchy = getDeepFieldHierarchy(destObj, fieldMap.getDestDeepIndexHintContainer());
 150  
     // first, iteratate through hierarchy and instantiate any objects that are null
 151  131362
     Object parentObj = destObj;
 152  131362
     int hierarchyLength = hierarchy.length - 1;
 153  131362
     int hintIndex = 0;
 154  262756
     for (int i = 0; i < hierarchyLength; i++) {
 155  131394
       DeepHierarchyElement hierarchyElement = hierarchy[i];
 156  131394
       PropertyDescriptor pd = hierarchyElement.getPropDescriptor();
 157  131394
       Object value = ReflectionUtils.invoke(pd.getReadMethod(), parentObj, null);
 158  
       Class<?> clazz;
 159  
       Class<?> collectionEntryType;
 160  131394
       if (value == null) {
 161  131244
         clazz = pd.getPropertyType();
 162  131244
         if (clazz.isInterface() && (i + 1) == hierarchyLength && fieldMap.getDestHintContainer() != null) {
 163  
           // before setting the property on the destination object we should check for a destination hint. need to know
 164  
           // that we are at the end of the line determine the property type
 165  6
           clazz = fieldMap.getDestHintContainer().getHint();
 166  
         }
 167  131244
         Object o = null;
 168  131244
         if (clazz.isArray()) {
 169  8
           o = MappingUtils.prepareIndexedCollection(clazz, null, DestBeanCreator.create(clazz.getComponentType()),
 170  
                   hierarchyElement.getIndex());
 171  131236
         } else if (Collection.class.isAssignableFrom(clazz)) {
 172  
 
 173  12
           Class<?> genericType = ReflectionUtils.determineGenericsType(parentObj.getClass(), pd);
 174  12
           if (genericType != null) {
 175  2
             collectionEntryType = genericType;
 176  
           } else {
 177  10
             collectionEntryType = fieldMap.getDestDeepIndexHintContainer().getHint(hintIndex);
 178  
             //hint index is used to handle multiple hints
 179  10
             hintIndex += 1;
 180  
           }
 181  
 
 182  12
           o = MappingUtils.prepareIndexedCollection(clazz, null, DestBeanCreator.create(collectionEntryType), hierarchyElement
 183  
                   .getIndex());
 184  12
         } else {
 185  
           try {
 186  131224
             o = DestBeanCreator.create(clazz);
 187  0
           } catch (Exception e) {
 188  
             //lets see if they have a factory we can try as a last ditch. If not...throw the exception:
 189  0
             if (fieldMap.getClassMap().getDestClassBeanFactory() != null) {
 190  0
               o = DestBeanCreator.create(new BeanCreationDirective(null, fieldMap.getClassMap().getSrcClassToMap(), clazz, clazz, fieldMap.getClassMap()
 191  
                       .getDestClassBeanFactory(), fieldMap.getClassMap().getDestClassBeanFactoryId(), null));
 192  
             } else {
 193  0
               MappingUtils.throwMappingException(e);
 194  
             }
 195  131224
           }
 196  
         }
 197  
 
 198  131244
         ReflectionUtils.invoke(pd.getWriteMethod(), parentObj, new Object[]{o});
 199  131244
         value = ReflectionUtils.invoke(pd.getReadMethod(), parentObj, null);
 200  
       }
 201  
 
 202  
       //Check to see if collection needs to be resized
 203  131394
       if (MappingUtils.isSupportedCollection(value.getClass())) {
 204  38
         int currentSize = CollectionUtils.getLengthOfCollection(value);
 205  38
         if (currentSize < hierarchyElement.getIndex() + 1) {
 206  6
           collectionEntryType = pd.getPropertyType().getComponentType();
 207  
 
 208  6
           if (collectionEntryType == null) {
 209  4
             collectionEntryType = ReflectionUtils.determineGenericsType(parentObj.getClass(), pd);
 210  
 
 211  
             // if the target list is a List that doesn't have type specified, we can
 212  
             // try to use the deep-index-hint.  If the list has more than 1 element, there is no
 213  
             // way to tell which class to use, so we'll just try the first one.
 214  4
             if (collectionEntryType == null) {
 215  2
               if (log.isWarnEnabled()) {
 216  2
                 log.warn(fieldName + " is in a Collection with an unspecified type.");
 217  
               }
 218  2
               if (destDeepIndexHintContainer != null && destDeepIndexHintContainer.getHints() != null
 219  
                       && destDeepIndexHintContainer.getHints().size() > 0) {
 220  2
                 collectionEntryType = destDeepIndexHintContainer.getHints().get(0);
 221  2
                 if (log.isWarnEnabled()) {
 222  2
                   log.warn("Using deep-index-hint to predict containing Collection type for field "
 223  
                           + fieldName + " to be " + collectionEntryType);
 224  
                 }
 225  
               }
 226  
             }
 227  
           }
 228  
 
 229  
 
 230  6
           value = MappingUtils.prepareIndexedCollection(pd.getPropertyType(), value, DestBeanCreator.create(collectionEntryType), hierarchyElement.getIndex());
 231  
           //value = MappingUtils.prepareIndexedCollection(pd.getPropertyType(), value, DestBeanCreator.create(collectionEntryType), hierarchyElement.getIndex());
 232  6
           ReflectionUtils.invoke(pd.getWriteMethod(), parentObj, new Object[]{value});
 233  
         }
 234  
       }
 235  
 
 236  131394
       if (value != null && value.getClass().isArray()) {
 237  14
         parentObj = Array.get(value, hierarchyElement.getIndex());
 238  131380
       } else if (value != null && Collection.class.isAssignableFrom(value.getClass())) {
 239  24
         parentObj = MappingUtils.getIndexedValue(value, hierarchyElement.getIndex());
 240  
       } else {
 241  131356
         parentObj = value;
 242  
       }
 243  
     }
 244  
     // second, set the very last field in the deep hierarchy
 245  131362
     PropertyDescriptor pd = hierarchy[hierarchy.length - 1].getPropDescriptor();
 246  
 
 247  
     Class<?> type;
 248  
     // For one-way mappings there could be no read method
 249  131362
     if (pd.getReadMethod() != null) {
 250  131360
       type = pd.getReadMethod().getReturnType();
 251  
     } else {
 252  2
       type = pd.getWriteMethod().getParameterTypes()[0];
 253  
     }
 254  
 
 255  131362
     if (!type.isPrimitive() || destFieldValue != null) {
 256  131358
       if (!isIndexed) {
 257  131350
         Method method = null;
 258  131350
         if (!isCustomSetMethod()) {
 259  131336
           method = pd.getWriteMethod();
 260  
         } else {
 261  
           try {
 262  14
             method = ReflectionUtils.findAMethod(parentObj.getClass(), getSetMethodName());
 263  0
           } catch (NoSuchMethodException e) {
 264  0
             MappingUtils.throwMappingException(e);
 265  14
           }
 266  
         }
 267  
 
 268  131350
         ReflectionUtils.invoke(method, parentObj, new Object[]{destFieldValue});
 269  131350
       } else {
 270  8
         writeIndexedValue(parentObj, destFieldValue);
 271  
       }
 272  
     }
 273  131362
   }
 274  
 
 275  
   protected Object invokeReadMethod(Object target) {
 276  269030
     Object result = null;
 277  
     try {
 278  269030
       result = ReflectionUtils.invoke(getReadMethod(), target, null);
 279  2
     } catch (NoSuchMethodException e) {
 280  2
       MappingUtils.throwMappingException(e);
 281  269020
     }
 282  269020
     return result;
 283  
   }
 284  
 
 285  
   protected void invokeWriteMethod(Object target, Object value) {
 286  
     try {
 287  44198
       ReflectionUtils.invoke(getWriteMethod(), target, new Object[]{value});
 288  0
     } catch (NoSuchMethodException e) {
 289  0
       MappingUtils.throwMappingException(e);
 290  44194
     }
 291  44194
   }
 292  
 
 293  
   private DeepHierarchyElement[] getDeepFieldHierarchy(Object obj, HintContainer deepIndexHintContainer) {
 294  131773
     return ReflectionUtils.getDeepFieldHierarchy(obj.getClass(), fieldName, deepIndexHintContainer);
 295  
   }
 296  
 
 297  
   private void writeIndexedValue(Object destObj, Object destFieldValue) {
 298  40
     Object existingValue = invokeReadMethod(destObj);
 299  40
     Object indexedValue = MappingUtils.prepareIndexedCollection(getPropertyType(), existingValue, destFieldValue, index);
 300  40
     invokeWriteMethod(destObj, indexedValue);
 301  40
   }
 302  
 
 303  
   private Class determinePropertyType() {
 304  56701
     Method readMethod = getBridgedReadMethod();
 305  56701
     Method writeMethod = getBridgedWriteMethod();
 306  
 
 307  56701
     Class returnType = null;
 308  
 
 309  
     try {
 310  56701
       returnType = TypeResolver.resolvePropertyType(clazz, readMethod, writeMethod);
 311  181
     } catch (Exception ignore) {
 312  56520
     }
 313  
 
 314  56701
     if (returnType != null) {
 315  55796
       return returnType;
 316  
     }
 317  
 
 318  905
     if (readMethod == null && writeMethod == null) {
 319  4
       throw new MappingException("No read or write method found for field (" + fieldName
 320  
               + ") in class (" + clazz + ")");
 321  
     }
 322  
 
 323  901
     if (readMethod == null) {
 324  0
       return determineByWriteMethod(writeMethod);
 325  
     } else {
 326  
       try {
 327  901
         return readMethod.getReturnType();
 328  0
       } catch (Exception e) {
 329  
         // let us try the set method - the field might have inacessible 'get' method
 330  0
         return determineByWriteMethod(writeMethod);
 331  
       }
 332  
     }
 333  
   }
 334  
 
 335  
   private Class determineByWriteMethod(Method writeMethod) {
 336  
     try {
 337  0
       return writeMethod.getParameterTypes()[0];
 338  0
     } catch (Exception e) {
 339  0
       throw new MappingException(e);
 340  
     }
 341  
   }
 342  
 
 343  
   private Method getBridgedReadMethod() {
 344  
     try {
 345  56701
       return BridgedMethodFinder.findMethod(getReadMethod(), clazz);
 346  199
     } catch (Exception ignore) {
 347  
     }
 348  199
     return null;
 349  
   }
 350  
 
 351  
   private Method getBridgedWriteMethod() {
 352  
     try {
 353  56701
       return BridgedMethodFinder.findMethod(getWriteMethod(), clazz);
 354  400
     } catch (Exception ignore) {
 355  
     }
 356  400
     return null;
 357  
   }
 358  
 
 359  
   public Class<?> genericType() {
 360  453
     Class<?> genericType = null;
 361  
     try {
 362  453
       Method method = getWriteMethod();
 363  451
       genericType = ReflectionUtils.determineGenericsType(clazz, method, false);
 364  2
     } catch (NoSuchMethodException e) {
 365  2
       log.warn("The destination object: {} does not have a write method for property : {}", e);
 366  451
     }
 367  
 
 368  453
     return genericType;
 369  
   }
 370  
 
 371  
 }