#2135 improved messages for not able to select qualified method (#2141)

* #2135 improved messages for not able to select qualified method
This commit is contained in:
Sjaak Derksen 2020-07-05 22:22:01 +02:00 committed by Filip Hrisafov
parent c0d88f86bf
commit 74f281fa3e
8 changed files with 302 additions and 10 deletions

View File

@ -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<String> 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<String> 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;

View File

@ -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;

View File

@ -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() {
}
}

View File

@ -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,

View File

@ -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 {
}
}

View File

@ -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 {
}
}

View File

@ -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 {
}
}

View File

@ -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() {
}
}