diff --git a/core-common/src/main/java/org/mapstruct/MapNullToDefault.java b/core-common/src/main/java/org/mapstruct/MapNullToDefault.java
new file mode 100644
index 000000000..a61fc41e7
--- /dev/null
+++ b/core-common/src/main/java/org/mapstruct/MapNullToDefault.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
+ * and/or other contributors as indicated by the @authors tag. See the
+ * copyright.txt file in the distribution for a full listing of all
+ * contributors.
+ *
+ * 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.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Advises the code generator to apply all the {@link Mapping}s from an inverse mapping method to the annotated method
+ * as well. An inverse mapping method is a method which has the annotated method's source type as target type (return
+ * type or indicated through a parameter annotated with {@link MappingTarget}) and the annotated method's target type as
+ * source type.
+ *
+ * Any mappings given on the annotated method itself are added to those mappings inherited from the inverse method. In
+ * case of a conflict local mappings take precedence over inherited mappings.
+ *
+ * If more than one matching inverse method exists, the name of the method to inherit the configuration from must be
+ * specified via {@link #name()}
+ *
+ * @author Sjaak Derksen
+ */
+@Target( { ElementType.METHOD, ElementType.TYPE } )
+@Retention( RetentionPolicy.SOURCE )
+public @interface MapNullToDefault {
+
+ boolean value() default true;
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java
index 401fd9e40..b7c605146 100644
--- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java
@@ -40,6 +40,7 @@ import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.SourceReference;
import org.mapstruct.ap.option.ReportingPolicy;
+import org.mapstruct.ap.prism.MapNullToDefaultPrism;
import org.mapstruct.ap.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.util.Executables;
import org.mapstruct.ap.util.MapperConfig;
@@ -58,6 +59,7 @@ public class BeanMappingMethod extends MappingMethod {
private final Map> mappingsByParameter;
private final List constantMappings;
private final MethodReference factoryMethod;
+ private final boolean mapNullToDefault;
public static class Builder {
@@ -97,8 +99,12 @@ public class BeanMappingMethod extends MappingMethod {
// report errors on unmapped properties
reportErrorForUnmappedTargetPropertiesIfRequired();
+ // mapNullToDefault
+ MapNullToDefaultPrism prism = MapNullToDefaultPrism.getInstanceOn( method.getExecutable() );
+ boolean mapNullToDefault = ( prism != null ) && prism.value();
+
MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
- return new BeanMappingMethod( method, propertyMappings, factoryMethod );
+ return new BeanMappingMethod( method, propertyMappings, factoryMethod, mapNullToDefault );
}
/**
@@ -472,7 +478,8 @@ public class BeanMappingMethod extends MappingMethod {
private BeanMappingMethod(SourceMethod method,
List propertyMappings,
- MethodReference factoryMethod) {
+ MethodReference factoryMethod,
+ boolean mapNullToDefault ) {
super( method );
this.propertyMappings = propertyMappings;
@@ -491,6 +498,7 @@ public class BeanMappingMethod extends MappingMethod {
}
}
this.factoryMethod = factoryMethod;
+ this.mapNullToDefault = mapNullToDefault;
}
public List getPropertyMappings() {
@@ -505,6 +513,10 @@ public class BeanMappingMethod extends MappingMethod {
return mappingsByParameter;
}
+ public boolean isMapNullToDefault() {
+ return mapNullToDefault;
+ }
+
@Override
public Set getImportTypes() {
Set types = super.getImportTypes();
diff --git a/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java b/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java
index 3178ee7c8..2750f732c 100644
--- a/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java
+++ b/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java
@@ -27,6 +27,7 @@ import org.mapstruct.DecoratedWith;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.IterableMapping;
import org.mapstruct.MapMapping;
+import org.mapstruct.MapNullToDefault;
import org.mapstruct.Mapper;
import org.mapstruct.MapperConfig;
import org.mapstruct.Mapping;
@@ -52,6 +53,7 @@ import org.mapstruct.TargetType;
@GeneratePrism(value = MapperConfig.class, publicAccess = true),
@GeneratePrism(value = InheritInverseConfiguration.class, publicAccess = true),
@GeneratePrism( value = Qualifier.class, publicAccess = true ),
+ @GeneratePrism( value = MapNullToDefault.class, publicAccess = true ),
// external types
@GeneratePrism(value = XmlElementDecl.class, publicAccess = true)
diff --git a/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl
index 2db9a52e9..a02209ed4 100644
--- a/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl
+++ b/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl
@@ -20,9 +20,11 @@
-->
@Override
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, #if>#list>) <@throws/> {
+ <#if !mapNullToDefault>
if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && #if>#list> ) {
return<#if returnType.name != "void"> null#if>;
}
+ #if>
<#if !existingInstanceMapping><@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType raw=true/><#else>new <@includeModel object=resultType/>()#if>;#if>
<#if (sourceParameters?size > 1)>
@@ -43,9 +45,11 @@
#if>
#list>
<#else>
+ <#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) {#if>
<#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
#list>
+ <#if mapNullToDefault>}#if>
#if>
<#list constantMappings as constantMapping>
<@includeModel object=constantMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/CarMapper.java
new file mode 100644
index 000000000..2d3ff0482
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/CarMapper.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
+ * and/or other contributors as indicated by the @authors tag. See the
+ * copyright.txt file in the distribution for a full listing of all
+ * contributors.
+ *
+ * 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.mapstruct.ap.test.mapnulltodefault;
+
+import java.util.UUID;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.MapNullToDefault;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.mapnulltodefault.source.Car;
+import org.mapstruct.ap.test.mapnulltodefault.target.CarDto;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(imports = UUID.class)
+public interface CarMapper {
+
+ CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
+
+ @MapNullToDefault
+ @Mappings({
+ @Mapping(target = "seatCount", source = "numberOfSeats"),
+ @Mapping(target = "model", constant = "ModelT"),
+ @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )")
+ })
+ CarDto carToCarDto(Car car);
+
+ @MapNullToDefault
+ @Mappings({
+ @Mapping(target = "seatCount", source = "car.numberOfSeats"),
+ @Mapping(target = "model", source = "model"), // TODO, should not be needed, must be made based on name only
+ @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )")
+ })
+ CarDto carToCarDto(Car car, String model);
+
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/CarMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/CarMapperTest.java
new file mode 100644
index 000000000..a42408c7d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/CarMapperTest.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
+ * and/or other contributors as indicated by the @authors tag. See the
+ * copyright.txt file in the distribution for a full listing of all
+ * contributors.
+ *
+ * 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.mapstruct.ap.test.mapnulltodefault;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.mapnulltodefault.source.Car;
+import org.mapstruct.ap.test.mapnulltodefault.target.CarDto;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+@WithClasses({
+ Car.class,
+ CarDto.class,
+ CarMapper.class
+})
+@RunWith(AnnotationProcessorTestRunner.class)
+public class CarMapperTest {
+
+ @Test
+ public void shouldProvideMapperInstance() throws Exception {
+ assertThat( CarMapper.INSTANCE ).isNotNull();
+ }
+
+ @Test
+ public void shouldMapExpressionAndConstantRegardlessNullArg() {
+ //given
+ Car car = new Car( "Morris", 2 );
+
+ //when
+ CarDto carDto1 = CarMapper.INSTANCE.carToCarDto( car );
+
+ //then
+ assertThat( carDto1 ).isNotNull();
+ assertThat( carDto1.getMake() ).isEqualTo( car.getMake() );
+ assertThat( carDto1.getSeatCount() ).isEqualTo( car.getNumberOfSeats() );
+ assertThat( carDto1.getModel() ).isEqualTo( "ModelT" );
+ assertThat( carDto1.getCatalogId() ).isNotEmpty();
+
+ //when
+ CarDto carDto2 = CarMapper.INSTANCE.carToCarDto( null );
+
+ //then
+ assertThat( carDto2 ).isNotNull();
+ assertThat( carDto2.getMake() ).isNull();
+ assertThat( carDto2.getSeatCount() ).isEqualTo( 0 );
+ assertThat( carDto2.getModel() ).isEqualTo( "ModelT" );
+ assertThat( carDto2.getCatalogId() ).isNotEmpty();
+ }
+
+ @Test
+ public void shouldMapExpressionAndConstantRegardlessNullArgSeveralSources() {
+ //given
+ Car car = new Car( "Morris", 2 );
+
+ //when
+ CarDto carDto1 = CarMapper.INSTANCE.carToCarDto( car, "ModelT" );
+
+ //then
+ assertThat( carDto1 ).isNotNull();
+ assertThat( carDto1.getMake() ).isEqualTo( car.getMake() );
+ assertThat( carDto1.getSeatCount() ).isEqualTo( car.getNumberOfSeats() );
+ assertThat( carDto1.getCatalogId() ).isNotEmpty();
+
+ //when
+ CarDto carDto2 = CarMapper.INSTANCE.carToCarDto( null, "ModelT" );
+
+ //then
+ assertThat( carDto2 ).isNotNull();
+ assertThat( carDto2.getMake() ).isNull();
+ assertThat( carDto2.getSeatCount() ).isEqualTo( 0 );
+ assertThat( carDto2.getModel() ).isEqualTo( "ModelT" );
+ assertThat( carDto2.getCatalogId() ).isNotEmpty();
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/source/Car.java b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/source/Car.java
new file mode 100644
index 000000000..576f53604
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/source/Car.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
+ * and/or other contributors as indicated by the @authors tag. See the
+ * copyright.txt file in the distribution for a full listing of all
+ * contributors.
+ *
+ * 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.mapstruct.ap.test.mapnulltodefault.source;
+
+
+public class Car {
+
+ private String make;
+ private int numberOfSeats;
+
+
+ public Car() {
+ }
+
+ public Car(String make, int numberOfSeats) {
+ this.make = make;
+ this.numberOfSeats = numberOfSeats;
+
+ }
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public int getNumberOfSeats() {
+ return numberOfSeats;
+ }
+
+ public void setNumberOfSeats(int numberOfSeats) {
+ this.numberOfSeats = numberOfSeats;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/target/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/target/CarDto.java
new file mode 100644
index 000000000..8e4ccb9f1
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mapnulltodefault/target/CarDto.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
+ * and/or other contributors as indicated by the @authors tag. See the
+ * copyright.txt file in the distribution for a full listing of all
+ * contributors.
+ *
+ * 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.mapstruct.ap.test.mapnulltodefault.target;
+
+public class CarDto {
+
+ private String make;
+ private int seatCount;
+ private String model;
+ private String catalogId;
+
+
+ public CarDto() {
+ }
+
+ public CarDto(String make, int seatCount) {
+ this.make = make;
+ this.seatCount = seatCount;
+ }
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public int getSeatCount() {
+ return seatCount;
+ }
+
+ public void setSeatCount(int seatCount) {
+ this.seatCount = seatCount;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel( String model ) {
+ this.model = model;
+ }
+
+ public String getCatalogId() {
+ return catalogId;
+ }
+
+ public void setCatalogId( String catalogId ) {
+ this.catalogId = catalogId;
+ }
+
+}