Repository: orika-mapper/orika (java)

Issue:
Default mapper executes getters on destination object
I have two classes `Source` and `Dest`. Both have getters and setters for the property `myProperty`. I've found that when I use a default mapper and map source into destination then getters from the destination object is called also (i.e. getMyProperty() is called in the `dest` object). Here is the code:
```
mapperFactory.classMap(Source.class, Dest.class).byDefault();
MapperFacade mapper = mapperFactory.getMapperFacade();
Source src = new Source("Test", 10);
Dest dest = new Dest();
mapper.map(src, dest);
```
Is there a way to prevent default mapper from calling getters on the destination object?

Pull request:
Add possibility to disable destination value retrieval on mapping #301
This PR adds possibility to disable destination property value retrieval. Default behaviour isn't changed, so this PR doesn't add any breaking changes.

Possible use cases:
- Destination object getters are expensive to be executed.
- Destination object getters throws exceptions when the value isn't set yet.

Fixes #301

Patch:
diff --git a/core/src/main/java/ma/glasnost/orika/OrikaSystemProperties.java b/core/src/main/java/ma/glasnost/orika/OrikaSystemProperties.java
index 6c07aea1..b5740073 100644
--- a/core/src/main/java/ma/glasnost/orika/OrikaSystemProperties.java
+++ b/core/src/main/java/ma/glasnost/orika/OrikaSystemProperties.java
@@ -171,4 +171,10 @@ public final class OrikaSystemProperties {
      * Default value is <code>false</code>
      */
     public static final String CAPTURE_FIELD_CONTEXT = "ma.glasnost.orika.captureFieldContext";
+
+    /**
+     * Specifies whether destination properies' values will be retrieved on mappings or not.
+     * default value is <code>true</code>
+     */
+    public static final String GET_DESTINATION_ON_MAPPING = "ma.glasnost.orika.getDestinationOnMapping";
 }
diff --git a/core/src/main/java/ma/glasnost/orika/Properties.java b/core/src/main/java/ma/glasnost/orika/Properties.java
index 52269397..841fdd93 100644
--- a/core/src/main/java/ma/glasnost/orika/Properties.java
+++ b/core/src/main/java/ma/glasnost/orika/Properties.java
@@ -30,5 +30,6 @@ public enum Properties {
     COMPILER_STRATEGY,
     UNENHANCE_STRATEGY,
     FILTERS,
-    CAPTURE_FIELD_CONTEXT
+    CAPTURE_FIELD_CONTEXT,
+    SHOULD_GET_DESTINATION_ON_MAPPING
 }
diff --git a/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java b/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java
index 5f79a8b7..aedd4e76 100644
--- a/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java
+++ b/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java
@@ -237,6 +237,6 @@ public class SimpleConstructorResolverStrategy implements ConstructorResolverStr
                 .build();
         return new FieldMap(existing.getSource(), destProp, null,
                 null, MappingDirection.A_TO_B, false, existing.getConverterId(),
-                byDefault, null, null);
+                byDefault, null, null, null);
     }
 }
