#1821 nullpointer due to @BeanMapping via inheritance (#1822)

This commit is contained in:
Sjaak Derksen 2019-09-22 19:25:43 +02:00 committed by GitHub
parent 1c4bbba442
commit ade4f4d7e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 199 additions and 101 deletions

View File

@ -42,7 +42,6 @@ import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.prism.BeanMappingPrism;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; import org.mapstruct.ap.internal.prism.ReportingPolicyPrism;
@ -393,7 +392,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
if ( resultType.isAbstract() ) { if ( resultType.isAbstract() ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, method.getMappingOptions().getBeanMapping().getMirror(),
BEANMAPPING_ABSTRACT, BEANMAPPING_ABSTRACT,
resultType, resultType,
method.getResultType() method.getResultType()
@ -403,7 +402,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
else if ( !resultType.isAssignableTo( method.getResultType() ) ) { else if ( !resultType.isAssignableTo( method.getResultType() ) ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, method.getMappingOptions().getBeanMapping().getMirror(),
BEANMAPPING_NOT_ASSIGNABLE, BEANMAPPING_NOT_ASSIGNABLE,
resultType, resultType,
method.getResultType() method.getResultType()
@ -413,7 +412,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
else if ( !resultType.hasEmptyAccessibleConstructor() ) { else if ( !resultType.hasEmptyAccessibleConstructor() ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, method.getMappingOptions().getBeanMapping().getMirror(),
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
resultType resultType
); );

View File

@ -6,11 +6,14 @@
package org.mapstruct.ap.internal.model.source; package org.mapstruct.ap.internal.model.source;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.prism.BeanMappingPrism; import org.mapstruct.ap.internal.prism.BeanMappingPrism;
import org.mapstruct.ap.internal.prism.BuilderPrism; import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism;
@ -36,57 +39,101 @@ public class BeanMapping {
private final BuilderPrism builder; private final BuilderPrism builder;
private final NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy; private final NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy;
private final AnnotationMirror mirror;
/** /**
* creates a mapping for inheritance. Will set ignoreByDefault to false. * creates a mapping for inheritance. Will set
* - ignoreByDefault to false.
* - resultType to null
* *
* @param map * @return new mapping
* @return
*/ */
public static BeanMapping forInheritance( BeanMapping map ) { public static BeanMapping forInheritance(BeanMapping beanMapping) {
return new BeanMapping( return new BeanMapping(
map.selectionParameters, SelectionParameters.forInheritance( beanMapping.selectionParameters ),
map.nullValueMappingStrategy, beanMapping.nullValueMappingStrategy,
map.nullValuePropertyMappingStrategy, beanMapping.nullValuePropertyMappingStrategy,
map.nullValueCheckStrategy, beanMapping.nullValueCheckStrategy,
map.reportingPolicy, beanMapping.reportingPolicy,
false, false,
map.ignoreUnmappedSourceProperties, beanMapping.ignoreUnmappedSourceProperties,
map.builder beanMapping.builder,
beanMapping.mirror
); );
} }
public static BeanMapping fromPrism(BeanMappingPrism beanMapping, ExecutableElement method, public static class Builder {
FormattingMessager messager, Types typeUtils) {
if ( beanMapping == null ) { private BeanMappingPrism prism;
private ExecutableElement method;
private FormattingMessager messager;
private Types typeUtils;
private TypeFactory typeFactory;
public Builder beanMappingPrism(BeanMappingPrism beanMappingPrism) {
this.prism = beanMappingPrism;
return this;
}
public Builder method(ExecutableElement method) {
this.method = method;
return this;
}
public Builder messager(FormattingMessager messager) {
this.messager = messager;
return this;
}
public Builder typeUtils(Types typeUtils) {
this.typeUtils = typeUtils;
return this;
}
public Builder typeFactory(TypeFactory typeFactory) {
this.typeFactory = typeFactory;
return this;
}
public BeanMapping build() {
if ( prism == null ) {
return null; return null;
} }
boolean resultTypeIsDefined = !TypeKind.VOID.equals( beanMapping.resultType().getKind() ); Objects.requireNonNull( method );
Objects.requireNonNull( messager );
Objects.requireNonNull( method );
Objects.requireNonNull( typeUtils );
Objects.requireNonNull( typeFactory );
boolean resultTypeIsDefined = !TypeKind.VOID.equals( prism.resultType().getKind() );
NullValueMappingStrategyPrism nullValueMappingStrategy = NullValueMappingStrategyPrism nullValueMappingStrategy =
null == beanMapping.values.nullValueMappingStrategy() null == prism.values.nullValueMappingStrategy()
? null ? null
: NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); : NullValueMappingStrategyPrism.valueOf( prism.nullValueMappingStrategy() );
NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy =
null == beanMapping.values.nullValuePropertyMappingStrategy() null == prism.values.nullValuePropertyMappingStrategy()
? null ? null
: NullValuePropertyMappingStrategyPrism.valueOf( beanMapping.nullValuePropertyMappingStrategy() ); :
NullValuePropertyMappingStrategyPrism.valueOf( prism.nullValuePropertyMappingStrategy() );
NullValueCheckStrategyPrism nullValueCheckStrategy = NullValueCheckStrategyPrism nullValueCheckStrategy =
null == beanMapping.values.nullValueCheckStrategy() null == prism.values.nullValueCheckStrategy()
? null ? null
: NullValueCheckStrategyPrism.valueOf( beanMapping.nullValueCheckStrategy() ); : NullValueCheckStrategyPrism.valueOf( prism.nullValueCheckStrategy() );
boolean ignoreByDefault = beanMapping.ignoreByDefault(); boolean ignoreByDefault = prism.ignoreByDefault();
BuilderPrism builderMapping = null; BuilderPrism builderMapping = null;
if ( beanMapping.values.builder() != null ) { if ( prism.values.builder() != null ) {
builderMapping = beanMapping.builder(); builderMapping = prism.builder();
} }
if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty() if ( !resultTypeIsDefined && prism.qualifiedBy().isEmpty() &&
&& beanMapping.ignoreUnmappedSourceProperties().isEmpty() prism.qualifiedByName().isEmpty()
&& prism.ignoreUnmappedSourceProperties().isEmpty()
&& ( nullValueMappingStrategy == null ) && ( nullValuePropertyMappingStrategy == null ) && ( nullValueMappingStrategy == null ) && ( nullValuePropertyMappingStrategy == null )
&& ( nullValueCheckStrategy == null ) && !ignoreByDefault && builderMapping == null ) { && ( nullValueCheckStrategy == null ) && !ignoreByDefault && builderMapping == null ) {
@ -94,9 +141,9 @@ public class BeanMapping {
} }
SelectionParameters cmp = new SelectionParameters( SelectionParameters cmp = new SelectionParameters(
beanMapping.qualifiedBy(), prism.qualifiedBy(),
beanMapping.qualifiedByName(), prism.qualifiedByName(),
resultTypeIsDefined ? beanMapping.resultType() : null, resultTypeIsDefined ? prism.resultType() : null,
typeUtils typeUtils
); );
@ -108,15 +155,18 @@ public class BeanMapping {
nullValueCheckStrategy, nullValueCheckStrategy,
null, null,
ignoreByDefault, ignoreByDefault,
beanMapping.ignoreUnmappedSourceProperties(), prism.ignoreUnmappedSourceProperties(),
builderMapping builderMapping,
prism.mirror
); );
} }
}
private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms, private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms,
NullValuePropertyMappingStrategyPrism nvpms, NullValueCheckStrategyPrism nvcs, NullValuePropertyMappingStrategyPrism nvpms, NullValueCheckStrategyPrism nvcs,
ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault, ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault,
List<String> ignoreUnmappedSourceProperties, BuilderPrism builder) { List<String> ignoreUnmappedSourceProperties, BuilderPrism builder,
AnnotationMirror mirror) {
this.selectionParameters = selectionParameters; this.selectionParameters = selectionParameters;
this.nullValueMappingStrategy = nvms; this.nullValueMappingStrategy = nvms;
this.nullValuePropertyMappingStrategy = nvpms; this.nullValuePropertyMappingStrategy = nvpms;
@ -125,6 +175,7 @@ public class BeanMapping {
this.ignoreByDefault = ignoreByDefault; this.ignoreByDefault = ignoreByDefault;
this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties; this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties;
this.builder = builder; this.builder = builder;
this.mirror = mirror;
} }
public SelectionParameters getSelectionParameters() { public SelectionParameters getSelectionParameters() {
@ -155,6 +206,10 @@ public class BeanMapping {
return ignoreUnmappedSourceProperties; return ignoreUnmappedSourceProperties;
} }
public AnnotationMirror getMirror() {
return mirror;
}
/** /**
* derives the builder prism given the options and configuration * derives the builder prism given the options and configuration
* @param method containing mandatory configuration and the mapping options (optionally containing a beanmapping) * @param method containing mandatory configuration and the mapping options (optionally containing a beanmapping)

View File

@ -1,36 +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.internal.model.source;
/**
* Represents the mapping between one enum constant and another.
*
* @author Gunnar Morling
*/
public class EnumMapping {
private final String source;
private final String target;
public EnumMapping(String source, String target) {
this.source = source;
this.target = target;
}
/**
* @return the name of the constant in the source enum.
*/
public String getSource() {
return source;
}
/**
* @return the name of the constant in the target enum.
*/
public String getTarget() {
return target;
}
}

View File

@ -26,6 +26,23 @@ public class SelectionParameters {
private final Types typeUtils; private final Types typeUtils;
private final SourceRHS sourceRHS; private final SourceRHS sourceRHS;
/**
* Returns new selection parameters
*
* ResultType is not inherited.
*
* @param selectionParameters
* @return
*/
public static SelectionParameters forInheritance(SelectionParameters selectionParameters) {
return new SelectionParameters(
selectionParameters.qualifiers,
selectionParameters.qualifyingNames,
null,
selectionParameters.typeUtils
);
}
public SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames, TypeMirror resultType, public SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames, TypeMirror resultType,
Types typeUtils) { Types typeUtils) {
this( qualifiers, qualifyingNames, resultType, typeUtils, null ); this( qualifiers, qualifyingNames, resultType, typeUtils, null );

View File

@ -254,7 +254,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
.setMapMapping( .setMapMapping(
MapMapping.fromPrism( MapMappingPrism.getInstanceOn( method ), method, messager, typeUtils ) ) MapMapping.fromPrism( MapMappingPrism.getInstanceOn( method ), method, messager, typeUtils ) )
.setBeanMapping( .setBeanMapping(
BeanMapping.fromPrism( BeanMappingPrism.getInstanceOn( method ), method, messager, typeUtils ) ) new BeanMapping.Builder()
.beanMappingPrism( BeanMappingPrism.getInstanceOn( method ) )
.messager( messager )
.method( method )
.typeUtils( typeUtils )
.typeFactory( typeFactory )
.build()
)
.setValueMappings( getValueMappings( method ) ) .setValueMappings( getValueMappings( method ) )
.setTypeUtils( typeUtils ) .setTypeUtils( typeUtils )
.setTypeFactory( typeFactory ) .setTypeFactory( typeFactory )

View File

@ -0,0 +1,32 @@
/*
* 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._1821;
import org.mapstruct.BeanMapping;
import org.mapstruct.InheritConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface Issue1821Mapper {
Issue1821Mapper INSTANCE = Mappers.getMapper( Issue1821Mapper.class );
@BeanMapping( resultType = Target.class )
Target map(Source source);
@InheritConfiguration( name = "map" )
ExtendedTarget mapExtended(Source source);
class Target {
}
class ExtendedTarget extends Target {
}
class Source {
}
}

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.bugs._1821;
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;
@IssueKey("1797")
@RunWith( AnnotationProcessorTestRunner.class)
@WithClasses( Issue1821Mapper.class )
public class Issue1821Test {
@Test
public void shouldNotGiveNullPtr() {
Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source() );
}
}