Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
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;

Expand Down Expand Up @@ -665,6 +665,15 @@ private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences m
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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;

Expand Down Expand Up @@ -322,23 +323,63 @@ private List<PropertyEntry> matchWithSourceAccessorTypes(Type type, String[] ent
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;
Expand All @@ -347,6 +388,17 @@ private List<PropertyEntry> matchWithSourceAccessorTypes(Type type, String[] ent
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 );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -145,7 +146,7 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact
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,
Expand All @@ -170,6 +171,7 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact
this.isEnumType = isEnumType;
this.isIterableType = isIterableType;
this.isCollectionType = isCollectionType;
this.isListType = isListType;
this.isMapType = isMapType;
this.isStream = isStreamType;
this.isVoid = typeMirror.getKind() == TypeKind.VOID;
Expand Down Expand Up @@ -344,6 +346,10 @@ public boolean isCollectionType() {
return isCollectionType;
}

public boolean isListType() {
return isListType;
}

public boolean isMapType() {
return isMapType;
}
Expand Down Expand Up @@ -552,6 +558,7 @@ public Type erasure() {
isEnumType,
isIterableType,
isCollectionType,
isListType,
isMapType,
isStream,
toBeImportedTypes,
Expand Down Expand Up @@ -595,6 +602,7 @@ public Type withoutBounds() {
isEnumType,
isIterableType,
isCollectionType,
isListType,
isMapType,
isStream,
toBeImportedTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -115,6 +116,8 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes
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() );
Expand Down Expand Up @@ -235,6 +238,7 @@ private Type getType(TypeMirror mirror, boolean isLiteral, Boolean alwaysImport)

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

Expand Down Expand Up @@ -344,6 +348,7 @@ else if ( mirror.getKind() == TypeKind.TYPEVAR ) {
isEnumType,
isIterableType,
isCollectionType,
isListType,
isMapType,
isStreamType,
toBeImportedTypes,
Expand Down Expand Up @@ -574,6 +579,7 @@ private ImplementationType getImplementationType(TypeMirror mirror) {
implementationType.isEnumType(),
implementationType.isIterableType(),
implementationType.isCollectionType(),
implementationType.isListType(),
implementationType.isMapType(),
implementationType.isStreamType(),
toBeImportedTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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." ),
Expand Down Expand Up @@ -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\"." ),
Expand Down
Original file line number Diff line number Diff line change
@@ -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 + " )";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ static PresenceCheckAccessor mapContainsKey(String propertyName) {
return suffix( ".containsKey( \"" + propertyName + "\" )" );
}

static PresenceCheckAccessor listSizeGreaterThan(ReadAccessor listAccessor, int index) {
return suffix( "." + listAccessor.getSimpleName() + "().size() > " + index );
}

static PresenceCheckAccessor suffix(String suffix) {
return () -> suffix;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@
*/
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;
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;
Expand All @@ -24,6 +21,8 @@
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}.
*
Expand Down Expand Up @@ -174,6 +173,7 @@ private Type typeWithFQN(String fullQualifiedName) {
false,
false,
false,
false,
new HashMap<>( ),
new HashMap<>( ),
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +22,8 @@
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
*
Expand Down Expand Up @@ -122,6 +121,7 @@ private Type typeWithFQN(String fullQualifiedName) {
false,
false,
false,
false,
new HashMap<>( ),
new HashMap<>( ),
false,
Expand Down
Loading