diff --git a/core/src/main/java/ma/glasnost/orika/impl/DefaultMapperFactory.java b/core/src/main/java/ma/glasnost/orika/impl/DefaultMapperFactory.java
index e66e7cbc..39d69f29 100644
--- a/core/src/main/java/ma/glasnost/orika/impl/DefaultMapperFactory.java
+++ b/core/src/main/java/ma/glasnost/orika/impl/DefaultMapperFactory.java
@@ -155,7 +155,9 @@ public class DefaultMapperFactory implements MapperFactory, Reportable {
         props.put(Properties.MAPPER_FACTORY, this);
         props.put(Properties.FILTERS, this.filtersRegistry);
         props.put(Properties.CAPTURE_FIELD_CONTEXT, builder.captureFieldContext);
-        
+        props.put(Properties.SHOULD_GET_DESTINATION_ON_MAPPING, builder.getDestinationOnMapping);
+
+
         /*
          * Register default concrete types for common collection types; these
          * can be overridden as needed by user code.
@@ -273,7 +275,19 @@ public class DefaultMapperFactory implements MapperFactory, Reportable {
          * But for Testing the MultipleMapperWrapper should be use in one TestSuite for all TestCase to test as much scenarios as possible.
          */
         protected boolean alwaysCreateMultipleMapperWrapper;
-        
+        /**
+         * The configured value of whether or not to retrieve destination property value on mapping;
+         * if false, it will be assumed that the value of the destination property is always null;
+         * if true, the real value of the destination property will be used.
+         * <p>
+         * Sometimes it may be useful to set this value to false. For example, in case when you don't need to know
+         * current destination value and you want to map the value from source to distination regardless of the current
+         * destination value. Also, it may be useful if your logic to retrieve a destination value is too expensive or
+         * produces exceptions if the destination value isn't specified yet.
+         * <p>
+         */
+        protected Boolean getDestinationOnMapping;
+
         /**
          * Instantiates a new MapperFactoryBuilder
          */
@@ -294,6 +308,7 @@ public class DefaultMapperFactory implements MapperFactory, Reportable {
             alwaysCreateMultipleMapperWrapper = valueOf(
                     getProperty("ma.glasnost.orika.alwaysCreateMultipleMapperWrapper", "false"));
             codeGenerationStrategy = new DefaultCodeGenerationStrategy();
+            getDestinationOnMapping = valueOf(getProperty(GET_DESTINATION_ON_MAPPING, "true"));
         }
         
         /**
@@ -456,7 +471,7 @@ public class DefaultMapperFactory implements MapperFactory, Reportable {
             this.mapNulls = mapNulls;
             return self();
         }
-        
+
         /**
          * Configure whether to dump the current state of the mapping
          * infrastructure objects upon occurrence of an exception while mapping.
@@ -532,7 +547,18 @@ public class DefaultMapperFactory implements MapperFactory, Reportable {
             this.codeGenerationStrategy  = codeGenerationStrategy ;
             return self();
         }
-        
+
+        /**
+         * Configure whether to retrieve distination property value during mapping in generated mapper code
+         *
+         * @param getDestinationOnMapping
+         * @return a reference to <code>this</code> MapperFactoryBuilder
+         */
+        public B getDestinationOnMapping(boolean getDestinationOnMapping) {
+            this.getDestinationOnMapping = getDestinationOnMapping;
+            return self();
+        }
+
         /**
          * @return a new instance of the Factory for which this builder is
          *         defined. The construction should be performed via the
diff --git a/core/src/main/java/ma/glasnost/orika/impl/generator/SourceCodeContext.java b/core/src/main/java/ma/glasnost/orika/impl/generator/SourceCodeContext.java
index 02e17758..d3486f79 100644
--- a/core/src/main/java/ma/glasnost/orika/impl/generator/SourceCodeContext.java
+++ b/core/src/main/java/ma/glasnost/orika/impl/generator/SourceCodeContext.java
@@ -178,11 +178,15 @@ public class SourceCodeContext {
     List<String> getMethods() {
         return methods;
     }
-    
+
     public boolean shouldMapNulls() {
         return (Boolean) mappingContext.getProperty(Properties.SHOULD_MAP_NULLS);
     }
-    
+
+    public boolean shouldGetDestinationOnMapping() {
+        return (Boolean) mappingContext.getProperty(Properties.SHOULD_GET_DESTINATION_ON_MAPPING);
+    }
+
     public MappingContext getMappingContext() {
         return mappingContext;
     }
@@ -745,12 +749,14 @@ public class SourceCodeContext {
 
             Converter<Object, Object> converter = getConverter(fieldMap, fieldMap.getConverterId());
             source.setConverter(converter);
-            
+
+            boolean getDestinationOnMapping = AbstractSpecification.shouldGetDestinationOnMapping(fieldMap, this);
+
             if (shouldCaptureFieldContext) {
-                beginCaptureFieldContext(out, fieldMap, source, destination);
+                beginCaptureFieldContext(out, fieldMap, source, destination, getDestinationOnMapping);
             }
             StringBuilder filterClosing = new StringBuilder();
-            VariableRef[] filteredProperties = applyFilters(source, destination, out, filterClosing);
+            VariableRef[] filteredProperties = applyFilters(source, destination, out, filterClosing, getDestinationOnMapping);
             source = filteredProperties[0];
             destination = filteredProperties[1];
             
@@ -774,11 +780,11 @@ public class SourceCodeContext {
         }
         return out.toString();
     }
-    
-    private void beginCaptureFieldContext(StringBuilder out, FieldMap fieldMap, VariableRef source, VariableRef dest) {
+
+    private void beginCaptureFieldContext(StringBuilder out, FieldMap fieldMap, VariableRef source, VariableRef dest, boolean getDestinationValueOnMapping) {
         out.append(format("mappingContext.beginMappingField(\"%s\", %s, %s, \"%s\", %s, %s);\n" + "try{\n",
                 escapeQuotes(fieldMap.getSource().getExpression()), usedType(fieldMap.getAType()), source.asWrapper(),
-                escapeQuotes(fieldMap.getDestination().getExpression()), usedType(fieldMap.getBType()), dest.asWrapper()));
+                escapeQuotes(fieldMap.getDestination().getExpression()), usedType(fieldMap.getBType()), (getDestinationValueOnMapping)?dest.asWrapper():"null"));
     }
     
     private void endCaptureFieldContext(StringBuilder out) {
@@ -789,7 +795,7 @@ public class SourceCodeContext {
         return string.replaceAll("(?<!\\\\)\"", "\\\\\"");
     }
     
-    public VariableRef[] applyFilters(VariableRef sourceProperty, VariableRef destinationProperty, StringBuilder out, StringBuilder closing) {
+    public VariableRef[] applyFilters(VariableRef sourceProperty, VariableRef destinationProperty, StringBuilder out, StringBuilder closing, boolean getDestinationOnMapping) {
         /*
          * TODO: need code which collects all of the applicable filters and adds
          * them into an aggregate filter object
@@ -800,7 +806,7 @@ public class SourceCodeContext {
                 out.append("if (");
                 out.append(format("(%s && %s.shouldMap(%s, \"%s\", %s, %s, \"%s\", %s, mappingContext))", destinationProperty.pathNotNull(),
                     usedFilter(filter), usedType(sourceProperty.type()), varPath(sourceProperty), sourceProperty.asWrapper(),
-                    usedType(destinationProperty.type()), varPath(destinationProperty), destinationProperty.asWrapper()));
+                    usedType(destinationProperty.type()), varPath(destinationProperty), (getDestinationOnMapping)?destinationProperty.asWrapper():"null"));
 
                 out.append(" || ");
 
@@ -812,7 +818,7 @@ public class SourceCodeContext {
             } else {
                 out.append(format("if (%s.shouldMap(%s, \"%s\", %s, %s, \"%s\", %s, mappingContext)) {", usedFilter(filter),
                     usedType(sourceProperty.type()), varPath(sourceProperty), sourceProperty.asWrapper(),
-                    usedType(destinationProperty.type()), varPath(destinationProperty), destinationProperty.asWrapper()));
+                    usedType(destinationProperty.type()), varPath(destinationProperty), (getDestinationOnMapping)?destinationProperty.asWrapper():"null"));
             }
 
             sourceProperty = getSourceFilter(sourceProperty, destinationProperty, filter);
diff --git a/core/src/main/java/ma/glasnost/orika/impl/generator/specification/AbstractSpecification.java b/core/src/main/java/ma/glasnost/orika/impl/generator/specification/AbstractSpecification.java
index d04ca587..51cafec7 100644
--- a/core/src/main/java/ma/glasnost/orika/impl/generator/specification/AbstractSpecification.java
+++ b/core/src/main/java/ma/glasnost/orika/impl/generator/specification/AbstractSpecification.java
@@ -53,7 +53,22 @@ public abstract class AbstractSpecification implements Specification {
         }
         return mapNull;
     }
-    
+
+    /**
+     * Tests whether this fieldMap should get destination property value on mapping;
+     *
+     * @param fieldMap
+     * @param context
+     * @return true if destination should be retrieved, otherwise false.
+     */
+    public static boolean shouldGetDestinationOnMapping(FieldMap fieldMap, SourceCodeContext context) {
+        Boolean getDestinationOnMapping = fieldMap.isDestinationValueRetrievedOnMapping();
+        if (getDestinationOnMapping == null) {
+            getDestinationOnMapping = context.shouldGetDestinationOnMapping();
+        }
+        return getDestinationOnMapping;
+    }
+
     public abstract boolean appliesTo(FieldMap fieldMap);
     
     public String generateEqualityTestCode(FieldMap fieldMap, VariableRef source, VariableRef destination, SourceCodeContext code) {
diff --git a/core/src/main/java/ma/glasnost/orika/impl/generator/specification/MultiOccurrenceToMultiOccurrence.java b/core/src/main/java/ma/glasnost/orika/impl/generator/specification/MultiOccurrenceToMultiOccurrence.java
index 0df1bc72..c6225b7b 100644
--- a/core/src/main/java/ma/glasnost/orika/impl/generator/specification/MultiOccurrenceToMultiOccurrence.java
+++ b/core/src/main/java/ma/glasnost/orika/impl/generator/specification/MultiOccurrenceToMultiOccurrence.java
@@ -260,14 +260,16 @@ public class MultiOccurrenceToMultiOccurrence implements AggregateSpecification
                 } else {
                     VariableRef s = makeVariable(srcNode.property, srcNode, "source");
                     VariableRef d = makeVariable(currentNode.property, currentNode, "destination");
-                    code.applyFilters(s, d, out, endWhiles);
+
+                    boolean getDestinationOnMapping = AbstractSpecification.shouldGetDestinationOnMapping(currentNode.getMap(), code);
+                    code.applyFilters(s, d, out, endWhiles, getDestinationOnMapping);
 
                     d = currentNode.isRoot() ? currentNode.newDestination : currentNode.multiOccurrenceVar;
                     out.append(format("\nmappingContext.beginMapping(%s, %s, %s, %s);\n",
                                 code.usedType(s.type()),
                                 s.asWrapper(),
                                 code.usedType(d.type()),
-                                d.asWrapper()));
+                                (getDestinationOnMapping)?d.asWrapper():"null"));
                     out.append("try {\n");
                     endWhiles.insert(0, "\n} finally {\n  mappingContext.endMapping();\n}\n");
                 }
