Skip to content

Commit 845d83e

Browse files
committed
mapstruct#2439 Do not throw NPE getting accessors for null typeElement
Provide better error message if the source type has no read properties
1 parent 934a473 commit 845d83e

5 files changed

Lines changed: 124 additions & 14 deletions

File tree

processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.List;
1111
import java.util.Map;
1212
import java.util.Objects;
13+
import java.util.Set;
1314
import javax.lang.model.element.AnnotationMirror;
1415
import javax.lang.model.element.AnnotationValue;
1516
import javax.lang.model.type.DeclaredType;
@@ -277,17 +278,30 @@ private void reportErrorOnNoMatch( Parameter parameter, String[] propertyNames,
277278
notFoundPropertyIndex = entries.size();
278279
sourceType = last( entries ).getType();
279280
}
280-
String mostSimilarWord = Strings.getMostSimilarWord(
281-
propertyNames[notFoundPropertyIndex],
282-
sourceType.getPropertyReadAccessors().keySet()
283-
);
284-
List<String> elements = new ArrayList<>(
285-
Arrays.asList( propertyNames ).subList( 0, notFoundPropertyIndex )
286-
);
287-
elements.add( mostSimilarWord );
288-
reportMappingError(
289-
Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, sourceName, Strings.join( elements, "." )
290-
);
281+
282+
Set<String> readProperties = sourceType.getPropertyReadAccessors().keySet();
283+
284+
if ( !readProperties.isEmpty() ) {
285+
String mostSimilarWord = Strings.getMostSimilarWord(
286+
propertyNames[notFoundPropertyIndex],
287+
readProperties
288+
);
289+
290+
List<String> elements = new ArrayList<>(
291+
Arrays.asList( propertyNames ).subList( 0, notFoundPropertyIndex )
292+
);
293+
elements.add( mostSimilarWord );
294+
reportMappingError(
295+
Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, sourceName, Strings.join( elements, "." )
296+
);
297+
}
298+
else {
299+
reportMappingError(
300+
Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME_SOURCE_HAS_NO_PROPERTIES,
301+
sourceName,
302+
sourceType.describe()
303+
);
304+
}
291305
}
292306
}
293307

processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Map;
1515
import java.util.Objects;
1616
import java.util.Set;
17+
import java.util.function.Function;
1718
import java.util.stream.Collectors;
1819
import java.util.stream.Stream;
1920

@@ -693,7 +694,7 @@ else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.is
693694

694695
public List<Element> getRecordComponents() {
695696
if ( recordComponents == null ) {
696-
recordComponents = filters.recordComponentsIn( typeElement );
697+
recordComponents = nullSafeTypeElementListConversion( filters::recordComponentsIn );
697698
}
698699

699700
return recordComponents;
@@ -720,20 +721,28 @@ else if ( candidate.getAccessorType() == AccessorType.GETTER
720721

721722
private List<ExecutableElement> getAllMethods() {
722723
if ( allMethods == null ) {
723-
allMethods = elementUtils.getAllEnclosedExecutableElements( typeElement );
724+
allMethods = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedExecutableElements );
724725
}
725726

726727
return allMethods;
727728
}
728729

729730
private List<VariableElement> getAllFields() {
730731
if ( allFields == null ) {
731-
allFields = elementUtils.getAllEnclosedFields( typeElement );
732+
allFields = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedFields );
732733
}
733734

734735
return allFields;
735736
}
736737

738+
private <T> List<T> nullSafeTypeElementListConversion(Function<TypeElement, List<T>> conversionFunction) {
739+
if ( typeElement != null ) {
740+
return conversionFunction.apply( typeElement );
741+
}
742+
743+
return Collections.emptyList();
744+
}
745+
737746
private String getPropertyName(Accessor accessor ) {
738747
Element accessorElement = accessor.getElement();
739748
if ( accessorElement instanceof ExecutableElement ) {

processor/src/main/java/org/mapstruct/ap/internal/util/Message.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public enum Message {
7070
PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no source parameter named \"%s\". Method source parameters are: \"%s\"." ),
7171
PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER( "The type of parameter \"%s\" has no property named \"%s\"." ),
7272
PROPERTYMAPPING_INVALID_PROPERTY_NAME( "No property named \"%s\" exists in source parameter(s). Did you mean \"%s\"?" ),
73+
PROPERTYMAPPING_INVALID_PROPERTY_NAME_SOURCE_HAS_NO_PROPERTIES( "No property named \"%s\" exists in source parameter(s). Type \"%s\" has no properties." ),
7374
PROPERTYMAPPING_NO_PRESENCE_CHECKER_FOR_SOURCE_TYPE( "Using custom source value presence checking strategy, but no presence checker found for %s in source type." ),
7475
PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ),
7576
PROPERTYMAPPING_NO_WRITE_ACCESSOR_FOR_TARGET_TYPE( "No write accessor found for property \"%s\" in target type." ),
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.bugs._2439;
7+
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.Mapping;
10+
11+
/**
12+
* @author Filip Hrisafov
13+
*/
14+
@Mapper
15+
public interface ErroneousIssue2439Mapper {
16+
17+
@Mapping(target = "modeName", source = "mode.desc")
18+
LiveDto map(LiveEntity entity);
19+
20+
class LiveEntity {
21+
private final LiveMode[] mode;
22+
23+
public LiveEntity(LiveMode... mode) {
24+
this.mode = mode;
25+
}
26+
27+
public LiveMode[] getMode() {
28+
return mode;
29+
}
30+
}
31+
32+
class LiveDto {
33+
private String modeName;
34+
35+
public String getModeName() {
36+
return modeName;
37+
}
38+
39+
public void setModeName(String modeName) {
40+
this.modeName = modeName;
41+
}
42+
}
43+
44+
enum LiveMode {
45+
TEST,
46+
PROD,
47+
}
48+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.bugs._2439;
7+
8+
import org.mapstruct.ap.testutil.IssueKey;
9+
import org.mapstruct.ap.testutil.ProcessorTest;
10+
import org.mapstruct.ap.testutil.WithClasses;
11+
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
12+
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
13+
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
14+
15+
/**
16+
* @author Filip Hrisafov
17+
*/
18+
@IssueKey("2439")
19+
@WithClasses({
20+
ErroneousIssue2439Mapper.class
21+
})
22+
class Issuer2439Test {
23+
24+
@ProcessorTest
25+
@ExpectedCompilationOutcome(
26+
value = CompilationResult.FAILED,
27+
diagnostics = {
28+
@Diagnostic(type = ErroneousIssue2439Mapper.class,
29+
kind = javax.tools.Diagnostic.Kind.ERROR,
30+
line = 17,
31+
message = "No property named \"mode.desc\" exists in source parameter(s)." +
32+
" Type \"ErroneousIssue2439Mapper.LiveMode[]\" has no properties.")
33+
}
34+
)
35+
void shouldProvideGoodErrorMessage() {
36+
37+
}
38+
}

0 commit comments

Comments
 (0)