Coverage Report - org.dozer.MappingProcessor
 
Classes in this File Line Coverage Branch Coverage Complexity
MappingProcessor
96%
460/476
88%
329/372
6.711
 
 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;
 17  
 
 18  
 import org.apache.commons.lang3.StringUtils;
 19  
 import org.dozer.builder.BuilderUtil;
 20  
 import org.dozer.builder.DestBeanBuilderCreator;
 21  
 import org.dozer.cache.Cache;
 22  
 import org.dozer.cache.CacheKeyFactory;
 23  
 import org.dozer.cache.CacheManager;
 24  
 import org.dozer.cache.DozerCacheType;
 25  
 import org.dozer.classmap.*;
 26  
 import org.dozer.converters.DateFormatContainer;
 27  
 import org.dozer.converters.PrimitiveOrWrapperConverter;
 28  
 import org.dozer.event.DozerEvent;
 29  
 import org.dozer.event.DozerEventManager;
 30  
 import org.dozer.event.DozerEventType;
 31  
 import org.dozer.event.EventManager;
 32  
 import org.dozer.factory.BeanCreationDirective;
 33  
 import org.dozer.factory.DestBeanCreator;
 34  
 import org.dozer.fieldmap.*;
 35  
 import org.dozer.stats.StatisticType;
 36  
 import org.dozer.stats.StatisticsManager;
 37  
 import org.dozer.util.*;
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 
 41  
 import java.lang.reflect.Array;
 42  
 import java.lang.reflect.Modifier;
 43  
 import java.lang.reflect.InvocationTargetException;
 44  
 import java.util.*;
 45  
 import java.util.Map.Entry;
 46  
 
 47  
 import static org.dozer.util.DozerConstants.BASE_CLASS;
 48  
 import static org.dozer.util.DozerConstants.ITERATE;
 49  
 
 50  
 /**
 51  
  * Internal Mapping Engine. Not intended for direct use by Application code.
 52  
  * This class does most of the heavy lifting and is very recursive in nature.
 53  
  * <p/>
 54  
  * This class is not threadsafe and is instantiated for each new mapping request.
 55  
  *
 56  
  * @author garsombke.franz
 57  
  * @author sullins.ben
 58  
  * @author tierney.matt
 59  
  * @author dmitry.buzdin
 60  
  * @author johnsen.knut-erik
 61  
  */
 62  
 public class MappingProcessor implements Mapper {
 63  
 
 64  132059
   private final Logger log = LoggerFactory.getLogger(MappingProcessor.class);
 65  
 
 66  
   private final ClassMappings classMappings;
 67  
   private final Configuration globalConfiguration;
 68  
   private final List<CustomConverter> customConverterObjects;
 69  
   private final Map<String, CustomConverter> customConverterObjectsWithId;
 70  
   private final StatisticsManager statsMgr;
 71  
   private final EventManager eventMgr;
 72  
   private final CustomFieldMapper customFieldMapper;
 73  
 
 74  132059
   private final MappedFieldsTracker mappedFields = new MappedFieldsTracker();
 75  
 
 76  
   private final Cache converterByDestTypeCache;
 77  
   private final Cache superTypeCache;
 78  132059
   private final PrimitiveOrWrapperConverter primitiveConverter = new PrimitiveOrWrapperConverter();
 79  132059
   private final LogMsgFactory logMsgFactory = new LogMsgFactory();
 80  
 
 81  
   protected MappingProcessor(ClassMappings classMappings, Configuration globalConfiguration, CacheManager cacheMgr,
 82  
                              StatisticsManager statsMgr, List<CustomConverter> customConverterObjects,
 83  
                              DozerEventManager eventManager, CustomFieldMapper customFieldMapper,
 84  132059
                              Map<String, CustomConverter> customConverterObjectsWithId) {
 85  132059
     this.classMappings = classMappings;
 86  132059
     this.globalConfiguration = globalConfiguration;
 87  132059
     this.statsMgr = statsMgr;
 88  132059
     this.customConverterObjects = customConverterObjects;
 89  132059
     this.eventMgr = eventManager;
 90  132059
     this.customFieldMapper = customFieldMapper;
 91  132059
     this.converterByDestTypeCache = cacheMgr.getCache(DozerCacheType.CONVERTER_BY_DEST_TYPE.name());
 92  132059
     this.superTypeCache = cacheMgr.getCache(DozerCacheType.SUPER_TYPE_CHECK.name());
 93  132059
     this.customConverterObjectsWithId = customConverterObjectsWithId;
 94  132059
   }
 95  
 
 96  
   /* Mapper Interface Implementation */
 97  
 
 98  
   public <T> T map(final Object srcObj, final Class<T> destClass) {
 99  785
     return map(srcObj, destClass, null);
 100  
   }
 101  
 
 102  
   public <T> T map(final Object srcObj, final Class<T> destClass, final String mapId) {
 103  858
     MappingValidator.validateMappingRequest(srcObj, destClass);
 104  856
     return mapGeneral(srcObj, destClass, null, mapId);
 105  
   }
 106  
 
 107  
   public void map(final Object srcObj, final Object destObj) {
 108  131182
     map(srcObj, destObj, null);
 109  131181
   }
 110  
 
 111  
   public void map(final Object srcObj, final Object destObj, final String mapId) {
 112  131204
     MappingValidator.validateMappingRequest(srcObj, destObj);
 113  131203
     mapGeneral(srcObj, null, destObj, mapId);
 114  131203
   }
 115  
   /* End of Mapper Interface Implementation */
 116  
 
 117  
   /**
 118  
    * Single point of entry for atomic mapping operations
 119  
    *
 120  
    * @param srcObj    source object
 121  
    * @param destClass destination class
 122  
    * @param destObj   destination object
 123  
    * @param mapId     mapping identifier
 124  
    * @param <T>       destination object type
 125  
    * @return new or updated destination object
 126  
    */
 127  
   private <T> T mapGeneral(Object srcObj, final Class<T> destClass, final T destObj, final String mapId) {
 128  132059
     srcObj = MappingUtils.deProxy(srcObj);
 129  
 
 130  
     Class<T> destType;
 131  
     T result;
 132  132059
     if (destClass == null) {
 133  131203
       destType = (Class<T>) destObj.getClass();
 134  131203
       result = destObj;
 135  
     } else {
 136  856
       destType = destClass;
 137  856
       result = null;
 138  
     }
 139  
 
 140  132059
     ClassMap classMap = null;
 141  
     try {
 142  132059
       classMap = getClassMap(srcObj.getClass(), destType, mapId);
 143  
 
 144  132058
       eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_STARTED, classMap, null, srcObj, result, null));
 145  
 
 146  
       // TODO Check if any proxy issues are here
 147  
       // Check to see if custom converter has been specified for this mapping
 148  
       // combination. If so, just use it.
 149  132058
       Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcObj
 150  
           .getClass(), destType);
 151  
 
 152  
 
 153  132058
       if (destObj == null) {
 154  
         // If this is a nested MapperAware conversion this mapping can be already processed
 155  
         // but we can do this optimization only in case of no destObject, instead we must copy to the dest object
 156  855
         Object alreadyMappedValue = mappedFields.getMappedValue(srcObj, destType);
 157  855
         if (alreadyMappedValue != null) {
 158  1
           return (T) alreadyMappedValue;
 159  
         }
 160  
       }
 161  
 
 162  132057
       if (converterClass != null) {
 163  22
         return (T) mapUsingCustomConverter(converterClass, srcObj.getClass(), srcObj, destType, result, null, true);
 164  
       }
 165  
 
 166  132035
       BeanCreationDirective creationDirective =
 167  
               new BeanCreationDirective(srcObj, classMap.getSrcClassToMap(), classMap.getDestClassToMap(), destType,
 168  
               classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(), classMap.getDestClassCreateMethod());
 169  
 
 170  132035
       result = createByCreationDirectiveAndMap(creationDirective, classMap, srcObj, result, false, null);
 171  21
     } catch (Throwable e) {
 172  21
       MappingUtils.throwMappingException(e);
 173  132015
     }
 174  132015
     eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_FINISHED, classMap, null, srcObj, result, null));
 175  
 
 176  132015
     return result;
 177  
   }
 178  
 
 179  
   /**
 180  
    * Create builder or target object if needed and call
 181  
    * {@link MappingProcessor#mapToDestObject(org.dozer.classmap.ClassMap, Object, Object, boolean, String)} function with
 182  
    * arguments {@code classMap}, {@code srcObj}, {@code result}, {@code bypassSuperMappings}, {@code mapId}
 183  
    * @param creationDirective   directive for concrete mapping (based mostly on {@code classMap})
 184  
    * @param classMap            class map information for concrete class
 185  
    * @param srcObj              source object
 186  
    * @param result              target entity for mapping
 187  
    * @param bypassSuperMappings //TODO
 188  
    * @param mapId               mapping identifier
 189  
    * @return                    result or created target entity for mapping
 190  
    */
 191  
   private <T> T createByCreationDirectiveAndMap(BeanCreationDirective creationDirective, ClassMap classMap, Object srcObj, T result, boolean bypassSuperMappings, String mapId) {
 192  172840
     if (result == null) {
 193  41637
       BeanBuilder beanBuilder = DestBeanBuilderCreator.create(creationDirective);
 194  41637
       if (beanBuilder == null) {
 195  41637
         result = (T) DestBeanCreator.create(creationDirective);
 196  41633
         mapToDestObject(classMap, srcObj, result, bypassSuperMappings, mapId);
 197  
       } else {
 198  0
         mapToDestObject(classMap, srcObj, beanBuilder, bypassSuperMappings, mapId);
 199  0
         result = (T) beanBuilder.build();
 200  
       }
 201  41617
     } else {
 202  131203
       mapToDestObject(classMap, srcObj, result, bypassSuperMappings, mapId);
 203  
     }
 204  172820
     return result;
 205  
   }
 206  
 
 207  
   /**
 208  
    * This function used to map into created instance of destination class
 209  
    * @param classMap            object with mapping configuration
 210  
    * @param srcObj              source object
 211  
    * @param destObj             destination object
 212  
    * @param bypassSuperMappings //TODO
 213  
    * @param mapId               mapping identifier
 214  
    */
 215  
   private void mapToDestObject(ClassMap classMap, Object srcObj, Object destObj, boolean bypassSuperMappings, String mapId) {
 216  172938
     map(classMap, srcObj, destObj, bypassSuperMappings, new ArrayList<String>(), mapId);
 217  172922
   }
 218  
 
 219  
   private void map(ClassMap classMap, Object srcObj, Object destObj, boolean bypassSuperMappings, List<String> mappedParentFields, String mapId) {
 220  304367
     srcObj = MappingUtils.deProxy(srcObj);
 221  
 
 222  
     // 1596766 - Recursive object mapping issue. Prevent recursive mapping
 223  
     // infinite loop. Keep a record of mapped fields
 224  
     // by storing the id of the sourceObj and the destObj to be mapped. This can
 225  
     // be referred to later to avoid recursive mapping loops
 226  304367
     mappedFields.put(srcObj, destObj);
 227  
 
 228  
     // If class map hasn't already been determined, find the appropriate one for
 229  
     // the src/dest object combination
 230  304367
     if (classMap == null) {
 231  102
       classMap = getClassMap(srcObj.getClass(), destObj.getClass(), mapId);
 232  
     }
 233  
 
 234  304367
     Class<?> srcClass = srcObj.getClass();
 235  304367
     Class<?> destClass = destObj.getClass();
 236  
 
 237  
     // Check to see if custom converter has been specified for this mapping
 238  
     // combination. If so, just use it.
 239  304367
     Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcClass,
 240  
         destClass);
 241  304367
     if (converterClass != null) {
 242  0
       mapUsingCustomConverter(converterClass, srcClass, srcObj, destClass, destObj, null, true);
 243  0
       return;
 244  
     }
 245  
 
 246  
     // Now check for super class mappings.  Process super class mappings first.
 247  304367
     if (!bypassSuperMappings) {
 248  172938
       Collection<ClassMap> superMappings = new ArrayList<ClassMap>();
 249  
 
 250  172938
       Collection<ClassMap> superClasses = checkForSuperTypeMapping(srcClass, destClass);
 251  
       //List<ClassMap> interfaceMappings = classMappings.findInterfaceMappings(srcClass, destClass);
 252  
 
 253  172938
       superMappings.addAll(superClasses);
 254  
       //superMappings.addAll(interfaceMappings);
 255  172938
       if (!superMappings.isEmpty()) {
 256  131373
         processSuperTypeMapping(superMappings, srcObj, destObj, mappedParentFields, mapId);
 257  
       }
 258  
     }
 259  
 
 260  
     // Perform mappings for each field. Iterate through Fields Maps for this class mapping
 261  304367
     for (FieldMap fieldMapping : classMap.getFieldMaps()) {
 262  
       //Bypass field if it has already been mapped as part of super class mappings.
 263  311877
       String key = MappingUtils.getMappedParentFieldKey(destObj, fieldMapping);
 264  311877
       if (mappedParentFields != null && mappedParentFields.contains(key)) {
 265  131461
         continue;
 266  
       }
 267  180416
       mapField(fieldMapping, srcObj, destObj);
 268  180400
     }
 269  304351
   }
 270  
 
 271  
   /**
 272  
    * Perform mapping of a field.
 273  
    * Uses {@link #mapFromFieldMap(Object, Object, Object, FieldMap)} to do the real work, unless
 274  
    * if iterate, where {@link #mapFromIterateMethodFieldMap(Object, Object, Object, FieldMap)} is used. 
 275  
    * 
 276  
    * @param fieldMapping Field mapping.
 277  
    * @param srcObj Source object.
 278  
    * @param destObj Destination object.
 279  
    */
 280  
   private void mapField(FieldMap fieldMapping, Object srcObj, Object destObj) {
 281  
 
 282  
     // The field has been explicitly excluded from mapping. So just return, as
 283  
     // no further processing is needed for this field
 284  180416
     if (fieldMapping instanceof ExcludeFieldMap) {
 285  299
       return;
 286  
     }
 287  
 
 288  180117
     Object srcFieldValue = null;
 289  
     try {
 290  
       // If a custom field mapper was specified, then invoke it. If not, or the
 291  
       // custom field mapper returns false(indicating the
 292  
       // field was not actually mapped by the custom field mapper), proceed as
 293  
       // normal(use Dozer to map the field)
 294  180117
       srcFieldValue = fieldMapping.getSrcFieldValue(srcObj);
 295  180117
       boolean fieldMapped = false;
 296  180117
       if (customFieldMapper != null) {
 297  12
         fieldMapped = customFieldMapper.mapField(srcObj, destObj, srcFieldValue, fieldMapping.getClassMap(), fieldMapping);
 298  
       }
 299  
 
 300  180117
       if (!fieldMapped) {
 301  180115
         if (fieldMapping.getDestFieldType() != null && ITERATE.equals(fieldMapping.getDestFieldType())) {
 302  
           // special logic for iterate feature
 303  37
           mapFromIterateMethodFieldMap(srcObj, destObj, srcFieldValue, fieldMapping);
 304  
         } else {
 305  
           // either deep field map or generic map. The is the most likely
 306  
           // scenario
 307  180078
           mapFromFieldMap(srcObj, destObj, srcFieldValue, fieldMapping);
 308  
         }
 309  
       }
 310  
 
 311  180101
       statsMgr.increment(StatisticType.FIELD_MAPPING_SUCCESS_COUNT);
 312  
 
 313  16
     } catch (Throwable e) {
 314  16
       log.error(logMsgFactory.createFieldMappingErrorMsg(srcObj, fieldMapping, srcFieldValue, destObj), e);
 315  16
       statsMgr.increment(StatisticType.FIELD_MAPPING_FAILURE_COUNT);
 316  
 
 317  
       // check error handling policy.
 318  16
       if (fieldMapping.isStopOnErrors()) {
 319  12
         MappingUtils.throwMappingException(e);
 320  
       } else {
 321  
         // check if any Exceptions should be allowed to be thrown
 322  4
         if (!fieldMapping.getClassMap().getAllowedExceptions().isEmpty() && e.getCause() instanceof InvocationTargetException) {
 323  4
           Throwable thrownType = ((InvocationTargetException) e.getCause()).getTargetException();
 324  4
           Class<? extends Throwable> exceptionClass = thrownType.getClass();
 325  4
           if (fieldMapping.getClassMap().getAllowedExceptions().contains(exceptionClass)) {
 326  4
             throw (RuntimeException) thrownType;
 327  
           }
 328  
         }
 329  0
         statsMgr.increment(StatisticType.FIELD_MAPPING_FAILURE_IGNORED_COUNT);
 330  
       }
 331  180101
     }
 332  180101
   }
 333  
 
 334  
   private void mapFromFieldMap(Object srcObj, Object destObj, Object srcFieldValue, FieldMap fieldMapping) {
 335  
     Class<?> destFieldType;
 336  180078
     if (fieldMapping instanceof CustomGetSetMethodFieldMap) {
 337  
       try {
 338  54
         destFieldType = fieldMapping.getDestFieldWriteMethodParameter(destObj.getClass());
 339  2
       } catch (Throwable e) {
 340  
         // try traditional way
 341  2
         destFieldType = fieldMapping.getDestFieldType(BuilderUtil.unwrapDestClassFromBuilder(destObj));
 342  54
       }
 343  
     } else {
 344  180024
       destFieldType = fieldMapping.getDestFieldType(BuilderUtil.unwrapDestClassFromBuilder(destObj));
 345  
     }
 346  
 
 347  
     // 1476780 - 12/2006 mht - Add support for field level custom converters
 348  
     // Use field level custom converter if one was specified. Otherwise, map or
 349  
     // recurse the object as normal
 350  
     // 1770440 - fdg - Using multiple instances of CustomConverter
 351  
     Object destFieldValue;
 352  180078
     if (!MappingUtils.isBlankOrNull(fieldMapping.getCustomConverterId())) {
 353  5
       if (customConverterObjectsWithId != null && customConverterObjectsWithId.containsKey(fieldMapping.getCustomConverterId())) {
 354  5
         Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMapping.getSrcFieldType(srcObj.getClass());
 355  5
         destFieldValue = mapUsingCustomConverterInstance(customConverterObjectsWithId.get(fieldMapping.getCustomConverterId()),
 356  
             srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMapping, false);
 357  5
       } else {
 358  0
         throw new MappingException("CustomConverter instance not found with id:" + fieldMapping.getCustomConverterId());
 359  
       }
 360  180073
     } else if (MappingUtils.isBlankOrNull(fieldMapping.getCustomConverter())) {
 361  180045
       destFieldValue = mapOrRecurseObject(srcObj, srcFieldValue, destFieldType, fieldMapping, destObj);
 362  
     } else {
 363  28
       Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMapping.getSrcFieldType(srcObj.getClass());
 364  28
       destFieldValue = mapUsingCustomConverter(MappingUtils.loadClass(fieldMapping.getCustomConverter()), srcFieldClass,
 365  
           srcFieldValue, destFieldType, destObj, fieldMapping, false);
 366  
     }
 367  
 
 368  180066
     writeDestinationValue(destObj, destFieldValue, fieldMapping, srcObj);
 369  
 
 370  180062
     if (log.isDebugEnabled()) {
 371  0
       log.debug(logMsgFactory.createFieldMappingSuccessMsg(srcObj.getClass(), destObj.getClass(), fieldMapping.getSrcFieldName(),
 372  
           fieldMapping.getDestFieldName(), srcFieldValue, destFieldValue, fieldMapping.getClassMap().getMapId()));
 373  
     }
 374  180062
   }
 375  
 
 376  
   private Object mapOrRecurseObject(Object srcObj, Object srcFieldValue, Class<?> destFieldType, FieldMap fieldMap, Object destObj) {
 377  224053
     Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMap.getSrcFieldType(srcObj.getClass());
 378  224053
     Class<?> converterClass = MappingUtils.determineCustomConverter(fieldMap, converterByDestTypeCache, fieldMap.getClassMap()
 379  
         .getCustomConverters(), srcFieldClass, destFieldType);
 380  
 
 381  
     // 1-2007 mht: Invoke custom converter even if the src value is null.
 382  
     // #1563795
 383  224053
     if (converterClass != null) {
 384  82
       return mapUsingCustomConverter(converterClass, srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMap, false);
 385  
     }
 386  
 
 387  223971
     if (srcFieldValue == null) {
 388  135928
       return null;
 389  
     }
 390  
 
 391  
 
 392  88043
     String srcFieldName = fieldMap.getSrcFieldName();
 393  88043
     String destFieldName = fieldMap.getDestFieldName();
 394  
 
 395  
     // 1596766 - Recursive object mapping issue. Prevent recursive mapping
 396  
     // infinite loop
 397  
     // In case of "this->this" mapping this rule should be omitted as processing is done on objects, which has been
 398  
     // just marked as mapped.
 399  88043
     if (!(DozerConstants.SELF_KEYWORD.equals(srcFieldName) && DozerConstants.SELF_KEYWORD.equals(destFieldName))) {
 400  88023
       Object alreadyMappedValue = mappedFields.getMappedValue(srcFieldValue, destFieldType);
 401  88023
       if (alreadyMappedValue != null) {
 402  55
         return alreadyMappedValue;
 403  
       }
 404  
     }
 405  
 
 406  87988
     if (fieldMap.isCopyByReference()) {
 407  
       // just get the src and return it, no transformation.
 408  131
       return srcFieldValue;
 409  
     }
 410  
 
 411  87857
     boolean isSrcFieldClassSupportedMap = MappingUtils.isSupportedMap(srcFieldClass);
 412  87857
     boolean isDestFieldTypeSupportedMap = MappingUtils.isSupportedMap(destFieldType);
 413  87857
     if (isSrcFieldClassSupportedMap && isDestFieldTypeSupportedMap) {
 414  34
       return mapMap(srcObj, (Map<?, ?>) srcFieldValue, fieldMap, destObj);
 415  
     }
 416  87823
     if (fieldMap instanceof MapFieldMap && destFieldType.equals(Object.class)) {
 417  
       // TODO: find better place for this logic. try to encapsulate in FieldMap?
 418  95
       destFieldType = fieldMap.getDestHintContainer() != null ? fieldMap.getDestHintContainer().getHint() : srcFieldClass;
 419  
     }
 420  
 
 421  87823
     if (primitiveConverter.accepts(srcFieldClass) || primitiveConverter.accepts(destFieldType)) {
 422  
       // Primitive or Wrapper conversion
 423  46257
       if (fieldMap.getDestHintContainer() != null) {
 424  133
         destFieldType = fieldMap.getDestHintContainer().getHint();
 425  
       }
 426  
 
 427  
       //#1841448 - if trim-strings=true, then use a trimmed src string value when converting to dest value
 428  46257
       Object convertSrcFieldValue = srcFieldValue;
 429  46257
       if (fieldMap.isTrimStrings() && srcFieldValue.getClass().equals(String.class)) {
 430  8
         convertSrcFieldValue = ((String) srcFieldValue).trim();
 431  
       }
 432  
 
 433  46257
       DateFormatContainer dfContainer = new DateFormatContainer(fieldMap.getDateFormat());
 434  
 
 435  46257
       if (fieldMap instanceof MapFieldMap && !primitiveConverter.accepts(destFieldType)) {
 436  
         // This handles a very special/rare use case(see indexMapping.xml + unit
 437  
         // test
 438  
         // testStringToIndexedSet_UsingMapSetMethod). If the destFieldType is a
 439  
         // custom object AND has a String param
 440  
         // constructor, we don't want to construct the custom object with the
 441  
         // src value because the map backed property
 442  
         // logic at lower layers handles setting the value on the custom object.
 443  
         // Without this special logic, the
 444  
         // destination map backed custom object would contain a value that is
 445  
         // the custom object dest type instead of the
 446  
         // desired src value.
 447  20
         return primitiveConverter.convert(convertSrcFieldValue, convertSrcFieldValue.getClass(), dfContainer);
 448  
       } else {
 449  46237
         return primitiveConverter.convert(convertSrcFieldValue, destFieldType, dfContainer);
 450  
       }
 451  
     }
 452  41566
     if (MappingUtils.isSupportedCollection(srcFieldClass) && (MappingUtils.isSupportedCollection(destFieldType))) {
 453  675
       return mapCollection(srcObj, srcFieldValue, fieldMap, destObj);
 454  
     }
 455  
 
 456  40891
     if (MappingUtils.isEnumType(srcFieldClass, destFieldType)) {
 457  14
       return mapEnum((Enum) srcFieldValue, (Class<Enum>) destFieldType);
 458  
     }
 459  
 
 460  40877
     if (fieldMap.getDestDeepIndexHintContainer() != null) {
 461  4
       destFieldType = fieldMap.getDestDeepIndexHintContainer().getHint();
 462  
     }
 463  
 
 464  
     // Default: Map from one custom data object to another custom data object
 465  40877
     return mapCustomObject(fieldMap, destObj, destFieldType, srcFieldValue);
 466  
   }
 467  
 
 468  
   private <T extends Enum<T>> T mapEnum(Enum<T> srcFieldValue, Class<T> destFieldType) {
 469  14
     String name = srcFieldValue.name();
 470  14
     return Enum.valueOf(destFieldType, name);
 471  
   }
 472  
 
 473  
   private Object mapCustomObject(FieldMap fieldMap, Object destObj, Class<?> destFieldType, Object srcFieldValue) {
 474  40877
     srcFieldValue = MappingUtils.deProxy(srcFieldValue);
 475  
 
 476  
     // Custom java bean. Need to make sure that the destination object is not
 477  
     // already instantiated.
 478  40877
     Object result = null;
 479  
     // in case of iterate feature new objects are created in any case
 480  40877
     if (!DozerConstants.ITERATE.equals(fieldMap.getDestFieldType())) {
 481  40845
       result = getExistingValue(fieldMap, destObj, destFieldType);
 482  
     }
 483  
 
 484  
     // if the field is not null than we don't want a new instance
 485  40877
     if (result == null) {
 486  
       // first check to see if this plain old field map has hints to the actual
 487  
       // type.
 488  40805
       if (fieldMap.getDestHintContainer() != null) {
 489  385
         Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());
 490  
         // if the destType is null this means that there was more than one hint.
 491  
         // we must have already set the destType then.
 492  385
         if (destHintType != null) {
 493  385
           destFieldType = destHintType;
 494  
         }
 495  
       }
 496  
       // Check to see if explicit map-id has been specified for the field
 497  
       // mapping
 498  40805
       String mapId = fieldMap.getMapId();
 499  
 
 500  
       Class<?> targetClass;
 501  40805
       if (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().getHint() != null) {
 502  289
         targetClass = fieldMap.getDestHintContainer().getHint();
 503  
       } else {
 504  40516
         targetClass = destFieldType;
 505  
       }
 506  40805
       ClassMap classMap = getClassMap(srcFieldValue.getClass(), targetClass, mapId);
 507  
 
 508  40805
       BeanCreationDirective creationDirective = new BeanCreationDirective(srcFieldValue, classMap.getSrcClassToMap(), classMap.getDestClassToMap(),
 509  
               destFieldType, classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(),
 510  
               fieldMap.getDestFieldCreateMethod() != null ? fieldMap.getDestFieldCreateMethod() : classMap.getDestClassCreateMethod());
 511  
 
 512  40805
       result = createByCreationDirectiveAndMap(creationDirective, classMap, srcFieldValue, null, false, fieldMap.getMapId());
 513  40805
     } else {
 514  72
       mapToDestObject(null, srcFieldValue, result, false, fieldMap.getMapId());
 515  
     }
 516  
 
 517  40877
     return result;
 518  
   }
 519  
 
 520  
   private Object mapCollection(Object srcObj, Object srcCollectionValue, FieldMap fieldMap, Object destObj) {
 521  
     // since we are mapping some sort of collection now is a good time to decide
 522  
     // if they provided hints
 523  
     // if no hint is provided then we will use generics to determine the mapping type
 524  675
     if (fieldMap.getDestHintContainer() == null) {
 525  464
       Class<?> genericType = fieldMap.getGenericType(BuilderUtil.unwrapDestClassFromBuilder(destObj));
 526  464
       if (genericType != null) {
 527  30
         HintContainer destHintContainer = new HintContainer();
 528  30
         destHintContainer.setHintName(genericType.getName());
 529  30
         FieldMap cloneFieldMap = (FieldMap) fieldMap.clone();
 530  30
         cloneFieldMap.setDestHintContainer(destHintContainer); // should affect only this time as fieldMap is cloned
 531  30
         fieldMap = cloneFieldMap;
 532  
       }
 533  
     }
 534  
 
 535  
     // if it is an iterator object turn it into a List
 536  675
     if (srcCollectionValue instanceof Iterator) {
 537  0
       srcCollectionValue = IteratorUtils.toList((Iterator<?>) srcCollectionValue);
 538  
     }
 539  
 
 540  675
     Class<?> destCollectionType = fieldMap.getDestFieldType(BuilderUtil.unwrapDestClassFromBuilder(destObj));
 541  675
     Class<?> srcFieldType = srcCollectionValue.getClass();
 542  675
     Object result = null;
 543  
 
 544  
     // if they use a standard Collection we have to assume it is a List...better
 545  
     // way to handle this?
 546  675
     if (destCollectionType.getName().equals(Collection.class.getName())) {
 547  6
       destCollectionType = List.class;
 548  
     }
 549  
     // Array to Array
 550  675
     if (CollectionUtils.isArray(srcFieldType) && (CollectionUtils.isArray(destCollectionType))) {
 551  120
       result = mapArrayToArray(srcObj, srcCollectionValue, fieldMap, destObj);
 552  
       // Array to List
 553  555
     } else if (CollectionUtils.isArray(srcFieldType) && (CollectionUtils.isList(destCollectionType))) {
 554  106
       result = mapArrayToList(srcObj, srcCollectionValue, fieldMap, destObj);
 555  
     }
 556  
     // List to Array
 557  449
     else if (CollectionUtils.isList(srcFieldType) && (CollectionUtils.isArray(destCollectionType))) {
 558  78
       result = mapListToArray(srcObj, (List<?>) srcCollectionValue, fieldMap, destObj);
 559  
     }
 560  
     // Set to Array
 561  371
     else if (CollectionUtils.isSet(srcFieldType) && CollectionUtils.isArray(destCollectionType)) {
 562  10
       result = mapSetToArray(srcObj, (Set<?>) srcCollectionValue, fieldMap, destObj);
 563  
     }
 564  
     // Array to Set
 565  361
     else if (CollectionUtils.isArray(srcFieldType) && CollectionUtils.isSet(destCollectionType)) {
 566  14
       result = addToSet(srcObj, fieldMap, Arrays.asList((Object[]) srcCollectionValue), destObj);
 567  
     }
 568  
     // Collection to Set
 569  347
     else if (CollectionUtils.isCollection(srcFieldType) && CollectionUtils.isSet(destCollectionType)) {
 570  54
       result = addToSet(srcObj, fieldMap, (Collection<?>) srcCollectionValue, destObj);
 571  
     }
 572  
     // List to Map value
 573  293
     else if (CollectionUtils.isCollection(srcFieldType) && MappingUtils.isSupportedMap(destCollectionType)) {
 574  6
       result = mapListToList(srcObj, (List<?>) srcCollectionValue, fieldMap, destObj);
 575  
     }
 576  
         // List to List
 577  
         // Set to List
 578  
         // Collection to List. Fix for 3378952, http://sourceforge.net/tracker/index.php?func=detail&aid=3378952&group_id=133517&atid=727368
 579  287
         else if (CollectionUtils.isCollection(srcFieldType) && CollectionUtils.isList(destCollectionType)) {
 580  287
                 result = mapListToList(srcObj, (Collection<?>) srcCollectionValue, fieldMap, destObj);
 581  
         }
 582  675
     return result;
 583  
   }
 584  
 
 585  
   private Object mapMap(Object srcObj, Map srcMapValue, FieldMap fieldMap, Object destObj) {
 586  
     Map result;
 587  34
     Map destinationMap = (Map) fieldMap.getDestValue(destObj);
 588  34
     if (destinationMap == null) {
 589  18
       result = DestBeanCreator.create(srcMapValue.getClass());
 590  
     } else {
 591  16
       result = destinationMap;
 592  16
       if (fieldMap.isRemoveOrphans()) {
 593  2
         result.clear();
 594  
       }
 595  
     }
 596  
 
 597  34
     for (Entry<?, Object> srcEntry : ((Map<?, Object>) srcMapValue).entrySet()) {
 598  52
       Object srcEntryValue = srcEntry.getValue();
 599  
 
 600  52
       if (srcEntryValue == null) { // overwrites with null in any case
 601  8
         result.put(srcEntry.getKey(), null);
 602  8
         continue;
 603  
       }
 604  
 
 605  44
       Object destEntryValue = mapOrRecurseObject(srcObj, srcEntryValue, srcEntryValue.getClass(), fieldMap, destObj);
 606  44
       Object obj = result.get(srcEntry.getKey());
 607  44
       if (obj != null && obj.equals(destEntryValue) && fieldMap.isNonCumulativeRelationship()) {
 608  0
         mapToDestObject(null, srcEntryValue, obj, false, null);
 609  
       } else {
 610  44
         result.put(srcEntry.getKey(), destEntryValue);
 611  
       }
 612  44
     }
 613  34
     return result;
 614  
   }
 615  
 
 616  
   private Object mapArrayToArray(Object srcObj, Object srcCollectionValue, FieldMap fieldMap, Object destObj) {
 617  120
     Class destEntryType = fieldMap.getDestFieldType(destObj.getClass()).getComponentType();
 618  120
     Class srcEntryType = srcCollectionValue.getClass().getComponentType();
 619  120
     int size = Array.getLength(srcCollectionValue);
 620  
     
 621  120
     CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
 622  120
     boolean isPrimitiveArray = CollectionUtils.isPrimitiveArray(srcCollectionValue.getClass());
 623  120
     boolean isFinal = Modifier.isFinal(srcEntryType.getModifiers());
 624  120
     boolean isCopyByReference = copyByReferences.contains(srcEntryType);
 625  
 
 626  120
     if (destEntryType.isAssignableFrom(srcEntryType) && isFinal && (isPrimitiveArray || isCopyByReference)) {
 627  45
       return addArrayContentCopy(fieldMap, size, srcCollectionValue, destObj, destEntryType);
 628  75
     } else if (isPrimitiveArray) {
 629  65
       return addToPrimitiveArray(srcObj, fieldMap, size, srcCollectionValue, destObj, destEntryType);
 630  
     } else {
 631  10
       List<?> list = Arrays.asList((Object[]) srcCollectionValue);
 632  
       List<?> returnList;
 633  10
       if (!destEntryType.getName().equals(BASE_CLASS)) {
 634  10
         returnList = addOrUpdateToList(srcObj, fieldMap, list, destObj, destEntryType);
 635  
       } else {
 636  0
         returnList = addOrUpdateToList(srcObj, fieldMap, list, destObj, null);
 637  
       }
 638  10
       return CollectionUtils.convertListToArray(returnList, destEntryType);
 639  
     }
 640  
   }
 641  
 
 642  
   private void mapFromIterateMethodFieldMap(Object srcObj, Object destObj, Object srcFieldValue, FieldMap fieldMapping) {
 643  
     // Iterate over the destFieldValue - iterating is fine unless we are mapping
 644  
     // in the other direction.
 645  
     // Verify that it is truly a collection if it is an iterator object turn it
 646  
     // into a List
 647  37
     if (srcFieldValue instanceof Iterator) {
 648  2
       srcFieldValue = IteratorUtils.toList((Iterator<?>) srcFieldValue);
 649  
     }
 650  37
     if (srcFieldValue != null) {
 651  70
       for (int i = 0; i < CollectionUtils.getLengthOfCollection(srcFieldValue); i++) {
 652  39
         final Object value = CollectionUtils.getValueFromCollection(srcFieldValue, i);
 653  
 
 654  
         // map this value
 655  39
         if (fieldMapping.getDestHintContainer() == null) {
 656  0
           MappingUtils.throwMappingException("<field type=\"iterate\"> must have a source or destination type hint");
 657  
         }
 658  
 
 659  39
         Class<?> destinationHint = fieldMapping.getDestHintType(value.getClass());
 660  
 
 661  39
         Object result = mapOrRecurseObject(srcObj, value, destinationHint, fieldMapping, destObj);
 662  
 
 663  39
         if (value != null) {
 664  39
           writeDestinationValue(destObj, result, fieldMapping, srcObj);
 665  
         }
 666  
       }
 667  
     }
 668  37
     if (log.isDebugEnabled()) {
 669  0
       log.debug(logMsgFactory.createFieldMappingSuccessMsg(srcObj.getClass(), destObj.getClass(), fieldMapping.getSrcFieldName(),
 670  
           fieldMapping.getDestFieldName(), srcFieldValue, null, fieldMapping.getClassMap().getMapId()));
 671  
     }
 672  37
   }
 673  
   
 674  
   private Object addArrayContentCopy(FieldMap fieldMap, int size, Object srcCollectionValue, Object destObj,
 675  
           Class destEntryType) {
 676  
       Object result;
 677  45
       Object field = fieldMap.getDestValue(destObj);
 678  45
       int arraySize = 0;
 679  45
       if (field == null) {
 680  41
           result = Array.newInstance(destEntryType, size);
 681  
       } else {
 682  4
           result = Array.newInstance(destEntryType, size + Array.getLength(field));
 683  4
           arraySize = Array.getLength(field);
 684  4
           System.arraycopy(field, 0, result, 0, arraySize);
 685  
       }
 686  45
       System.arraycopy(srcCollectionValue, 0, result, arraySize, size);
 687  45
       return result;
 688  
   }
 689  
 
 690  
   private Object addToPrimitiveArray(Object srcObj, FieldMap fieldMap, int size, Object srcCollectionValue, Object destObj,
 691  
                                      Class<?> destEntryType) {
 692  
 
 693  
     Object result;
 694  65
     Object field = fieldMap.getDestValue(destObj);
 695  65
     int arraySize = 0;
 696  65
     if (field == null) {
 697  51
       result = Array.newInstance(destEntryType, size);
 698  
     } else {
 699  14
       result = Array.newInstance(destEntryType, size + Array.getLength(field));
 700  14
       arraySize = Array.getLength(field);
 701  14
       System.arraycopy(field, 0, result, 0, arraySize);
 702  
     }
 703  
     // primitive arrays are ALWAYS cumulative
 704  354
     for (int i = 0; i < size; i++) {
 705  289
       CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
 706  
       Object toValue;
 707  289
       if (srcCollectionValue != null && copyByReferences.contains(srcCollectionValue.getClass())) {
 708  0
         toValue = srcCollectionValue;
 709  
       } else {
 710  289
         toValue = mapOrRecurseObject(srcObj, Array.get(srcCollectionValue, i), destEntryType, fieldMap, destObj);
 711  
       }
 712  289
       Array.set(result, arraySize, toValue);
 713  289
       arraySize++;
 714  
     }
 715  65
     return result;
 716  
   }
 717  
 
 718  
   private Object mapListToArray(Object srcObj, Collection<?> srcCollectionValue, FieldMap fieldMap, Object destObj) {
 719  88
     Class destEntryType = fieldMap.getDestFieldType(destObj.getClass()).getComponentType();
 720  
     List list;
 721  88
     if (!destEntryType.getName().equals(BASE_CLASS)) {
 722  72
       list = addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj, destEntryType);
 723  
     } else {
 724  16
       list = addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj);
 725  
     }
 726  88
     return CollectionUtils.convertListToArray(list, destEntryType);
 727  
   }
 728  
 
 729  
   private List<?> mapListToList(Object srcObj, Collection<?> srcCollectionValue, FieldMap fieldMap, Object destObj) {
 730  293
     return addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj);
 731  
   }
 732  
 
 733  
   private Set<?> addToSet(Object srcObj, FieldMap fieldMap, Collection<?> srcCollectionValue, Object destObj) {
 734  
     // create a list here so we can keep track of which elements we have mapped, and remove all others if removeOrphans = true
 735  68
     Set<Object> mappedElements = new HashSet<Object>();
 736  
 
 737  68
     LinkedHashSet<Object> result = new LinkedHashSet<Object>();
 738  
     // don't want to create the set if it already exists.
 739  68
     Object field = fieldMap.getDestValue(destObj);
 740  68
     if (field != null) {
 741  18
       result.addAll((Collection<?>) field);
 742  
     }
 743  
     Object destValue;
 744  
 
 745  68
     Class<?> destEntryType = null;
 746  68
     Class<?> prevDestEntryType = null;
 747  68
     for (Object srcValue : srcCollectionValue) {
 748  98
       if (destEntryType == null
 749  
               || (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().hasMoreThanOneHint())) {
 750  54
         destEntryType = determineCollectionItemType(fieldMap, destObj, srcValue, prevDestEntryType);
 751  
       }
 752  
 
 753  98
       CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
 754  98
       if (srcValue != null && copyByReferences.contains(srcValue.getClass())) {
 755  4
         destValue = srcValue;
 756  
       } else {
 757  94
         destValue = mapOrRecurseObject(srcObj, srcValue, destEntryType, fieldMap, destObj);
 758  
       }
 759  98
       prevDestEntryType = destEntryType;
 760  
 
 761  98
       if (RelationshipType.NON_CUMULATIVE.equals(fieldMap.getRelationshipType())
 762  
           && result.contains(destValue)) {
 763  6
         List<Object> resultAsList = new ArrayList<Object>(result);
 764  6
         int index = resultAsList.indexOf(destValue);
 765  
         // perform an update if complex type - can't map strings
 766  6
         Object obj = resultAsList.get(index);
 767  
         // make sure it is not a String
 768  6
         if (!obj.getClass().isAssignableFrom(String.class)) {
 769  6
           mapToDestObject(null, srcValue, obj, false, fieldMap.getMapId());
 770  6
           mappedElements.add(obj);
 771  
         }
 772  6
       } else {
 773  92
         if (destValue != null || fieldMap.isDestMapNull()) {
 774  91
           result.add(destValue);
 775  
         }
 776  92
         mappedElements.add(destValue);
 777  
       }
 778  98
     }
 779  
 
 780  
     // If remove orphans - we only want to keep the objects we've mapped from the src collection
 781  
     // so we'll clear result and replace all entries with the ones in mappedElements
 782  68
     if (fieldMap.isRemoveOrphans()) {
 783  7
       result.clear();
 784  7
       result.addAll(mappedElements);
 785  
     }
 786  
 
 787  68
     if (field == null) {
 788  50
       Class<? extends Set<?>> destSetType = (Class<? extends Set<?>>) fieldMap.getDestFieldType(destObj.getClass());
 789  50
       return CollectionUtils.createNewSet(destSetType, result);
 790  
     } else {
 791  
       // Bug #1822421 - Clear first so we don't end up with the removed orphans again
 792  18
       ((Set) field).clear();
 793  18
       ((Set) field).addAll(result);
 794  18
       return (Set<?>) field;
 795  
     }
 796  
   }
 797  
 
 798  
   private List<?> addOrUpdateToList(Object srcObj, FieldMap fieldMap, Collection<?> srcCollectionValue, Object destObj,
 799  
                                     Class<?> destEntryType) {
 800  
     // create a Set here so we can keep track of which elements we have mapped, and remove all others if removeOrphans = true
 801  497
     List<Object> mappedElements = new ArrayList<Object>();
 802  
     List result;
 803  
     // don't want to create the list if it already exists.
 804  
     // these maps are special cases which do not fall under what we are looking for
 805  497
     Object field = fieldMap.getDestValue(destObj);
 806  497
     result = prepareDestinationList(srcCollectionValue, field);
 807  
 
 808  
     Object destValue;
 809  497
     Class<?> prevDestEntryType = null;
 810  497
     for (Object srcValue : srcCollectionValue) {
 811  43548
       if (destEntryType == null
 812  
               || (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().hasMoreThanOneHint())) {
 813  349
         destEntryType = determineCollectionItemType(fieldMap, destObj, srcValue, prevDestEntryType);
 814  
       }
 815  
 
 816  43548
       CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
 817  43548
       if (srcValue != null && copyByReferences.contains(srcValue.getClass())) {
 818  6
         destValue = srcValue;
 819  
       } else {
 820  43542
         destValue = mapOrRecurseObject(srcObj, srcValue, destEntryType, fieldMap, destObj);
 821  
       }
 822  43548
       prevDestEntryType = destEntryType;
 823  
 
 824  43548
       if (RelationshipType.NON_CUMULATIVE.equals(fieldMap.getRelationshipType())
 825  
           && result.contains(destValue)) {
 826  232
         int index = result.indexOf(destValue);
 827  
         // perform an update if complex type - can't map strings
 828  232
         Object obj = result.get(index);
 829  
         // make sure it is not a String
 830  232
         if (obj != null && !obj.getClass().isAssignableFrom(String.class)) {
 831  24
           mapToDestObject(null, srcValue, obj, false, fieldMap.getMapId());
 832  24
           mappedElements.add(obj);
 833  
         }
 834  232
       } else {
 835  
         // respect null mappings
 836  43316
         if (destValue != null || fieldMap.isDestMapNull()) {
 837  43313
           result.add(destValue);
 838  
         }
 839  43316
         mappedElements.add(destValue);
 840  
       }
 841  43548
     }
 842  
 
 843  
     // If remove orphans - we only want to keep the objects we've mapped from the src collection
 844  497
     if (fieldMap.isRemoveOrphans()) {
 845  11
       removeOrphans(mappedElements, result);
 846  
     }
 847  
 
 848  497
     return result;
 849  
   }
 850  
 
 851  
   private Class<?> determineCollectionItemType(FieldMap fieldMap, Object destObj, Object srcValue, Class<?> prevDestEntryType) {
 852  403
     if (srcValue == null && fieldMap.getDestHintType(destObj.getClass()) != null) {
 853  
       // try to get a possible configured dest hint for the dest obj
 854  4
       return fieldMap.getDestHintType(destObj.getClass());
 855  399
     } else if (srcValue == null && prevDestEntryType != null) {
 856  
       // if we already evaluated the dest type, use it
 857  0
       return prevDestEntryType;
 858  399
     } else if (srcValue != null) {
 859  
       // if there's no dest hint for the dest obj, take the src hint
 860  399
       return fieldMap.getDestHintType(srcValue.getClass());
 861  
     }
 862  0
     throw new MappingException("Unable to determine type for value '" + srcValue + "'. Use hints or generic collections.");
 863  
   }
 864  
 
 865  
   static void removeOrphans(Collection<?> mappedElements, List<Object> result) {
 866  14
     for (Iterator<?> iterator = result.iterator(); iterator.hasNext();) {
 867  31
       Object object = iterator.next();
 868  31
       if (!mappedElements.contains(object)) {
 869  11
         iterator.remove();
 870  
       }
 871  31
     }
 872  14
     for (Object object : mappedElements) {
 873  22
       if (!result.contains(object)) {
 874  2
         result.add(object);
 875  
       }
 876  22
     }
 877  14
   }
 878  
 
 879  
   static List<?> prepareDestinationList(Collection<?> srcCollectionValue, Object field) {
 880  502
     if (field == null) {
 881  406
       return new ArrayList<Object>(srcCollectionValue.size());
 882  
     } else {
 883  96
       if (CollectionUtils.isList(field.getClass())) {
 884  74
         return (List<?>) field;
 885  22
       } else if (CollectionUtils.isArray(field.getClass())) {
 886  17
         return new ArrayList<Object>(Arrays.asList((Object[]) field));
 887  
       } else { // assume it is neither - safest way is to create new List
 888  5
         return new ArrayList<Object>(srcCollectionValue.size());
 889  
       }
 890  
     }
 891  
   }
 892  
 
 893  
   private List<?> addOrUpdateToList(Object srcObj, FieldMap fieldMap, Collection<?> srcCollectionValue, Object destObj) {
 894  309
     return addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj, null);
 895  
   }
 896  
 
 897  
   private Object mapSetToArray(Object srcObj, Collection<?> srcCollectionValue, FieldMap fieldMap, Object destObj) {
 898  10
     return mapListToArray(srcObj, srcCollectionValue, fieldMap, destObj);
 899  
   }
 900  
 
 901  
   private List<?> mapArrayToList(Object srcObj, Object srcCollectionValue, FieldMap fieldMap, Object destObj) {
 902  
     Class<?> destEntryType;
 903  106
     if (fieldMap.getDestHintContainer() != null) {
 904  44
       destEntryType = fieldMap.getDestHintContainer().getHint();
 905  
     } else {
 906  62
       destEntryType = srcCollectionValue.getClass().getComponentType();
 907  
     }
 908  
     List<?> srcValueList;
 909  106
     if (CollectionUtils.isPrimitiveArray(srcCollectionValue.getClass())) {
 910  6
       srcValueList = CollectionUtils.convertPrimitiveArrayToList(srcCollectionValue);
 911  
     } else {
 912  100
       srcValueList = Arrays.asList((Object[]) srcCollectionValue);
 913  
     }
 914  106
     return addOrUpdateToList(srcObj, fieldMap, srcValueList, destObj, destEntryType);
 915  
   }
 916  
 
 917  
   private void writeDestinationValue(Object destObj, Object destFieldValue, FieldMap fieldMap, Object srcObj) {
 918  180105
     boolean bypass = false;
 919  
     // don't map null to dest field if map-null="false"
 920  180105
     if (destFieldValue == null && !fieldMap.isDestMapNull()) {
 921  80
       bypass = true;
 922  
     }
 923  
 
 924  
     // don't map "" to dest field if map-empty-string="false"
 925  180105
     if (destFieldValue != null && !fieldMap.isDestMapEmptyString() && destFieldValue.getClass().equals(String.class)
 926  
         && StringUtils.isEmpty((String) destFieldValue)) {
 927  10
       bypass = true;
 928  
     }
 929  
 
 930  
     // trim string value if trim-strings="true"
 931  180105
     if (destFieldValue != null && fieldMap.isTrimStrings() && destFieldValue.getClass().equals(String.class)) {
 932  6
       destFieldValue = ((String) destFieldValue).trim();
 933  
     }
 934  
 
 935  180105
     if (!bypass) {
 936  180015
       eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_PRE_WRITING_DEST_VALUE, fieldMap.getClassMap(), fieldMap, srcObj,
 937  
           destObj, destFieldValue));
 938  
 
 939  180015
       fieldMap.writeDestValue(destObj, destFieldValue);
 940  
 
 941  180011
       eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_POST_WRITING_DEST_VALUE, fieldMap.getClassMap(), fieldMap, srcObj,
 942  
           destObj, destFieldValue));
 943  
     }
 944  180101
   }
 945  
 
 946  
   private Object mapUsingCustomConverterInstance(CustomConverter converterInstance, Class<?> srcFieldClass, Object srcFieldValue,
 947  
                                                  Class<?> destFieldClass, Object existingDestFieldValue, FieldMap fieldMap, boolean topLevel) {
 948  
 
 949  
     //1792048 - If map-null = "false" and src value is null, then don't even invoke custom converter
 950  137
     if (srcFieldValue == null && !fieldMap.isDestMapNull()) {
 951  2
       return null;
 952  
     }
 953  
 
 954  135
     long start = System.currentTimeMillis();
 955  
 
 956  135
     if (converterInstance instanceof MapperAware) {
 957  9
       ((MapperAware) converterInstance).setMapper(this);
 958  
     }
 959  
 
 960  
     // TODO Remove code duplication
 961  
     Object result;
 962  135
     if (converterInstance instanceof ConfigurableCustomConverter) {
 963  32
       ConfigurableCustomConverter theConverter = (ConfigurableCustomConverter) converterInstance;
 964  
 
 965  
       // Converter could be not configured for this particular case
 966  32
       if (fieldMap != null) {
 967  20
         String param = fieldMap.getCustomConverterParam();
 968  20
         theConverter.setParameter(param);
 969  
       }
 970  
 
 971  
       // if this is a top level mapping the destObj is the highest level
 972  
       // mapping...not a recursive mapping
 973  32
       if (topLevel) {
 974  12
         result = theConverter.convert(existingDestFieldValue, srcFieldValue, destFieldClass, srcFieldClass);
 975  
       } else {
 976  20
         Object existingValue = getExistingValue(fieldMap, existingDestFieldValue, destFieldClass);
 977  20
         result = theConverter.convert(existingValue, srcFieldValue, destFieldClass, srcFieldClass);
 978  
       }
 979  32
     } else {
 980  
       // if this is a top level mapping the destObj is the highest level
 981  
       // mapping...not a recursive mapping
 982  103
       if (topLevel) {
 983  10
         result = converterInstance.convert(existingDestFieldValue, srcFieldValue, destFieldClass, srcFieldClass);
 984  
       } else {
 985  93
         Object existingValue = getExistingValue(fieldMap, existingDestFieldValue, destFieldClass);
 986  93
         result = converterInstance.convert(existingValue, srcFieldValue, destFieldClass, srcFieldClass);
 987  
       }
 988  
     }
 989  
 
 990  135
     long stop = System.currentTimeMillis();
 991  135
     statsMgr.increment(StatisticType.CUSTOM_CONVERTER_SUCCESS_COUNT);
 992  135
     statsMgr.increment(StatisticType.CUSTOM_CONVERTER_TIME, stop - start);
 993  
 
 994  135
     return result;
 995  
   }
 996  
 
 997  
   // TODO: possibly extract this to a separate class
 998  
 
 999  
   private Object mapUsingCustomConverter(Class<?> customConverterClass, Class<?> srcFieldClass, Object srcFieldValue,
 1000  
                                          Class<?> destFieldClass, Object existingDestFieldValue, FieldMap fieldMap, boolean topLevel) {
 1001  132
     CustomConverter converterInstance = null;
 1002  
     // search our injected customconverters for a match
 1003  132
     if (customConverterObjects != null) {
 1004  132
       for (CustomConverter customConverterObject : customConverterObjects) {
 1005  2
         if (customConverterClass.isInstance(customConverterObject)) {
 1006  
           // we have a match
 1007  2
           converterInstance = customConverterObject;
 1008  
         }
 1009  2
       }
 1010  
     }
 1011  
     // if converter object instances were not injected, then create new instance
 1012  
     // of the converter for each conversion
 1013  
     // TODO : Should we really create it each time?
 1014  132
     if (converterInstance == null) {
 1015  130
       converterInstance = (CustomConverter) ReflectionUtils.newInstance(customConverterClass);
 1016  
     }
 1017  132
     return mapUsingCustomConverterInstance(converterInstance, srcFieldClass, srcFieldValue, destFieldClass, existingDestFieldValue,
 1018  
         fieldMap, topLevel);
 1019  
   }
 1020  
 
 1021  
   private Collection<ClassMap> checkForSuperTypeMapping(Class<?> srcClass, Class<?> destClass) {
 1022  
     // Check cache first
 1023  172938
     Object cacheKey = CacheKeyFactory.createKey(destClass, srcClass);
 1024  172938
     Collection<ClassMap> cachedResult = (Collection<ClassMap>) superTypeCache.get(cacheKey);
 1025  172938
     if (cachedResult != null) {
 1026  171684
       return cachedResult;
 1027  
     }
 1028  
 
 1029  
     // If no existing cache entry is found, determine super type mappings.
 1030  
     // Recursively walk the inheritance hierarchy.
 1031  1254
     List<ClassMap> superClasses = new ArrayList<ClassMap>();
 1032  
     // Need to call getRealSuperclass because proxied data objects will not return correct
 1033  
     // superclass when using basic reflection
 1034  
 
 1035  1254
     List<Class<?>> superSrcClasses = MappingUtils.getSuperClassesAndInterfaces(srcClass);
 1036  1254
     List<Class<?>> superDestClasses = MappingUtils.getSuperClassesAndInterfaces(destClass);
 1037  
 
 1038  
     // add the actual classes to check for mappings between the original and the opposite 
 1039  
     // super classes
 1040  1254
     superSrcClasses.add(0, srcClass);
 1041  1254
     superDestClasses.add(0, destClass);
 1042  
 
 1043  1254
     for (Class<?> superSrcClass : superSrcClasses) {
 1044  3727
       for (Class<?> superDestClass : superDestClasses) {
 1045  12528
         if (!(superSrcClass.equals(srcClass) && superDestClass.equals(destClass))) {
 1046  11274
           checkForClassMapping(superSrcClass, superClasses, superDestClass);
 1047  
         }
 1048  12528
       }
 1049  3727
     }
 1050  
 
 1051  1254
     Collections.reverse(superClasses); // Done so base classes are processed first
 1052  
 
 1053  1254
     superTypeCache.put(cacheKey, superClasses);
 1054  
 
 1055  1254
     return superClasses;
 1056  
   }
 1057  
 
 1058  
   private void checkForClassMapping(Class<?> srcClass, List<ClassMap> superClasses, Class<?> superDestClass) {
 1059  11274
     ClassMap srcClassMap = classMappings.find(srcClass, superDestClass);
 1060  11274
     if (srcClassMap != null) {
 1061  215
       superClasses.add(srcClassMap);
 1062  
     }
 1063  11274
   }
 1064  
 
 1065  
   private void processSuperTypeMapping(Collection<ClassMap> superClasses, Object srcObj, Object destObj, List<String> mappedParentFields, String mapId) {
 1066  131373
     for (ClassMap map : superClasses) {
 1067  131429
       map(map, srcObj, destObj, true, mappedParentFields ,mapId);
 1068  131429
       for (FieldMap fieldMapping : map.getFieldMaps()) {
 1069  131747
         String key = MappingUtils.getMappedParentFieldKey(destObj, fieldMapping);
 1070  131747
         mappedParentFields.add(key);
 1071  131747
       }
 1072  131429
     }
 1073  131373
   }
 1074  
 
 1075  
   private static Object getExistingValue(FieldMap fieldMap, Object destObj, Class<?> destFieldType) {
 1076  
     // verify that the dest obj is not null
 1077  40958
     if (destObj == null) {
 1078  0
       return null;
 1079  
     }
 1080  
     // call the getXX method to see if the field is already instantiated
 1081  40958
     Object result = fieldMap.getDestValue(destObj);
 1082  
 
 1083  
     // When we are recursing through a list we need to make sure that we are not
 1084  
     // in the list
 1085  
     // by checking the destFieldType
 1086  40958
     if (result != null) {
 1087  185
       if (CollectionUtils.isList(result.getClass()) || CollectionUtils.isArray(result.getClass())
 1088  
           || CollectionUtils.isSet(result.getClass()) || MappingUtils.isSupportedMap(result.getClass())) {
 1089  95
         if (!CollectionUtils.isList(destFieldType) && !CollectionUtils.isArray(destFieldType)
 1090  
             && !CollectionUtils.isSet(destFieldType) && !MappingUtils.isSupportedMap(destFieldType)) {
 1091  
           // this means the getXX field is a List but we are actually trying to
 1092  
           // map one of its elements
 1093  92
           result = null;
 1094  
         }
 1095  
       }
 1096  
     }
 1097  40958
     return result;
 1098  
   }
 1099  
 
 1100  
   private ClassMap getClassMap(Class<?> srcClass, Class<?> destClass, String mapId) {
 1101  172966
     ClassMap mapping = classMappings.find(srcClass, destClass, mapId);
 1102  
 
 1103  172965
     if (mapping == null) {
 1104  
       // If mapping not found in existing custom mapping collection, create
 1105  
       // default as an explicit mapping must not
 1106  
       // exist. The create default class map method will also add all default
 1107  
       // mappings that it can determine.
 1108  460
       mapping = ClassMapBuilder.createDefaultClassMap(globalConfiguration, srcClass, destClass);
 1109  460
       classMappings.addDefault(srcClass, destClass, mapping);
 1110  
     }
 1111  
 
 1112  172965
     return mapping;
 1113  
   }
 1114  
 
 1115  
 }