From 74f281fa3e7359bcac4f86c3221ae5c392013a67 Mon Sep 17 00:00:00 2001 From: Sjaak Derksen Date: Sun, 5 Jul 2020 22:22:01 +0200 Subject: [PATCH] #2135 improved messages for not able to select qualified method (#2141) * #2135 improved messages for not able to select qualified method --- .../creation/MappingResolverImpl.java | 52 +++++++++-- .../mapstruct/ap/internal/util/Message.java | 7 +- .../ap/internal/util/MessageConstants.java | 15 ++++ .../selection/qualifier/QualifierTest.java | 4 +- ...eousMessageByAnnotationAndNamedMapper.java | 54 +++++++++++ .../ErroneousMessageByAnnotationMapper.java | 45 ++++++++++ .../errors/ErroneousMessageByNamedMapper.java | 45 ++++++++++ .../qualifier/errors/QualfierMessageTest.java | 90 +++++++++++++++++++ 8 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationAndNamedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 67eebbb30..5427ddefa 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -10,8 +10,11 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; @@ -48,6 +51,7 @@ import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.MessageConstants; import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.Strings; @@ -260,13 +264,7 @@ public class MappingResolverImpl implements MappingResolver { } if ( hasQualfiers() ) { - messager.printMessage( - mappingMethod.getExecutable(), - positionHint, - Message.GENERAL_NO_QUALIFYING_METHOD, - Strings.join( selectionCriteria.getQualifiers(), ", " ), - Strings.join( selectionCriteria.getQualifiedByNames(), ", " ) - ); + printQualifierMessage( selectionCriteria ); } else if ( allowMappingMethod() ) { // only forge if we would allow mapping method @@ -281,6 +279,46 @@ public class MappingResolverImpl implements MappingResolver { return selectionCriteria != null && selectionCriteria.hasQualfiers(); } + private void printQualifierMessage(SelectionCriteria selectionCriteria ) { + + List annotations = selectionCriteria.getQualifiers().stream() + .filter( DeclaredType.class::isInstance ) + .map( DeclaredType.class::cast ) + .map( DeclaredType::asElement ) + .map( Element::getSimpleName ) + .map( Name::toString ) + .map( a -> "@" + a ) + .collect( Collectors.toList() ); + List names = selectionCriteria.getQualifiedByNames(); + + if ( !annotations.isEmpty() && !names.isEmpty() ) { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_COMBINED, + Strings.join( names, MessageConstants.AND ), + Strings.join( annotations, MessageConstants.AND ) + ); + } + else if ( !annotations.isEmpty() ) { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_ANNOTATION, + Strings.join( annotations, MessageConstants.AND ) + ); + } + else { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_NAMED, + Strings.join( names, MessageConstants.AND ) + ); + } + + } + private boolean allowDirect( Type sourceType, Type targetType ) { if ( selectionCriteria != null && selectionCriteria.isAllowDirect() ) { return true; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 4c46c0991..ae66d3877 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -7,6 +7,8 @@ package org.mapstruct.ap.internal.util; import javax.tools.Diagnostic; +import static org.mapstruct.ap.internal.util.MessageConstants.FAQ_QUALIFIER_URL; + /** * A message used in warnings/errors raised by the annotation processor. * @@ -119,7 +121,9 @@ public enum Message { GENERAL_JODA_NOT_ON_CLASSPATH( "Cannot validate Joda dateformat, no Joda on classpath. Consider adding Joda to the annotation processorpath.", Diagnostic.Kind.WARNING ), GENERAL_NOT_ALL_FORGED_CREATED( "Internal Error in creation of Forged Methods, it was expected all Forged Methods to finished with creation, but %s did not" ), GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible constructor." ), - GENERAL_NO_QUALIFYING_METHOD( "No qualifying method found for qualifiers: %s and / or qualifying names: %s" ), + GENERAL_NO_QUALIFYING_METHOD_ANNOTATION( "Qualifier error. No method found annotated with: [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ), + GENERAL_NO_QUALIFYING_METHOD_NAMED( "Qualifier error. No method found annotated with @Named#value: [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ), + GENERAL_NO_QUALIFYING_METHOD_COMBINED( "Qualifier error. No method found annotated with @Named#value: [ %s ], annotated with [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ), BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ), BUILDER_NO_BUILD_METHOD_FOUND("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\".", Diagnostic.Kind.ERROR ), @@ -162,6 +166,7 @@ public enum Message { VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ); // CHECKSTYLE:ON + private final String description; private final Diagnostic.Kind kind; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java new file mode 100644 index 000000000..d3dabdb2d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java @@ -0,0 +1,15 @@ +/* + * 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; + +public final class MessageConstants { + + public static final String AND = " and "; + public static final String FAQ_QUALIFIER_URL = "https://mapstruct.org/faq/#qualifier"; + + private MessageConstants() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java index 912affa1a..37ecfb1be 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java @@ -103,8 +103,8 @@ public class QualifierTest { kind = Kind.ERROR, line = 28, message = - "No qualifying method found for qualifiers: org.mapstruct.ap.test.selection.qualifier.annotation" + - ".NonQualifierAnnotated and / or qualifying names: "), + "Qualifier error. No method found annotated with: [ @NonQualifierAnnotated ]. " + + "See https://mapstruct.org/faq/#qualifier for more info."), @Diagnostic( type = ErroneousMapper.class, kind = Kind.ERROR, diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationAndNamedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationAndNamedMapper.java new file mode 100644 index 000000000..acf9674fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationAndNamedMapper.java @@ -0,0 +1,54 @@ +/* + * 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.selection.qualifier.errors; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Qualifier; + +@Mapper +public interface ErroneousMessageByAnnotationAndNamedMapper { + + @Mapping(target = "nested", source = "value", qualifiedBy = { + SelectMe1.class, + SelectMe2.class + }, qualifiedByName = { "selectMe1", "selectMe2" }) + Target map(Source source); + + default Nested map(String in) { + return null; + } + + // CHECKSTYLE:OFF + class Source { + public String value; + } + + class Target { + public Nested nested; + } + + class Nested { + public String value; + } + // CHECKSTYLE ON + + @Qualifier + @java.lang.annotation.Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface SelectMe1 { + } + + @Qualifier + @java.lang.annotation.Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface SelectMe2 { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationMapper.java new file mode 100644 index 000000000..d5aa391f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByAnnotationMapper.java @@ -0,0 +1,45 @@ +/* + * 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.selection.qualifier.errors; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Qualifier; + +@Mapper +public interface ErroneousMessageByAnnotationMapper { + + @Mapping(target = "nested", source = "value", qualifiedBy = SelectMe.class) + Target map(Source source); + + default Nested map(String in) { + return null; + } + + // CHECKSTYLE:OFF + class Source { + public String value; + } + + class Target { + public Nested nested; + } + + class Nested { + public String value; + } + // CHECKSTYLE ON + + @Qualifier + @java.lang.annotation.Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface SelectMe { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedMapper.java new file mode 100644 index 000000000..44f0a5776 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedMapper.java @@ -0,0 +1,45 @@ +/* + * 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.selection.qualifier.errors; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Qualifier; + +@Mapper +public interface ErroneousMessageByNamedMapper { + + @Mapping(target = "nested", source = "value", qualifiedByName = "SelectMe") + Target map(Source source); + + default Nested map(String in) { + return null; + } + + // CHECKSTYLE:OFF + class Source { + public String value; + } + + class Target { + public Nested nested; + } + + class Nested { + public String value; + } + // CHECKSTYLE ON + + @Qualifier + @java.lang.annotation.Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface SelectMe { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java new file mode 100644 index 000000000..4c8edc64d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java @@ -0,0 +1,90 @@ +/* + * 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.selection.qualifier.errors; + +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; + +import static javax.tools.Diagnostic.Kind.ERROR; + +@IssueKey("2135") +@RunWith(AnnotationProcessorTestRunner.class) +public class QualfierMessageTest { + + @Test + @WithClasses(ErroneousMessageByAnnotationMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousMessageByAnnotationMapper.class, + kind = ERROR, + line = 19, + message = "Qualifier error. No method found annotated with: [ @SelectMe ]. " + + "See https://mapstruct.org/faq/#qualifier for more info."), + @Diagnostic( + type = ErroneousMessageByAnnotationMapper.class, + kind = ERROR, + line = 19, + messageRegExp = "Can't map property.*") + + } + ) + public void testNoQualifyingMethodByAnnotationFound() { + } + + @Test + @WithClasses(ErroneousMessageByNamedMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousMessageByNamedMapper.class, + kind = ERROR, + line = 19, + message = "Qualifier error. No method found annotated with @Named#value: [ SelectMe ]. " + + "See https://mapstruct.org/faq/#qualifier for more info."), + @Diagnostic( + type = ErroneousMessageByNamedMapper.class, + kind = ERROR, + line = 19, + messageRegExp = "Can't map property.*") + + } + ) + public void testNoQualifyingMethodByNamedFound() { + } + + @Test + @WithClasses(ErroneousMessageByAnnotationAndNamedMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousMessageByAnnotationAndNamedMapper.class, + kind = ERROR, + line = 19, + message = + "Qualifier error. No method found annotated with @Named#value: [ selectMe1 and selectMe2 ], " + + "annotated with [ @SelectMe1 and @SelectMe2 ]. " + + "See https://mapstruct.org/faq/#qualifier for more info."), + @Diagnostic( + type = ErroneousMessageByAnnotationAndNamedMapper.class, + kind = ERROR, + line = 19, + messageRegExp = "Can't map property.*") + + } + ) + public void testNoQualifyingMethodByAnnotationAndNamedFound() { + } +}