diff --git a/core-jdk8/src/main/java/org/mapstruct/Mapping.java b/core-jdk8/src/main/java/org/mapstruct/Mapping.java index d9c716fb2..60ce8795c 100644 --- a/core-jdk8/src/main/java/org/mapstruct/Mapping.java +++ b/core-jdk8/src/main/java/org/mapstruct/Mapping.java @@ -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; } diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 7bd95d7e3..e968d51b4 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -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; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java index 25c6d0b5c..492de5967 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java @@ -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> fromMappingsPrism(MappingsPrism mappingsAnnotation, Element element, Messager messager) { Map> mappings = new HashMap>(); @@ -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 diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index 7056bfb8e..a9705109d 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -483,9 +483,9 @@ public class MapperCreationProcessor implements ModelElementProcessor propertyMappings = new ArrayList(); Set mappedTargetProperties = new HashSet(); + Set ignoredTargetProperties = new HashSet(); if ( !reportErrorIfMappedPropertiesDontExist( method ) ) { return null; @@ -501,6 +501,11 @@ public class MapperCreationProcessor implements ModelElementProcessor targetProperties, - Set mappedTargetProperties) { + Set mappedTargetProperties, + Set ignoredTargetProperties) { - if ( targetProperties.size() > mappedTargetProperties.size() && - unmappedTargetPolicy.requiresReport() ) { - targetProperties.removeAll( mappedTargetProperties ); + Set unmappedTargetProperties = new HashSet(); + + 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 mappedProperties : method.getMappings().values() ) { for ( Mapping mappedProperty : mappedProperties ) { + if ( mappedProperty.isIgnored() ) { + continue; + } + if ( mappedProperty.getSourceParameterName() != null ) { Parameter sourceParameter = method.getSourceParameter( mappedProperty.getSourceParameterName() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java index 415a0773c..b6502586b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java @@ -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); diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/Animal.java new file mode 100644 index 000000000..d3fd00dc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/Animal.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/AnimalDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/AnimalDto.java new file mode 100644 index 000000000..3bafc18e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/AnimalDto.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/AnimalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/AnimalMapper.java new file mode 100644 index 000000000..4a2bfe2f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/AnimalMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java new file mode 100644 index 000000000..2c823910c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java @@ -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(); + } +}