Package org.dozer

Source Code of org.dozer.MappingProcessor

/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dozer;

import org.apache.commons.lang.StringUtils;
import org.dozer.cache.Cache;
import org.dozer.cache.CacheKeyFactory;
import org.dozer.cache.CacheManager;
import org.dozer.cache.DozerCacheType;
import org.dozer.classmap.ClassMap;
import org.dozer.classmap.ClassMapBuilder;
import org.dozer.classmap.ClassMappings;
import org.dozer.classmap.Configuration;
import org.dozer.classmap.CopyByReferenceContainer;
import org.dozer.classmap.RelationshipType;
import org.dozer.converters.DateFormatContainer;
import org.dozer.converters.PrimitiveOrWrapperConverter;
import org.dozer.event.DozerEvent;
import org.dozer.event.DozerEventManager;
import org.dozer.event.DozerEventType;
import org.dozer.event.EventManager;
import org.dozer.factory.BeanCreationDirective;
import org.dozer.factory.DestBeanCreator;
import org.dozer.fieldmap.CustomGetSetMethodFieldMap;
import org.dozer.fieldmap.ExcludeFieldMap;
import org.dozer.fieldmap.FieldMap;
import org.dozer.fieldmap.HintContainer;
import org.dozer.fieldmap.MapFieldMap;
import org.dozer.stats.StatisticType;
import org.dozer.stats.StatisticsManager;
import org.dozer.util.CollectionUtils;
import org.dozer.util.DozerConstants;
import org.dozer.util.IteratorUtils;
import org.dozer.util.LogMsgFactory;
import org.dozer.util.MappingUtils;
import org.dozer.util.MappingValidator;
import org.dozer.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import static org.dozer.util.DozerConstants.BASE_CLASS;
import static org.dozer.util.DozerConstants.ITERATE;

/**
* Internal Mapping Engine. Not intended for direct use by Application code.
* This class does most of the heavy lifting and is very recursive in nature.
* <p/>
* This class is not threadsafe and is instantiated for each new mapping request.
*
* @author garsombke.franz
* @author sullins.ben
* @author tierney.matt
* @author dmitry.buzdin
* @author johnsen.knut-erik
*/
public class MappingProcessor implements Mapper {

  private static final Logger log = LoggerFactory.getLogger(MappingProcessor.class);

  private final ClassMappings classMappings;
  private final Configuration globalConfiguration;
  private final List<CustomConverter> customConverterObjects;
  private final Map<String, CustomConverter> customConverterObjectsWithId;
  private final StatisticsManager statsMgr;
  private final EventManager eventMgr;
  private final CustomFieldMapper customFieldMapper;

  private final MappedFieldsTracker mappedFields = new MappedFieldsTracker();

  private final Cache converterByDestTypeCache;
  private final Cache superTypeCache;
  private final PrimitiveOrWrapperConverter primitiveConverter = new PrimitiveOrWrapperConverter();

  protected MappingProcessor(ClassMappings classMappings, Configuration globalConfiguration, CacheManager cacheMgr,
                             StatisticsManager statsMgr, List<CustomConverter> customConverterObjects,
                             DozerEventManager eventManager, CustomFieldMapper customFieldMapper,
                             Map<String, CustomConverter> customConverterObjectsWithId) {
    this.classMappings = classMappings;
    this.globalConfiguration = globalConfiguration;
    this.statsMgr = statsMgr;
    this.customConverterObjects = customConverterObjects;
    this.eventMgr = eventManager;
    this.customFieldMapper = customFieldMapper;
    this.converterByDestTypeCache = cacheMgr.getCache(DozerCacheType.CONVERTER_BY_DEST_TYPE.name());
    this.superTypeCache = cacheMgr.getCache(DozerCacheType.SUPER_TYPE_CHECK.name());
    this.customConverterObjectsWithId = customConverterObjectsWithId;
  }

  /* Mapper Interface Implementation */

  public <T> T map(final Object srcObj, final Class<T> destClass) {
    return map(srcObj, destClass, null);
  }

  public <T> T map(final Object srcObj, final Class<T> destClass, final String mapId) {
    MappingValidator.validateMappingRequest(srcObj, destClass);
    return map(srcObj, destClass, null, mapId);
  }

  public void map(final Object srcObj, final Object destObj) {
    map(srcObj, destObj, null);
  }

  public void map(final Object srcObj, final Object destObj, final String mapId) {
    MappingValidator.validateMappingRequest(srcObj, destObj);
    map(srcObj, null, destObj, mapId);
  }
  /* End of Mapper Interface Implementation */

  /**
   * Single point of entry for atomic mapping operations
   *
   * @param srcObj    source object
   * @param destClass destination class
   * @param destObj   destination object
   * @param mapId     mapping identifier
   * @param <T>       destination object type
   * @return new or updated destination object
   */
  private <T> T map(Object srcObj, final Class<T> destClass, final T destObj, final String mapId) {
    srcObj = MappingUtils.deProxy(srcObj);

    Class<T> destType;
    T result;
    if (destClass == null) {
      destType = (Class<T>) destObj.getClass();
      result = destObj;
    } else {
      destType = destClass;
      result = null;
    }

    ClassMap classMap = null;
    try {
      classMap = getClassMap(srcObj.getClass(), destType, mapId);

      eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_STARTED, classMap, null, srcObj, result, null));

