#3659: Support @AnnotatedWith on decorators

Signed-off-by: TangYang <tangyang9464@163.com>
This commit is contained in:
Yang Tang 2025-05-31 23:52:05 +08:00 committed by GitHub
parent bff88297e3
commit ce84c81de2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 794 additions and 52 deletions

View File

@ -7,14 +7,15 @@ package org.mapstruct.ap.internal.model;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.gem.DecoratedWithGem;
import org.mapstruct.ap.internal.model.common.Accessibility;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.gem.DecoratedWithGem;
import org.mapstruct.ap.internal.version.VersionInformation;
/**
@ -33,6 +34,7 @@ public class Decorator extends GeneratedType {
private String implName;
private String implPackage;
private boolean suppressGeneratorTimestamp;
private Set<Annotation> customAnnotations;
public Builder() {
super( Builder.class );
@ -68,6 +70,11 @@ public class Decorator extends GeneratedType {
return this;
}
public Builder additionalAnnotations(Set<Annotation> customAnnotations) {
this.customAnnotations = customAnnotations;
return this;
}
public Decorator build() {
String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER,
Mapper.getFlatName( mapperElement ) );
@ -95,7 +102,8 @@ public class Decorator extends GeneratedType {
suppressGeneratorTimestamp,
Accessibility.fromModifiers( mapperElement.getModifiers() ),
extraImportedTypes,
decoratorConstructor
decoratorConstructor,
customAnnotations
);
}
}
@ -110,7 +118,8 @@ public class Decorator extends GeneratedType {
Options options, VersionInformation versionInformation,
boolean suppressGeneratorTimestamp,
Accessibility accessibility, SortedSet<Type> extraImports,
DecoratorConstructor decoratorConstructor) {
DecoratorConstructor decoratorConstructor,
Set<Annotation> customAnnotations) {
super(
typeFactory,
packageName,
@ -128,6 +137,11 @@ public class Decorator extends GeneratedType {
this.decoratorType = decoratorType;
this.mapperType = mapperType;
// Add custom annotations
if ( customAnnotations != null ) {
customAnnotations.forEach( this::addAnnotation );
}
}
@Override

View File

@ -14,6 +14,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.gem.InjectionStrategyGem;
import org.mapstruct.ap.internal.model.AnnotatedConstructor;
import org.mapstruct.ap.internal.model.AnnotatedSetter;
import org.mapstruct.ap.internal.model.Annotation;
@ -24,7 +25,6 @@ import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.MapperReference;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.gem.InjectionStrategyGem;
import org.mapstruct.ap.internal.model.source.MapperOptions;
/**
@ -88,7 +88,7 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
protected void adjustDecorator(Mapper mapper, InjectionStrategyGem injectionStrategy) {
Decorator decorator = mapper.getDecorator();
for ( Annotation typeAnnotation : getDecoratorAnnotations() ) {
for ( Annotation typeAnnotation : getDecoratorAnnotations( decorator ) ) {
decorator.addAnnotation( typeAnnotation );
}
@ -308,7 +308,7 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
/**
* @return the annotation(s) to be added at the decorator of the mapper
*/
protected List<Annotation> getDecoratorAnnotations() {
protected List<Annotation> getDecoratorAnnotations(Decorator decorator) {
return Collections.emptyList();
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Decorator;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
@ -39,7 +40,7 @@ public class JakartaComponentProcessor extends AnnotationBasedComponentModelProc
}
@Override
protected List<Annotation> getDecoratorAnnotations() {
protected List<Annotation> getDecoratorAnnotations(Decorator decorator) {
return Arrays.asList( singleton(), named() );
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Decorator;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
@ -42,7 +43,7 @@ public class Jsr330ComponentProcessor extends AnnotationBasedComponentModelProce
}
@Override
protected List<Annotation> getDecoratorAnnotations() {
protected List<Annotation> getDecoratorAnnotations(Decorator decorator) {
return Arrays.asList( singleton(), named() );
}

View File

@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.gem.MapperGem;
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.BeanMappingMethod;
import org.mapstruct.ap.internal.model.ContainerMappingMethod;
import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder;
@ -287,6 +288,9 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
messager.printMessage( element, decoratedWith.mirror(), Message.DECORATOR_CONSTRUCTOR );
}
// Get annotations from the decorator class
Set<Annotation> decoratorAnnotations = additionalAnnotationsBuilder.getProcessedAnnotations( decoratorElement );
Decorator decorator = new Decorator.Builder()
.elementUtils( elementUtils )
.typeFactory( typeFactory )
@ -300,6 +304,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.implPackage( mapperOptions.implementationPackage() )
.extraImports( getExtraImports( element, mapperOptions ) )
.suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() )
.additionalAnnotations( decoratorAnnotations )
.build();
return decorator;

View File

@ -9,20 +9,22 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Decorator;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
import static javax.lang.model.element.ElementKind.PACKAGE;
/**
@ -35,6 +37,9 @@ import static javax.lang.model.element.ElementKind.PACKAGE;
*/
public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor {
private static final String SPRING_COMPONENT_ANNOTATION = "org.springframework.stereotype.Component";
private static final String SPRING_PRIMARY_ANNOTATION = "org.springframework.context.annotation.Primary";
@Override
protected String getComponentModelIdentifier() {
return MappingConstantsGem.ComponentModelGem.SPRING;
@ -54,12 +59,37 @@ public class SpringComponentProcessor extends AnnotationBasedComponentModelProce
return typeAnnotations;
}
/**
* Returns the annotations that need to be added to the generated decorator, filtering out any annotations
* that are already present or represented as meta-annotations.
*
* @param decorator the decorator to process
* @return A list of annotations that should be added to the generated decorator.
*/
@Override
protected List<Annotation> getDecoratorAnnotations() {
return Arrays.asList(
component(),
primary()
);
protected List<Annotation> getDecoratorAnnotations(Decorator decorator) {
Set<String> desiredAnnotationNames = new LinkedHashSet<>();
desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION );
desiredAnnotationNames.add( SPRING_PRIMARY_ANNOTATION );
List<Annotation> decoratorAnnotations = decorator.getAnnotations();
if ( !decoratorAnnotations.isEmpty() ) {
Set<Element> handledElements = new HashSet<>();
for ( Annotation annotation : decoratorAnnotations ) {
removeAnnotationsPresentOnElement(
annotation.getType().getTypeElement(),
desiredAnnotationNames,
handledElements
);
if ( desiredAnnotationNames.isEmpty() ) {
// If all annotations are removed, we can stop further processing
return Collections.emptyList();
}
}
}
return desiredAnnotationNames.stream()
.map( this::createAnnotation )
.collect( Collectors.toList() );
}
@Override
@ -82,8 +112,12 @@ public class SpringComponentProcessor extends AnnotationBasedComponentModelProce
return true;
}
private Annotation createAnnotation(String canonicalName) {
return new Annotation( getTypeFactory().getType( canonicalName ) );
}
private Annotation autowired() {
return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) );
return createAnnotation( "org.springframework.beans.factory.annotation.Autowired" );
}
private Annotation qualifierDelegate() {
@ -96,34 +130,51 @@ public class SpringComponentProcessor extends AnnotationBasedComponentModelProce
) ) );
}
private Annotation primary() {
return new Annotation( getTypeFactory().getType( "org.springframework.context.annotation.Primary" ) );
}
private Annotation component() {
return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) );
return createAnnotation( SPRING_COMPONENT_ANNOTATION );
}
private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) {
Set<Element> handledElements = new HashSet<>();
return mapper.getAnnotations()
.stream()
.anyMatch(
annotation -> isOrIncludesComponentAnnotation( annotation, handledElements )
);
Set<String> desiredAnnotationNames = new LinkedHashSet<>();
desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION );
List<Annotation> mapperAnnotations = mapper.getAnnotations();
if ( !mapperAnnotations.isEmpty() ) {
Set<Element> handledElements = new HashSet<>();
for ( Annotation annotation : mapperAnnotations ) {
removeAnnotationsPresentOnElement(
annotation.getType().getTypeElement(),
desiredAnnotationNames,
handledElements
);
if ( desiredAnnotationNames.isEmpty() ) {
// If all annotations are removed, we can stop further processing
return true;
}
}
}
return false;
}
private boolean isOrIncludesComponentAnnotation(Annotation annotation, Set<Element> handledElements) {
return isOrIncludesComponentAnnotation(
annotation.getType().getTypeElement(), handledElements
);
}
private boolean isOrIncludesComponentAnnotation(Element element, Set<Element> handledElements) {
if ( "org.springframework.stereotype.Component".equals(
( (TypeElement) element ).getQualifiedName().toString()
)) {
return true;
/**
* Removes all the annotations and meta-annotations from {@code annotations} which are on the given element.
*
* @param element the element to check
* @param annotations the annotations to check for
* @param handledElements set of already handled elements to avoid infinite recursion
*/
private void removeAnnotationsPresentOnElement(Element element, Set<String> annotations,
Set<Element> handledElements) {
if ( annotations.isEmpty() ) {
return;
}
if ( element instanceof TypeElement &&
annotations.remove( ( (TypeElement) element ).getQualifiedName().toString() ) ) {
if ( annotations.isEmpty() ) {
// If all annotations are removed, we can stop further processing
return;
}
}
for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) {
@ -132,17 +183,16 @@ public class SpringComponentProcessor extends AnnotationBasedComponentModelProce
if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) &&
!handledElements.contains( annotationMirrorElement ) ) {
handledElements.add( annotationMirrorElement );
boolean isOrIncludesComponentAnnotation = isOrIncludesComponentAnnotation(
annotationMirrorElement, handledElements
);
if ( isOrIncludesComponentAnnotation ) {
return true;
if ( annotations.remove( ( (TypeElement) annotationMirrorElement ).getQualifiedName().toString() ) ) {
if ( annotations.isEmpty() ) {
// If all annotations are removed, we can stop further processing
return;
}
}
removeAnnotationsPresentOnElement( element, annotations, handledElements );
}
}
return false;
}
private PackageElement getPackageOf( Element element ) {

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
*/
package org.mapstruct.ap.test.decorator;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
@DecoratedWith(AnnotatedMapperDecorator.class)
public interface AnnotatedMapper {
AnnotatedMapper INSTANCE = Mappers.getMapper( AnnotatedMapper.class );
Target toTarget(Source source);
class Source {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
class Target {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,18 @@
/*
* 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.decorator;
import org.mapstruct.AnnotateWith;
@AnnotateWith(value = TestAnnotation.class, elements = @AnnotateWith.Element(strings = "decoratorValue"))
public abstract class AnnotatedMapperDecorator implements AnnotatedMapper {
private final AnnotatedMapper delegate;
public AnnotatedMapperDecorator(AnnotatedMapper delegate) {
this.delegate = delegate;
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.decorator;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for the application of @AnnotatedWith on decorator classes.
*/
@IssueKey("3659")
@WithClasses({
TestAnnotation.class,
AnnotatedMapper.class,
AnnotatedMapperDecorator.class
})
public class DecoratedWithAnnotatedWithTest {
@ProcessorTest
public void shouldApplyAnnotationFromDecorator() {
Class<?> implementationClass = AnnotatedMapper.INSTANCE.getClass();
assertThat( implementationClass ).hasAnnotation( TestAnnotation.class );
assertThat( implementationClass.getAnnotation( TestAnnotation.class ).value() ).isEqualTo( "decoratorValue" );
}
}

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.ap.test.decorator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test annotation for testing @AnnotatedWith on decorators.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String value() default "";
}

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.decorator.jakarta.annotatewith;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
/**
* A mapper using Jakarta component model with a decorator.
*/
@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA)
@DecoratedWith(JakartaAnnotateWithWithMapperDecorator.class)
public interface JakartaAnnotateWithMapper {
/**
* Maps a person to a person DTO.
*
* @param person the person to map
* @return the person DTO
*/
@Mapping(target = "name", ignore = true)
PersonDto personToPersonDto(Person person);
/**
* Maps an address to an address DTO.
*
* @param address the address to map
* @return the address DTO
*/
AddressDto addressToAddressDto(Address address);
}

View File

@ -0,0 +1,31 @@
/*
* 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.decorator.jakarta.annotatewith;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.mapstruct.AnnotateWith;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.test.decorator.TestAnnotation;
/**
* A decorator for {@link JakartaAnnotateWithMapper}.
*/
@AnnotateWith(value = TestAnnotation.class)
public abstract class JakartaAnnotateWithWithMapperDecorator implements JakartaAnnotateWithMapper {
@Inject
@Named("org.mapstruct.ap.test.decorator.jakarta.annotatewith.JakartaAnnotateWithMapperImpl_")
private JakartaAnnotateWithMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.decorator.jakarta.annotatewith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.test.decorator.TestAnnotation;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithJakartaInject;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import static java.lang.System.lineSeparator;
/**
* Test for the application of @AnnotateWith on decorator classes with Jakarta component model.
*/
@IssueKey("3659")
@WithClasses({
Person.class,
PersonDto.class,
Address.class,
AddressDto.class,
JakartaAnnotateWithMapper.class,
TestAnnotation.class,
JakartaAnnotateWithWithMapperDecorator.class
})
@WithJakartaInject
public class JakartaDecoratorAnnotateWithTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@ProcessorTest
public void hasCorrectImports() {
// check the decorator
generatedSource.forMapper( JakartaAnnotateWithMapper.class )
.content()
.contains( "import jakarta.inject.Inject;" )
.contains( "import jakarta.inject.Named;" )
.contains( "import jakarta.inject.Singleton;" )
.contains( "@TestAnnotation" )
.contains( "@Singleton" + lineSeparator() + "@Named" )
.doesNotContain( "javax.inject" );
// check the plain mapper
generatedSource.forDecoratedMapper( JakartaAnnotateWithMapper.class ).content()
.contains( "import jakarta.inject.Named;" )
.contains( "import jakarta.inject.Singleton;" )
.contains( "@Singleton" + lineSeparator() + "@Named" )
.doesNotContain( "javax.inject" );
}
}

View File

@ -18,6 +18,7 @@ import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330DecoratorAnnotateWithTest;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
@ -27,6 +28,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static java.lang.System.lineSeparator;
import static org.assertj.core.api.Assertions.assertThat;
@ -45,7 +47,13 @@ import static org.assertj.core.api.Assertions.assertThat;
PersonMapperDecorator.class
})
@IssueKey("592")
@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class)
@ComponentScan(
basePackageClasses = Jsr330DecoratorTest.class,
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = { Jsr330DecoratorAnnotateWithTest.class }
)
)
@Configuration
@WithJavaxInject
@DisabledOnJre(JRE.OTHER)

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.decorator.jsr330.annotatewith;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
/**
* A mapper using JSR-330 component model with a decorator.
*/
@Mapper(componentModel = MappingConstants.ComponentModel.JSR330)
@DecoratedWith(Jsr330AnnotateWithMapperDecorator.class)
public interface Jsr330AnnotateWithMapper {
@Mapping(target = "name", ignore = true)
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}

