#1904 Create compilation error if a mapper could not be created due to a TypeHierarchyErroneousException

This commit is contained in:
Filip Hrisafov 2019-09-21 10:06:53 +02:00
parent 750ce48023
commit e92e3b45c6
5 changed files with 185 additions and 3 deletions

View File

@ -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<TypeElement> deferredMappers = new HashSet<>();
private Set<DeferredMapper> deferredMappers = new HashSet<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
@ -163,6 +164,40 @@ public class MappingProcessor extends AbstractProcessor {
Set<TypeElement> 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<TypeElement> getAndResetDeferredMappers() {
Set<TypeElement> 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;
}
}
}

View File

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

View File

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

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

View File

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