      // TODO Check if any proxy issues are here
      // Check to see if custom converter has been specified for this mapping
      // combination. If so, just use it.
      Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcObj
          .getClass(), destType);

      if (converterClass != null) {
        return (T) mapUsingCustomConverter(converterClass, srcObj.getClass(), srcObj, destType, result, null, true);
      }

      if (result == null) {
        result = (T) DestBeanCreator.create(new BeanCreationDirective(srcObj, classMap.getSrcClassToMap(), classMap.getDestClassToMap(), destType, classMap
            .getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(), classMap.getDestClassCreateMethod()));
      }

      // If this is a nested MapperAware conversion this mapping can be already processed
      Object alreadyMappedValue = mappedFields.getMappedValue(srcObj, destClass);
      if (alreadyMappedValue != null) {
        return (T) alreadyMappedValue;
      }

      map(classMap, srcObj, result, false, null);
    } catch (Throwable e) {
      MappingUtils.throwMappingException(e);
    }
    eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_FINISHED, classMap, null, srcObj, result, null));

    return result;
  }

  private void map(ClassMap classMap, Object srcObj, Object destObj, boolean bypassSuperMappings, String mapId) {
    srcObj = MappingUtils.deProxy(srcObj);

    // 1596766 - Recursive object mapping issue. Prevent recursive mapping
    // infinite loop. Keep a record of mapped fields
    // by storing the id of the sourceObj and the destObj to be mapped. This can
    // be referred to later to avoid recursive mapping loops
    mappedFields.put(srcObj, destObj);

    // If class map hasnt already been determined, find the appropriate one for
    // the src/dest object combination
    if (classMap == null) {
      classMap = getClassMap(srcObj.getClass(), destObj.getClass(), mapId);
    }

    Class<?> srcClass = srcObj.getClass();
    Class<?> destClass = destObj.getClass();

    // Check to see if custom converter has been specified for this mapping
    // combination. If so, just use it.
    Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcClass,
        destClass);
    if (converterClass != null) {
      mapUsingCustomConverter(converterClass, srcClass, srcObj, destClass, destObj, null, true);
      return;
    }

    // Now check for super class mappings.  Process super class mappings first.
    List<String> mappedParentFields = null;
    if (!bypassSuperMappings) {
      Collection<ClassMap> superMappings = new ArrayList<ClassMap>();

      Collection<ClassMap> superClasses = checkForSuperTypeMapping(srcClass, destClass);
      //List<ClassMap> interfaceMappings = classMappings.findInterfaceMappings(srcClass, destClass);

      superMappings.addAll(superClasses);
      //superMappings.addAll(interfaceMappings);
      if (!superMappings.isEmpty()) {
        mappedParentFields = processSuperTypeMapping(superMappings, srcObj, destObj, mapId);
      }
    }

    // Perform mappings for each field. Iterate through Fields Maps for this class mapping
    for (FieldMap fieldMapping : classMap.getFieldMaps()) {
      //Bypass field if it has already been mapped as part of super class mappings.
      String key = MappingUtils.getMappedParentFieldKey(destObj, fieldMapping);
      if (mappedParentFields != null && mappedParentFields.contains(key)) {
        continue;
      }
      mapField(fieldMapping, srcObj, destObj);
    }
  }

  private void mapField(FieldMap fieldMapping, Object srcObj, Object destObj) {

    // The field has been explicitly excluded from mapping. So just return, as
    // no further processing is needed for this field
    if (fieldMapping instanceof ExcludeFieldMap) {
      return;
    }

    Object srcFieldValue = null;
    try {
      // If a custom field mapper was specified, then invoke it. If not, or the
      // custom field mapper returns false(indicating the
      // field was not actually mapped by the custom field mapper), proceed as
      // normal(use Dozer to map the field)
      srcFieldValue = fieldMapping.getSrcFieldValue(srcObj);
      boolean fieldMapped = false;
      if (customFieldMapper != null) {
        fieldMapped = customFieldMapper.mapField(srcObj, destObj, srcFieldValue, fieldMapping.getClassMap(), fieldMapping);
      }

      if (!fieldMapped) {
        if (fieldMapping.getDestFieldType() != null && ITERATE.equals(fieldMapping.getDestFieldType())) {
          // special logic for iterate feature
          mapFromIterateMethodFieldMap(srcObj, destObj, srcFieldValue, fieldMapping);
        } else {
          // either deep field map or generic map. The is the most likely
          // scenario
          mapFromFieldMap(srcObj, destObj, srcFieldValue, fieldMapping);
        }
      }

      statsMgr.increment(StatisticType.FIELD_MAPPING_SUCCESS_COUNT);

    } catch (Throwable e) {
      log.error(LogMsgFactory.createFieldMappingErrorMsg(srcObj, fieldMapping, srcFieldValue, destObj), e);
      statsMgr.increment(StatisticType.FIELD_MAPPING_FAILURE_COUNT);

      // check error handling policy.
      if (fieldMapping.isStopOnErrors()) {
        MappingUtils.throwMappingException(e);
      } else {
        // check if any Exceptions should be allowed to be thrown
        if (!fieldMapping.getClassMap().getAllowedExceptions().isEmpty() && e.getCause() instanceof InvocationTargetException) {
          Throwable thrownType = ((InvocationTargetException) e.getCause()).getTargetException();
          Class<? extends Throwable> exceptionClass = thrownType.getClass();
          if (fieldMapping.getClassMap().getAllowedExceptions().contains(exceptionClass)) {
            throw (RuntimeException) thrownType;
          }
        }
        statsMgr.increment(StatisticType.FIELD_MAPPING_FAILURE_IGNORED_COUNT);
      }
    }
  }

  private void mapFromFieldMap(Object srcObj, Object destObj, Object srcFieldValue, FieldMap fieldMapping) {
    Class<?> destFieldType;
    if (fieldMapping instanceof CustomGetSetMethodFieldMap) {
      try {
        destFieldType = fieldMapping.getDestFieldWriteMethod(destObj.getClass()).getParameterTypes()[0];
      } catch (Throwable e) {
        // try traditional way
        destFieldType = fieldMapping.getDestFieldType(destObj.getClass());
      }
    } else {
      destFieldType = fieldMapping.getDestFieldType(destObj.getClass());
    }

    // 1476780 - 12/2006 mht - Add support for field level custom converters
    // Use field level custom converter if one was specified. Otherwise, map or
    // recurse the object as normal
    // 1770440 - fdg - Using multiple instances of CustomConverter
    Object destFieldValue;
    if (!MappingUtils.isBlankOrNull(fieldMapping.getCustomConverterId())) {
      if (customConverterObjectsWithId != null && customConverterObjectsWithId.containsKey(fieldMapping.getCustomConverterId())) {
        Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMapping.getSrcFieldType(srcObj.getClass());
        destFieldValue = mapUsingCustomConverterInstance(customConverterObjectsWithId.get(fieldMapping.getCustomConverterId()),
            srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMapping, false);
      } else {
        throw new MappingException("CustomConverter instance not found with id:" + fieldMapping.getCustomConverterId());
      }
    } else if (MappingUtils.isBlankOrNull(fieldMapping.getCustomConverter())) {
      destFieldValue = mapOrRecurseObject(srcObj, srcFieldValue, destFieldType, fieldMapping, destObj);
    } else {
      Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMapping.getSrcFieldType(srcObj.getClass());
      destFieldValue = mapUsingCustomConverter(MappingUtils.loadClass(fieldMapping.getCustomConverter()), srcFieldClass,
          srcFieldValue, destFieldType, destObj, fieldMapping, false);
    }

    writeDestinationValue(destObj, destFieldValue, fieldMapping, srcObj);

    if (log.isDebugEnabled()) {
      log.debug(LogMsgFactory.createFieldMappingSuccessMsg(srcObj.getClass(), destObj.getClass(), fieldMapping.getSrcFieldName(),
          fieldMapping.getDestFieldName(), srcFieldValue, destFieldValue, fieldMapping.getClassMap().getMapId()));
    }
  }

  private Object mapOrRecurseObject(Object srcObj, Object srcFieldValue, Class<?> destFieldType, FieldMap fieldMap, Object destObj) {
    Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMap.getSrcFieldType(srcObj.getClass());
    Class<?> converterClass = MappingUtils.determineCustomConverter(fieldMap, converterByDestTypeCache, fieldMap.getClassMap()
        .getCustomConverters(), srcFieldClass, destFieldType);

    // 1-2007 mht: Invoke custom converter even if the src value is null.
    // #1563795
    if (converterClass != null) {
      return mapUsingCustomConverter(converterClass, srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMap, false);
    }

    if (srcFieldValue == null) {
      return null;
    }


    String srcFieldName = fieldMap.getSrcFieldName();
    String destFieldName = fieldMap.getDestFieldName();

    // 1596766 - Recursive object mapping issue. Prevent recursive mapping
    // infinite loop
    // In case of "this->this" mapping this rule should be omitted as processing is done on objects, which has been
    // just marked as mapped.
    if (!(DozerConstants.SELF_KEYWORD.equals(srcFieldName) && DozerConstants.SELF_KEYWORD.equals(destFieldName))) {
      Object alreadyMappedValue = mappedFields.getMappedValue(srcFieldValue, destFieldType);
      if (alreadyMappedValue != null) {
        return alreadyMappedValue;
      }
    }

    if (fieldMap.isCopyByReference()) {
      // just get the src and return it, no transformation.
      return srcFieldValue;
    }

    boolean isSrcFieldClassSupportedMap = MappingUtils.isSupportedMap(srcFieldClass);
    boolean isDestFieldTypeSupportedMap = MappingUtils.isSupportedMap(destFieldType);
    if (isSrcFieldClassSupportedMap && isDestFieldTypeSupportedMap) {
      return mapMap(srcObj, (Map<?, ?>) srcFieldValue, fieldMap, destObj);
    }
    if (fieldMap instanceof MapFieldMap && destFieldType.equals(Object.class)) {
      // TODO: find better place for this logic. try to encapsulate in FieldMap?
      destFieldType = fieldMap.getDestHintContainer() != null ? fieldMap.getDestHintContainer().getHint() : srcFieldClass;
    }

    if (primitiveConverter.accepts(srcFieldClass) || primitiveConverter.accepts(destFieldType)) {
      // Primitive or Wrapper conversion
      if (fieldMap.getDestHintContainer() != null) {
        destFieldType = fieldMap.getDestHintContainer().getHint();
      }

      //#1841448 - if trim-strings=true, then use a trimmed src string value when converting to dest value
      Object convertSrcFieldValue = srcFieldValue;
      if (fieldMap.isTrimStrings() && srcFieldValue.getClass().equals(String.class)) {
        convertSrcFieldValue = ((String) srcFieldValue).trim();
      }

      DateFormatContainer dfContainer = new DateFormatContainer(fieldMap.getDateFormat());

      if (fieldMap instanceof MapFieldMap && !primitiveConverter.accepts(destFieldType)) {
        // This handles a very special/rare use case(see indexMapping.xml + unit
        // test
        // testStringToIndexedSet_UsingMapSetMethod). If the destFieldType is a
        // custom object AND has a String param
        // constructor, we don't want to construct the custom object with the
        // src value because the map backed property
        // logic at lower layers handles setting the value on the custom object.
        // Without this special logic, the
        // destination map backed custom object would contain a value that is
        // the custom object dest type instead of the
        // desired src value.
        return primitiveConverter.convert(convertSrcFieldValue, convertSrcFieldValue.getClass(), dfContainer);
      } else {
        return primitiveConverter.convert(convertSrcFieldValue, destFieldType, dfContainer);
      }
    }
    if (MappingUtils.isSupportedCollection(srcFieldClass) && (MappingUtils.isSupportedCollection(destFieldType))) {
      return mapCollection(srcObj, srcFieldValue, fieldMap, destObj);
    }

    if (MappingUtils.isEnumType(srcFieldClass, destFieldType)) {
      return mapEnum((Enum) srcFieldValue, (Class<Enum>) destFieldType);
    }

    if (fieldMap.getDestDeepIndexHintContainer() != null) {
      destFieldType = fieldMap.getDestDeepIndexHintContainer().getHint();
    }

    // Default: Map from one custom data object to another custom data object
    return mapCustomObject(fieldMap, destObj, destFieldType, srcFieldValue);
  }

  private <T extends Enum<T>> T mapEnum(Enum<T> srcFieldValue, Class<T> destFieldType) {
    String name = srcFieldValue.name();
    return Enum.valueOf(destFieldType, name);
  }

  private Object mapCustomObject(FieldMap fieldMap, Object destObj, Class<?> destFieldType, Object srcFieldValue) {
    srcFieldValue = MappingUtils.deProxy(srcFieldValue);

    // Custom java bean. Need to make sure that the destination object is not
    // already instantiated.
    Object result = null;
    // in case of iterate feature new objects are created in any case
    if (!DozerConstants.ITERATE.equals(fieldMap.getDestFieldType())) {
      result = getExistingValue(fieldMap, destObj, destFieldType);
    }
    ClassMap classMap = null;
    // if the field is not null than we don't want a new instance
    if (result == null) {
      // first check to see if this plain old field map has hints to the actual
      // type.
      if (fieldMap.getDestHintContainer() != null) {
        Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());
        // if the destType is null this means that there was more than one hint.
        // we must have already set the destType then.
        if (destHintType != null) {
          destFieldType = destHintType;
        }
      }
      // Check to see if explicit map-id has been specified for the field
      // mapping
      String mapId = fieldMap.getMapId();

      Class<? extends Object> targetClass;
      if (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().getHint() != null) {
        targetClass = fieldMap.getDestHintContainer().getHint();
      } else {
        targetClass = destFieldType;
      }
      classMap = getClassMap(srcFieldValue.getClass(), targetClass, mapId);

      result = DestBeanCreator.create(
          new BeanCreationDirective(srcFieldValue, classMap.getSrcClassToMap(), classMap.getDestClassToMap(),
              destFieldType, classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(),
              fieldMap.getDestFieldCreateMethod() != null ? fieldMap.getDestFieldCreateMethod() : classMap.getDestClassCreateMethod()));
    }

    map(classMap, srcFieldValue, result, false, fieldMap.getMapId());

    return result;
  }

  private Object mapCollection(Object srcObj, Object srcCollectionValue, FieldMap fieldMap, Object destObj) {
    // since we are mapping some sort of collection now is a good time to decide
    // if they provided hints
    // if no hint is provided then we will use generics to determine the mapping type
    if (fieldMap.getDestHintContainer() == null) {
      Class<?> genericType = fieldMap.getGenericType(destObj.getClass());
      if (genericType != null) {
        HintContainer destHintContainer = new HintContainer();
        destHintContainer.setHintName(genericType.getName());
        FieldMap cloneFieldMap = (FieldMap) fieldMap.clone();
        cloneFieldMap.setDestHintContainer(destHintContainer); // should affect only this time as fieldMap is cloned
        fieldMap = cloneFieldMap;
      }
    }

    // if it is an iterator object turn it into a List
    if (srcCollectionValue instanceof Iterator) {
      srcCollectionValue = IteratorUtils.toList((Iterator<?>) srcCollectionValue);
    }

    Class<?> destCollectionType = fieldMap.getDestFieldType(destObj.getClass());
    Class<?> srcFieldType = srcCollectionValue.getClass();
    Object result = null;

    // if they use a standard Collection we have to assume it is a List...better
    // way to handle this?
    if (destCollectionType.getName().equals(Collection.class.getName())) {
      destCollectionType = List.class;
    }
    // Array to Array
    if (CollectionUtils.isArray(srcFieldType) && (CollectionUtils.isArray(destCollectionType))) {
      result = mapArrayToArray(srcObj, srcCollectionValue, fieldMap, destObj);
      // Array to List
    } else if (CollectionUtils.isArray(srcFieldType) && (CollectionUtils.isList(destCollectionType))) {
      result = mapArrayToList(srcObj, srcCollectionValue, fieldMap, destObj);
    }
    // List to Array
    else if (CollectionUtils.isList(srcFieldType) && (CollectionUtils.isArray(destCollectionType))) {
      result = mapListToArray(srcObj, (List<?>) srcCollectionValue, fieldMap, destObj);
      // List to List
    } else if (CollectionUtils.isList(srcFieldType) && (CollectionUtils.isList(destCollectionType))) {
      result = mapListToList(srcObj, (List<?>) srcCollectionValue, fieldMap, destObj);
    }
    // Set to Array
    else if (CollectionUtils.isSet(srcFieldType) && CollectionUtils.isArray(destCollectionType)) {
      result = mapSetToArray(srcObj, (Set<?>) srcCollectionValue, fieldMap, destObj);
    }
    // Array to Set
    else if (CollectionUtils.isArray(srcFieldType) && CollectionUtils.isSet(destCollectionType)) {
      result = addToSet(srcObj, fieldMap, Arrays.asList((Object[]) srcCollectionValue), destObj);
    }
    // Set to List
    else if (CollectionUtils.isSet(srcFieldType) && CollectionUtils.isList(destCollectionType)) {
      result = mapListToList(srcObj, (Set<?>) srcCollectionValue, fieldMap, destObj);
    }
    // Collection to Set
    else if (CollectionUtils.isCollection(srcFieldType) && CollectionUtils.isSet(destCollectionType)) {
      result = addToSet(srcObj, fieldMap, (Collection<?>) srcCollectionValue, destObj);
    }
    // List to Map value
    else if (CollectionUtils.isCollection(srcFieldType) && MappingUtils.isSupportedMap(destCollectionType)) {
      result = mapListToList(srcObj, (List<?>) srcCollectionValue, fieldMap, destObj);
    }
    return result;
  }

  private Object mapMap(Object srcObj, Map srcMapValue, FieldMap fieldMap, Object destObj) {
    Map result;
    Map destinationMap = (Map) fieldMap.getDestValue(destObj);
    if (destinationMap == null) {
      result = DestBeanCreator.create(srcMapValue.getClass());
    } else {
      result = destinationMap;
      if (fieldMap.isRemoveOrphans()) {
        result.clear();
      }
    }

    for (Entry<?, Object> srcEntry : ((Map<?, Object>) srcMapValue).entrySet()) {
      Object srcEntryValue = srcEntry.getValue();

      if (srcEntryValue == null) { // overwrites with null in any case
        result.put(srcEntry.getKey(), null);
        continue;
      }

      Object destEntryValue = mapOrRecurseObject(srcObj, srcEntryValue, srcEntryValue.getClass(), fieldMap, destObj);
      Object obj = result.get(srcEntry.getKey());
      if (obj != null && obj.equals(destEntryValue) && fieldMap.isNonCumulativeRelationship()) {
        map(null, srcEntryValue, obj, false, null);
      } else {
        result.put(srcEntry.getKey(), destEntryValue);
      }
    }
    return result;
  }

  private Object mapArrayToArray(Object srcObj, Object srcCollectionValue, FieldMap fieldMap, Object destObj) {
    Class destEntryType = fieldMap.getDestFieldType(destObj.getClass()).getComponentType();
    int size = Array.getLength(srcCollectionValue);
    if (CollectionUtils.isPrimitiveArray(srcCollectionValue.getClass())) {
      return addToPrimitiveArray(srcObj, fieldMap, size, srcCollectionValue, destObj, destEntryType);
    } else {
      List<?> list = Arrays.asList((Object[]) srcCollectionValue);
      List<?> returnList;
      if (!destEntryType.getName().equals(BASE_CLASS)) {
        returnList = addOrUpdateToList(srcObj, fieldMap, list, destObj, destEntryType);
      } else {
        returnList = addOrUpdateToList(srcObj, fieldMap, list, destObj, null);
      }
      return CollectionUtils.convertListToArray(returnList, destEntryType);
    }
  }

  private void mapFromIterateMethodFieldMap(Object srcObj, Object destObj, Object srcFieldValue, FieldMap fieldMapping) {
    // Iterate over the destFieldValue - iterating is fine unless we are mapping
    // in the other direction.
    // Verify that it is truly a collection if it is an iterator object turn it
    // into a List
    if (srcFieldValue instanceof Iterator) {
      srcFieldValue = IteratorUtils.toList((Iterator<?>) srcFieldValue);
    }
    if (srcFieldValue != null) {
      for (int i = 0; i < CollectionUtils.getLengthOfCollection(srcFieldValue); i++) {
        final Object value = CollectionUtils.getValueFromCollection(srcFieldValue, i);

        // map this value
        if (fieldMapping.getDestHintContainer() == null) {
          MappingUtils.throwMappingException("<field type=\"iterate\"> must have a source or destination type hint");
        }

        Class<?> destinationHint = fieldMapping.getDestHintContainer().getHint();

        Object result = mapOrRecurseObject(srcObj, value, destinationHint, fieldMapping, destObj);

        if (value != null) {
          writeDestinationValue(destObj, result, fieldMapping, srcObj);
        }
      }
    }
    if (log.isDebugEnabled()) {
      log.debug(LogMsgFactory.createFieldMappingSuccessMsg(srcObj.getClass(), destObj.getClass(), fieldMapping.getSrcFieldName(),
          fieldMapping.getDestFieldName(), srcFieldValue, null, fieldMapping.getClassMap().getMapId()));
    }
  }

  private Object addToPrimitiveArray(Object srcObj, FieldMap fieldMap, int size, Object srcCollectionValue, Object destObj,
                                     Class<?> destEntryType) {

    Object result;
    Object field = fieldMap.getDestValue(destObj);
    int arraySize = 0;
    if (field == null) {
      result = Array.newInstance(destEntryType, size);
    } else {
      result = Array.newInstance(destEntryType, size + Array.getLength(field));
      arraySize = Array.getLength(field);
      System.arraycopy(field, 0, result, 0, arraySize);
    }
    // primitive arrays are ALWAYS cumulative
    for (int i = 0; i < size; i++) {
      CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
      Object toValue;
      if (srcCollectionValue != null && copyByReferences.contains(srcCollectionValue.getClass())) {
        toValue = srcCollectionValue;
      } else {
        toValue = mapOrRecurseObject(srcObj, Array.get(srcCollectionValue, i), destEntryType, fieldMap, destObj);
      }
      Array.set(result, arraySize, toValue);
      arraySize++;
    }
    return result;
  }

  private Object mapListToArray(Object srcObj, Collection<?> srcCollectionValue, FieldMap fieldMap, Object destObj) {
    Class destEntryType = fieldMap.getDestFieldType(destObj.getClass()).getComponentType();
    List list;
    if (!destEntryType.getName().equals(BASE_CLASS)) {
      list = addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj, destEntryType);
    } else {
      list = addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj);
    }
    return CollectionUtils.convertListToArray(list, destEntryType);
  }

  private List<?> mapListToList(Object srcObj, Collection<?> srcCollectionValue, FieldMap fieldMap, Object destObj) {
    return addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj);
  }

  private Set<?> addToSet(Object srcObj, FieldMap fieldMap, Collection<?> srcCollectionValue, Object destObj) {
    // create a list here so we can keep track of which elements we have mapped, and remove all others if removeOrphans = true
    Set<Object> mappedElements = new HashSet<Object>();
    Class<?> destEntryType = null;

    LinkedHashSet<Object> result = new LinkedHashSet<Object>();
    // don't want to create the set if it already exists.
    Object field = fieldMap.getDestValue(destObj);
    if (field != null) {
      result.addAll((Collection<?>) field);
    }
    Object destValue;
    Class<?> prevDestEntryType = null;
    for (Object srcValue : srcCollectionValue) {
      if (destEntryType == null
          || (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().hasMoreThanOneHint())) {
        if (srcValue == null) {
          destEntryType = prevDestEntryType;
        } else {
          destEntryType = fieldMap.getDestHintType(srcValue.getClass());
        }
      }
      CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
      if (srcValue != null && copyByReferences.contains(srcValue.getClass())) {
        destValue = srcValue;
      } else {
        destValue = mapOrRecurseObject(srcObj, srcValue, destEntryType, fieldMap, destObj);
      }
      prevDestEntryType = destEntryType;

      if (RelationshipType.NON_CUMULATIVE.equals(fieldMap.getRelationshipType())
          && result.contains(destValue)) {
        List<Object> resultAsList = new ArrayList<Object>(result);
        int index = resultAsList.indexOf(destValue);
        // perform an update if complex type - can't map strings
        Object obj = resultAsList.get(index);
        // make sure it is not a String
        if (!obj.getClass().isAssignableFrom(String.class)) {
          map(null, srcValue, obj, false, null);
          mappedElements.add(obj);
        }
      } else {
        result.add(destValue);
        mappedElements.add(destValue);
      }
    }

    // If remove orphans - we only want to keep the objects we've mapped from the src collection
    // so we'll clear result and replace all entries with the ones in mappedElements
    if (fieldMap.isRemoveOrphans()) {
      result.clear();
      result.addAll(mappedElements);
    }

    if (field == null) {
      Class<? extends Set<?>> destSetType = (Class<? extends Set<?>>) fieldMap.getDestFieldType(destObj.getClass());
      return CollectionUtils.createNewSet(destSetType, result);
    } else {
      // Bug #1822421 - Clear first so we don't end up with the removed orphans again
      ((Set) field).clear();
      ((Set) field).addAll(result);
      return (Set<?>) field;
    }
  }

  private List<?> addOrUpdateToList(Object srcObj, FieldMap fieldMap, Collection<?> srcCollectionValue, Object destObj,
                                    Class<?> destEntryType) {
    // create a Set here so we can keep track of which elements we have mapped, and remove all others if removeOrphans = true
    List<Object> mappedElements = new ArrayList<Object>();
    List result;
    // don't want to create the list if it already exists.
    // these maps are special cases which do not fall under what we are looking for
    Object field = fieldMap.getDestValue(destObj);
    result = prepareDestinationList(srcCollectionValue, field);

    Object destValue;
    Class<?> prevDestEntryType = null;
    for (Object srcValue : srcCollectionValue) {
      if (destEntryType == null
          || (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().hasMoreThanOneHint())) {
        if (srcValue == null) {
          destEntryType = prevDestEntryType;
        } else {
          destEntryType = fieldMap.getDestHintType(srcValue.getClass());
        }
      }
      CopyByReferenceContainer copyByReferences = globalConfiguration.getCopyByReferences();
      if (srcValue != null && copyByReferences.contains(srcValue.getClass())) {
        destValue = srcValue;
      } else {
        destValue = mapOrRecurseObject(srcObj, srcValue, destEntryType, fieldMap, destObj);
      }
      prevDestEntryType = destEntryType;

      if (RelationshipType.NON_CUMULATIVE.equals(fieldMap.getRelationshipType())
          && result.contains(destValue)) {
        int index = result.indexOf(destValue);
        // perform an update if complex type - can't map strings
        Object obj = result.get(index);
        // make sure it is not a String
        if (obj != null && !obj.getClass().isAssignableFrom(String.class)) {
          map(null, srcValue, obj, false, null);
          mappedElements.add(obj);
        }
      } else {
        result.add(destValue);
        mappedElements.add(destValue);
      }

    }

    // If remove orphans - we only want to keep the objects we've mapped from the src collection
    if (fieldMap.isRemoveOrphans()) {
      removeOrphans(mappedElements, result);
    }

    return result;
  }

  static void removeOrphans(Collection<?> mappedElements, List<Object> result) {
    for (Iterator<?> iterator = result.iterator(); iterator.hasNext();) {
      Object object = iterator.next();
      if (!mappedElements.contains(object)) {
        iterator.remove();
      }
    }
    for (Object object : mappedElements) {
      if (!result.contains(object)) {
        result.add(object);
      }
    }
  }

  static List<?> prepareDestinationList(Collection<?> srcCollectionValue, Object field) {
    if (field == null) {
      return new ArrayList<Object>(srcCollectionValue.size());
    } else {
      if (CollectionUtils.isList(field.getClass())) {
        return (List<?>) field;
      } else if (CollectionUtils.isArray(field.getClass())) {
        return new ArrayList<Object>(Arrays.asList((Object[]) field));
      } else { // assume it is neither - safest way is to create new List
        return new ArrayList<Object>(srcCollectionValue.size());
      }
    }
  }

  private List<?> addOrUpdateToList(Object srcObj, FieldMap fieldMap, Collection<?> srcCollectionValue, Object destObj) {
    return addOrUpdateToList(srcObj, fieldMap, srcCollectionValue, destObj, null);
  }

  private Object mapSetToArray(Object srcObj, Collection<?> srcCollectionValue, FieldMap fieldMap, Object destObj) {
    return mapListToArray(srcObj, srcCollectionValue, fieldMap, destObj);
  }

  private List<?> mapArrayToList(Object srcObj, Object srcCollectionValue, FieldMap fieldMap, Object destObj) {
    Class<?> destEntryType;
    if (fieldMap.getDestHintContainer() != null) {
      destEntryType = fieldMap.getDestHintContainer().getHint();
    } else {
      destEntryType = srcCollectionValue.getClass().getComponentType();
    }
    List<?> srcValueList;
    if (CollectionUtils.isPrimitiveArray(srcCollectionValue.getClass())) {
      srcValueList = CollectionUtils.convertPrimitiveArrayToList(srcCollectionValue);
    } else {
      srcValueList = Arrays.asList((Object[]) srcCollectionValue);
    }
    return addOrUpdateToList(srcObj, fieldMap, srcValueList, destObj, destEntryType);
  }

  private void writeDestinationValue(Object destObj, Object destFieldValue, FieldMap fieldMap, Object srcObj) {
    boolean bypass = false;
    // don't map null to dest field if map-null="false"
    if (destFieldValue == null && !fieldMap.isDestMapNull()) {
      bypass = true;
    }

    // don't map "" to dest field if map-empty-string="false"
    if (destFieldValue != null && !fieldMap.isDestMapEmptyString() && destFieldValue.getClass().equals(String.class)
        && StringUtils.isEmpty((String) destFieldValue)) {
      bypass = true;
    }

    // trim string value if trim-strings="true"
    if (destFieldValue != null && fieldMap.isTrimStrings() && destFieldValue.getClass().equals(String.class)) {
      destFieldValue = ((String) destFieldValue).trim();
    }

    if (!bypass) {
      eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_PRE_WRITING_DEST_VALUE, fieldMap.getClassMap(), fieldMap, srcObj,
          destObj, destFieldValue));

      fieldMap.writeDestValue(destObj, destFieldValue);

      eventMgr.fireEvent(new DozerEvent(DozerEventType.MAPPING_POST_WRITING_DEST_VALUE, fieldMap.getClassMap(), fieldMap, srcObj,
          destObj, destFieldValue));
    }
  }

  private Object mapUsingCustomConverterInstance(CustomConverter converterInstance, Class<?> srcFieldClass, Object srcFieldValue,
                                                 Class<?> destFieldClass, Object existingDestFieldValue, FieldMap fieldMap, boolean topLevel) {

    //1792048 - If map-null = "false" and src value is null, then don't even invoke custom converter
    if (srcFieldValue == null && !fieldMap.isDestMapNull()) {
      return null;
    }

    long start = System.currentTimeMillis();

    if (converterInstance instanceof MapperAware) {
      ((MapperAware) converterInstance).setMapper(this);
    }

    // TODO Remove code duplication
    Object result;
    if (converterInstance instanceof ConfigurableCustomConverter) {
      ConfigurableCustomConverter theConverter = (ConfigurableCustomConverter) converterInstance;

      // Converter could be not configured for this particular case
      if (fieldMap != null) {
        String param = fieldMap.getCustomConverterParam();
        theConverter.setParameter(param);
      }

      // if this is a top level mapping the destObj is the highest level
      // mapping...not a recursive mapping
      if (topLevel) {
        result = theConverter.convert(existingDestFieldValue, srcFieldValue, destFieldClass, srcFieldClass);
      } else {
        Object existingValue = getExistingValue(fieldMap, existingDestFieldValue, destFieldClass);
        result = theConverter.convert(existingValue, srcFieldValue, destFieldClass, srcFieldClass);
      }
    } else {
      // if this is a top level mapping the destObj is the highest level
      // mapping...not a recursive mapping
      if (topLevel) {
        result = converterInstance.convert(existingDestFieldValue, srcFieldValue, destFieldClass, srcFieldClass);
      } else {
        Object existingValue = getExistingValue(fieldMap, existingDestFieldValue, destFieldClass);
        result = converterInstance.convert(existingValue, srcFieldValue, destFieldClass, srcFieldClass);
      }
    }

    long stop = System.currentTimeMillis();
    statsMgr.increment(StatisticType.CUSTOM_CONVERTER_SUCCESS_COUNT);
    statsMgr.increment(StatisticType.CUSTOM_CONVERTER_TIME, stop - start);

    return result;
  }

  // TODO: possibly extract this to a separate class

  private Object mapUsingCustomConverter(Class<?> customConverterClass, Class<?> srcFieldClass, Object srcFieldValue,
                                         Class<?> destFieldClass, Object existingDestFieldValue, FieldMap fieldMap, boolean topLevel) {
    CustomConverter converterInstance = null;
    // search our injected customconverters for a match
    if (customConverterObjects != null) {
      for (CustomConverter customConverterObject : customConverterObjects) {
        if (customConverterObject.getClass().isAssignableFrom(customConverterClass)) {
          // we have a match
          converterInstance = customConverterObject;
        }
      }
    }
    // if converter object instances were not injected, then create new instance
    // of the converter for each conversion
    // TODO : Should we really create it each time?
    if (converterInstance == null) {
      converterInstance = (CustomConverter) ReflectionUtils.newInstance(customConverterClass);
    }
    return mapUsingCustomConverterInstance(converterInstance, srcFieldClass, srcFieldValue, destFieldClass, existingDestFieldValue,
        fieldMap, topLevel);
  }

  private Collection<ClassMap> checkForSuperTypeMapping(Class<?> srcClass, Class<?> destClass) {
    // Check cache first
    Object cacheKey = CacheKeyFactory.createKey(destClass, srcClass);
    Collection<ClassMap> cachedResult = (Collection<ClassMap>) superTypeCache.get(cacheKey);
    if (cachedResult != null) {
      return cachedResult;
    }

    // If no existing cache entry is found, determine super type mappings.
    // Recursively walk the inheritance hierarchy.
    List<ClassMap> superClasses = new ArrayList<ClassMap>();
    // Need to call getRealSuperclass because proxied data objects will not return correct
    // superclass when using basic reflection

    List<Class<?>> superSrcClasses = MappingUtils.getSuperClassesAndInterfaces(srcClass);
    List<Class<?>> superDestClasses = MappingUtils.getSuperClassesAndInterfaces(destClass);

    // add the actual classes to check for mappings between the original and the opposite
    // super classes
    superSrcClasses.add(0, srcClass);
    superDestClasses.add(0, destClass);

    for (Class<?> superSrcClass : superSrcClasses) {
      for (Class<?> superDestClass : superDestClasses) {
        if (!(superSrcClass.equals(srcClass) && superDestClass.equals(destClass))) {
          checkForClassMapping(superSrcClass, superClasses, superDestClass);
        }
      }
    }

    Collections.reverse(superClasses); // Done so base classes are processed first

    superTypeCache.put(cacheKey, superClasses);

    return superClasses;
  }

  private void checkForClassMapping(Class<?> srcClass, List<ClassMap> superClasses, Class<?> superDestClass) {
    ClassMap srcClassMap = classMappings.find(srcClass, superDestClass);
    if (srcClassMap != null) {
      superClasses.add(srcClassMap);
    }
  }

  private List<String> processSuperTypeMapping(Collection<ClassMap> superClasses, Object srcObj, Object destObj, String mapId) {
    List<String> mappedFields = new ArrayList<String>();
    for (ClassMap map : superClasses) {
      map(map, srcObj, destObj, true, mapId);
      for (FieldMap fieldMapping : map.getFieldMaps()) {
        String key = MappingUtils.getMappedParentFieldKey(destObj, fieldMapping);
        mappedFields.add(key);
      }
    }
    return mappedFields;
  }

  private static Object getExistingValue(FieldMap fieldMap, Object destObj, Class<?> destFieldType) {
    // verify that the dest obj is not null
    if (destObj == null) {
      return null;
    }
    // call the getXX method to see if the field is already instantiated
    Object result = fieldMap.getDestValue(destObj);

    // When we are recursing through a list we need to make sure that we are not
    // in the list
    // by checking the destFieldType
    if (result != null) {
      if (CollectionUtils.isList(result.getClass()) || CollectionUtils.isArray(result.getClass())
          || CollectionUtils.isSet(result.getClass()) || MappingUtils.isSupportedMap(result.getClass())) {
        if (!CollectionUtils.isList(destFieldType) && !CollectionUtils.isArray(destFieldType)
            && !CollectionUtils.isSet(destFieldType) && !MappingUtils.isSupportedMap(destFieldType)) {
          // this means the getXX field is a List but we are actually trying to
          // map one of its elements
          result = null;
        }
      }
    }
    return result;
  }

  private ClassMap getClassMap(Class<?> srcClass, Class<?> destClass, String mapId) {
    ClassMap mapping = classMappings.find(srcClass, destClass, mapId);

    if (mapping == null) {
      // If mapping not found in existing custom mapping collection, create
      // default as an explicit mapping must not
      // exist. The create default class map method will also add all default
      // mappings that it can determine.
      mapping = ClassMapBuilder.createDefaultClassMap(globalConfiguration, srcClass, destClass);
      classMappings.addDefault(srcClass, destClass, mapping);
    }

    return mapping;
  }

}
TOP

Related Classes of org.dozer.MappingProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.