diff --git a/core/src/main/java/ma/glasnost/orika/metadata/ClassMapBuilder.java b/core/src/main/java/ma/glasnost/orika/metadata/ClassMapBuilder.java
index eac1526b..f0708993 100644
--- a/core/src/main/java/ma/glasnost/orika/metadata/ClassMapBuilder.java
+++ b/core/src/main/java/ma/glasnost/orika/metadata/ClassMapBuilder.java
@@ -89,7 +89,8 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
     private Boolean sourcesMappedOnNull;
     private Boolean destinationsMappedOnNull;
     private Boolean favorsExtension;
-    
+    private Boolean destinationValueRetrievedOnMapping;
+
     private static final Logger LOGGER = LoggerFactory.getLogger(ClassMapBuilder.class);
     
     /**
@@ -326,7 +327,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
         
         try {
             final FieldMapBuilder<A, B> fieldMapBuilder = new FieldMapBuilder<A, B>(this, fieldNameA, fieldNameB, byDefault,
-                    sourcesMappedOnNull, destinationsMappedOnNull);
+                    sourcesMappedOnNull, destinationsMappedOnNull, destinationValueRetrievedOnMapping);
             
             return fieldMapBuilder;
         } catch (MappingException e) {
@@ -348,7 +349,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
      * @return
      */
     public FieldMapBuilder<A, B> fieldMap(Property fieldA, Property fieldB, boolean byDefault) {
-        return new FieldMapBuilder<A, B>(this, fieldA, fieldB, byDefault, sourcesMappedOnNull, destinationsMappedOnNull);
+        return new FieldMapBuilder<A, B>(this, fieldA, fieldB, byDefault, sourcesMappedOnNull, destinationsMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -361,7 +362,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
      */
     public FieldMapBuilder<A, B> fieldMap(String fieldNameA, Property fieldB, boolean byDefault) {
         return new FieldMapBuilder<A, B>(this, resolvePropertyForA(fieldNameA), fieldB, byDefault, sourcesMappedOnNull,
-                destinationsMappedOnNull);
+                destinationsMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -374,7 +375,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
      */
     public FieldMapBuilder<A, B> fieldMap(Property fieldA, String fieldNameB, boolean byDefault) {
         return new FieldMapBuilder<A, B>(this, fieldA, resolvePropertyForB(fieldNameB), byDefault, sourcesMappedOnNull,
-                destinationsMappedOnNull);
+                destinationsMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -387,7 +388,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
      */
     public FieldMapBuilder<A, B> fieldMap(Property.Builder fieldA, Property.Builder fieldB, boolean byDefault) {
         return new FieldMapBuilder<A, B>(this, fieldA.build((PropertyResolver) propertyResolver),
-                fieldB.build((PropertyResolver) propertyResolver), byDefault, sourcesMappedOnNull, destinationsMappedOnNull);
+                fieldB.build((PropertyResolver) propertyResolver), byDefault, sourcesMappedOnNull, destinationsMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -400,7 +401,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
      */
     public FieldMapBuilder<A, B> fieldMap(String fieldNameA, Property.Builder fieldB, boolean byDefault) {
         return new FieldMapBuilder<A, B>(this, resolvePropertyForA(fieldNameA), fieldB.build((PropertyResolver) propertyResolver),
-                byDefault, sourcesMappedOnNull, destinationsMappedOnNull);
+                byDefault, sourcesMappedOnNull, destinationsMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -413,7 +414,7 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
      */
     public FieldMapBuilder<A, B> fieldMap(Property.Builder fieldA, String fieldNameB, boolean byDefault) {
         return new FieldMapBuilder<A, B>(this, fieldA.build((PropertyResolver) propertyResolver), resolvePropertyForB(fieldNameB),
-                byDefault, sourcesMappedOnNull, destinationsMappedOnNull);
+                byDefault, sourcesMappedOnNull, destinationsMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -756,7 +757,22 @@ public class ClassMapBuilder<A, B> implements MappedTypePair<A, B> {
         
         return this;
     }
-    
+
+    /**
+     * @param destinationValueRetrievedOnMapping
+     *            true|false to indicate whether the destination properties values of
+     *            this class map's fields should be retrieved on mapping (when mapping
+     *            in any direction). If false, it will be assumed that the  distination value
+     *            is always null. If true, a real destination value will be used.
+     *
+     * @return this FieldMapBuilder
+     */
+    public ClassMapBuilder<A, B> retrieveDestinationValueOnMapping(boolean destinationValueRetrievedOnMapping) {
+        this.destinationValueRetrievedOnMapping = destinationValueRetrievedOnMapping;
+
+        return this;
+    }
+
     /**
      * Registers the ClassMap defined by this builder with it's initiating
      * MapperFactory
diff --git a/core/src/main/java/ma/glasnost/orika/metadata/FieldMap.java b/core/src/main/java/ma/glasnost/orika/metadata/FieldMap.java
index 4b66b298..b54f78b1 100644
--- a/core/src/main/java/ma/glasnost/orika/metadata/FieldMap.java
+++ b/core/src/main/java/ma/glasnost/orika/metadata/FieldMap.java
@@ -35,9 +35,10 @@ public class FieldMap implements MappedTypePair<Object, Object> {
     private final String destinationExpression;
     private final Boolean sourceMappedOnNull;
     private final Boolean destinationMappedOnNull;
+    private final Boolean destinationValueRetrievedOnMapping;
     
     public FieldMap(Property a, Property b, Property aInverse, Property bInverse, MappingDirection mappingDirection, boolean excluded,
-            String converterId, boolean byDefault, Boolean sourceMappedOnNull, Boolean destinationMappedOnNull) {
+            String converterId, boolean byDefault, Boolean sourceMappedOnNull, Boolean destinationMappedOnNull, Boolean destinationValueRetrievedOnMapping) {
         this.source = a;
         this.destination = b;
         this.aInverse = aInverse;
@@ -48,6 +49,7 @@ public class FieldMap implements MappedTypePair<Object, Object> {
         this.byDefault = byDefault;
         this.sourceMappedOnNull = sourceMappedOnNull;
         this.destinationMappedOnNull = destinationMappedOnNull;
+        this.destinationValueRetrievedOnMapping = destinationValueRetrievedOnMapping;
         this.sourceExpression = this.source.getExpression();
         this.destinationExpression = this.destination.getExpression();
     }
@@ -55,7 +57,7 @@ public class FieldMap implements MappedTypePair<Object, Object> {
     public FieldMap copy() {
         
         return new FieldMap(copy(source), copy(destination), copy(aInverse), copy(bInverse), mappingDirection, excluded, converterId,
-                byDefault, sourceMappedOnNull, destinationMappedOnNull);
+                byDefault, sourceMappedOnNull, destinationMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     private Property copy(Property property) {
@@ -115,7 +117,15 @@ public class FieldMap implements MappedTypePair<Object, Object> {
     public Boolean isDestinationMappedOnNull() {
         return destinationMappedOnNull;
     }
-    
+
+    /**
+     * @return destinationValueRetrievedOnMapping; can be null, which indicates no
+     *         preference and that the global value should be used.
+     */
+    public Boolean isDestinationValueRetrievedOnMapping() {
+        return destinationValueRetrievedOnMapping;
+    }
+
     public Property getInverse() {
         return bInverse;
     }
@@ -126,7 +136,7 @@ public class FieldMap implements MappedTypePair<Object, Object> {
     
     public FieldMap flip() {
         return new FieldMap(destination, source, bInverse, aInverse, mappingDirection.flip(), excluded, converterId, byDefault,
-                destinationMappedOnNull, sourceMappedOnNull);
+                destinationMappedOnNull, sourceMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     public boolean is(Specification specification) {
diff --git a/core/src/main/java/ma/glasnost/orika/metadata/FieldMapBuilder.java b/core/src/main/java/ma/glasnost/orika/metadata/FieldMapBuilder.java
index ec456674..d65d5b60 100644
--- a/core/src/main/java/ma/glasnost/orika/metadata/FieldMapBuilder.java
+++ b/core/src/main/java/ma/glasnost/orika/metadata/FieldMapBuilder.java
@@ -54,12 +54,14 @@ public class FieldMapBuilder<A, B> {
     private Boolean sourceMappedOnNull;
     
     private Boolean destinationMappedOnNull;
+
+    private Boolean destinationValueRetrievedOnMapping;
     
     FieldMapBuilder(final ClassMapBuilder<A, B> classMapBuilder, final String a, final String b, final boolean byDefault,
-            final Boolean sourceMappedOnNull, final Boolean destinationMappedOnNull) {
+            final Boolean sourceMappedOnNull, final Boolean destinationMappedOnNull, final Boolean destinationValueRetrievedOnMapping) {
         
         this(classMapBuilder, a, b, classMapBuilder.getAType(), classMapBuilder.getBType(), byDefault, sourceMappedOnNull,
-                destinationMappedOnNull);
+                destinationMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -73,10 +75,10 @@ public class FieldMapBuilder<A, B> {
      * @param bType
      */
     FieldMapBuilder(final ClassMapBuilder<A, B> classMapBuilder, final String a, final String b, final Type<?> aType, final Type<?> bType,
-            final boolean byDefault, final Boolean sourceMappedOnNull, final Boolean destinationMappedOnNull) {
+            final boolean byDefault, final Boolean sourceMappedOnNull, final Boolean destinationMappedOnNull, final Boolean destinationValueRetrievedOnMapping) {
         
         this(classMapBuilder, classMapBuilder.resolveProperty(aType, a), classMapBuilder.resolveProperty(bType, b), byDefault,
-                sourceMappedOnNull, destinationMappedOnNull);
+                sourceMappedOnNull, destinationMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -92,13 +94,14 @@ public class FieldMapBuilder<A, B> {
      *            whether this FieldMapBuilder was generated by default or not
      */
     FieldMapBuilder(final ClassMapBuilder<A, B> classMapBuilder, final Property a, final Property b, final boolean byDefault,
-            final Boolean sourceMappedOnNull, final Boolean destinationMappedOnNull) {
+            final Boolean sourceMappedOnNull, final Boolean destinationMappedOnNull, final Boolean destinationValueRetrievedOnMapping) {
         this.classMapBuilder = classMapBuilder;
         this.byDefault = byDefault;
         this.aProperty = a;
         this.bProperty = b;
         this.sourceMappedOnNull = sourceMappedOnNull;
         this.destinationMappedOnNull = destinationMappedOnNull;
+        this.destinationValueRetrievedOnMapping = destinationValueRetrievedOnMapping;
     }
     
     /**
@@ -181,7 +184,7 @@ public class FieldMapBuilder<A, B> {
     
     private FieldMap toFieldMap() {
         return new FieldMap(aProperty, bProperty, aInverseProperty, bInverseProperty, mappingDirection, excluded, converterId, byDefault,
-                sourceMappedOnNull, destinationMappedOnNull);
+                sourceMappedOnNull, destinationMappedOnNull, destinationValueRetrievedOnMapping);
     }
     
     /**
@@ -309,7 +312,7 @@ public class FieldMapBuilder<A, B> {
         
         Property bProperty = aProperty.copy(bType);
         
-        return new FieldMap(aProperty, bProperty, null, null, MappingDirection.A_TO_B, false, null, false, null, null);
+        return new FieldMap(aProperty, bProperty, null, null, MappingDirection.A_TO_B, false, null, false, null, null, null);
     }
     
     /**
@@ -325,6 +328,6 @@ public class FieldMapBuilder<A, B> {
         
         Property bProperty = aProperty.copy(bType);
         
-        return new FieldMap(aProperty, bProperty, null, null, MappingDirection.A_TO_B, false, null, false, null, null);
+        return new FieldMap(aProperty, bProperty, null, null, MappingDirection.A_TO_B, false, null, false, null, null, null);
     }
 }


Test patch:
diff --git a/tests/src/main/java/ma/glasnost/orika/test/generator/RetrieveDestinationValueOnMappingTestCase.java b/tests/src/main/java/ma/glasnost/orika/test/generator/RetrieveDestinationValueOnMappingTestCase.java
new file mode 100644
index 00000000..5ef7ce82
--- /dev/null
+++ b/tests/src/main/java/ma/glasnost/orika/test/generator/RetrieveDestinationValueOnMappingTestCase.java
@@ -0,0 +1,305 @@
+/*
+ * Orika - simpler, better and faster Java bean mapping
+ *
+ * Copyright (C) 2011-2019 Orika 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 ma.glasnost.orika.test.generator;
+
+import com.google.common.collect.ImmutableMap;
+import ma.glasnost.orika.MapperFactory;
+import ma.glasnost.orika.impl.DefaultMapperFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class RetrieveDestinationValueOnMappingTestCase {
+
+    public static class Container1 {
+        private long longValue1;
+        private String stringValue1;
+        private List<String> listOfString1;
+        private String[] arrayOfString1;
+        private int[] arrayOfInt1;
+        private Map<String, Object> map1;
+        private MapNullsTestCase.Position enumValue1;
+
+        public long getLongValue() {
+            return longValue1;
+        }
+
+        public void setLongValue(long longValue) {
+            this.longValue1 = longValue;
+        }
+
+        public String getStringValue() {
+            return stringValue1;
+        }
+
+        public void setStringValue(String stringValue) {
+            this.stringValue1 = stringValue;
+        }
+
+        public List<String> getListOfString() {
+            return listOfString1;
+        }
+
+        public void setListOfString(List<String> listOfString) {
+            this.listOfString1 = listOfString;
+        }
+
+        public String[] getArrayOfString() {
+            return arrayOfString1;
+        }
+
+        public void setArrayOfString(String[] arrayOfString) {
+            this.arrayOfString1 = arrayOfString;
+        }
+
+        public int[] getArrayOfInt() {
+            return arrayOfInt1;
+        }
+
+        public void setArrayOfInt(int[] arrayOfInt) {
+            this.arrayOfInt1 = arrayOfInt;
+        }
+
+        public Map<String, Object> getMap() {
+            return map1;
+        }
+
+        public void setMap(Map<String, Object> map) {
+            this.map1 = map;
+        }
+
+        public MapNullsTestCase.Position getEnumValue() {
+            return enumValue1;
+        }
+
+        public void setEnumValue(MapNullsTestCase.Position enumValue) {
+            this.enumValue1 = enumValue;
+        }
+    }
+
+    public static class Container2 {
+        private long myLongValue;
+        private String myStringValue;
+        private List<String> myListOfString;
+        private String[] myArrayOfString;
+        private int[] myArrayOfInt;
+        private Map<String, Object> myMap;
+        private MapNullsTestCase.Position myEnumValue;
+        private Object myObject;
+
+        private boolean longSet, stringSet, listOfStringSet, arrayOfStringSet, arrayOfIntSet, mapSet, enumSet;
+
+        public long getLongValue() {
+            if(!longSet){
+                throw new RuntimeException();
+            }
+            return myLongValue;
+        }
+
+        public void setLongValue(long longValue) {
+            this.myLongValue = longValue;
+            longSet = true;
+        }
+
+        public String getStringValue() {
+            if(!stringSet){
+                throw new RuntimeException();
+            }
+            return myStringValue;
+        }
+
+        public void setStringValue(String stringValue) {
+            this.myStringValue = stringValue;
+            stringSet = true;
+        }
+
+        public List<String> getListOfString() {
+            if(!listOfStringSet){
+                throw new RuntimeException();
+            }
+            return myListOfString;
+        }
+
+        public void setListOfString(List<String> listOfString) {
+            this.myListOfString = listOfString;
+            listOfStringSet = true;
+        }
+
+        public String[] getArrayOfString() {
+            if(!arrayOfStringSet){
+                throw new RuntimeException();
+            }
+            return myArrayOfString;
+        }
+
+        public void setArrayOfString(String[] arrayOfString) {
+            this.myArrayOfString = arrayOfString;
+            arrayOfStringSet = true;
+        }
+
+        public int[] getArrayOfInt() {
+            if(!arrayOfIntSet){
+                throw new RuntimeException();
+            }
+            return myArrayOfInt;
+        }
+
+        public void setArrayOfInt(int[] arrayOfInt) {
+            this.myArrayOfInt = arrayOfInt;
+            arrayOfIntSet = true;
+        }
+
+        public Map<String, Object> getMap() {
+            if(!mapSet){
+                throw new RuntimeException();
+            }
+            return myMap;
+        }
+
+        public void setMap(Map<String, Object> map) {
+            this.myMap = map;
+            mapSet = true;
+        }
+
+        public MapNullsTestCase.Position getEnumValue() {
+            if(!enumSet){
+                throw new RuntimeException();
+            }
+            return myEnumValue;
+        }
+
+        public void setEnumValue(MapNullsTestCase.Position enumValue) {
+            this.myEnumValue = enumValue;
+            enumSet = true;
+        }
+
+        public Object getMyObject() {
+            return myObject;
+        }
+
+        public void setMyObject(Object myObject) {
+            this.myObject = myObject;
+        }
+    }
+
+    @Test
+    public void shouldMapWithDisabledRetrievalOfDestinationValueOnMapping(){
+
+        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().captureFieldContext(true)
+                .getDestinationOnMapping(false).build();
+        mapperFactory.classMap(Container1.class, Container2.class)
+                .mapNulls(true)
+                .mapNullsInReverse(true)
+                .byDefault()
+                .register();
+
+        Container1 container1 = buildContainer1();
+        Container2 container2 = new Container2();
+        Object myObject = new Object();
+        container2.setMyObject(myObject);
+        mapperFactory.getMapperFacade().map(container1, container2);
+
+        assertContainersEquals(container1, container2);
+        Assert.assertEquals(myObject, container2.getMyObject());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void shouldFailWithEnabledRetrievalOfDestinationValueOnMapping(){
+
+        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().captureFieldContext(true).build();
+        mapperFactory.classMap(Container1.class, Container2.class)
+                .mapNulls(true)
+                .mapNullsInReverse(true)
+                .byDefault()
+                .register();
+
+        Container1 container1 = buildContainer1();
+        Container2 container2 = new Container2();
+        Object myObject = new Object();
+        container2.setMyObject(myObject);
+
+        mapperFactory.getMapperFacade().map(container1, container2);
+    }
+
+    @Test
+    public void shouldMapWithEnabledRetrievalOfDestinationValueOnMapping(){
+
+        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().captureFieldContext(true).build();
+        mapperFactory.classMap(Container1.class, Container2.class)
+                .mapNulls(true)
+                .mapNullsInReverse(true)
+                .byDefault()
+                .register();
+
+        Container1 container1 = buildContainer1();
+        Container2 container2 = buildContainer2();
+        Object myObject = new Object();
+        container2.setMyObject(myObject);
+
+        mapperFactory.getMapperFacade().map(container1, container2);
+
+        assertContainersEquals(container1, container2);
+        Assert.assertEquals(myObject, container2.getMyObject());
+    }
+
+    private void assertContainersEquals(Container1 container1, Container2 container2){
+
+        Assert.assertEquals(container1.getLongValue(), container2.getLongValue());
+        Assert.assertEquals(container1.getStringValue(), container2.getStringValue());
+        Assert.assertEquals(container1.getListOfString(), container2.getListOfString());
+        Assert.assertArrayEquals(container1.getArrayOfInt(), container2.getArrayOfInt());
+        Assert.assertArrayEquals(container1.getArrayOfString(), container2.getArrayOfString());
+        Assert.assertEquals(container1.getMap(), container2.getMap());
+        Assert.assertEquals(container1.getEnumValue(), container2.getEnumValue());
+    }
+
+    private Container1 buildContainer1(){
+
+        Container1 container1 = new Container1();
+
+        container1.longValue1 = 1L;
+        container1.stringValue1 = "TEST A";
+        container1.arrayOfString1 = new String[]{"a", "b", "c"};
+        container1.arrayOfInt1 = new int[] {1,2,3};
+        container1.listOfString1 = Arrays.asList("l1","l2");
+        container1.map1 = Collections.singletonMap("key1", "value1");
+        container1.enumValue1 = MapNullsTestCase.Position.FIRST;
+
+        return container1;
+    }
+
+    private Container2 buildContainer2(){
+
+        Container2 container2 = new Container2();
+
+        container2.setLongValue(0L);
+        container2.setStringValue("TEST B");
+        container2.setArrayOfString(new String[]{"d", "e"});
+        container2.setArrayOfInt(new int[] {4, 5});
+
+        container2.setListOfString(Arrays.asList("l3","l4","l5"));
+        container2.setMap(ImmutableMap.of("key2", "value2", "key3", 3));
+        container2.setEnumValue(MapNullsTestCase.Position.LAST);
+
+        return container2;
+    }
+}