From e92e3b45c6c8bc1bd4a501e6432f884e80153a29 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 21 Sep 2019 10:06:53 +0200 Subject: [PATCH] #1904 Create compilation error if a mapper could not be created due to a TypeHierarchyErroneousException --- .../org/mapstruct/ap/MappingProcessor.java | 56 ++++++++++++++++++- ...AstModifyingAnnotationProcessorSaysNo.java | 23 ++++++++ .../ap/test/bugs/_1904/Issue1904Mapper.java | 49 ++++++++++++++++ .../ap/test/bugs/_1904/Issue1904Test.java | 48 ++++++++++++++++ .../ap/test/verbose/VerboseTest.java | 12 ++++ 5 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index d9686e963..e31b2a07b 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -27,6 +27,7 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; +import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementKindVisitor6; import javax.tools.Diagnostic.Kind; @@ -115,7 +116,7 @@ public class MappingProcessor extends AbstractProcessor { * generated by other processors), this mapper will not be generated; That's fine, the compiler will raise an error * due to the inconsistent Java types used as source or target anyways. */ - private Set deferredMappers = new HashSet<>(); + private Set deferredMappers = new HashSet<>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { @@ -163,6 +164,40 @@ public class MappingProcessor extends AbstractProcessor { Set mappers = getMappers( annotations, roundEnvironment ); processMapperElements( mappers, roundContext ); } + else if ( !deferredMappers.isEmpty() ) { + // If the processing is over and there are deferred mappers it means something wrong occurred and + // MapStruct didn't generate implementations for those + for ( DeferredMapper deferredMapper : deferredMappers ) { + + TypeElement deferredMapperElement = deferredMapper.deferredMapperElement; + Element erroneousElement = deferredMapper.erroneousElement; + String erroneousElementName; + + if ( erroneousElement instanceof QualifiedNameable ) { + erroneousElementName = ( (QualifiedNameable) erroneousElement ).getQualifiedName().toString(); + } + else { + erroneousElementName = erroneousElement.getSimpleName().toString(); + } + + // When running on Java 8 we need to fetch the deferredMapperElement again. + // Otherwise the reporting will not work properly + deferredMapperElement = annotationProcessorContext.getElementUtils() + .getTypeElement( deferredMapperElement.getQualifiedName() ); + + processingEnv.getMessager() + .printMessage( + Kind.ERROR, + "No implementation was created for " + deferredMapperElement.getSimpleName() + + " due to having a problem in the erroneous element " + erroneousElementName + "." + + " Hint: this often means that some other annotation processor was supposed to" + + " process the erroneous element. You can also enable MapStruct verbose mode by setting" + + " -Amapstruct.verbose=true as a compilation argument.", + deferredMapperElement + ); + } + + } return ANNOTATIONS_CLAIMED_EXCLUSIVELY; } @@ -174,7 +209,8 @@ public class MappingProcessor extends AbstractProcessor { private Set getAndResetDeferredMappers() { Set deferred = new HashSet<>( deferredMappers.size() ); - for (TypeElement element : deferredMappers ) { + for ( DeferredMapper deferredMapper : deferredMappers ) { + TypeElement element = deferredMapper.deferredMapperElement; deferred.add( processingEnv.getElementUtils().getTypeElement( element.getQualifiedName() ) ); } @@ -229,12 +265,15 @@ public class MappingProcessor extends AbstractProcessor { processMapperTypeElement( context, mapperElement ); } catch ( TypeHierarchyErroneousException thie ) { + Element erroneousElement = roundContext.getAnnotationProcessorContext() + .getTypeUtils() + .asElement( thie.getType() ); if ( options.isVerbose() ) { processingEnv.getMessager().printMessage( Kind.NOTE, "MapStruct: referred types not available (yet), deferring mapper: " + mapperElement ); } - deferredMappers.add( mapperElement ); + deferredMappers.add( new DeferredMapper( mapperElement, erroneousElement ) ); } catch ( Throwable t ) { handleUncaughtError( mapperElement, t ); @@ -347,4 +386,15 @@ public class MappingProcessor extends AbstractProcessor { return Integer.compare( o1.getPriority(), o2.getPriority() ); } } + + private static class DeferredMapper { + + private final TypeElement deferredMapperElement; + private final Element erroneousElement; + + private DeferredMapper(TypeElement deferredMapperElement, Element erroneousElement) { + this.deferredMapperElement = deferredMapperElement; + this.erroneousElement = erroneousElement; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.java new file mode 100644 index 000000000..74cbc7a20 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.java @@ -0,0 +1,23 @@ +/* + * 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._1904; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * @author Filip Hrisafov + */ +public class AstModifyingAnnotationProcessorSaysNo implements AstModifyingAnnotationProcessor { + @Override + public boolean isTypeComplete(TypeMirror type) { + if ( type.toString().contains( "CarManualDto" ) ) { + return false; + } + return true; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.java new file mode 100644 index 000000000..a4f67cb77 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.java @@ -0,0 +1,49 @@ +/* + * 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._1904; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1904Mapper { + + CarManualDto translateManual(CarManual manual); + + /** + * @author Filip Hrisafov + */ + class CarManual { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + + /** + * @author Filip Hrisafov + */ + class CarManualDto { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.java new file mode 100644 index 000000000..7c2b95bbf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.java @@ -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._1904; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +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; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1904") +@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses({ + Issue1904Mapper.class, +}) +@WithServiceImplementation( + provides = AstModifyingAnnotationProcessor.class, + value = AstModifyingAnnotationProcessorSaysNo.class +) +public class Issue1904Test { + + @Test + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic( + type = Issue1904Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + messageRegExp = ".*No implementation was created for Issue1904Mapper due to having a problem in the " + + "erroneous element .*\\.CarManualDto. Hint: this often means that some other annotation processor " + + "was supposed to process the erroneous element. You can also enable MapStruct verbose mode by " + + "setting -Amapstruct.verbose=true as a compilation argument." + ) + }) + public void shouldHaveCompilationErrorIfMapperCouldNotBeCreated() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java index 99b77b122..ba5187e74 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java @@ -16,6 +16,9 @@ import org.mapstruct.ap.spi.ImmutablesBuilderProvider; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithServiceImplementation; +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.compilation.annotation.ExpectedNote; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; @@ -57,6 +60,15 @@ public class VerboseTest { @ProcessorOption(name = "mapstruct.verbose", value = "true") @WithClasses(CreateBeanMapping.class) @ExpectedNote("^MapStruct: referred types not available \\(yet\\), deferring mapper:.*CreateBeanMapping.*$") + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic( + type = CreateBeanMapping.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 12, + messageRegExp = ".*No implementation was created for CreateBeanMapping due to having a problem in the " + + "erroneous element .*" + ) + }) public void testDeferred() { }