#2393 Use includeModel when generating GeneratedType

With this we make sure that the implementation type will have a correct import in case of a clash with another mapper named the same
This commit is contained in:
Filip Hrisafov 2021-04-05 12:44:55 +02:00
parent 85d3b310f7
commit c9199b7068
10 changed files with 224 additions and 43 deletions

View File

@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.model;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.model.common.Accessibility;
@ -74,7 +73,8 @@ public class Decorator extends GeneratedType {
hasDelegateConstructor );
String elementPackage = elementUtils.getPackageOf( mapperElement ).getQualifiedName().toString();
Type mapperType = typeFactory.getType( mapperElement );
String elementPackage = mapperType.getPackageName();
String packageName = implPackage.replace( Mapper.PACKAGE_NAME_PLACEHOLDER, elementPackage );
return new Decorator(
@ -82,10 +82,8 @@ public class Decorator extends GeneratedType {
packageName,
implementationName,
decoratorType,
elementPackage,
mapperElement.getKind() == ElementKind.INTERFACE ? mapperElement.getSimpleName().toString() : null,
mapperType,
methods,
Arrays.asList( new Field( typeFactory.getType( mapperElement ), "delegate", true ) ),
options,
versionInformation,
Accessibility.fromModifiers( mapperElement.getModifiers() ),
@ -96,22 +94,22 @@ public class Decorator extends GeneratedType {
}
private final Type decoratorType;
private final Type mapperType;
@SuppressWarnings( "checkstyle:parameternumber" )
private Decorator(TypeFactory typeFactory, String packageName, String name, Type decoratorType,
String interfacePackage, String interfaceName, List<MappingMethod> methods,
List<Field> fields, Options options, VersionInformation versionInformation,
Type mapperType,
List<MappingMethod> methods,
Options options, VersionInformation versionInformation,
Accessibility accessibility, SortedSet<Type> extraImports,
DecoratorConstructor decoratorConstructor) {
super(
typeFactory,
packageName,
name,
decoratorType.getName(),
interfacePackage,
interfaceName,
decoratorType,
methods,
fields,
Arrays.asList( new Field( mapperType, "delegate", true ) ),
options,
versionInformation,
accessibility,
@ -120,6 +118,7 @@ public class Decorator extends GeneratedType {
);
this.decoratorType = decoratorType;
this.mapperType = mapperType;
}
@Override
@ -148,4 +147,8 @@ public class Decorator extends GeneratedType {
protected String getTemplateName() {
return getTemplateNameForClass( GeneratedType.class );
}
public Type getMapperType() {
return mapperType;
}
}

View File

@ -80,9 +80,7 @@ public abstract class GeneratedType extends ModelElement {
private final String packageName;
private final String name;
private final String superClassName;
private final String interfacePackage;
private final String interfaceName;
private final Type mapperDefinitionType;
private final List<Annotation> annotations;
private final List<MappingMethod> methods;
@ -102,15 +100,13 @@ public abstract class GeneratedType extends ModelElement {
private final boolean generatedTypeAvailable;
// CHECKSTYLE:OFF
protected GeneratedType(TypeFactory typeFactory, String packageName, String name, String superClassName,
String interfacePackage, String interfaceName, List<MappingMethod> methods,
protected GeneratedType(TypeFactory typeFactory, String packageName, String name,
Type mapperDefinitionType, List<MappingMethod> methods,
List<Field> fields, Options options, VersionInformation versionInformation,
Accessibility accessibility, SortedSet<Type> extraImportedTypes, Constructor constructor) {
this.packageName = packageName;
this.name = name;
this.superClassName = superClassName;
this.interfacePackage = interfacePackage;
this.interfaceName = interfaceName;
this.mapperDefinitionType = mapperDefinitionType;
this.extraImportedTypes = extraImportedTypes;
this.annotations = new ArrayList<>();
@ -153,16 +149,8 @@ public abstract class GeneratedType extends ModelElement {
return name;
}
public String getSuperClassName() {
return superClassName;
}
public String getInterfacePackage() {
return interfacePackage;
}
public String getInterfaceName() {
return interfaceName;
public Type getMapperDefinitionType() {
return mapperDefinitionType;
}
public List<Annotation> getAnnotations() {
@ -214,6 +202,8 @@ public abstract class GeneratedType extends ModelElement {
SortedSet<Type> importedTypes = new TreeSet<>();
addIfImportRequired( importedTypes, generatedType );
addIfImportRequired( importedTypes, mapperDefinitionType );
for ( MappingMethod mappingMethod : methods ) {
for ( Type type : mappingMethod.getImportTypes() ) {
addIfImportRequired( importedTypes, type );

View File

@ -10,7 +10,6 @@ import java.util.Set;
import java.util.SortedSet;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.model.common.Accessibility;
@ -90,13 +89,14 @@ public class Mapper extends GeneratedType {
if ( !fragments.isEmpty() ) {
constructor = new NoArgumentConstructor( implementationName, fragments );
}
Type definitionType = typeFactory.getType( element );
return new Mapper(
typeFactory,
packageName,
implementationName,
element.getKind() != ElementKind.INTERFACE ? element.getSimpleName().toString() : null,
elementPackage,
element.getKind() == ElementKind.INTERFACE ? element.getSimpleName().toString() : null,
definitionType,
customPackage,
customName,
methods,
@ -117,8 +117,9 @@ public class Mapper extends GeneratedType {
private Decorator decorator;
@SuppressWarnings( "checkstyle:parameternumber" )
private Mapper(TypeFactory typeFactory, String packageName, String name, String superClassName,
String interfacePackage, String interfaceName, boolean customPackage, boolean customImplName,
private Mapper(TypeFactory typeFactory, String packageName, String name,
Type mapperDefinitionType,
boolean customPackage, boolean customImplName,
List<MappingMethod> methods, Options options, VersionInformation versionInformation,
Accessibility accessibility, List<Field> fields, Constructor constructor,
Decorator decorator, SortedSet<Type> extraImportedTypes ) {
@ -127,9 +128,7 @@ public class Mapper extends GeneratedType {
typeFactory,
packageName,
name,
superClassName,
interfacePackage,
interfaceName,
mapperDefinitionType,
methods,
fields,
options,

View File

@ -12,9 +12,11 @@ import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Decorator;
import org.mapstruct.ap.internal.model.GeneratedType;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.ServicesEntry;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.MapperOptions;
import org.mapstruct.ap.internal.writer.ModelWriter;
@ -55,15 +57,28 @@ public class MapperServiceProcessor implements ModelElementProcessor<Mapper, Vo
private void writeToSourceFile(Filer filer, Mapper model) {
ModelWriter modelWriter = new ModelWriter();
ServicesEntry servicesEntry = getServicesEntry( model.getDecorator() == null ? model : model.getDecorator() );
ServicesEntry servicesEntry = getServicesEntry( model );
createSourceFile( servicesEntry, modelWriter, filer );
}
private ServicesEntry getServicesEntry(GeneratedType model) {
String mapperName = model.getInterfaceName() != null ? model.getInterfaceName() : model.getSuperClassName();
private ServicesEntry getServicesEntry(Mapper mapper) {
if ( mapper.getDecorator() != null ) {
return getServicesEntry( mapper.getDecorator() );
}
return new ServicesEntry(model.getInterfacePackage(), mapperName,
return getServicesEntry( mapper.getMapperDefinitionType(), mapper );
}
private ServicesEntry getServicesEntry(Decorator decorator) {
return getServicesEntry( decorator.getMapperType(), decorator );
}
private ServicesEntry getServicesEntry(Type mapperType, GeneratedType model) {
String mapperName = mapperType.getName();
String mapperPackageName = mapperType.getPackageName();
return new ServicesEntry(mapperPackageName, mapperName,
model.getPackageName(), model.getName());
}

View File

@ -24,7 +24,7 @@ import ${importedType};
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#lt>${accessibility.keyword} class ${name}<#if superClassName??> extends ${superClassName}</#if><#if interfaceName??> implements ${interfaceName}</#if> {
<#lt>${accessibility.keyword} class ${name} <#if mapperDefinitionType.interface>implements<#else>extends</#if> <@includeModel object=mapperDefinitionType/> {
<#list fields as field><#if field.used><#nt> <@includeModel object=field/>
</#if></#list>

View File

@ -0,0 +1,28 @@
/*
* 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._2393;
/**
* @author Filip Hrisafov
*/
public class Address {
private final String city;
private final Country country;
public Address(String city, Country country) {
this.city = city;
this.country = country;
}
public String getCity() {
return city;
}
public Country getCountry() {
return country;
}
}

View File

@ -0,0 +1,42 @@
/*
* 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._2393;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
public class AddressDto {
private String city;
private CountryDto country;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public CountryDto getCountry() {
return country;
}
public void setCountry(CountryDto country) {
this.country = country;
}
@Mapper(uses = CountryDto.Converter.class)
public interface Converter {
Converter INSTANCE = Mappers.getMapper( Converter.class );
AddressDto convert(Address address);
}
}

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.ap.test.bugs._2393;
/**
* @author Filip Hrisafov
*/
public class Country {
private final String name;
public Country(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,42 @@
/*
* 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._2393;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* @author Filip Hrisafov
*/
public class CountryDto {
private String name;
private String code;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Mapper
public interface Converter {
@Mapping(target = "code", constant = "UNKNOWN")
CountryDto convert(Country from);
}
}

View File

@ -0,0 +1,40 @@
/*
* 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._2393;
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.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@IssueKey("2393")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
Address.class,
AddressDto.class,
Country.class,
CountryDto.class,
})
public class Issue2393Test {
@Test
public void shouldUseCorrectImport() {
AddressDto dto = AddressDto.Converter.INSTANCE.convert( new Address(
"Zurich",
new Country( "Switzerland" )
) );
assertThat( dto.getCity() ).isEqualTo( "Zurich" );
assertThat( dto.getCountry().getName() ).isEqualTo( "Switzerland" );
assertThat( dto.getCountry().getCode() ).isEqualTo( "UNKNOWN" );
}
}