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

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.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