diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 2b407e240..f147c9d7e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -21,13 +21,13 @@ import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; import org.mapstruct.ap.internal.model.beanmapping.SourceReference; import org.mapstruct.ap.internal.model.beanmapping.TargetReference; import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; 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.ReadAccessor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -665,6 +665,15 @@ public class NestedTargetPropertyMappingHolder { pathProperties.set( pathProperties.size() - 1, mostSimilarProperty ); } + boolean reverseInheritedListElementAccessor = mapping.getInheritContext() != null + && mapping.getInheritContext().isReversed() + && targetPropertyName.contains( "[" ); + if ( reverseInheritedListElementAccessor ) { + // ignore reverse inherited source properties with index accessor like "names[0]" + errorOccurred = false; + return null; + } + mappingContext.getMessager() .printMessage( mapping.getElement(), 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 73ec84b6b..d46258896 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 @@ -23,6 +23,7 @@ import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.ListElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -322,23 +323,63 @@ public class SourceReference extends AbstractReference { boolean allowedMapToBean) { List sourceEntries = new ArrayList<>(); Type newType = type; - for ( int i = 0; i < entryNames.length; i++ ) { + for (int i = 0; i < entryNames.length; i++) { boolean matchFound = false; - Type noBoundsType = newType.withoutBounds(); - ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); - if ( readAccessor != null ) { - PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); - newType = typeFactory.getReturnType( - (DeclaredType) noBoundsType.getTypeMirror(), - readAccessor - ); - sourceEntries.add( forSourceReference( - Arrays.copyOf( entryNames, i + 1 ), - readAccessor, - presenceChecker, - newType - ) ); - matchFound = true; + + String entryName = entryNames[i]; + int bracketIndex = entryName.indexOf( '[' ); + boolean containsIndex = bracketIndex != -1 && entryName.endsWith( "]" ); + if ( containsIndex ) { + String baseName = entryName.substring( 0, bracketIndex ); + String rawIndex = entryName.substring( bracketIndex + 1, entryName.length() - 1 ); + + ReadAccessor listAccessor = newType.withoutBounds() + .getReadAccessor( baseName, i > 0 || allowedMapToBean ); + if ( listAccessor != null ) { + + newType = typeFactory.getReturnType( + (DeclaredType) newType.withoutBounds().getTypeMirror(), + listAccessor + ); + + if ( !newType.isListType() ) { + reportMappingError( Message.PROPERTYMAPPING_INDEX_ACCESSORS_ONLY_ON_LIST, baseName ); + continue; + } + + Type elementType = newType.getTypeParameters().get( 0 ); + int index = tryParseIndex( rawIndex ); + ReadAccessor listElementAccessor = + new ListElementAccessor( listAccessor, elementType.getTypeMirror(), index ); + + sourceEntries.add( forSourceReference( + Arrays.copyOf( entryNames, i + 1 ), + listElementAccessor, + PresenceCheckAccessor.listSizeGreaterThan( listAccessor, index ), + elementType + ) ); + newType = elementType; + matchFound = true; + } + + } + else { + Type noBoundsType = newType.withoutBounds(); + ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryName, i > 0 || allowedMapToBean ); + if (readAccessor != null) { + PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryName ); + newType = typeFactory.getReturnType( + (DeclaredType) noBoundsType.getTypeMirror(), + readAccessor + ); + sourceEntries.add( forSourceReference( + Arrays.copyOf( entryNames, i + 1 ), + readAccessor, + presenceChecker, + newType + ) ); + matchFound = true; + } } if ( !matchFound ) { break; @@ -347,6 +388,17 @@ public class SourceReference extends AbstractReference { return sourceEntries; } + private int tryParseIndex(String rawIndex) { + + try { + return Integer.parseInt( rawIndex ); + } + catch ( NumberFormatException e ) { + reportMappingError( Message.PROPERTYMAPPING_INDEX_NOT_A_NUMBER, rawIndex ); + return 0; + } + } + private void reportMappingError(Message msg, Object... objects) { messager.printMessage( method.getExecutable(), annotationMirror, sourceAnnotationValue, msg, objects ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 652083406..ca212c006 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -105,6 +105,7 @@ public class Type extends ModelElement implements Comparable { private final boolean isEnumType; private final boolean isIterableType; private final boolean isCollectionType; + private final boolean isListType; private final boolean isMapType; private final boolean isVoid; private final boolean isStream; @@ -145,7 +146,7 @@ public class Type extends ModelElement implements Comparable { List typeParameters, ImplementationType implementationType, Type componentType, String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, - boolean isCollectionType, boolean isMapType, boolean isStreamType, + boolean isCollectionType, boolean isListType, boolean isMapType, boolean isStreamType, Map toBeImportedTypes, Map notToBeImportedTypes, Boolean isToBeImported, @@ -170,6 +171,7 @@ public class Type extends ModelElement implements Comparable { this.isEnumType = isEnumType; this.isIterableType = isIterableType; this.isCollectionType = isCollectionType; + this.isListType = isListType; this.isMapType = isMapType; this.isStream = isStreamType; this.isVoid = typeMirror.getKind() == TypeKind.VOID; @@ -344,6 +346,10 @@ public class Type extends ModelElement implements Comparable { return isCollectionType; } + public boolean isListType() { + return isListType; + } + public boolean isMapType() { return isMapType; } @@ -552,6 +558,7 @@ public class Type extends ModelElement implements Comparable { isEnumType, isIterableType, isCollectionType, + isListType, isMapType, isStream, toBeImportedTypes, @@ -595,6 +602,7 @@ public class Type extends ModelElement implements Comparable { isEnumType, isIterableType, isCollectionType, + isListType, isMapType, isStream, toBeImportedTypes, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index ceb190a33..7fc387615 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -94,6 +94,7 @@ public class TypeFactory { private final TypeMirror iterableType; private final TypeMirror collectionType; + private final TypeMirror listType; private final TypeMirror mapType; private final TypeMirror streamType; @@ -115,6 +116,8 @@ public class TypeFactory { iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); collectionType = typeUtils.erasure( elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); + listType = + typeUtils.erasure( elementUtils.getTypeElement( List.class.getCanonicalName() ).asType() ); mapType = typeUtils.erasure( elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); TypeElement streamTypeElement = elementUtils.getTypeElement( JavaStreamConstants.STREAM_FQN ); streamType = streamTypeElement == null ? null : typeUtils.erasure( streamTypeElement.asType() ); @@ -235,6 +238,7 @@ public class TypeFactory { boolean isIterableType = typeUtils.isSubtypeErased( mirror, iterableType ); boolean isCollectionType = typeUtils.isSubtypeErased( mirror, collectionType ); + boolean isListType = typeUtils.isSubtypeErased( mirror, listType ); boolean isMapType = typeUtils.isSubtypeErased( mirror, mapType ); boolean isStreamType = streamType != null && typeUtils.isSubtypeErased( mirror, streamType ); @@ -344,6 +348,7 @@ public class TypeFactory { isEnumType, isIterableType, isCollectionType, + isListType, isMapType, isStreamType, toBeImportedTypes, @@ -574,6 +579,7 @@ public class TypeFactory { implementationType.isEnumType(), implementationType.isIterableType(), implementationType.isCollectionType(), + implementationType.isListType(), implementationType.isMapType(), implementationType.isStreamType(), toBeImportedTypes, 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 5887ee07f..c9e4bd84e 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 @@ -92,6 +92,8 @@ public enum Message { PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."), PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ), PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ), + PROPERTYMAPPING_INDEX_ACCESSORS_ONLY_ON_LIST( "Can't access element with index because \"%s\" is not of type List." ), + PROPERTYMAPPING_INDEX_NOT_A_NUMBER( "Index accessor must be an integer but was: \"%s\"." ), CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ), @@ -185,8 +187,8 @@ public enum Message { RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ), RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ), RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ), - RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only by applied to a String parameter." ), - RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ), + RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only be applied to a String parameter." ), + RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only be applied to a String parameter." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ListElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ListElementAccessor.java new file mode 100644 index 000000000..7a0864437 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ListElementAccessor.java @@ -0,0 +1,56 @@ +/* + * 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 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; + +public class ListElementAccessor implements ReadAccessor { + + private final ReadAccessor listAccessor; + private final TypeMirror elementTypeMirror; + private final int index; + + public ListElementAccessor(ReadAccessor listAccessor, TypeMirror elementTypeMirror, int index) { + this.listAccessor = listAccessor; + this.elementTypeMirror = elementTypeMirror; + this.index = index; + } + + @Override + public TypeMirror getAccessedType() { + return elementTypeMirror; + } + + @Override + public String getSimpleName() { + return listAccessor.getSimpleName() + "[" + index + "]"; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public Element getElement() { + return listAccessor.getElement(); + } + + @Override + public AccessorType getAccessorType() { + return AccessorType.GETTER; + } + + @Override + public String getReadValueSource() { + return listAccessor.getReadValueSource() + ".get( " + index + " )"; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java index cc974b939..d2fbdd540 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java @@ -22,6 +22,10 @@ public interface PresenceCheckAccessor { return suffix( ".containsKey( \"" + propertyName + "\" )" ); } + static PresenceCheckAccessor listSizeGreaterThan(ReadAccessor listAccessor, int index) { + return suffix( "." + listAccessor.getSimpleName() + "().size() > " + index ); + } + static PresenceCheckAccessor suffix(String suffix) { return () -> suffix; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index 8eabe23a1..ac8d92af8 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -9,7 +9,7 @@ <#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list propertyEntries as entry> <#if entry.presenceChecker?? > - if ( <#if entry_index != 0>${entry.previousPropertyName} == null || !<@includeModel object=entry.presenceChecker /> ) { + if ( <#if entry_index != 0>${entry.previousPropertyName} == null || <@includeModel object=entry.presenceChecker.negate() /> ) { return ${returnType.null}; } diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java index 646f89806..33f7539d7 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.internal.model.common; -import static org.assertj.core.api.Assertions.assertThat; - import java.lang.annotation.Annotation; import java.time.LocalDate; import java.time.LocalDateTime; @@ -14,7 +12,6 @@ import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.HashMap; import java.util.List; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -24,6 +21,8 @@ import org.junit.jupiter.api.Test; import org.mapstruct.ap.internal.util.JodaTimeConstants; import org.mapstruct.ap.testutil.IssueKey; +import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for {@link org.mapstruct.ap.internal.model.common.DateFormatValidatorFactory}. * @@ -174,6 +173,7 @@ public class DateFormatValidatorFactoryTest { false, false, false, + false, new HashMap<>( ), new HashMap<>( ), false, diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index 401a6b224..3463a38b4 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java @@ -5,13 +5,10 @@ */ package org.mapstruct.ap.internal.model.common; -import static org.assertj.core.api.Assertions.assertThat; - import java.lang.annotation.Annotation; import java.time.ZonedDateTime; import java.util.HashMap; import java.util.List; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -25,6 +22,8 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.testutil.IssueKey; +import static org.assertj.core.api.Assertions.assertThat; + /** * Testing DefaultConversionContext for dateFormat * @@ -122,6 +121,7 @@ public class DefaultConversionContextTest { false, false, false, + false, new HashMap<>( ), new HashMap<>( ), false, diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/Car.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/Car.java new file mode 100644 index 000000000..c253a343f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/Car.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.collection.index; + +import java.util.List; + +public class Car { + + String make; + List personList; + + public String getMake() { + return make; + } + + public void setMake(String make) { + this.make = make; + } + + public List getPersonList() { + return personList; + } + + public void setPersonList(List personList) { + this.personList = personList; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarDto.java new file mode 100644 index 000000000..8afde623c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarDto.java @@ -0,0 +1,28 @@ +/* + * 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.collection.index; + +public class CarDto { + + Person driver; + String manufacturer; + + public Person getDriver() { + return driver; + } + + public void setDriver(Person driver) { + this.driver = driver; + } + + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarWithArrayList.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarWithArrayList.java new file mode 100644 index 000000000..1e0319b35 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarWithArrayList.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.collection.index; + +import java.util.ArrayList; + +public class CarWithArrayList { + + String make; + ArrayList personList; + + public String getMake() { + return make; + } + + public void setMake(String make) { + this.make = make; + } + + public ArrayList getPersonList() { + return personList; + } + + public void setPersonList(ArrayList personList) { + this.personList = personList; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarWithDriverNameDto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarWithDriverNameDto.java new file mode 100644 index 000000000..b2c884891 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/CarWithDriverNameDto.java @@ -0,0 +1,37 @@ +/* + * 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.collection.index; + +public class CarWithDriverNameDto { + + String driverName; + Person driver; + String manufacturer; + + public String getDriverName() { + return driverName; + } + + public void setDriverName(String driverName) { + this.driverName = driverName; + } + + public Person getDriver() { + return driver; + } + + public void setDriver(Person driver) { + this.driver = driver; + } + + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/DriverList.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/DriverList.java new file mode 100644 index 000000000..ca9410370 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/DriverList.java @@ -0,0 +1,21 @@ +/* + * 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.collection.index; + +import java.util.Set; + +public class DriverList { + + Set drivers; + + public Set getDrivers() { + return drivers; + } + + public void setDrivers(Set drivers) { + this.drivers = drivers; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexIsNoNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexIsNoNumberMapper.java new file mode 100644 index 000000000..e6d832b74 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexIsNoNumberMapper.java @@ -0,0 +1,18 @@ +/* + * 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.collection.index; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ErroneousListIndexIsNoNumberMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "driver", source = "personList[x]") + CarDto mapList(Car source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexOnSetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexOnSetMapper.java new file mode 100644 index 000000000..6c42fa4ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexOnSetMapper.java @@ -0,0 +1,17 @@ +/* + * 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.collection.index; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ErroneousListIndexOnSetMapper { + + @Mapping(target = "name", source = "drivers[0].name") + PersonDto sourceToTarget(DriverList source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexOnStringMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexOnStringMapper.java new file mode 100644 index 000000000..86b562830 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ErroneousListIndexOnStringMapper.java @@ -0,0 +1,17 @@ +/* + * 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.collection.index; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ErroneousListIndexOnStringMapper { + + @Mapping(target = "name", source = "name[0]") + PersonDto sourceToTarget(Person source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexInheritInverseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexInheritInverseMapper.java new file mode 100644 index 000000000..ee4ca2850 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexInheritInverseMapper.java @@ -0,0 +1,23 @@ +/* + * 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.collection.index; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ListIndexInheritInverseMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "driver", source = "personList[0]") + CarDto mapList(Car source); + + @InheritInverseConfiguration + @Mapping(target = "personList", ignore = true) + Car mapListInverseInherited(CarDto source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexMapper.java new file mode 100644 index 000000000..553e7843d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexMapper.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.collection.index; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; + +@Mapper +public interface ListIndexMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "driver", source = "personList[0]") + CarDto mapList(Car source); + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "driver", source = "personList[0]") + void updateList(Car source, @MappingTarget CarDto target); + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "driver", source = "personList[0]") + CarDto mapArrayList(CarWithArrayList source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexMappingTest.java new file mode 100644 index 000000000..4a5443b45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexMappingTest.java @@ -0,0 +1,163 @@ +/* + * 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.collection.index; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Oliver Erhart + */ +@WithClasses({ + Car.class, + CarDto.class, + CarWithArrayList.class, + CarWithDriverNameDto.class, + Person.class, + PersonDto.class, + ListIndexMapper.class, + ListIndexNestedPropertyMapper.class +}) +@IssueKey("1321") +public class ListIndexMappingTest { + + @ProcessorTest + public void shouldMapWithDirectAssignment() { + + Car source = new Car(); + List personList = new ArrayList<>(); + personList.add( new Person( "First" ) ); + personList.add( new Person( "Second" ) ); + source.setPersonList( personList ); + + CarDto target = Mappers.getMapper( ListIndexMapper.class ).mapList( source ); + + assertThat( target.getDriver().getName() ).isEqualTo( "First" ); + } + + @ProcessorTest + public void shouldMapWithDirectArrayListAssignment() { + + CarWithArrayList source = new CarWithArrayList(); + ArrayList personList = new ArrayList<>(); + personList.add( new Person( "First" ) ); + personList.add( new Person( "Second" ) ); + source.setPersonList( personList ); + + CarDto target = Mappers.getMapper( ListIndexMapper.class ).mapArrayList( source ); + + assertThat( target.getDriver().getName() ).isEqualTo( "First" ); + } + + @ProcessorTest + public void shouldBeNullWhenDirectAssignmentIndexOutOfBounds() { + + Car source = new Car(); + source.setPersonList( new ArrayList<>() ); + + CarDto target = Mappers.getMapper( ListIndexMapper.class ).mapList( source ); + + assertThat( target.getDriver() ).isNull(); + } + + @ProcessorTest + public void shouldMapWithNestedPropertyAssignment() { + + Car source = new Car(); + List personList = new ArrayList<>(); + personList.add( new Person( "First" ) ); + personList.add( new Person( "Second" ) ); + source.setPersonList( personList ); + + CarWithDriverNameDto target = Mappers.getMapper( ListIndexNestedPropertyMapper.class ).sourceToTarget( source ); + + assertThat( target.getDriver().getName() ).isEqualTo( "First" ); + assertThat( target.getDriverName() ).isEqualTo( "First" ); + } + + @ProcessorTest + public void shouldBeNullWhenNestedPropertyAssignmentIndexOutOfBounds() { + + Car source = new Car(); + source.setPersonList( new ArrayList<>() ); + + CarWithDriverNameDto target = Mappers.getMapper( ListIndexNestedPropertyMapper.class ).sourceToTarget( source ); + + assertThat( target.getDriver() ).isNull(); + assertThat( target.getDriverName() ).isNull(); + } + + @ProcessorTest + @WithClasses(ErroneousListIndexOnStringMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousListIndexOnStringMapper.class, + line = 14, + message = "Can't access element with index because \"name\" is not of type List." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousListIndexOnStringMapper.class, + line = 14, + message = "No property named \"name[0]\" exists in source parameter(s). Did you mean \"name\"?" + ) + } + ) + public void errorWhenSourceIsStringInsteadOfList() { } + + @ProcessorTest + @WithClasses(ErroneousListIndexIsNoNumberMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousListIndexIsNoNumberMapper.class, + message = "Index accessor must be an integer but was: \"x\"." + ) + } + ) + public void errorWhenIndexIsNoNumber() { } + + @ProcessorTest + @WithClasses({ + ErroneousListIndexOnSetMapper.class, + DriverList.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousListIndexOnSetMapper.class, + line = 14, + message = "Can't access element with index because \"drivers\" is not of type List." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousListIndexOnSetMapper.class, + line = 14, + message = "No property named \"drivers[0].name\" exists in source parameter(s)." + + " Did you mean \"drivers\"?" + ) + } + ) + public void errorWhenSourceIsSetInsteadOfList() { } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexNestedPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexNestedPropertyMapper.java new file mode 100644 index 000000000..54f15a704 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/ListIndexNestedPropertyMapper.java @@ -0,0 +1,24 @@ +/* + * 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.collection.index; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ListIndexNestedPropertyMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "driver", source = "personList[0]") + @Mapping(target = "driverName", source = "source.personList[0].name") + CarWithDriverNameDto sourceToTarget(Car source); + + @Mapping(target = "personList", ignore = true) + @InheritInverseConfiguration + Car inverseInherited(CarWithDriverNameDto source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/index/Person.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/Person.java new file mode 100644 index 000000000..ad3005409 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/Person.java @@ -0,0 +1,23 @@ +/* + * 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.collection.index; + +public class Person { + + private String name; + + public Person(String name) { + this.name = 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/collection/index/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/index/PersonDto.java new file mode 100644 index 000000000..e5553f61f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/index/PersonDto.java @@ -0,0 +1,19 @@ +/* + * 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.collection.index; + +public class PersonDto { + + 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/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java index 2471ed647..13ec11de9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java @@ -302,7 +302,7 @@ public class SourcePropertyNameTest { kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousNonStringSourcePropertyNameParameter.class, line = 23, - message = "@SourcePropertyName can only by applied to a String parameter." + message = "@SourcePropertyName can only be applied to a String parameter." ) } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java index bb90c0b06..f28840402 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java @@ -301,7 +301,7 @@ public class TargetPropertyNameTest { kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousNonStringTargetPropertyNameParameter.class, line = 23, - message = "@TargetPropertyName can only by applied to a String parameter." + message = "@TargetPropertyName can only be applied to a String parameter." ) } )