View File

@ -0,0 +1,32 @@
/*
* 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.decorator.jsr330.annotatewith;
import javax.inject.Inject;
import javax.inject.Named;
import org.mapstruct.AnnotateWith;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.test.decorator.TestAnnotation;
/**
* A decorator for {@link Jsr330AnnotateWithMapper}.
*/
@AnnotateWith(value = TestAnnotation.class)
public abstract class Jsr330AnnotateWithMapperDecorator implements Jsr330AnnotateWithMapper {
@Inject
@Named("org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330AnnotateWithMapperImpl_")
private Jsr330AnnotateWithMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}

View File

@ -0,0 +1,95 @@
/*
* 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.decorator.jsr330.annotatewith;
import java.util.Calendar;
import javax.inject.Inject;
import javax.inject.Named;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.test.decorator.TestAnnotation;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithJavaxInject;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for the application of @AnnotateWith on decorator classes with JSR-330 component model.
*/
@IssueKey("3659")
@WithClasses({
Person.class,
PersonDto.class,
Address.class,
AddressDto.class,
Jsr330AnnotateWithMapper.class,
Jsr330AnnotateWithMapperDecorator.class,
TestAnnotation.class
})
@ComponentScan(basePackageClasses = Jsr330DecoratorAnnotateWithTest.class)
@Configuration
@WithJavaxInject
@DisabledOnJre(JRE.OTHER)
public class Jsr330DecoratorAnnotateWithTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@Inject
@Named
private Jsr330AnnotateWithMapper jsr330AnnotateWithMapper;
private ConfigurableApplicationContext context;
@BeforeEach
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@AfterEach
public void springDown() {
if ( context != null ) {
context.close();
}
}
@ProcessorTest
public void shouldContainCustomAnnotation() {
generatedSource.forMapper( Jsr330AnnotateWithMapper.class )
.content()
.contains( "@TestAnnotation" );
}
@ProcessorTest
public void shouldInvokeDecoratorMethods() {
Calendar birthday = Calendar.getInstance();
birthday.set( 1928, Calendar.MAY, 23 );
Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) );
PersonDto personDto = jsr330AnnotateWithMapper.personToPersonDto( person );
assertThat( personDto ).isNotNull();
assertThat( personDto.getName() ).isEqualTo( "Gary Crant" );
assertThat( personDto.getAddress() ).isNotNull();
assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" );
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.decorator.spring.annotatewith;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
@DecoratedWith(AnnotateMapperDecorator.class)
public interface AnnotateMapper {
@Mapping(target = "name", ignore = true)
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}

