#2439 Do not throw NPE getting accessors for null typeElement

Provide better error message if the source type has no read properties
This commit is contained in:
Filip Hrisafov 2021-06-05 11:39:52 +02:00
parent 934a47323a
commit 845d83e9d5
5 changed files with 124 additions and 14 deletions

View File

@ -10,6 +10,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValue;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -277,17 +278,30 @@ public class SourceReference extends AbstractReference {
notFoundPropertyIndex = entries.size(); notFoundPropertyIndex = entries.size();
sourceType = last( entries ).getType(); sourceType = last( entries ).getType();
} }
String mostSimilarWord = Strings.getMostSimilarWord(
propertyNames[notFoundPropertyIndex], Set<String> readProperties = sourceType.getPropertyReadAccessors().keySet();
sourceType.getPropertyReadAccessors().keySet()
); if ( !readProperties.isEmpty() ) {
List<String> elements = new ArrayList<>( String mostSimilarWord = Strings.getMostSimilarWord(
Arrays.asList( propertyNames ).subList( 0, notFoundPropertyIndex ) propertyNames[notFoundPropertyIndex],
); readProperties
elements.add( mostSimilarWord ); );
reportMappingError(
Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, sourceName, Strings.join( elements, "." ) List<String> elements = new ArrayList<>(
); Arrays.asList( propertyNames ).subList( 0, notFoundPropertyIndex )
);
elements.add( mostSimilarWord );
reportMappingError(
Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, sourceName, Strings.join( elements, "." )
);
}
else {
reportMappingError(
Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME_SOURCE_HAS_NO_PROPERTIES,
sourceName,
sourceType.describe()
);
}
} }
} }

View File

@ -14,6 +14,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -693,7 +694,7 @@ public class Type extends ModelElement implements Comparable<Type> {
public List<Element> getRecordComponents() { public List<Element> getRecordComponents() {
if ( recordComponents == null ) { if ( recordComponents == null ) {
recordComponents = filters.recordComponentsIn( typeElement ); recordComponents = nullSafeTypeElementListConversion( filters::recordComponentsIn );
} }
return recordComponents; return recordComponents;
@ -720,7 +721,7 @@ public class Type extends ModelElement implements Comparable<Type> {
private List<ExecutableElement> getAllMethods() { private List<ExecutableElement> getAllMethods() {
if ( allMethods == null ) { if ( allMethods == null ) {
allMethods = elementUtils.getAllEnclosedExecutableElements( typeElement ); allMethods = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedExecutableElements );
} }
return allMethods; return allMethods;
@ -728,12 +729,20 @@ public class Type extends ModelElement implements Comparable<Type> {
private List<VariableElement> getAllFields() { private List<VariableElement> getAllFields() {
if ( allFields == null ) { if ( allFields == null ) {
allFields = elementUtils.getAllEnclosedFields( typeElement ); allFields = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedFields );
} }
return allFields; return allFields;
} }
private <T> List<T> nullSafeTypeElementListConversion(Function<TypeElement, List<T>> conversionFunction) {
if ( typeElement != null ) {
return conversionFunction.apply( typeElement );
}
return Collections.emptyList();
}
private String getPropertyName(Accessor accessor ) { private String getPropertyName(Accessor accessor ) {
Element accessorElement = accessor.getElement(); Element accessorElement = accessor.getElement();
if ( accessorElement instanceof ExecutableElement ) { if ( accessorElement instanceof ExecutableElement ) {

View File

@ -70,6 +70,7 @@ public enum Message {
PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no source parameter named \"%s\". Method source parameters are: \"%s\"." ), PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no source parameter named \"%s\". Method source parameters are: \"%s\"." ),
PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER( "The type of parameter \"%s\" has no property named \"%s\"." ), PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER( "The type of parameter \"%s\" has no property named \"%s\"." ),
PROPERTYMAPPING_INVALID_PROPERTY_NAME( "No property named \"%s\" exists in source parameter(s). Did you mean \"%s\"?" ), PROPERTYMAPPING_INVALID_PROPERTY_NAME( "No property named \"%s\" exists in source parameter(s). Did you mean \"%s\"?" ),
PROPERTYMAPPING_INVALID_PROPERTY_NAME_SOURCE_HAS_NO_PROPERTIES( "No property named \"%s\" exists in source parameter(s). Type \"%s\" has no properties." ),
PROPERTYMAPPING_NO_PRESENCE_CHECKER_FOR_SOURCE_TYPE( "Using custom source value presence checking strategy, but no presence checker found for %s in source type." ), PROPERTYMAPPING_NO_PRESENCE_CHECKER_FOR_SOURCE_TYPE( "Using custom source value presence checking strategy, but no presence checker found for %s in source type." ),
PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ), PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ),
PROPERTYMAPPING_NO_WRITE_ACCESSOR_FOR_TARGET_TYPE( "No write accessor found for property \"%s\" in target type." ), PROPERTYMAPPING_NO_WRITE_ACCESSOR_FOR_TARGET_TYPE( "No write accessor found for property \"%s\" in target type." ),

View File

@ -0,0 +1,48 @@
/*
* 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._2439;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface ErroneousIssue2439Mapper {
@Mapping(target = "modeName", source = "mode.desc")
LiveDto map(LiveEntity entity);
class LiveEntity {
private final LiveMode[] mode;
public LiveEntity(LiveMode... mode) {
this.mode = mode;
}
public LiveMode[] getMode() {
return mode;
}
}
class LiveDto {
private String modeName;
public String getModeName() {
return modeName;
}
public void setModeName(String modeName) {
this.modeName = modeName;
}
}
enum LiveMode {
TEST,
PROD,
}
}

View File

@ -0,0 +1,38 @@
/*
* 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._2439;
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;
/**
* @author Filip Hrisafov
*/
@IssueKey("2439")
@WithClasses({
ErroneousIssue2439Mapper.class
})
class Issuer2439Test {
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousIssue2439Mapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 17,
message = "No property named \"mode.desc\" exists in source parameter(s)." +
" Type \"ErroneousIssue2439Mapper.LiveMode[]\" has no properties.")
}
)
void shouldProvideGoodErrorMessage() {
}
}