#295 NullValueMapping for BeanMapping on mapping method level

This commit is contained in:
sjaakd 2014-11-14 20:00:38 +01:00
parent 77937259b5
commit 24dc73765f
8 changed files with 335 additions and 2 deletions

View File

@ -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.
* <p>
* 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.
* <p>
* 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;
}

View File

@ -40,6 +40,7 @@ import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.SourceReference; import org.mapstruct.ap.model.source.SourceReference;
import org.mapstruct.ap.option.ReportingPolicy; import org.mapstruct.ap.option.ReportingPolicy;
import org.mapstruct.ap.prism.MapNullToDefaultPrism;
import org.mapstruct.ap.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.util.Executables; import org.mapstruct.ap.util.Executables;
import org.mapstruct.ap.util.MapperConfig; import org.mapstruct.ap.util.MapperConfig;
@ -58,6 +59,7 @@ public class BeanMappingMethod extends MappingMethod {
private final Map<String, List<PropertyMapping>> mappingsByParameter; private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final List<PropertyMapping> constantMappings; private final List<PropertyMapping> constantMappings;
private final MethodReference factoryMethod; private final MethodReference factoryMethod;
private final boolean mapNullToDefault;
public static class Builder { public static class Builder {
@ -97,8 +99,12 @@ public class BeanMappingMethod extends MappingMethod {
// report errors on unmapped properties // report errors on unmapped properties
reportErrorForUnmappedTargetPropertiesIfRequired(); reportErrorForUnmappedTargetPropertiesIfRequired();
// mapNullToDefault
MapNullToDefaultPrism prism = MapNullToDefaultPrism.getInstanceOn( method.getExecutable() );
boolean mapNullToDefault = ( prism != null ) && prism.value();
MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx ); 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, private BeanMappingMethod(SourceMethod method,
List<PropertyMapping> propertyMappings, List<PropertyMapping> propertyMappings,
MethodReference factoryMethod) { MethodReference factoryMethod,
boolean mapNullToDefault ) {
super( method ); super( method );
this.propertyMappings = propertyMappings; this.propertyMappings = propertyMappings;
@ -491,6 +498,7 @@ public class BeanMappingMethod extends MappingMethod {
} }
} }
this.factoryMethod = factoryMethod; this.factoryMethod = factoryMethod;
this.mapNullToDefault = mapNullToDefault;
} }
public List<PropertyMapping> getPropertyMappings() { public List<PropertyMapping> getPropertyMappings() {
@ -505,6 +513,10 @@ public class BeanMappingMethod extends MappingMethod {
return mappingsByParameter; return mappingsByParameter;
} }
public boolean isMapNullToDefault() {
return mapNullToDefault;
}
@Override @Override
public Set<Type> getImportTypes() { public Set<Type> getImportTypes() {
Set<Type> types = super.getImportTypes(); Set<Type> types = super.getImportTypes();

View File

@ -27,6 +27,7 @@ import org.mapstruct.DecoratedWith;
import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.IterableMapping; import org.mapstruct.IterableMapping;
import org.mapstruct.MapMapping; import org.mapstruct.MapMapping;
import org.mapstruct.MapNullToDefault;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.MapperConfig; import org.mapstruct.MapperConfig;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@ -52,6 +53,7 @@ import org.mapstruct.TargetType;
@GeneratePrism(value = MapperConfig.class, publicAccess = true), @GeneratePrism(value = MapperConfig.class, publicAccess = true),
@GeneratePrism(value = InheritInverseConfiguration.class, publicAccess = true), @GeneratePrism(value = InheritInverseConfiguration.class, publicAccess = true),
@GeneratePrism( value = Qualifier.class, publicAccess = true ), @GeneratePrism( value = Qualifier.class, publicAccess = true ),
@GeneratePrism( value = MapNullToDefault.class, publicAccess = true ),
// external types // external types
@GeneratePrism(value = XmlElementDecl.class, publicAccess = true) @GeneratePrism(value = XmlElementDecl.class, publicAccess = true)

View File

@ -20,9 +20,11 @@
--> -->
@Override @Override
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) <@throws/> { <#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> ) { if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
return<#if returnType.name != "void"> null</#if>; 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 !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)> <#if (sourceParameters?size > 1)>
@ -43,9 +45,11 @@
</#if> </#if>
</#list> </#list>
<#else> <#else>
<#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) {</#if>
<#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping> <#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
</#list> </#list>
<#if mapNullToDefault>}</#if>
</#if> </#if>
<#list constantMappings as constantMapping> <#list constantMappings as constantMapping>
<@includeModel object=constantMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> <@includeModel object=constantMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}