diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index cd220ac7b..63a9892ee 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -18,6 +18,12 @@ */ package org.mapstruct.ap.internal.model; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED_TYPE_CONVERTED; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -34,6 +40,7 @@ import org.mapstruct.ap.internal.model.assignment.NewCollectionOrMapWrapper; import org.mapstruct.ap.internal.model.assignment.NullCheckWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps; +import org.mapstruct.ap.internal.model.assignment.UpdateNullCheckWrapper; import org.mapstruct.ap.internal.model.assignment.UpdateWrapper; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Parameter; @@ -46,11 +53,6 @@ import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED_TYPE_CONVERTED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; - /** * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to * {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the @@ -260,14 +262,19 @@ public class PropertyMapping extends ModelElement { result = new SetterWrapper( result, method.getThrownTypes() ); } if ( !sourceType.isPrimitive() - && !sourceReference.getPropertyEntries().isEmpty() /* parameter null taken care of by beanmapper */ - && ( result.getType() == TYPE_CONVERTED - || result.getType() == TYPE_CONVERTED_MAPPED - || result.getType() == MAPPED_TYPE_CONVERTED - || ( result.getType() == DIRECT && targetType.isPrimitive() ) ) ) { - // for primitive types null check is not possible at all, but a conversion needs - // a null check. - result = new NullCheckWrapper( result ); + && !sourceReference.getPropertyEntries().isEmpty() ) { // parameter null taken care of by beanmapper + + if ( result.isUpdateMethod() ) { + result = new UpdateNullCheckWrapper( result ); + } + else if ( result.getType() == TYPE_CONVERTED + || result.getType() == TYPE_CONVERTED_MAPPED + || result.getType() == MAPPED_TYPE_CONVERTED + || ( result.getType() == DIRECT && targetType.isPrimitive() ) ) { + // for primitive types null check is not possible at all, but a conversion needs + // a null check. + result = new NullCheckWrapper( result ); + } } } else { @@ -354,6 +361,9 @@ public class PropertyMapping extends ModelElement { if ( result.getType() == DIRECT ) { result = new NullCheckWrapper( result ); } + else if ( result.getType() == MAPPED && result.isUpdateMethod() ) { + result = new UpdateNullCheckWrapper( result ); + } return result; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.java new file mode 100644 index 000000000..ff3e3b613 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.java @@ -0,0 +1,32 @@ +/** + * Copyright 2012-2015 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.internal.model.assignment; + +/** + * Wraps an update-assignment in a null check and sets the target property to {@code null}, if the source is + * {@code null}. + * + * @author Andreas Gudian + */ +public class UpdateNullCheckWrapper extends AssignmentWrapper { + + public UpdateNullCheckWrapper( Assignment decoratedAssignment ) { + super( decoratedAssignment ); + } +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.ftl new file mode 100644 index 000000000..1f0964d68 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.ftl @@ -0,0 +1,31 @@ +<#-- + + Copyright 2012-2015 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. + +--> +if ( ${sourceReference} != null ) { + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetType=ext.targetType/> +} +else { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}( null ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java b/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java index 0c0bc5125..fc187d20e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java @@ -20,9 +20,6 @@ package org.mapstruct.ap.test.updatemethods; import org.junit.Test; import org.junit.runner.RunWith; - -import static org.fest.assertions.Assertions.assertThat; - import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -30,6 +27,8 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import static org.fest.assertions.Assertions.assertThat; + /** * * @author Sjaak Derksen @@ -85,6 +84,30 @@ public class UpdateMethodsTest { @WithClasses( { OrganizationMapper.class } ) + public void testUpdateMethodClearsExistingValues() { + + OrganizationEntity organizationEntity = new OrganizationEntity(); + CompanyEntity companyEntity = new CompanyEntity(); + organizationEntity.setCompany( companyEntity ); + companyEntity.setName( "CocaCola" ); + DepartmentEntity department = new DepartmentEntity( null ); + department.setName( "recipies" ); + companyEntity.setDepartment( department ); + + OrganizationDto organizationDto = new OrganizationDto(); + organizationDto.setCompany( null ); + + OrganizationMapper.INSTANCE.toOrganizationEntity( organizationDto, organizationEntity ); + + assertThat( organizationEntity.getCompany() ).isNull(); + assertThat( organizationEntity.getType().getType() ).isEqualTo( "commercial" ); + assertThat( organizationEntity.getTypeNr().getNumber() ).isEqualTo( 5 ); + } + + @Test + @WithClasses({ + OrganizationMapper.class + }) public void testPreferUpdateMethodSourceObjectNotDefined() { OrganizationEntity organizationEntity = new OrganizationEntity(); @@ -105,7 +128,7 @@ public class UpdateMethodsTest { assertThat( organizationEntity.getCompany().getDepartment().getName() ).isEqualTo( "finance" ); } - @Test + @Test @WithClasses( { CompanyMapper.class, DepartmentInBetween.class @@ -156,7 +179,7 @@ public class UpdateMethodsTest { } ) public void testShouldFailOnConstantMappingNoPropertyGetter() { } - @Test + @Test @WithClasses( { ErroneousCompanyMapper1.class, DepartmentInBetween.class