From 2844c60821682a766ceccb77d4a20ce96240ce6f Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Wed, 30 Dec 2020 10:39:23 +0100 Subject: [PATCH 01/11] #1075, Mapping from Maps to Beans -> Accessor approach --- .../ap/internal/model/BeanMappingMethod.java | 2 +- .../ap/internal/model/PropertyMapping.java | 6 ++ .../model/beanmapping/PropertyEntry.java | 1 + .../model/beanmapping/SourceReference.java | 40 +++++++++++- .../ap/internal/util/ValueProvider.java | 5 ++ .../internal/util/accessor/AccessorType.java | 1 + .../util/accessor/MapValueAccessor.java | 61 +++++++++++++++++++ .../accessor/MapValuePresenceChecker.java | 59 ++++++++++++++++++ .../ap/test/frommap/FromMapMappingTest.java | 40 ++++++++++++ .../org/mapstruct/ap/test/frommap/Source.java | 34 +++++++++++ .../ap/test/frommap/SourceTargetMapper.java | 30 +++++++++ .../org/mapstruct/ap/test/frommap/Target.java | 27 ++++++++ 12 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 7ea17bf27b..536d601241 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -237,7 +237,7 @@ else if ( !method.isUpdateMethod() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { + if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() || sourceParameter.getType().isMapType()) { continue; } Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 61e1fca864..e012775423 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -612,6 +612,12 @@ private String getSourcePresenceCheckerRef( SourceReference sourceReference ) { // in the forged method? PropertyEntry propertyEntry = sourceReference.getShallowestProperty(); if ( propertyEntry.getPresenceChecker() != null ) { + + if (propertyEntry.getPresenceChecker().getAccessorType() == AccessorType.MAP) { + return sourceParam.getName() + + "." + "containsKey( \"" + propertyEntry.getPresenceChecker().getSimpleName() + "\" )"; + } + sourcePresenceChecker = sourceParam.getName() + "." + propertyEntry.getPresenceChecker().getSimpleName() + "()"; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java index 8ac5e46671..a785d89854 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java @@ -50,6 +50,7 @@ public static PropertyEntry forSourceReference(String[] name, Accessor readAcces return new PropertyEntry( name, readAccessor, presenceChecker, type ); } + public String getName() { return fullName[fullName.length - 1]; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 1d747c190e..1af49c581b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -7,12 +7,14 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; @@ -23,6 +25,8 @@ import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; +import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker; import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference; import static org.mapstruct.ap.internal.util.Collections.last; @@ -51,6 +55,8 @@ */ public class SourceReference extends AbstractReference { + private final String sourceName; + /** * Builds a {@link SourceReference} from an {@code @Mappping}. */ @@ -120,7 +126,7 @@ public SourceReference build() { String[] segments = sourceNameTrimmed.split( "\\." ); // start with an invalid source reference - SourceReference result = new SourceReference( null, new ArrayList<>( ), false ); + SourceReference result = new SourceReference( null, new ArrayList<>( ), false); if ( method.getSourceParameters().size() > 1 ) { Parameter parameter = fetchMatchingParameterFromFirstSegment( segments ); if ( parameter != null ) { @@ -148,6 +154,10 @@ public SourceReference build() { */ private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { + if (parameter.getType().isMapType()) { + return forMapSource(segments, parameter); + } + boolean foundEntryMatch; String[] propertyNames = segments; @@ -174,6 +184,23 @@ private SourceReference buildFromSingleSourceParameters(String[] segments, Param return new SourceReference( parameter, entries, foundEntryMatch ); } + private SourceReference forMapSource(String[] segments, Parameter parameter) { + Type valueType = null; + final List typeParameters = parameter.getType().getTypeParameters(); + if (typeParameters.size() == 2) { + valueType = typeParameters.get(1); + } else { + //TODO: this should be a type mirror of Object by default + valueType = parameter.getType(); + } + MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); + MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); + List entries = Collections.singletonList( + PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType) + ); + return new SourceReference( parameter, entries, true ); + } + /** * When there are more than one source parameters, the first segment name of the property * needs to match the parameter name to avoid ambiguity @@ -184,6 +211,10 @@ private SourceReference buildFromSingleSourceParameters(String[] segments, Param */ private SourceReference buildFromMultipleSourceParameters(String[] segments, Parameter parameter) { + if (parameter.getType().isMapType()) { + return forMapSource(segments, parameter); + } + boolean foundEntryMatch; String[] propertyNames = new String[0]; @@ -400,8 +431,13 @@ public SourceReference build() { } } - private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { + private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid, String sourceName) { super( sourceParameter, sourcePropertyEntries, isValid ); + this.sourceName = sourceName; + } + + private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { + this( sourceParameter, sourcePropertyEntries, isValid, null ); } public SourceReference pop() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java index bbd73943db..2b574e4419 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.util; import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; /** * This a wrapper class which provides the value that needs to be used in the models. @@ -45,6 +46,10 @@ public static ValueProvider of(Accessor accessor) { return null; } String value = accessor.getSimpleName(); + if (accessor.getAccessorType() == AccessorType.MAP) { + value = "get( \"" + value + "\" )"; + return new ValueProvider( value ); + } if ( !accessor.getAccessorType().isFieldAssignment() ) { value += "()"; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java index 23448a9e98..fe6c84e2ce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java @@ -12,6 +12,7 @@ public enum AccessorType { GETTER, SETTER, ADDER, + MAP, PRESENCE_CHECKER; public boolean isFieldAssignment() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java new file mode 100644 index 0000000000..5f42212715 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java @@ -0,0 +1,61 @@ +/* + * 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.internal.util.accessor; + +import org.mapstruct.ap.internal.model.common.Type; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * An {@link Accessor} that wraps a Map value. + * + * @author Christian Kosmowski + */ +public class MapValueAccessor implements Accessor { + + private final Type mapType; + private final TypeMirror valueTypeMirror; + private final String simpleName; + + public MapValueAccessor(Type mapType, TypeMirror valueTypeMirror, String simpleName) { + this.mapType = mapType; + this.valueTypeMirror = valueTypeMirror; + this.simpleName = simpleName; + } + + @Override + public TypeMirror getAccessedType() { + return valueTypeMirror; + } + + @Override + public String getSimpleName() { + return this.simpleName; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public Element getElement() { + return mapType.getTypeElement().getEnclosedElements().stream().filter(element -> { + return element.getSimpleName().contentEquals("get"); + }).findFirst().orElse(null); + } + + @Override + public AccessorType getAccessorType() { + return AccessorType.MAP; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java new file mode 100644 index 0000000000..b712214014 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java @@ -0,0 +1,59 @@ +/* + * 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.internal.util.accessor; + +import org.mapstruct.ap.internal.model.common.Type; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; +import java.util.Collections; +import java.util.Set; + +/** + * An {@link Accessor} that wraps a Map value. + * + * @author Christian Kosmowski + */ +public class MapValuePresenceChecker implements Accessor { + + private final Type mapType; + private final TypeMirror valueTypeMirror; + private final String simpleName; + + public MapValuePresenceChecker(Type mapType, TypeMirror valueTypeMirror, String simpleName) { + this.mapType = mapType; + this.valueTypeMirror = valueTypeMirror; + this.simpleName = simpleName; + } + + @Override + public TypeMirror getAccessedType() { + return valueTypeMirror; + } + + @Override + public String getSimpleName() { + return this.simpleName; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public Element getElement() { + return mapType.getTypeElement().getEnclosedElements().stream().filter(element -> { + return element.getSimpleName().contentEquals("containsKey"); + }).findFirst().orElse(null); + } + + @Override + public AccessorType getAccessorType() { + return AccessorType.MAP; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java new file mode 100644 index 0000000000..48f7c4812d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -0,0 +1,40 @@ +/* + * 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.frommap; + +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 java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Kosmowski + */ +@RunWith(AnnotationProcessorTestRunner.class) +@IssueKey( "1075" ) +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +public class FromMapMappingTest { + + @Test + public void shouldMapSourceToTarget() { + Map sourceMap = new HashMap<>(); + sourceMap.put("theInt", "1"); + sourceMap.put("fieldWithMethods", "aTestValue"); + + Target target = SourceTargetMapper.INSTANCE.toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.normalInt ).isEqualTo( "1" ); + //assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java new file mode 100644 index 0000000000..e3c758dffd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java @@ -0,0 +1,34 @@ +/* + * 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.frommap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Source { + + // CHECKSTYLE:OFF + public final int finalInt = 10; + public int normalInt; + public final List finalList = Arrays.asList( 1, 2, 3 ); + public List normalList; + public Integer fieldOnlyWithGetter; + // CHECKSTYLE:ON + + private final List privateFinalList = new ArrayList<>( Arrays.asList( 3, 4, 5 ) ); + + public List getPrivateFinalList() { + return privateFinalList; + } + + public Integer getFieldOnlyWithGetter() { + return fieldOnlyWithGetter + 21; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java new file mode 100644 index 0000000000..afaa3cabf5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java @@ -0,0 +1,30 @@ +/* + * 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.frommap; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.Map; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(source = "theInt", target = "normalInt") + @Mapping(target = "fieldWithMethods", ignore = true) + Target toTarget(Map source); + + default String map(Object object) { + return object.toString(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java new file mode 100644 index 0000000000..22cbf47a7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java @@ -0,0 +1,27 @@ +/* + * 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.frommap; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Target { + + // CHECKSTYLE:OFF + public String normalInt; + public String fieldWithMethods; + // CHECKSTYLE:ON + + public String getFieldWithMethods() { + return fieldWithMethods + "23"; + } + + public void setFieldWithMethods(String fieldWithMethods) { + this.fieldWithMethods = fieldWithMethods + "11"; + } +} From 3eabd20416fef3c062634345a59dba08e946dd27 Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Wed, 30 Dec 2020 11:54:53 +0100 Subject: [PATCH 02/11] #1075, Added Implicit mappings, and tests for multiple source parameters --- .../ap/internal/model/BeanMappingMethod.java | 38 ++++++++++++++ .../model/beanmapping/SourceReference.java | 52 +++++++++---------- .../ap/test/frommap/FromMapMappingTest.java | 21 +++++++- .../org/mapstruct/ap/test/frommap/Source.java | 4 +- .../ap/test/frommap/SourceTargetMapper.java | 12 +++-- .../org/mapstruct/ap/test/frommap/Target.java | 5 +- 6 files changed, 96 insertions(+), 36 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 536d601241..a5980d1227 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -7,6 +7,7 @@ import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -56,6 +57,7 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; @@ -272,6 +274,9 @@ else if ( !method.isUpdateMethod() ) { // map parameters without a mapping applyParameterNameBasedMapping(); + + // map properties without a mapping from map parameters + applyPropertyNameBasedMappingForMapSources(); } // Process the unprocessed defined targets @@ -1231,6 +1236,28 @@ private void applyPropertyNameBasedMapping() { applyPropertyNameBasedMapping( sourceReferences ); } + /** + * Iterates over all target properties and all source parameters. + *

