#596 Add null-check on source for update-method invocations and set target property to null, if source is null

This commit is contained in:
Andreas Gudian 2015-07-14 20:36:47 +02:00 committed by Gunnar Morling
parent abecb0d888
commit 1d1b215a93
4 changed files with 114 additions and 18 deletions

View File

@ -18,6 +18,12 @@
*/ */
package org.mapstruct.ap.internal.model; 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.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.NullCheckWrapper;
import org.mapstruct.ap.internal.model.assignment.SetterWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapper;
import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps; 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.assignment.UpdateWrapper;
import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Parameter; 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.Message;
import org.mapstruct.ap.internal.util.Strings; 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 * 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 * {@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() ); result = new SetterWrapper( result, method.getThrownTypes() );
} }
if ( !sourceType.isPrimitive() if ( !sourceType.isPrimitive()
&& !sourceReference.getPropertyEntries().isEmpty() /* parameter null taken care of by beanmapper */ && !sourceReference.getPropertyEntries().isEmpty() ) { // parameter null taken care of by beanmapper
&& ( result.getType() == TYPE_CONVERTED
|| result.getType() == TYPE_CONVERTED_MAPPED if ( result.isUpdateMethod() ) {
|| result.getType() == MAPPED_TYPE_CONVERTED result = new UpdateNullCheckWrapper( result );
|| ( result.getType() == DIRECT && targetType.isPrimitive() ) ) ) { }
// for primitive types null check is not possible at all, but a conversion needs else if ( result.getType() == TYPE_CONVERTED
// a null check. || result.getType() == TYPE_CONVERTED_MAPPED
result = new NullCheckWrapper( result ); || 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 { else {
@ -354,6 +361,9 @@ public class PropertyMapping extends ModelElement {
if ( result.getType() == DIRECT ) { if ( result.getType() == DIRECT ) {
result = new NullCheckWrapper( result ); result = new NullCheckWrapper( result );
} }
else if ( result.getType() == MAPPED && result.isUpdateMethod() ) {
result = new UpdateNullCheckWrapper( result );
}
return result; return result;
} }

View File

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

View File

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

View File

@ -20,9 +20,6 @@ package org.mapstruct.ap.test.updatemethods;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.fest.assertions.Assertions.assertThat;
import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; 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.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.fest.assertions.Assertions.assertThat;
/** /**
* *
* @author Sjaak Derksen * @author Sjaak Derksen
@ -85,6 +84,30 @@ public class UpdateMethodsTest {
@WithClasses( { @WithClasses( {
OrganizationMapper.class 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() { public void testPreferUpdateMethodSourceObjectNotDefined() {
OrganizationEntity organizationEntity = new OrganizationEntity(); OrganizationEntity organizationEntity = new OrganizationEntity();
@ -105,7 +128,7 @@ public class UpdateMethodsTest {
assertThat( organizationEntity.getCompany().getDepartment().getName() ).isEqualTo( "finance" ); assertThat( organizationEntity.getCompany().getDepartment().getName() ).isEqualTo( "finance" );
} }
@Test @Test
@WithClasses( { @WithClasses( {
CompanyMapper.class, CompanyMapper.class,
DepartmentInBetween.class DepartmentInBetween.class
@ -156,7 +179,7 @@ public class UpdateMethodsTest {
} ) } )
public void testShouldFailOnConstantMappingNoPropertyGetter() { } public void testShouldFailOnConstantMappingNoPropertyGetter() { }
@Test @Test
@WithClasses( { @WithClasses( {
ErroneousCompanyMapper1.class, ErroneousCompanyMapper1.class,
DepartmentInBetween.class DepartmentInBetween.class