#72 Adding Mapping#ignore() to exclude specified properties from mapping

This commit is contained in:
Gunnar Morling 2014-06-26 22:58:53 +02:00
parent 68282180b9
commit 1c18516793
9 changed files with 293 additions and 16 deletions

View File

@ -93,4 +93,14 @@ public @interface Mapping {
* @return A constant {@code String} constant specifying the value for the designated target property
*/
String expression() default "";
/**
* Whether the property specified via {@link #source()} or {@link #target()} should be ignored by the generated
* mapping method or not. This can be useful when certain attributes should not be propagated from source or target
* or when properties in the target object are populated using a decorator and thus would be reported as unmapped
* target property by default.
*
* @return {@code true} if the given property should be ignored, {@code false} otherwise
*/
boolean ignore() default false;
}

View File

@ -92,4 +92,13 @@ public @interface Mapping {
*/
String expression() default "";
/**
* Whether the property specified via {@link #source()} or {@link #target()} should be ignored by the generated
* mapping method or not. This can be useful when certain attributes should not be propagated from source or target
* or when properties in the target object are populated using a decorator and thus would be reported as unmapped
* target property by default.
*
* @return {@code true} if the given property should be ignored, {@code false} otherwise
*/
boolean ignore() default false;
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.processing.Messager;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
@ -51,10 +52,12 @@ public class Mapping {
private final String javaExpression;
private final String targetName;
private final String dateFormat;
private final boolean isIgnored;
private final AnnotationMirror mirror;
private final AnnotationValue sourceAnnotationValue;
private final AnnotationValue targetAnnotationValue;
public static Map<String, List<Mapping>> fromMappingsPrism(MappingsPrism mappingsAnnotation, Element element,
Messager messager) {
Map<String, List<Mapping>> mappings = new HashMap<String, List<Mapping>>();
@ -81,8 +84,9 @@ public class Mapping {
);
if ( mappingPrism.source().isEmpty() &&
mappingPrism.constant().isEmpty() &&
mappingPrism.expression().isEmpty() ) {
mappingPrism.constant().isEmpty() &&
mappingPrism.expression().isEmpty() &&
!mappingPrism.ignore() ) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Either define a source, a constant or an expression in a Mapping",
@ -122,6 +126,7 @@ public class Mapping {
mappingPrism.expression(),
mappingPrism.target(),
mappingPrism.dateFormat(),
mappingPrism.ignore(),
mappingPrism.mirror,
mappingPrism.values.source(),
mappingPrism.values.target()
@ -147,8 +152,9 @@ public class Mapping {
return parts;
}
//CHECKSTYLE:OFF
private Mapping(String sourceName, String sourceParameterName, String sourcePropertyName, String constant,
String expression, String targetName, String dateFormat, AnnotationMirror mirror,
String expression, String targetName, String dateFormat, boolean isIgnored, AnnotationMirror mirror,
AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue) {
this.sourceName = sourceName;
this.sourceParameterName = sourceParameterName;
@ -159,10 +165,12 @@ public class Mapping {
this.javaExpression = javaExpressionMatcher.matches() ? javaExpressionMatcher.group( 1 ).trim() : "";
this.targetName = targetName.equals( "" ) ? sourceName : targetName;
this.dateFormat = dateFormat;
this.isIgnored = isIgnored;
this.mirror = mirror;
this.sourceAnnotationValue = sourceAnnotationValue;
this.targetAnnotationValue = targetAnnotationValue;
}
//CHECKSTYLE:ON
/**
* Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or
@ -208,6 +216,10 @@ public class Mapping {
return dateFormat;
}
public boolean isIgnored() {
return isIgnored;
}
public AnnotationMirror getMirror() {
return mirror;
}
@ -220,11 +232,10 @@ public class Mapping {
return targetAnnotationValue;
}
public Mapping reverse() {
Mapping reverse = null;
if ( constant != null ) {
/* mapping can only be reversed if the source was not a constant */
// mapping can only be reversed if the source was not a constant nor an expression
if ( constant != null && expression != null ) {
reverse = new Mapping(
targetName,
null,
@ -233,6 +244,7 @@ public class Mapping {
expression,
sourceName,
dateFormat,
isIgnored,
mirror,
sourceAnnotationValue,
targetAnnotationValue

View File

@ -483,9 +483,9 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
CollectionMappingStrategy cmStrategy = getEffectiveCollectionMappingStrategy( element );
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
Set<String> mappedTargetProperties = new HashSet<String>();
Set<String> ignoredTargetProperties = new HashSet<String>();
if ( !reportErrorIfMappedPropertiesDontExist( method ) ) {
return null;
@ -501,6 +501,11 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName );
if ( mapping != null && mapping.isIgnored() ) {
ignoredTargetProperties.add( targetPropertyName );
continue;
}
// A target access is in general a setter method on the target object. However, in case of collections,
// the current target accessor can also be a getter method.
//
@ -578,7 +583,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
method,
unmappedTargetPolicy,
targetProperties,
mappedTargetProperties
mappedTargetProperties,
ignoredTargetProperties
);
FactoryMethod factoryMethod = getFactoryMethod( mapperReferences, methods, method.getReturnType() );
@ -588,17 +594,24 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private void reportErrorForUnmappedTargetPropertiesIfRequired(SourceMethod method,
ReportingPolicy unmappedTargetPolicy,
Set<String> targetProperties,
Set<String> mappedTargetProperties) {
Set<String> mappedTargetProperties,
Set<String> ignoredTargetProperties) {
if ( targetProperties.size() > mappedTargetProperties.size() &&
unmappedTargetPolicy.requiresReport() ) {
targetProperties.removeAll( mappedTargetProperties );
Set<String> unmappedTargetProperties = new HashSet<String>();
for ( String property : targetProperties ) {
if ( !mappedTargetProperties.contains( property ) && !ignoredTargetProperties.contains( property ) ) {
unmappedTargetProperties.add( property );
}
}
if ( !unmappedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) {
messager.printMessage(
unmappedTargetPolicy.getDiagnosticKind(),
MessageFormat.format(
"Unmapped target {0,choice,1#property|1<properties}: \"{1}\"",
targetProperties.size(),
Strings.join( targetProperties, ", " )
unmappedTargetProperties.size(),
Strings.join( unmappedTargetProperties, ", " )
),
method.getExecutable()
);
@ -646,6 +659,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
for ( List<Mapping> mappedProperties : method.getMappings().values() ) {
for ( Mapping mappedProperty : mappedProperties ) {
if ( mappedProperty.isIgnored() ) {
continue;
}
if ( mappedProperty.getSourceParameterName() != null ) {
Parameter sourceParameter = method.getSourceParameter( mappedProperty.getSourceParameterName() );

View File

@ -20,15 +20,16 @@ package org.mapstruct.ap.test.decorator;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
@Mapping( target = "name", ignore = true )
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);

View File

@ -0,0 +1,59 @@
/**
* 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.ignore;
public class Animal {
private String name;
private int size;
private Integer age;
public Animal() {
}
public Animal(String name, int size, Integer age) {
this.name = name;
this.size = size;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}

View File

@ -0,0 +1,60 @@
/**
* 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.ignore;
public class AnimalDto {
private String name;
private Integer size;
private Integer age;
public AnimalDto() {
}
public AnimalDto(String name, Integer size, Integer age) {
this.name = name;
this.size = size;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}

View File

@ -0,0 +1,38 @@
/**
* 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.ignore;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AnimalMapper {
AnimalMapper INSTANCE = Mappers.getMapper( AnimalMapper.class );
@Mappings({
@Mapping(source = "size", ignore = true),
@Mapping(target = "age", ignore = true)
})
AnimalDto animalToDto(Animal animal);
Animal animalDtoToAnimal(AnimalDto animalDto);
}

View File

@ -0,0 +1,71 @@
/**
* 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.ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.fest.assertions.Assertions.assertThat;
/**
* Test for ignoring properties during the mapping.
*
* @author Gunnar Morling
*/
@WithClasses({ Animal.class, AnimalDto.class, AnimalMapper.class })
@RunWith(AnnotationProcessorTestRunner.class)
public class IgnorePropertyTest {
@Test
@IssueKey("72")
public void shouldNotPropagateIgnoredPropertyGivenViaSourceAttribute() {
Animal animal = new Animal( "Bruno", 100, 23 );
AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal );
assertThat( animalDto ).isNotNull();
assertThat( animalDto.getName() ).isEqualTo( "Bruno" );
assertThat( animalDto.getSize() ).isNull();
}
@Test
@IssueKey("72")
public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() {
Animal animal = new Animal( "Bruno", 100, 23 );
AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal );
assertThat( animalDto ).isNotNull();
assertThat( animalDto.getAge() ).isNull();
}
@Test
@IssueKey("72")
public void shouldNotPropagateIgnoredPropertyInReverseMapping() {
AnimalDto animalDto = new AnimalDto( "Bruno", 100, 23 );
Animal animal = AnimalMapper.INSTANCE.animalDtoToAnimal( animalDto );
assertThat( animal ).isNotNull();
assertThat( animal.getAge() ).isNull();
}
}