#2056: Handle null TypeMirror in uses and import gracefully

Due to a bug in javac (JDK-8229535) for an annotation with Class values, instead of returning a TypeMirror with TypeKind#ERROR the compiler returns the string "<error>". Eclipse doesn't have this problem currently.
This commit is contained in:
Filip Hrisafov 2020-04-10 10:04:51 +02:00
parent c410379f83
commit a845197b0b
13 changed files with 318 additions and 8 deletions

View File

@ -135,4 +135,25 @@ public class MavenIntegrationTest {
}
/**
* Tests usage of MapStruct with another processor that generates the uses type of a mapper.
*/
@ProcessorTest(baseDir = "usesTypeGenerationTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
})
void usesTypeGenerationTest() {
}
/**
* Tests usage of MapStruct with another processor that generates the uses type of a mapper.
*/
@ProcessorTest(baseDir = "usesTypeGenerationTest", processorTypes = {
ProcessorTest.ProcessorType.ECLIPSE_JDT
})
@EnabledForJreRange(min = JRE.JAVA_11)
// For some reason the second run with eclipse does not load the ModelElementProcessor(s) on java 8,
// therefore we run this only on Java 11
void usesTypeGenerationTestEclipse() {
}
}

View File

@ -0,0 +1,43 @@
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mapstruct.itest</groupId>
<artifactId>itest-usestypegeneration-aggregator</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>itest-usestypegeneration-generator</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<compilerArg>-proc:none</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,60 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.usestypegeneration;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
/**
* Generate conversion uses.
*
* @author Filip Hrisafov
*
*/
@SupportedAnnotationTypes("*")
public class UsesTypeGenerationProcessor extends AbstractProcessor {
private boolean hasRun = false;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if ( !hasRun ) {
try {
JavaFileObject dto = processingEnv.getFiler().createSourceFile( "org.mapstruct.itest.usestypegeneration.usage.StringUtils" );
Writer writer = dto.openWriter();
writer.append( "package org.mapstruct.itest.usestypegeneration.usage;" );
writer.append( "\n" );
writer.append( "public class StringUtils {" );
writer.append( "\n" );
writer.append( " public static String upperCase(String string) {" );
writer.append( "\n" );
writer.append( " return string == null ? null : string.toUpperCase();" );
writer.append( "\n" );
writer.append( " }" );
writer.append( "\n" );
writer.append( "}" );
writer.flush();
writer.close();
}
catch (IOException e) {
throw new RuntimeException( e );
}
hasRun = true;
}
return false;
}
}

View File

@ -0,0 +1,4 @@
# Copyright MapStruct Authors.
#
# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
org.mapstruct.itest.usestypegeneration.UsesTypeGenerationProcessor

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-it-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.mapstruct.itest</groupId>
<artifactId>itest-usestypegeneration-aggregator</artifactId>
<packaging>pom</packaging>
<modules>
<module>generator</module>
<module>usage</module>
</modules>
</project>

View File

@ -0,0 +1,52 @@
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mapstruct.itest</groupId>
<artifactId>itest-usestypegeneration-aggregator</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>itest-usestypegeneration-usage</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mapstruct.itest</groupId>
<artifactId>itest-usestypegeneration-generator</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<compilerArg>-XprintProcessorInfo</compilerArg>
<compilerArg>-XprintRounds</compilerArg>
</compilerArgs>
<testCompilerArgument>-proc:none</testCompilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.usestypegeneration.usage;
/**
* @author Filip Hrisafov
*/
public class Order {
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.usestypegeneration.usage;
/**
* @author Filip Hrisafov
*/
public class OrderDto {
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.usestypegeneration.usage;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(uses = StringUtils.class)
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
OrderDto orderToDto(Order order);
}

View File

@ -0,0 +1,27 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.usestypegeneration.usage;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration test for using MapStruct with another annotation processor that generates the other mappers for uses
*
* @author Filip Hrisafov
*/
public class GeneratedUsesTypeTest {
@Test
public void considersPropertiesOnGeneratedSourceAndTargetTypes() {
Order order = new Order();
order.setItem( "my item" );
OrderDto dto = OrderMapper.INSTANCE.orderToDto( order );
assertThat( dto.getItem() ).isEqualTo( "MY ITEM" );
}
}

View File

@ -21,7 +21,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.mapstruct.gem.version>1.0.0.Alpha1</org.mapstruct.gem.version>
<org.mapstruct.gem.version>1.0.0.Alpha2</org.mapstruct.gem.version>
<!-- We can't go to 3.0.0-M2 as it has a regression. See https://issues.apache.org/jira/browse/MENFORCER-306 -->
<org.apache.maven.plugins.enforcer.version>3.0.0-M1</org.apache.maven.plugins.enforcer.version>
<org.apache.maven.plugins.surefire.version>3.0.0-M3</org.apache.maven.plugins.surefire.version>

View File

@ -29,6 +29,7 @@ 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.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind;
@ -177,7 +178,8 @@ public class MappingProcessor extends AbstractProcessor {
erroneousElementName = ( (QualifiedNameable) erroneousElement ).getQualifiedName().toString();
}
else {
erroneousElementName = erroneousElement.getSimpleName().toString();
erroneousElementName =
erroneousElement != null ? erroneousElement.getSimpleName().toString() : null;
}
// When running on Java 8 we need to fetch the deferredMapperElement again.
@ -265,9 +267,10 @@ public class MappingProcessor extends AbstractProcessor {
processMapperTypeElement( context, mapperElement );
}
catch ( TypeHierarchyErroneousException thie ) {
Element erroneousElement = roundContext.getAnnotationProcessorContext()
TypeMirror erroneousType = thie.getType();
Element erroneousElement = erroneousType != null ? roundContext.getAnnotationProcessorContext()
.getTypeUtils()
.asElement( thie.getType() );
.asElement( erroneousType ) : null;
if ( options.isVerbose() ) {
processingEnv.getMessager().printMessage(
Kind.NOTE, "MapStruct: referred types not available (yet), deferring mapper: "

View File

@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.model.source;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
@ -21,6 +20,7 @@ import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem;
import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
/**
* Chain Of Responsibility Pattern.
@ -110,9 +110,18 @@ public abstract class DelegatingOptions {
}
protected Set<DeclaredType> toDeclaredTypes(List<TypeMirror> in, Set<DeclaredType> next) {
Set result = in.stream()
.map( DeclaredType.class::cast )
.collect( Collectors.toCollection( LinkedHashSet::new ) );
Set<DeclaredType> result = new LinkedHashSet<>();
for ( TypeMirror typeMirror : in ) {
if ( typeMirror == null ) {
// When a class used in uses or imports is created by another annotation processor
// then javac will not return correct TypeMirror with TypeKind#ERROR, but rather a string "<error>"
// the gem tools would return a null TypeMirror in that case.
// Therefore throw TypeHierarchyErroneousException so we can postpone the generation of the mapper
throw new TypeHierarchyErroneousException( typeMirror );
}
result.add( (DeclaredType) typeMirror );
}
result.addAll( next );
return result;
}