#2149 Do not allow using BeanMapping(ignoreByDefault = true) in combination with Mapping(target = ".")

This fixes an ArrayIndexOutOfBoundsException when they were used together
This commit is contained in:
Filip Hrisafov 2020-07-19 15:26:48 +02:00
parent 28017e2b0c
commit 0495cb7fa7
5 changed files with 166 additions and 11 deletions

View File

@ -11,11 +11,12 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy;
@ -250,7 +251,8 @@ public class MappingMethodOptions {
return false;
}
public void applyIgnoreAll(SourceMethod method, TypeFactory typeFactory ) {
public void applyIgnoreAll(SourceMethod method, TypeFactory typeFactory,
FormattingMessager messager) {
CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy();
Type writeType = method.getResultType();
if ( !method.isUpdateMethod() ) {
@ -262,15 +264,27 @@ public class MappingMethodOptions {
Map<String, Accessor> writeAccessors = writeType.getPropertyWriteAccessors( cms );
Set<String> mappedPropertyNames = mappings.stream()
.map( m -> getPropertyEntries( m )[0] )
.collect( Collectors.toSet() );
for ( String targetPropertyName : writeAccessors.keySet() ) {
if ( !mappedPropertyNames.contains( targetPropertyName ) ) {
MappingOptions mapping = MappingOptions.forIgnore( targetPropertyName );
mappings.add( mapping );
for ( MappingOptions mapping : mappings ) {
String mappedTargetProperty = getFirstTargetPropertyName( mapping );
if ( !".".equals( mappedTargetProperty ) ) {
// Remove the mapped target property from the write accessors
writeAccessors.remove( mappedTargetProperty );
}
else {
messager.printMessage(
method.getExecutable(),
getBeanMapping().getMirror(),
Message.BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS
);
// Nothing more to do if this is reached
return;
}
}
// The writeAccessors now contains only the accessors that should be ignored
for ( String targetPropertyName : writeAccessors.keySet() ) {
MappingOptions mapping = MappingOptions.forIgnore( targetPropertyName );
mappings.add( mapping );
}
}
@ -290,4 +304,13 @@ public class MappingMethodOptions {
return mapping.getTargetName().split( "\\." );
}
private String getFirstTargetPropertyName(MappingOptions mapping) {
String targetName = mapping.getTargetName();
if ( ".".equals( targetName ) ) {
return targetName;
}
return getPropertyEntries( mapping )[0];
}
}

View File

@ -486,7 +486,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// @BeanMapping( ignoreByDefault = true )
if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isignoreByDefault() ) {
mappingOptions.applyIgnoreAll( method, typeFactory );
mappingOptions.applyIgnoreAll( method, typeFactory, mappingContext.getMessager() );
}
mappingOptions.markAsFullyInitialized();

View File

@ -39,6 +39,7 @@ public enum Message {
BEANMAPPING_UNMAPPED_SOURCES_ERROR( "Unmapped source %s." ),
BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ),
BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ),
BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ),
PROPERTYMAPPING_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ),
PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ),

View File

@ -0,0 +1,84 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._2149;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Erroneous2149Mapper {
@BeanMapping(ignoreByDefault = true)
@Mapping(target = ".", source = "name")
Target map(Source source);
class Target {
private String firstName;
private String age;
private String address;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
class Source {
private final String age;
private final Name name;
public Source(String age, Name name) {
this.age = age;
this.name = name;
}
public String getAge() {
return age;
}
public Name getName() {
return name;
}
}
class Name {
private final String firstName;
public Name(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
}
}

View File

@ -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.bugs._2149;
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.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;
/**
* @author Filip Hrisafov
*/
@IssueKey("2149")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
Erroneous2149Mapper.class
})
public class Issue2149Test {
@Test
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
type = Erroneous2149Mapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 18,
message = "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not " +
"allowed. You'll need to explicitly ignore the target properties that should be ignored instead."
),
@Diagnostic(
type = Erroneous2149Mapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 20,
message = "Unmapped target property: \"address\"."
)
}
)
public void shouldGiveCompileErrorWhenBeanMappingIgnoreByDefaultIsCombinedWithMappingTargetThis() {
}
}