#1321 Support index for list properties

This commit is contained in:
thunderhook 2025-02-09 17:01:43 +01:00
parent e0a7d3d0e6
commit 0b095fb062
26 changed files with 643 additions and 29 deletions

View File

@ -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(),

View File

@ -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<PropertyEntry> 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 );
}

View File

@ -105,6 +105,7 @@ public class Type extends ModelElement implements Comparable<Type> {
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<Type> {
List<Type> 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<String, String> toBeImportedTypes,
Map<String, String> notToBeImportedTypes,
Boolean isToBeImported,
@ -170,6 +171,7 @@ public class Type extends ModelElement implements Comparable<Type> {
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<Type> {
return isCollectionType;
}
public boolean isListType() {
return isListType;
}
public boolean isMapType() {
return isMapType;
}
@ -552,6 +558,7 @@ public class Type extends ModelElement implements Comparable<Type> {
isEnumType,
isIterableType,
isCollectionType,
isListType,
isMapType,
isStream,
toBeImportedTypes,
@ -595,6 +602,7 @@ public class Type extends ModelElement implements Comparable<Type> {
isEnumType,
isIterableType,
isCollectionType,
isListType,
isMapType,
isStream,
toBeImportedTypes,

View File

@ -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,

View File

@ -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\"." ),

View File

@ -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<Modifier> 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 + " )";
}
}

View File

@ -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;
}

View File

@ -9,7 +9,7 @@
<#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#list propertyEntries as entry>
<#if entry.presenceChecker?? >
if ( <#if entry_index != 0>${entry.previousPropertyName} == null || </#if>!<@includeModel object=entry.presenceChecker /> ) {
if ( <#if entry_index != 0>${entry.previousPropertyName} == null || </#if><@includeModel object=entry.presenceChecker.negate() /> ) {
return ${returnType.null};
}
</#if>

View File

@ -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,

View File

@ -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,

View File

@ -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<Person> personList;
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public List<Person> getPersonList() {
return personList;
}
public void setPersonList(List<Person> personList) {
this.personList = personList;
}
}

View File

@ -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;
}
}

View File

@ -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<Person> personList;
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public ArrayList<Person> getPersonList() {
return personList;
}
public void setPersonList(ArrayList<Person> personList) {
this.personList = personList;
}
}

View File

@ -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;
}
}

View File

@ -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<Person> drivers;
public Set<Person> getDrivers() {
return drivers;
}
public void setDrivers(Set<Person> drivers) {
this.drivers = drivers;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<Person> 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<Person> 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<Person> 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() { }
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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."
)
}
)

View File

@ -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."
)
}
)