diff --git a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java index afaba9737..0ea3ee7df 100644 --- a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java @@ -7,6 +7,54 @@ package org.mapstruct; /** * Strategy for propagating the value of collection-typed properties from source to target. + *

+ * In the table below, the dash {@code -} indicates a property name. + * Next, the trailing {@code s} indicates the plural form. + * The table explains the options and how they are applied to the presence / absence of a + * {@code set-s}, {@code add-} and / or {@code get-s} method on the target object. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Collection mapping strategy options
OptionOnly target set-s AvailableOnly target add- AvailableBoth set-s/add- AvailableNo set-s/add- AvailableExisting Target ({@code @TargetType})
{@link #ACCESSOR_ONLY}set-sget-sset-sget-sget-s
{@link #SETTER_PREFERRED}set-sadd-set-sget-sget-s
{@link #ADDER_PREFERRED}set-sadd-add-get-sget-s
{@link #TARGET_IMMUTABLE}set-sexceptionset-sexceptionset-s
* * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6d86aad45..689490626 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -788,6 +788,13 @@ public class Type extends ModelElement implements Comparable { // an adder has been found (according strategy) so overrule current choice. candidate = adderMethod; } + + if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE + && candidate.getAccessorType() == AccessorType.GETTER ) { + // If the collection mapping strategy is target immutable + // then the getter method cannot be used as a setter + continue; + } } else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.isFinal( candidate ) || result.containsKey( targetPropertyName ) ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java new file mode 100644 index 000000000..885d74448 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java @@ -0,0 +1,62 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._2952; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE) +public interface Issue2952Mapper { + + Issue2952Mapper INSTANCE = Mappers.getMapper( Issue2952Mapper.class ); + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final Map attributes = new HashMap<>(); + private final List values = new ArrayList<>(); + private String value; + + public Map getAttributes() { + return Collections.unmodifiableMap( attributes ); + } + + public List getValues() { + return Collections.unmodifiableList( values ); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java new file mode 100644 index 000000000..7a517ec97 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._2952; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2952") +@WithClasses({ + Issue2952Mapper.class +}) +class Issue2952Test { + + @ProcessorTest + void shouldCorrectIgnoreImmutableIterable() { + Issue2952Mapper.Target target = Issue2952Mapper.INSTANCE.map( new Issue2952Mapper.Source( "test" ) ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java index bbf3a6d24..1f2f2f00b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.collection.immutabletarget; +import java.util.ArrayList; import java.util.List; /** @@ -13,7 +14,7 @@ import java.util.List; */ public class CupboardEntityOnlyGetter { - private List content; + private List content = new ArrayList<>(); public List getContent() { return content; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java similarity index 80% rename from processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java index 028ac4d76..a6674e629 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java @@ -15,9 +15,9 @@ import org.mapstruct.factory.Mappers; * @author Sjaak Derksen */ @Mapper( collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE ) -public interface ErroneousCupboardMapper { +public interface CupboardNoSetterMapper { - ErroneousCupboardMapper INSTANCE = Mappers.getMapper( ErroneousCupboardMapper.class ); + CupboardNoSetterMapper INSTANCE = Mappers.getMapper( CupboardNoSetterMapper.class ); void map( CupboardDto in, @MappingTarget CupboardEntityOnlyGetter out ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java index b3d2096bc..de2243629 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java @@ -11,9 +11,6 @@ import java.util.Collections; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; -import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; -import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import static org.assertj.core.api.Assertions.assertThat; @@ -41,19 +38,17 @@ public class ImmutableProductTest { @ProcessorTest @WithClasses({ - ErroneousCupboardMapper.class, + CupboardNoSetterMapper.class, CupboardEntityOnlyGetter.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = ErroneousCupboardMapper.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 22, - message = "No write accessor found for property \"content\" in target type.") - } - ) - public void testShouldFailOnPropertyMappingNoPropertySetterOnlyGetter() { + public void shouldIgnoreImmutableTarget() { + CupboardDto in = new CupboardDto(); + in.setContent( Arrays.asList( "flour", "peas" ) ); + CupboardEntityOnlyGetter out = new CupboardEntityOnlyGetter(); + out.getContent().add( "bread" ); + CupboardNoSetterMapper.INSTANCE.map( in, out ); + + assertThat( out.getContent() ).containsExactly( "bread" ); } }