View File

@ -0,0 +1,30 @@
/*
* 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.decorator.spring.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = "decoratorComponent"))
@AnnotateWith(value = Primary.class)
public abstract class AnnotateMapperDecorator implements AnnotateMapper {
@Autowired
@Qualifier("delegate")
private AnnotateMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.decorator.spring.annotatewith;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
@DecoratedWith(CustomAnnotateMapperDecorator.class)
public interface CustomAnnotateMapper {
@Mapping(target = "name", ignore = true)
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}

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.decorator.spring.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@AnnotateWith(value = CustomComponent.class, elements = @AnnotateWith.Element(strings = "customComponentDecorator"))
@AnnotateWith(value = CustomPrimary.class)
public abstract class CustomAnnotateMapperDecorator implements CustomAnnotateMapper {
@Autowired
@Qualifier("delegate")
private CustomAnnotateMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}

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.ap.test.decorator.spring.annotatewith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomComponent {
String value() default "";
}

View File

@ -0,0 +1,19 @@
/*
* 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.decorator.spring.annotatewith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Primary;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Primary
public @interface CustomPrimary {
}

View File

@ -0,0 +1,118 @@
/*
* 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.decorator.spring.annotatewith;
import java.util.Calendar;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithSpring;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for the application of @AnnotateWith on decorator classes with Spring component model.
*/
@IssueKey("3659")
@WithClasses({
Person.class,
PersonDto.class,
Address.class,
AddressDto.class,
AnnotateMapper.class,
AnnotateMapperDecorator.class,
CustomComponent.class,
CustomPrimary.class,
CustomAnnotateMapper.class,
CustomAnnotateMapperDecorator.class
})
@WithSpring
@ComponentScan(basePackageClasses = SpringDecoratorAnnotateWithTest.class)
@Configuration
public class SpringDecoratorAnnotateWithTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@Autowired
private AnnotateMapper annotateMapper;
@Autowired
private CustomAnnotateMapper customAnnotateMapper;
private ConfigurableApplicationContext context;
@BeforeEach
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@AfterEach
public void springDown() {
if ( context != null ) {
context.close();
}
}
@ProcessorTest
public void shouldNotDuplicateComponentAnnotation() {
generatedSource.forMapper( AnnotateMapper.class )
.content()
.contains( "@Component(value = \"decoratorComponent\")", "@Primary" )
.doesNotContain( "@Component" + System.lineSeparator() );
}
@ProcessorTest
public void shouldNotDuplicateCustomComponentAnnotation() {
generatedSource.forMapper( CustomAnnotateMapper.class )
.content()
.contains( "@CustomComponent(value = \"customComponentDecorator\")", "@CustomPrimary" )
.doesNotContain( "@Component" );
}
@ProcessorTest
public void shouldInvokeAnnotateDecoratorMethods() {
Calendar birthday = Calendar.getInstance();
birthday.set( 1928, Calendar.MAY, 23 );
Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) );
PersonDto personDto = annotateMapper.personToPersonDto( person );
assertThat( personDto ).isNotNull();
assertThat( personDto.getName() ).isEqualTo( "Gary Crant" );
assertThat( personDto.getAddress() ).isNotNull();
assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" );
}
@ProcessorTest
public void shouldInvokeCustomAnnotateDecoratorMethods() {
Calendar birthday = Calendar.getInstance();
birthday.set( 1928, Calendar.MAY, 23 );
Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) );
PersonDto personDto = customAnnotateMapper.personToPersonDto( person );
assertThat( personDto ).isNotNull();
assertThat( personDto.getName() ).isEqualTo( "Gary Crant" );
assertThat( personDto.getAddress() ).isNotNull();
assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" );
}
}