+ * When a property name match occurs, the remainder will be checked for duplicates. Matches will be removed from + * the set of remaining target properties. + */ + private void applyPropertyNameBasedMappingForMapSources() { + List sourceReferences = new ArrayList<>(); + for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) { + for ( Parameter sourceParameter : method.getSourceParameters() ) { + if (!sourceParameter.getType().isMapType()) { + continue; + } + SourceReference sourceRef = getMapSourceRefByTargetName( sourceParameter, targetPropertyName ); + if ( sourceRef != null ) { + sourceReferences.add( sourceRef ); + } + } + } + applyPropertyNameBasedMapping( sourceReferences ); + } + /** * Iterates over all target properties and all source parameters. *

@@ -1358,6 +1385,17 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri return sourceRef; } + private SourceReference getMapSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName) { + + SourceReference sourceRef = null; + + if ( !sourceParameter.getType().isMapType()) { + return sourceRef; + } + + return SourceReference.fromMapSource(new String[]{targetPropertyName}, sourceParameter); + } + private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { Set mappings = unprocessedDefinedTargets.get( targetProperty ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 1af49c581b..0226289011 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -14,7 +14,6 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; @@ -55,7 +54,22 @@ */ public class SourceReference extends AbstractReference { - private final String sourceName; + public static SourceReference fromMapSource(String[] segments, Parameter parameter) { + Type valueType = null; + final List typeParameters = parameter.getType().getTypeParameters(); + if (typeParameters.size() == 2) { + valueType = typeParameters.get(1); + } else { + //TODO: this should be a type mirror of Object by default + valueType = parameter.getType(); + } + MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); + MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); + List entries = Collections.singletonList( + PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType) + ); + return new SourceReference( parameter, entries, true ); + } /** * Builds a {@link SourceReference} from an {@code @Mappping}. @@ -155,7 +169,7 @@ public SourceReference build() { private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { if (parameter.getType().isMapType()) { - return forMapSource(segments, parameter); + return fromMapSource(segments, parameter); } boolean foundEntryMatch; @@ -184,23 +198,6 @@ private SourceReference buildFromSingleSourceParameters(String[] segments, Param return new SourceReference( parameter, entries, foundEntryMatch ); } - private SourceReference forMapSource(String[] segments, Parameter parameter) { - Type valueType = null; - final List typeParameters = parameter.getType().getTypeParameters(); - if (typeParameters.size() == 2) { - valueType = typeParameters.get(1); - } else { - //TODO: this should be a type mirror of Object by default - valueType = parameter.getType(); - } - MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); - MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); - List entries = Collections.singletonList( - PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType) - ); - return new SourceReference( parameter, entries, true ); - } - /** * When there are more than one source parameters, the first segment name of the property * needs to match the parameter name to avoid ambiguity @@ -211,8 +208,12 @@ private SourceReference forMapSource(String[] segments, Parameter parameter) { */ private SourceReference buildFromMultipleSourceParameters(String[] segments, Parameter parameter) { - if (parameter.getType().isMapType()) { - return forMapSource(segments, parameter); + if (parameter != null && parameter.getType().isMapType()) { + String[] propertyNames = new String[0]; + if ( segments.length > 1) { + propertyNames = Arrays.copyOfRange(segments, 1, segments.length); + } + return fromMapSource(propertyNames, parameter); } boolean foundEntryMatch; @@ -431,13 +432,8 @@ public SourceReference build() { } } - private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid, String sourceName) { - super( sourceParameter, sourcePropertyEntries, isValid ); - this.sourceName = sourceName; - } - private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { - this( sourceParameter, sourcePropertyEntries, isValid, null ); + super( sourceParameter, sourcePropertyEntries, isValid ); } public SourceReference pop() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 48f7c4812d..e980825349 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -34,7 +35,25 @@ public void shouldMapSourceToTarget() { assertThat( target ).isNotNull(); assertThat( target.normalInt ).isEqualTo( "1" ); - //assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue" ); + assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue11" ); + } + + @Test + public void shouldMapSourcesToTarget() { + Map sourceMap = new HashMap<>(); + sourceMap.put("theInt", "1"); + sourceMap.put("fieldWithMethods", "aTestValue"); + + Source source = new Source(); + source.normalList = new ArrayList<>(); + source.fieldOnlyWithGetter = 12; + + Target target = SourceTargetMapper.INSTANCE.toTarget( sourceMap, source ); + + assertThat( target ).isNotNull(); + assertThat( target.normalInt ).isEqualTo( "1" ); + assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue11" ); + assertThat( target.fieldOnlyWithGetter ).isEqualTo( 33 ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java index e3c758dffd..7cef5e18bb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java @@ -10,13 +10,11 @@ import java.util.List; /** - * @author Filip Hrisafov + * @author Christian Kosmowski */ public class Source { // CHECKSTYLE:OFF - public final int finalInt = 10; - public int normalInt; public final List finalList = Arrays.asList( 1, 2, 3 ); public List normalList; public Integer fieldOnlyWithGetter; diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java index afaa3cabf5..be346617e3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java @@ -7,22 +7,28 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; import org.mapstruct.factory.Mappers; import java.util.Map; /** - * @author Filip Hrisafov + * @author Christian Kosmowski */ -@Mapper +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mapping(source = "theInt", target = "normalInt") - @Mapping(target = "fieldWithMethods", ignore = true) + @Mapping(target = "finalList", ignore = true) + @Mapping(target = "normalList", ignore = true) + @Mapping(target = "fieldOnlyWithGetter", ignore = true) Target toTarget(Map source); + @Mapping(source = "source.theInt", target = "normalInt") + Target toTarget(Map source, Source source2); + default String map(Object object) { return object.toString(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java index 22cbf47a7e..f7767b41ea 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java @@ -8,13 +8,16 @@ import java.util.List; /** - * @author Filip Hrisafov + * @author Christian Kosmowski */ public class Target { // CHECKSTYLE:OFF public String normalInt; public String fieldWithMethods; + public List finalList; + public List normalList; + public Integer fieldOnlyWithGetter; // CHECKSTYLE:ON public String getFieldWithMethods() { From b09b5b6ec4ce79b2f1b4f53da60b3834c5369d31 Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Wed, 30 Dec 2020 12:27:40 +0100 Subject: [PATCH 03/11] #1075, remove circular package dependencies --- .../ap/internal/model/beanmapping/SourceReference.java | 4 ++-- .../ap/internal/util/accessor/MapValueAccessor.java | 10 ++++------ .../util/accessor/MapValuePresenceChecker.java | 10 ++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 0226289011..8c119873ba 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -63,8 +63,8 @@ public static SourceReference fromMapSource(String[] segments, Parameter paramet //TODO: this should be a type mirror of Object by default valueType = parameter.getType(); } - MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); - MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(parameter.getType(), valueType.getTypeMirror(), String.join(".", segments)); + MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType().getTypeElement(), valueType.getTypeMirror(), String.join(".", segments)); + MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(parameter.getType().getTypeElement(), valueType.getTypeMirror(), String.join(".", segments)); List entries = Collections.singletonList( PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType) ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java index 5f42212715..38e18b53c9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java @@ -22,12 +22,12 @@ */ public class MapValueAccessor implements Accessor { - private final Type mapType; private final TypeMirror valueTypeMirror; private final String simpleName; + private final Element element; - public MapValueAccessor(Type mapType, TypeMirror valueTypeMirror, String simpleName) { - this.mapType = mapType; + public MapValueAccessor(Element element, TypeMirror valueTypeMirror, String simpleName) { + this.element = element; this.valueTypeMirror = valueTypeMirror; this.simpleName = simpleName; } @@ -49,9 +49,7 @@ public Set getModifiers() { @Override public Element getElement() { - return mapType.getTypeElement().getEnclosedElements().stream().filter(element -> { - return element.getSimpleName().contentEquals("get"); - }).findFirst().orElse(null); + return this.element; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java index b712214014..2ecdc00aca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java @@ -20,12 +20,12 @@ */ public class MapValuePresenceChecker implements Accessor { - private final Type mapType; + private final Element element; private final TypeMirror valueTypeMirror; private final String simpleName; - public MapValuePresenceChecker(Type mapType, TypeMirror valueTypeMirror, String simpleName) { - this.mapType = mapType; + public MapValuePresenceChecker(Element element, TypeMirror valueTypeMirror, String simpleName) { + this.element = element; this.valueTypeMirror = valueTypeMirror; this.simpleName = simpleName; } @@ -47,9 +47,7 @@ public Set getModifiers() { @Override public Element getElement() { - return mapType.getTypeElement().getEnclosedElements().stream().filter(element -> { - return element.getSimpleName().contentEquals("containsKey"); - }).findFirst().orElse(null); + return this.getElement(); } @Override From aa33db86dd41f7063d16c395b376e276b6fa0067 Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Wed, 30 Dec 2020 13:22:51 +0100 Subject: [PATCH 04/11] #1075, applied checkStyle rules. -> Formatting only --- .../ap/internal/model/BeanMappingMethod.java | 85 ++++++++++--------- .../model/beanmapping/PropertyEntry.java | 9 +- .../model/beanmapping/SourceReference.java | 19 +++-- .../util/accessor/MapValueAccessor.java | 8 +- .../accessor/MapValuePresenceChecker.java | 6 +- .../ap/test/frommap/FromMapMappingTest.java | 10 +-- 6 files changed, 67 insertions(+), 70 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index a5980d1227..ece3bab290 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -7,7 +7,6 @@ import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -57,7 +56,6 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; -import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; @@ -120,7 +118,7 @@ public Builder userDefinedReturnType(Type userDefinedReturnType) { return this; } - public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) { + public Builder returnTypeBuilder(BuilderType returnTypeBuilder) { this.returnTypeBuilder = returnTypeBuilder; return this; } @@ -134,7 +132,7 @@ public Builder forgedMethod(ForgedMethod forgedMethod) { this.method = forgedMethod; mappingReferences = forgedMethod.getMappingReferences(); Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) ); - for ( MappingReference mappingReference: mappingReferences.getMappingReferences() ) { + for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) { SourceReference sourceReference = mappingReference.getSourceReference(); if ( sourceReference != null ) { mappingReference.setSourceReference( new SourceReference.BuilderFromSourceReference() @@ -239,7 +237,8 @@ else if ( !method.isUpdateMethod() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() || sourceParameter.getType().isMapType()) { + if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() || + sourceParameter.getType().isMapType() ) { continue; } Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); @@ -300,18 +299,18 @@ else if ( !method.isUpdateMethod() ) { // before / after mappings List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( - method, - resultTypeToMap, - selectionParameters, - ctx, - existingVariableNames + method, + resultTypeToMap, + selectionParameters, + ctx, + existingVariableNames ); List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( - method, - resultTypeToMap, - selectionParameters, - ctx, - existingVariableNames + method, + resultTypeToMap, + selectionParameters, + ctx, + existingVariableNames ); if ( method instanceof ForgedMethod ) { @@ -370,10 +369,10 @@ private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { */ private boolean isBuilderRequired() { return returnTypeBuilder != null - && ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() ); + && ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() ); } - private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) { + private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct) { if ( returnTypeToConstruct == null ) { return false; } @@ -558,7 +557,8 @@ else if ( !returnType.hasAccessibleConstructor() ) { /** * Find a factory method for a return type or for a builder. - * @param returnTypeImpl the return type implementation to construct + * + * @param returnTypeImpl the return type implementation to construct * @param @selectionParameters * @return */ @@ -639,7 +639,7 @@ private ConstructorAccessor getConstructorAccessor(Type type) { ExecutableElement defaultAnnotatedConstructor = null; ExecutableElement parameterLessConstructor = null; List accessibleConstructors = new ArrayList<>( constructors.size() ); - List publicConstructors = new ArrayList<>( ); + List publicConstructors = new ArrayList<>(); for ( ExecutableElement constructor : constructors ) { if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { @@ -899,7 +899,7 @@ private boolean handleDefinedNestedTargetMapping(Set handledTargets, Typ handledTargets.addAll( holder.getHandledTargets() ); // Store all the unprocessed defined targets. for ( Entry> entry : holder.getUnprocessedDefinedTarget() - .entrySet() ) { + .entrySet() ) { if ( entry.getValue().isEmpty() ) { continue; } @@ -909,7 +909,7 @@ private boolean handleDefinedNestedTargetMapping(Set handledTargets, Typ } private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap, - Set handledTargets) { + Set handledTargets) { boolean errorOccured = false; PropertyMapping propertyMapping = null; @@ -1074,7 +1074,7 @@ else if ( mapping.getJavaExpression() != null ) { handledTargets.add( targetPropertyName ); } // its a plain-old property mapping - else { + else { SourceReference sourceRef = mappingRef.getSourceReference(); // sourceRef is not defined, check if a source property has the same name @@ -1190,7 +1190,7 @@ else if ( mapping.getJavaExpression() != null ) { *

* When a target property matches its name with the (nested) source property, it is added to the list if and * only if it is an unprocessed target property. - * + *

* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)} */ private void applyTargetThisMapping() { @@ -1246,7 +1246,7 @@ private void applyPropertyNameBasedMappingForMapSources() { List sourceReferences = new ArrayList<>(); for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { - if (!sourceParameter.getType().isMapType()) { + if ( !sourceParameter.getType().isMapType() ) { continue; } SourceReference sourceRef = getMapSourceRefByTargetName( sourceParameter, targetPropertyName ); @@ -1274,10 +1274,11 @@ private void applyPropertyNameBasedMapping(List sourceReference if ( targetPropertyWriteAccessor == null ) { // TODO improve error message ctx.getMessager() - .printMessage( method.getExecutable(), - Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, - targetPropertyName - ); + .printMessage( + method.getExecutable(), + Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, + targetPropertyName + ); continue; } @@ -1376,11 +1377,11 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter ) - .type( returnType ) - .readAccessor( sourceReadAccessor ) - .presenceChecker( sourcePresenceChecker ) - .name( targetPropertyName ) - .build(); + .type( returnType ) + .readAccessor( sourceReadAccessor ) + .presenceChecker( sourcePresenceChecker ) + .name( targetPropertyName ) + .build(); } return sourceRef; } @@ -1389,11 +1390,11 @@ private SourceReference getMapSourceRefByTargetName(Parameter sourceParameter, S SourceReference sourceRef = null; - if ( !sourceParameter.getType().isMapType()) { + if ( !sourceParameter.getType().isMapType() ) { return sourceRef; } - return SourceReference.fromMapSource(new String[]{targetPropertyName}, sourceParameter); + return SourceReference.fromMapSource( new String[] { targetPropertyName }, sourceParameter ); } private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { @@ -1641,7 +1642,7 @@ public Set getImportTypes() { } } - if ( returnTypeToConstruct != null ) { + if ( returnTypeToConstruct != null ) { types.addAll( returnTypeToConstruct.getImportTypes() ); } if ( returnTypeBuilder != null ) { @@ -1653,20 +1654,20 @@ public Set getImportTypes() { public List getSourceParametersExcludingPrimitives() { return getSourceParameters().stream() - .filter( parameter -> !parameter.getType().isPrimitive() ) - .collect( Collectors.toList() ); + .filter( parameter -> !parameter.getType().isPrimitive() ) + .collect( Collectors.toList() ); } public List getSourceParametersNeedingNullCheck() { return getSourceParameters().stream() - .filter( this::needsNullCheck ) - .collect( Collectors.toList() ); + .filter( this::needsNullCheck ) + .collect( Collectors.toList() ); } public List getSourceParametersNotNeedingNullCheck() { return getSourceParameters().stream() - .filter( parameter -> !needsNullCheck( parameter ) ) - .collect( Collectors.toList() ); + .filter( parameter -> !needsNullCheck( parameter ) ) + .collect( Collectors.toList() ); } private boolean needsNullCheck(Parameter parameter) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java index a785d89854..5ee79cc1c9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java @@ -39,10 +39,10 @@ private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor presenc /** * Constructor used to create {@link SourceReference} property entries from a mapping * - * @param name name of the property (dot separated) - * @param readAccessor its read accessor + * @param name name of the property (dot separated) + * @param readAccessor its read accessor * @param presenceChecker its presence Checker - * @param type type of the property + * @param type type of the property * @return the property entry for given parameters. */ public static PropertyEntry forSourceReference(String[] name, Accessor readAccessor, @@ -50,7 +50,6 @@ public static PropertyEntry forSourceReference(String[] name, Accessor readAcces return new PropertyEntry( name, readAccessor, presenceChecker, type ); } - public String getName() { return fullName[fullName.length - 1]; } @@ -68,7 +67,7 @@ public Type getType() { } public String getFullName() { - return Strings.join( Arrays.asList( fullName ), "." ); + return Strings.join( Arrays.asList( fullName ), "." ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 8c119873ba..89c0c1edc0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -58,15 +58,18 @@ public static SourceReference fromMapSource(String[] segments, Parameter paramet Type valueType = null; final List typeParameters = parameter.getType().getTypeParameters(); if (typeParameters.size() == 2) { - valueType = typeParameters.get(1); - } else { + valueType = typeParameters.get( 1 ); + } + else { //TODO: this should be a type mirror of Object by default valueType = parameter.getType(); } - MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType().getTypeElement(), valueType.getTypeMirror(), String.join(".", segments)); - MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(parameter.getType().getTypeElement(), valueType.getTypeMirror(), String.join(".", segments)); + MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType().getTypeElement(), + valueType.getTypeMirror(), String.join( ".", segments ) ); + MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker( + parameter.getType().getTypeElement(), valueType.getTypeMirror(), String.join( ".", segments ) ); List entries = Collections.singletonList( - PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType) + PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType ) ); return new SourceReference( parameter, entries, true ); } @@ -169,7 +172,7 @@ public SourceReference build() { private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { if (parameter.getType().isMapType()) { - return fromMapSource(segments, parameter); + return fromMapSource( segments, parameter ); } boolean foundEntryMatch; @@ -211,9 +214,9 @@ private SourceReference buildFromMultipleSourceParameters(String[] segments, Par if (parameter != null && parameter.getType().isMapType()) { String[] propertyNames = new String[0]; if ( segments.length > 1) { - propertyNames = Arrays.copyOfRange(segments, 1, segments.length); + propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); } - return fromMapSource(propertyNames, parameter); + return fromMapSource( propertyNames, parameter ); } boolean foundEntryMatch; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java index 38e18b53c9..98012c2a49 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java @@ -5,15 +5,11 @@ */ package org.mapstruct.ap.internal.util.accessor; -import org.mapstruct.ap.internal.model.common.Type; - +import java.util.Collections; +import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; -import java.util.Collections; -import java.util.List; -import java.util.Set; /** * An {@link Accessor} that wraps a Map value. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java index 2ecdc00aca..dff2f43ce4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.internal.util.accessor; -import org.mapstruct.ap.internal.model.common.Type; - +import java.util.Collections; +import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeMirror; -import java.util.Collections; -import java.util.Set; /** * An {@link Accessor} that wraps a Map value. diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index e980825349..bb7565b38d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -21,15 +21,15 @@ * @author Christian Kosmowski */ @RunWith(AnnotationProcessorTestRunner.class) -@IssueKey( "1075" ) +@IssueKey("1075") @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) public class FromMapMappingTest { @Test public void shouldMapSourceToTarget() { Map sourceMap = new HashMap<>(); - sourceMap.put("theInt", "1"); - sourceMap.put("fieldWithMethods", "aTestValue"); + sourceMap.put( "theInt", "1" ); + sourceMap.put( "fieldWithMethods", "aTestValue" ); Target target = SourceTargetMapper.INSTANCE.toTarget( sourceMap ); @@ -41,8 +41,8 @@ public void shouldMapSourceToTarget() { @Test public void shouldMapSourcesToTarget() { Map sourceMap = new HashMap<>(); - sourceMap.put("theInt", "1"); - sourceMap.put("fieldWithMethods", "aTestValue"); + sourceMap.put( "theInt", "1" ); + sourceMap.put( "fieldWithMethods", "aTestValue" ); Source source = new Source(); source.normalList = new ArrayList<>(); From ba3b0aff5b1b89f9f9d331ac93af1c49cd836157 Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Wed, 30 Dec 2020 15:46:03 +0100 Subject: [PATCH 05/11] #1075, renamed AccessorType (MAP_GET, MAP_CONTAINS), tests for update method and default values, use default type for untyped maps --- .../ap/internal/model/BeanMappingMethod.java | 3 ++- .../ap/internal/model/PropertyMapping.java | 2 +- .../model/beanmapping/SourceReference.java | 15 ++++-------- .../ap/internal/util/ValueProvider.java | 2 +- .../internal/util/accessor/AccessorType.java | 3 ++- .../util/accessor/MapValueAccessor.java | 2 +- .../accessor/MapValuePresenceChecker.java | 2 +- .../ap/test/frommap/FromMapMappingTest.java | 23 +++++++++++++++++++ .../ap/test/frommap/SourceTargetMapper.java | 6 ++++- 9 files changed, 40 insertions(+), 18 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index ece3bab290..39b746e4ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1394,7 +1394,8 @@ private SourceReference getMapSourceRefByTargetName(Parameter sourceParameter, S return sourceRef; } - return SourceReference.fromMapSource( new String[] { targetPropertyName }, sourceParameter ); + Type defaultType = ctx.getTypeFactory().getType( Object.class ); + return SourceReference.fromMapSource( new String[] { targetPropertyName }, sourceParameter, defaultType ); } private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index e012775423..598baf88da 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -613,7 +613,7 @@ private String getSourcePresenceCheckerRef( SourceReference sourceReference ) { PropertyEntry propertyEntry = sourceReference.getShallowestProperty(); if ( propertyEntry.getPresenceChecker() != null ) { - if (propertyEntry.getPresenceChecker().getAccessorType() == AccessorType.MAP) { + if (propertyEntry.getPresenceChecker().getAccessorType() == AccessorType.MAP_CONTAINS ) { return sourceParam.getName() + "." + "containsKey( \"" + propertyEntry.getPresenceChecker().getSimpleName() + "\" )"; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 89c0c1edc0..3a7638fedb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -54,16 +54,9 @@ */ public class SourceReference extends AbstractReference { - public static SourceReference fromMapSource(String[] segments, Parameter parameter) { - Type valueType = null; + public static SourceReference fromMapSource(String[] segments, Parameter parameter, Type defaultValueType) { final List typeParameters = parameter.getType().getTypeParameters(); - if (typeParameters.size() == 2) { - valueType = typeParameters.get( 1 ); - } - else { - //TODO: this should be a type mirror of Object by default - valueType = parameter.getType(); - } + Type valueType = typeParameters.size() == 2 ? typeParameters.get( 1 ) : defaultValueType; MapValueAccessor mapValueAccessor = new MapValueAccessor(parameter.getType().getTypeElement(), valueType.getTypeMirror(), String.join( ".", segments ) ); MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker( @@ -172,7 +165,7 @@ public SourceReference build() { private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { if (parameter.getType().isMapType()) { - return fromMapSource( segments, parameter ); + return fromMapSource( segments, parameter, typeFactory.getType( Object.class ) ); } boolean foundEntryMatch; @@ -216,7 +209,7 @@ private SourceReference buildFromMultipleSourceParameters(String[] segments, Par if ( segments.length > 1) { propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); } - return fromMapSource( propertyNames, parameter ); + return fromMapSource( propertyNames, parameter, typeFactory.getType( Object.class ) ); } boolean foundEntryMatch; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java index 2b574e4419..16abd074dd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java @@ -46,7 +46,7 @@ public static ValueProvider of(Accessor accessor) { return null; } String value = accessor.getSimpleName(); - if (accessor.getAccessorType() == AccessorType.MAP) { + if (accessor.getAccessorType() == AccessorType.MAP_GET ) { value = "get( \"" + value + "\" )"; return new ValueProvider( value ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java index fe6c84e2ce..4e3b7a4776 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java @@ -12,7 +12,8 @@ public enum AccessorType { GETTER, SETTER, ADDER, - MAP, + MAP_GET, + MAP_CONTAINS, PRESENCE_CHECKER; public boolean isFieldAssignment() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java index 98012c2a49..ab086d9b0e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java @@ -50,6 +50,6 @@ public Element getElement() { @Override public AccessorType getAccessorType() { - return AccessorType.MAP; + return AccessorType.MAP_GET; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java index dff2f43ce4..d3d4d735f4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java @@ -50,6 +50,6 @@ public Element getElement() { @Override public AccessorType getAccessorType() { - return AccessorType.MAP; + return AccessorType.MAP_CONTAINS; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index bb7565b38d..3307ce0d9d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -36,6 +36,7 @@ public void shouldMapSourceToTarget() { assertThat( target ).isNotNull(); assertThat( target.normalInt ).isEqualTo( "1" ); assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue11" ); + assertThat( target.fieldOnlyWithGetter ).isEqualTo( 44 ); } @Test @@ -56,4 +57,26 @@ public void shouldMapSourcesToTarget() { assertThat( target.fieldOnlyWithGetter ).isEqualTo( 33 ); } + @Test + public void shouldMapSourcesToExistingTarget() { + Target target = new Target(); + target.normalInt = "815"; + target.setFieldWithMethods( "aFieldWithMethod" ); + + Map sourceMap = new HashMap<>(); + sourceMap.put( "theInt", "1" ); + sourceMap.put( "fieldWithMethods", "aTestValue" ); + + Source source = new Source(); + source.normalList = new ArrayList<>(); + source.fieldOnlyWithGetter = 12; + + Target result = SourceTargetMapper.INSTANCE.toExistingTarget( target, sourceMap, source ); + + assertThat( result ).isNotNull(); + assertThat( result.normalInt ).isEqualTo( "1" ); + assertThat( result.fieldWithMethods ).isEqualTo( "aTestValue11" ); + assertThat( result.fieldOnlyWithGetter ).isEqualTo( 33 ); + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java index be346617e3..656fab3d00 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java @@ -7,6 +7,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.ReportingPolicy; import org.mapstruct.factory.Mappers; @@ -23,12 +24,15 @@ public interface SourceTargetMapper { @Mapping(source = "theInt", target = "normalInt") @Mapping(target = "finalList", ignore = true) @Mapping(target = "normalList", ignore = true) - @Mapping(target = "fieldOnlyWithGetter", ignore = true) + @Mapping(target = "fieldOnlyWithGetter", source = "nonExistent", defaultValue = "44") Target toTarget(Map source); @Mapping(source = "source.theInt", target = "normalInt") Target toTarget(Map source, Source source2); + @Mapping(source = "source.theInt", target = "normalInt") + Target toExistingTarget(@MappingTarget Target target, Map source, Source source2); + default String map(Object object) { return object.toString(); } From 1d257a41ca72a3d426c7b0627b99be588403e17a Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Fri, 8 Jan 2021 10:56:47 +0100 Subject: [PATCH 06/11] #1075, start unit tests the new way --- .../ap/test/frommap/FromMapMappingTest.java | 109 +++++++++++------- .../test/frommap/MapToBeanDefinedMapper.java | 38 ++++++ .../frommap/MapToBeanFromMultipleSources.java | 70 +++++++++++ .../test/frommap/MapToBeanImplicitMapper.java | 36 ++++++ .../MapToBeanUpdateImplicitMapper.java | 47 ++++++++ .../MapToBeanUsingMappingMethodMapper.java | 42 +++++++ .../frommap/MapToBeanWithDefaultMapper.java | 38 ++++++ .../org/mapstruct/ap/test/frommap/Source.java | 32 ----- .../ap/test/frommap/SourceTargetMapper.java | 40 ------- .../org/mapstruct/ap/test/frommap/Target.java | 30 ----- 10 files changed, 341 insertions(+), 141 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUpdateImplicitMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 3307ce0d9d..6559d63608 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -5,16 +5,15 @@ */ package org.mapstruct.ap.test.frommap; +import java.util.HashMap; +import java.util.Map; + 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 java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -22,61 +21,93 @@ */ @RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1075") -@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) public class FromMapMappingTest { @Test - public void shouldMapSourceToTarget() { - Map sourceMap = new HashMap<>(); - sourceMap.put( "theInt", "1" ); - sourceMap.put( "fieldWithMethods", "aTestValue" ); + @WithClasses(MapToBeanDefinedMapper.class) + public void shouldMapWithDefinedMapping() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "number", 44 ); - Target target = SourceTargetMapper.INSTANCE.toTarget( sourceMap ); + MapToBeanDefinedMapper.Target target = MapToBeanDefinedMapper.INSTANCE.toTarget( sourceMap ); assertThat( target ).isNotNull(); - assertThat( target.normalInt ).isEqualTo( "1" ); - assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue11" ); - assertThat( target.fieldOnlyWithGetter ).isEqualTo( 44 ); + assertThat( target.getNormalInt() ).isEqualTo( "44" ); } @Test - public void shouldMapSourcesToTarget() { - Map sourceMap = new HashMap<>(); - sourceMap.put( "theInt", "1" ); - sourceMap.put( "fieldWithMethods", "aTestValue" ); + @WithClasses(MapToBeanImplicitMapper.class) + public void shouldMapWithImpicitMapping() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "name", "mapstruct" ); - Source source = new Source(); - source.normalList = new ArrayList<>(); - source.fieldOnlyWithGetter = 12; + MapToBeanImplicitMapper.Target target = MapToBeanImplicitMapper.INSTANCE.toTarget( sourceMap ); - Target target = SourceTargetMapper.INSTANCE.toTarget( sourceMap, source ); + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "mapstruct" ); + } + + @Test + @WithClasses(MapToBeanUpdateImplicitMapper.class) + public void shouldMapToExistingTargetWithImpicitMapping() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "rating", 5 ); + + MapToBeanUpdateImplicitMapper.Target existingTarget = new MapToBeanUpdateImplicitMapper.Target(); + existingTarget.setRating( 4 ); + existingTarget.setName( "mapstruct" ); + + MapToBeanUpdateImplicitMapper.Target target = MapToBeanUpdateImplicitMapper.INSTANCE + .toTarget( existingTarget, sourceMap ); assertThat( target ).isNotNull(); - assertThat( target.normalInt ).isEqualTo( "1" ); - assertThat( target.fieldWithMethods ).isEqualTo( "aTestValue11" ); - assertThat( target.fieldOnlyWithGetter ).isEqualTo( 33 ); + assertThat( target.getName() ).isEqualTo( "mapstruct" ); + assertThat( target.getRating() ).isEqualTo( 5 ); } @Test - public void shouldMapSourcesToExistingTarget() { - Target target = new Target(); - target.normalInt = "815"; - target.setFieldWithMethods( "aFieldWithMethod" ); + @WithClasses(MapToBeanWithDefaultMapper.class) + public void shouldMapWithDefaultValue() { + Map sourceMap = new HashMap<>(); - Map sourceMap = new HashMap<>(); - sourceMap.put( "theInt", "1" ); - sourceMap.put( "fieldWithMethods", "aTestValue" ); + MapToBeanWithDefaultMapper.Target target = MapToBeanWithDefaultMapper.INSTANCE + .toTarget( sourceMap ); - Source source = new Source(); - source.normalList = new ArrayList<>(); - source.fieldOnlyWithGetter = 12; + assertThat( target ).isNotNull(); + assertThat( target.getNormalInt() ).isEqualTo( "4711" ); + } + + @Test + @WithClasses(MapToBeanUsingMappingMethodMapper.class) + public void shouldMapUsingMappingMethod() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "number", 23 ); + + MapToBeanUsingMappingMethodMapper.Target target = MapToBeanUsingMappingMethodMapper.INSTANCE + .toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getNormalInt() ).isEqualTo( "converted_23" ); + } + + @Test + @WithClasses(MapToBeanFromMultipleSources.class) + public void shouldMapFromMultipleSources() { + Map integers = new HashMap<>(); + integers.put( "number", 23 ); - Target result = SourceTargetMapper.INSTANCE.toExistingTarget( target, sourceMap, source ); + Map strings = new HashMap<>(); + strings.put( "string", "stringFromMap" ); - assertThat( result ).isNotNull(); - assertThat( result.normalInt ).isEqualTo( "1" ); - assertThat( result.fieldWithMethods ).isEqualTo( "aTestValue11" ); - assertThat( result.fieldOnlyWithGetter ).isEqualTo( 33 ); + MapToBeanFromMultipleSources.Source source = new MapToBeanFromMultipleSources.Source(); + + MapToBeanFromMultipleSources.Target target = MapToBeanFromMultipleSources.INSTANCE + .toTarget( integers, strings, source ); + + assertThat( target ).isNotNull(); + assertThat( target.getInteger() ).isEqualTo( 23 ); + assertThat( target.getString() ).isEqualTo( "stringFromMap" ); + assertThat( target.getStringFromBean() ).isEqualTo( "stringFromBean" ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.java new file mode 100644 index 0000000000..833f7e5620 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.java @@ -0,0 +1,38 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanDefinedMapper { + + MapToBeanDefinedMapper INSTANCE = Mappers.getMapper( MapToBeanDefinedMapper.class ); + + @Mapping(source = "number", target = "normalInt") + Target toTarget(Map source); + + class Target { + + private String normalInt; + + public String getNormalInt() { + return normalInt; + } + + public void setNormalInt(String normalInt) { + this.normalInt = normalInt; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.java new file mode 100644 index 0000000000..971105acf9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.java @@ -0,0 +1,70 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanFromMultipleSources { + + MapToBeanFromMultipleSources INSTANCE = Mappers.getMapper( MapToBeanFromMultipleSources.class ); + + @Mapping(source = "integers.number", target = "integer") + @Mapping(source = "strings.string", target = "string") + @Mapping(source = "bean.stringFromBean", target = "stringFromBean") + Target toTarget(Map integers, Map strings, Source bean); + + class Source { + private String stringFromBean = "stringFromBean"; + + public String getStringFromBean() { + return stringFromBean; + } + + public void setStringFromBean(String stringFromBean) { + this.stringFromBean = stringFromBean; + } + } + + class Target { + + private int integer; + private String string; + private String stringFromBean; + + public int getInteger() { + return integer; + } + + public void setInteger(int integer) { + this.integer = integer; + } + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } + + public String getStringFromBean() { + return stringFromBean; + } + + public void setStringFromBean(String stringFromBean) { + this.stringFromBean = stringFromBean; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.java new file mode 100644 index 0000000000..ca4b7a3d21 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.java @@ -0,0 +1,36 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanImplicitMapper { + + MapToBeanImplicitMapper INSTANCE = Mappers.getMapper( MapToBeanImplicitMapper.class ); + + Target toTarget(Map source); + + class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUpdateImplicitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUpdateImplicitMapper.java new file mode 100644 index 0000000000..82fe906bfe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUpdateImplicitMapper.java @@ -0,0 +1,47 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface MapToBeanUpdateImplicitMapper { + + MapToBeanUpdateImplicitMapper INSTANCE = Mappers.getMapper( MapToBeanUpdateImplicitMapper.class ); + + Target toTarget(@MappingTarget Target target, Map source); + + class Target { + + private String name; + private Integer rating; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getRating() { + return rating; + } + + public void setRating(Integer rating) { + this.rating = rating; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java new file mode 100644 index 0000000000..faab37bca8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java @@ -0,0 +1,42 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanUsingMappingMethodMapper { + + MapToBeanUsingMappingMethodMapper INSTANCE = Mappers.getMapper( MapToBeanUsingMappingMethodMapper.class ); + + @Mapping(source = "number", target = "normalInt") + Target toTarget(Map source); + + default String mapIntegerToString( Integer input ) { + return "converted_" + input; + } + + class Target { + + private String normalInt; + + public String getNormalInt() { + return normalInt; + } + + public void setNormalInt(String normalInt) { + this.normalInt = normalInt; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.java new file mode 100644 index 0000000000..ad3073ff57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.java @@ -0,0 +1,38 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanWithDefaultMapper { + + MapToBeanWithDefaultMapper INSTANCE = Mappers.getMapper( MapToBeanWithDefaultMapper.class ); + + @Mapping(source = "number", target = "normalInt", defaultValue = "4711") + Target toTarget(Map source); + + class Target { + + private String normalInt; + + public String getNormalInt() { + return normalInt; + } + + public void setNormalInt(String normalInt) { + this.normalInt = normalInt; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java deleted file mode 100644 index 7cef5e18bb..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/Source.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.frommap; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * @author Christian Kosmowski - */ -public class Source { - - // CHECKSTYLE:OFF - public final List finalList = Arrays.asList( 1, 2, 3 ); - public List normalList; - public Integer fieldOnlyWithGetter; - // CHECKSTYLE:ON - - private final List privateFinalList = new ArrayList<>( Arrays.asList( 3, 4, 5 ) ); - - public List getPrivateFinalList() { - return privateFinalList; - } - - public Integer getFieldOnlyWithGetter() { - return fieldOnlyWithGetter + 21; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java deleted file mode 100644 index 656fab3d00..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/SourceTargetMapper.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.frommap; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.factory.Mappers; - -import java.util.Map; - -/** - * @author Christian Kosmowski - */ -@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface SourceTargetMapper { - - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - @Mapping(source = "theInt", target = "normalInt") - @Mapping(target = "finalList", ignore = true) - @Mapping(target = "normalList", ignore = true) - @Mapping(target = "fieldOnlyWithGetter", source = "nonExistent", defaultValue = "44") - Target toTarget(Map source); - - @Mapping(source = "source.theInt", target = "normalInt") - Target toTarget(Map source, Source source2); - - @Mapping(source = "source.theInt", target = "normalInt") - Target toExistingTarget(@MappingTarget Target target, Map source, Source source2); - - default String map(Object object) { - return object.toString(); - } - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java deleted file mode 100644 index f7767b41ea..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/Target.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.frommap; - -import java.util.List; - -/** - * @author Christian Kosmowski - */ -public class Target { - - // CHECKSTYLE:OFF - public String normalInt; - public String fieldWithMethods; - public List finalList; - public List normalList; - public Integer fieldOnlyWithGetter; - // CHECKSTYLE:ON - - public String getFieldWithMethods() { - return fieldWithMethods + "23"; - } - - public void setFieldWithMethods(String fieldWithMethods) { - this.fieldWithMethods = fieldWithMethods + "11"; - } -} From 8657361b7355531bd6b3187b8d4092141c7f30ce Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Sat, 16 Jan 2021 20:14:12 +0100 Subject: [PATCH 07/11] #1075, unit test with nested source --- .../ap/test/frommap/FromMapMappingTest.java | 16 ++++ .../MapToBeanFromMapAndNestedSource.java | 79 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 6559d63608..3d9535b6af 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -110,4 +110,20 @@ public void shouldMapFromMultipleSources() { assertThat( target.getStringFromBean() ).isEqualTo( "stringFromBean" ); } + @Test + @WithClasses(MapToBeanFromMapAndNestedSource.class) + public void shouldMapFromNestedSource() { + Map integers = new HashMap<>(); + integers.put( "number", 23 ); + + MapToBeanFromMapAndNestedSource.Source source = new MapToBeanFromMapAndNestedSource.Source(); + + MapToBeanFromMapAndNestedSource.Target target = MapToBeanFromMapAndNestedSource.INSTANCE + .toTarget( integers, source ); + + assertThat( target ).isNotNull(); + assertThat( target.getInteger() ).isEqualTo( 23 ); + assertThat( target.getStringFromNestedSource() ).isEqualTo( "nestedString" ); + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.java new file mode 100644 index 0000000000..407e66add2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.java @@ -0,0 +1,79 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanFromMapAndNestedSource { + + MapToBeanFromMapAndNestedSource INSTANCE = Mappers.getMapper( MapToBeanFromMapAndNestedSource.class ); + + @Mapping(source = "integers.number", target = "integer") + @Mapping(source = "source.nestedSource.nestedString", target = "stringFromNestedSource") + Target toTarget(Map integers, Source source); + + class Source { + + private String stringFromBean = "stringFromBean"; + private NestedSource nestedSource = new NestedSource(); + + public String getStringFromBean() { + return stringFromBean; + } + + public void setStringFromBean(String stringFromBean) { + this.stringFromBean = stringFromBean; + } + + public NestedSource getNestedSource() { + return nestedSource; + } + + public void setNestedSource(NestedSource nestedSource) { + this.nestedSource = nestedSource; + } + + class NestedSource { + + private String nestedString = "nestedString"; + + public String getNestedString() { + return nestedString; + } + } + } + + class Target { + + private int integer; + private String stringFromNestedSource; + + public int getInteger() { + return integer; + } + + public void setInteger(int integer) { + this.integer = integer; + } + + public String getStringFromNestedSource() { + return stringFromNestedSource; + } + + public void setStringFromNestedSource(String stringFromNestedSource) { + this.stringFromNestedSource = stringFromNestedSource; + } + } + +} From 1f419948460c41389871d9b2b2cc3e3eb4852c33 Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Sat, 16 Jan 2021 20:38:36 +0100 Subject: [PATCH 08/11] #1075, add unit test for nested map in source bean --- .../ap/test/frommap/FromMapMappingTest.java | 13 ++++ .../frommap/MapToBeanFromMapAndNestedMap.java | 69 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 3d9535b6af..395be0b27b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -126,4 +126,17 @@ public void shouldMapFromNestedSource() { assertThat( target.getStringFromNestedSource() ).isEqualTo( "nestedString" ); } + @Test + @WithClasses(MapToBeanFromMapAndNestedMap.class) + public void shouldMapFromNestedMap() { + + MapToBeanFromMapAndNestedMap.Source source = new MapToBeanFromMapAndNestedMap.Source(); + MapToBeanFromMapAndNestedMap.Target target = MapToBeanFromMapAndNestedMap.INSTANCE + .toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNestedTarget() ).isNotNull(); + assertThat( target.getNestedTarget().getStringFromNestedMap() ).isEqualTo( "valueFromNestedMap" ); + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java new file mode 100644 index 0000000000..66c09247a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java @@ -0,0 +1,69 @@ +/* + * 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.frommap; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanFromMapAndNestedMap { + + MapToBeanFromMapAndNestedMap INSTANCE = Mappers.getMapper( MapToBeanFromMapAndNestedMap.class ); + + Target toTarget(Source source); + + NestedTarget toNestedTarget(Map nestedMap); + + class Source { + + private Map nestedTarget = new HashMap<>( ); + + public Map getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(Map nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public Source() { + nestedTarget.put( "stringFromNestedMap", "valueFromNestedMap" ); + } + } + + class Target { + + private NestedTarget nestedTarget; + + public NestedTarget getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(NestedTarget nestedTarget) { + this.nestedTarget = nestedTarget; + } + + } + + class NestedTarget { + private String stringFromNestedMap; + + public String getStringFromNestedMap() { + return stringFromNestedMap; + } + + public void setStringFromNestedMap(String stringFromNestedMap) { + this.stringFromNestedMap = stringFromNestedMap; + } + } + +} From f439ab29328cbad0a7f0a8eec4cf89b345a020df Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Sun, 17 Jan 2021 14:44:55 +0100 Subject: [PATCH 09/11] #1075, forge map to bean method --- .../ap/internal/model/PropertyMapping.java | 16 ++++++++++++++++ .../frommap/MapToBeanFromMapAndNestedMap.java | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 598baf88da..7121137e35 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -296,6 +296,9 @@ private Assignment forge( ) { else if ( sourceType.isMapType() && targetType.isMapType() ) { assignment = forgeMapMapping( sourceType, targetType, rightHandSide ); } + else if ( sourceType.isMapType() && !targetType.isMapType()) { + assignment = forgeMapToBeanMapping( sourceType, targetType, rightHandSide ); + } else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) || ( sourceType.isStreamType() && targetType.isStreamType() ) || ( sourceType.isStreamType() && targetType.isIterableType() ) ) { @@ -690,6 +693,19 @@ private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS s return createForgedAssignment( source, methodRef, mapMappingMethod ); } + private Assignment forgeMapToBeanMapping(Type sourceType, Type targetType, SourceRHS source) { + + targetType = targetType.withoutBounds(); + ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "{}" ); + + BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); + final BeanMappingMethod mapToBeanMappingMethod = builder.mappingContext( ctx ) + .forgedMethod( methodRef ) + .build(); + + return createForgedAssignment( source, methodRef, mapToBeanMappingMethod ); + } + private Assignment forgeMapping(SourceRHS sourceRHS) { Type sourceType; if ( targetWriteAccessorType == AccessorType.ADDER ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java index 66c09247a6..da47368e68 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java @@ -21,7 +21,7 @@ public interface MapToBeanFromMapAndNestedMap { Target toTarget(Source source); - NestedTarget toNestedTarget(Map nestedMap); + //NestedTarget toNestedTarget(Map nestedMap); class Source { From 598423e8fa2292b178d0af900110c882b58cb91e Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Fri, 22 Jan 2021 08:57:22 +0100 Subject: [PATCH 10/11] #1075, Add warning for untyped maps and for maps with non-String keys --- .../ap/internal/model/BeanMappingMethod.java | 26 +++++++++++ .../mapstruct/ap/internal/util/Message.java | 5 +- .../ap/test/frommap/FromMapMappingTest.java | 46 +++++++++++++++++++ .../frommap/MapToBeanTypeCheckMapper.java | 26 +++++++++++ .../frommap/MapToBeanUntypedMapMapper.java | 26 +++++++++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanTypeCheckMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUntypedMapMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 39b746e4ff..c53369f4b5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1243,12 +1243,38 @@ private void applyPropertyNameBasedMapping() { * the set of remaining target properties. */ private void applyPropertyNameBasedMappingForMapSources() { + final Type stringType = ctx.getTypeFactory().getType( String.class ); + for ( Parameter sourceParameter : method.getSourceParameters() ) { + if ( !sourceParameter.getType().isMapType() ) { + continue; + } + final List typeParameters = sourceParameter.getType().getTypeParameters(); + if (!(typeParameters.size() == 2 && typeParameters.get( 0 ).equals( stringType ))) { + Message message = typeParameters.isEmpty() ? Message.MAPTOBEANMAPPING_UNTYPED + : Message.MAPTOBEANMAPPING_WRONG_GENERIC_TYPES; + ctx.getMessager() + .printMessage( + method.getExecutable(), + message, + sourceParameter.getName(), + String.format( + "Map<%s,%s>", + !typeParameters.isEmpty() ? typeParameters.get( 0 ).getName() : "", + typeParameters.size() > 1 ? typeParameters.get( 1 ).getName() : "" + ) + ); + } + } List sourceReferences = new ArrayList<>(); for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { if ( !sourceParameter.getType().isMapType() ) { continue; } + final List typeParameters = sourceParameter.getType().getTypeParameters(); + if (!(typeParameters.size() == 2 && typeParameters.get( 0 ).equals( stringType ))) { + continue; + } SourceReference sourceRef = getMapSourceRefByTargetName( sourceParameter, targetPropertyName ); if ( sourceRef != null ) { sourceReferences.add( sourceRef ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index d88791fda1..e232f1211a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -174,7 +174,10 @@ public enum Message { VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM( "Source = \"\" can only be used on targets of type enum and not for %s." ), VALUEMAPPING_ANY_REMAINING_OR_UNMAPPED_MISSING( "Source = \"\" or \"\" is advisable for mapping of type String to an enum type.", Diagnostic.Kind.WARNING ), VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI( "Constant %s doesn't exist in enum type %s. Constant was returned from EnumMappingStrategy: %s"), - VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ); + VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ), + + MAPTOBEANMAPPING_WRONG_GENERIC_TYPES( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map but it was typed with %s.", Diagnostic.Kind.WARNING ), + MAPTOBEANMAPPING_UNTYPED( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map but it was untyped.", Diagnostic.Kind.WARNING ); // CHECKSTYLE:ON diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 395be0b27b..b70a7f1c9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -10,8 +10,12 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.mapstruct.ap.test.collection.erroneous.ErroneousCollectionToPrimitivePropertyMapper; import org.mapstruct.ap.testutil.IssueKey; 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -139,4 +143,46 @@ public void shouldMapFromNestedMap() { assertThat( target.getNestedTarget().getStringFromNestedMap() ).isEqualTo( "valueFromNestedMap" ); } + @Test + @WithClasses(MapToBeanTypeCheckMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = MapToBeanTypeCheckMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "The Map parameter \"source\" cannot be used for property mapping. " + + "It must be typed with Map but it was typed with Map.") + } + ) + public void shouldWarnAboutWrongMapTypes() { + + Map upsideDownMap = new HashMap<>(); + upsideDownMap.put( 23, "number" ); + + MapToBeanTypeCheckMapper.Target target = MapToBeanTypeCheckMapper.INSTANCE + .toTarget( upsideDownMap ); + } + + @Test + @WithClasses(MapToBeanUntypedMapMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = MapToBeanUntypedMapMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "The Map parameter \"source\" cannot be used for property mapping. " + + "It must be typed with Map but it was untyped.") + } + ) + public void shouldWarnAboutUntypedMapTypes() { + + Map upsideDownMap = new HashMap<>(); + upsideDownMap.put( 23, "number" ); + + MapToBeanUntypedMapMapper.Target target = MapToBeanUntypedMapMapper.INSTANCE + .toTarget( upsideDownMap ); + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanTypeCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanTypeCheckMapper.java new file mode 100644 index 0000000000..bdb06be3b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanTypeCheckMapper.java @@ -0,0 +1,26 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanTypeCheckMapper { + + MapToBeanTypeCheckMapper INSTANCE = Mappers.getMapper( MapToBeanTypeCheckMapper.class ); + + Target toTarget(Map source); + + class Target { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUntypedMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUntypedMapMapper.java new file mode 100644 index 0000000000..442ac18592 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUntypedMapMapper.java @@ -0,0 +1,26 @@ +/* + * 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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanUntypedMapMapper { + + MapToBeanUntypedMapMapper INSTANCE = Mappers.getMapper( MapToBeanUntypedMapMapper.class ); + + Target toTarget(Map source); + + class Target { + } + +} From 8e0e5818b658cb64332297c60efef3c0ccc73ce2 Mon Sep 17 00:00:00 2001 From: Christian Kosmowski Date: Fri, 22 Jan 2021 09:26:56 +0100 Subject: [PATCH 11/11] #1075, checkstyle only --- .../java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index b70a7f1c9f..d817809f83 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -10,7 +10,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.mapstruct.ap.test.collection.erroneous.ErroneousCollectionToPrimitivePropertyMapper; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;