#1574: Support for annotating the generated code with custom annotations

Add new `@AnnotateWith` annotation.
This annotation can be used to instruct the MapStruct processor
to generate custom annotations in the generated code.
This commit is contained in:
Zegveld 2022-08-20 12:59:38 +02:00 committed by GitHub
parent 8fa286fe4c
commit 849085e026
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2774 additions and 151 deletions

View File

@ -0,0 +1,176 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* This can be used to have mapstruct generate additional annotations on classes/methods.
* <p>
* Examples based on the spring framework annotations.
* </p>
* Marking a class as `Lazy`:
*
* <pre><code>
* &#64;AnnotateWith( value = Lazy.class )
* &#64;Mapper
* public interface FooMapper {
* // mapper code
* }
* </code></pre>
*
* The following code would be generated:
*
* <pre><code>
* &#64;Lazy
* public class FooMapperImpl implements FooMapper {
* // mapper code
* }
* </code></pre>
* Setting the profile on the generated implementation:
*
* <pre><code>
* &#64;AnnotateWith( value = Profile.class, elements = @AnnotateWith.Element( strings = "prod" ) )
* &#64;Mapper
* public interface FooMapper {
* // mapper code
* }
* </code></pre>
*
* The following code would be generated:
*
* <pre><code>
* &#64;Profile( value = "prod" )
* public class FooMapperImpl implements FooMapper {
* // mapper code
* }
* </code></pre>
*
* @author Ben Zegveld
* @since 1.6
*/
@Repeatable( AnnotateWiths.class )
@Retention( CLASS )
@Target( { TYPE, METHOD, ANNOTATION_TYPE } )
public @interface AnnotateWith {
/**
* @return the annotation class that needs to be added.
*/
Class<? extends Annotation> value();
/**
* @return the annotation elements that are to be applied to this annotation.
*/
Element[] elements() default {};
/**
* Used in combination with {@link AnnotateWith} to configure the annotation elements. Only 1 value type may be used
* within the same annotation at a time. For example mixing shorts and ints is not allowed.
*
* @author Ben Zegveld
* @since 1.6
*/
@interface Element {
/**
* @return name of the annotation element.
*/
String name() default "value";
/**
* cannot be used in conjunction with other value fields.
*
* @return short value(s) for the annotation element.
*/
short[] shorts() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return byte value(s) for the annotation element.
*/
byte[] bytes() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return int value(s) for the annotation element.
*/
int[] ints() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return long value(s) for the annotation element.
*/
long[] longs() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return float value(s) for the annotation element.
*/
float[] floats() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return double value(s) for the annotation element.
*/
double[] doubles() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return char value(s) for the annotation element.
*/
char[] chars() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return boolean value(s) for the annotation element.
*/
boolean[] booleans() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return string value(s) for the annotation element.
*/
String[] strings() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return class value(s) for the annotation element.
*/
Class<?>[] classes() default {};
/**
* only used in conjunction with the {@link #enums()} annotation element.
*
* @return the class of the enum.
*/
Class<? extends Enum<?>> enumClass() default NullEnum.class;
/**
* cannot be used in conjunction with other value fields. {@link #enumClass()} is also required when using
* {@link #enums()}
*
* @return enum value(s) for the annotation element.
*/
String[] enums() default {};
}
}

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;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* This can be used to have mapstruct generate additional annotations on classes/methods.
*
* @author Ben Zegveld
* @since 1.6
*/
@Retention( CLASS )
@Target( { TYPE, METHOD } )
public @interface AnnotateWiths {
/**
* The configuration of the additional annotations.
*
* @return The configuration of the additional annotations.
*/
AnnotateWith[] value();
}

View File

@ -0,0 +1,15 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
/**
* To be used as a default value for enum class annotation elements.
*
* @author Ben Zegveld
* @since 1.6
*/
enum NullEnum {
}

View File

@ -721,3 +721,43 @@ i.e. You can map from `Map<String, Integer>` where for each property a conversio
When a raw map or a map that does not have a String as a key is used, then a warning will be generated. When a raw map or a map that does not have a String as a key is used, then a warning will be generated.
The warning is not generated if the map itself is mapped into some other target property directly as is. The warning is not generated if the map itself is mapped into some other target property directly as is.
==== ====
[[adding-annotations]]
=== Adding annotations
Other frameworks sometimes requires you to add annotations to certain classes so that they can easily detect the mappers.
Using the `@AnnotateWith` annotation you can generate an annotation at the specified location.
For example Apache Camel has a `@Converter` annotation which you can apply to generated mappers using the `@AnnotateWith` annotation.
.AnnotateWith source example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@AnnotateWith(
value = Converter.class,
elements = @AnnotateWith.Element( name = "generateBulkLoader", booleans = true )
)
public interface MyConverter {
@AnnotateWith( Converter.class )
DomainObject map( DtoObject dto );
}
----
====
.AnnotateWith generated mapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Converter( generateBulkLoader = true )
public class MyConverterImpl implements MyConverter {
@Converter
public DomainObject map( DtoObject dto ) {
// default mapping behaviour
}
}
----
====

View File

@ -9,6 +9,8 @@ import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRef;
import org.mapstruct.AfterMapping; import org.mapstruct.AfterMapping;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWiths;
import org.mapstruct.BeanMapping; import org.mapstruct.BeanMapping;
import org.mapstruct.BeforeMapping; import org.mapstruct.BeforeMapping;
import org.mapstruct.Builder; import org.mapstruct.Builder;
@ -43,6 +45,9 @@ import org.mapstruct.tools.gem.GemDefinition;
* *
* @author Gunnar Morling * @author Gunnar Morling
*/ */
@GemDefinition(AnnotateWith.class)
@GemDefinition(AnnotateWith.Element.class)
@GemDefinition(AnnotateWiths.class)
@GemDefinition(Mapper.class) @GemDefinition(Mapper.class)
@GemDefinition(Mapping.class) @GemDefinition(Mapping.class)
@GemDefinition(Mappings.class) @GemDefinition(Mappings.class)

View File

@ -0,0 +1,602 @@
/*
* 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.internal.model;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.gem.AnnotateWithGem;
import org.mapstruct.ap.internal.gem.AnnotateWithsGem;
import org.mapstruct.ap.internal.gem.ElementGem;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
import org.mapstruct.ap.internal.model.annotation.EnumAnnotationElementHolder;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.RepeatableAnnotations;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
import org.mapstruct.tools.gem.GemValue;
import static javax.lang.model.util.ElementFilter.methodsIn;
/**
* @author Ben Zegveld
* @since 1.5
*/
public class AdditionalAnnotationsBuilder
extends RepeatableAnnotations<AnnotateWithGem, AnnotateWithsGem, Annotation> {
private static final String ANNOTATE_WITH_FQN = "org.mapstruct.AnnotateWith";
private static final String ANNOTATE_WITHS_FQN = "org.mapstruct.AnnotateWiths";
private TypeFactory typeFactory;
private FormattingMessager messager;
public AdditionalAnnotationsBuilder(ElementUtils elementUtils, TypeFactory typeFactory,
FormattingMessager messager) {
super( elementUtils, ANNOTATE_WITH_FQN, ANNOTATE_WITHS_FQN );
this.typeFactory = typeFactory;
this.messager = messager;
}
@Override
protected AnnotateWithGem singularInstanceOn(Element element) {
return AnnotateWithGem.instanceOn( element );
}
@Override
protected AnnotateWithsGem multipleInstanceOn(Element element) {
return AnnotateWithsGem.instanceOn( element );
}
@Override
protected void addInstance(AnnotateWithGem gem, Element source, Set<Annotation> mappings) {
buildAnnotation( gem, source ).ifPresent( t -> addAndValidateMapping( mappings, source, gem, t ) );
}
@Override
protected void addInstances(AnnotateWithsGem gem, Element source, Set<Annotation> mappings) {
for ( AnnotateWithGem annotateWithGem : gem.value().get() ) {
buildAnnotation(
annotateWithGem,
source ).ifPresent( t -> addAndValidateMapping( mappings, source, annotateWithGem, t ) );
}
}
private void addAndValidateMapping(Set<Annotation> mappings, Element source, AnnotateWithGem gem, Annotation anno) {
if ( anno.getType().getTypeElement().getAnnotation( Repeatable.class ) == null ) {
if ( mappings.stream().anyMatch( existing -> existing.getType().equals( anno.getType() ) ) ) {
messager.printMessage(
source,
gem.mirror(),
Message.ANNOTATE_WITH_ANNOTATION_IS_NOT_REPEATABLE,
anno.getType().describe() );
return;
}
}
if ( mappings.stream().anyMatch( existing -> {
return existing.getType().equals( anno.getType() )
&& existing.getProperties().equals( anno.getProperties() );
} ) ) {
messager.printMessage(
source,
gem.mirror(),
Message.ANNOTATE_WITH_DUPLICATE,
anno.getType().describe() );
return;
}
mappings.add( anno );
}
private Optional<Annotation> buildAnnotation(AnnotateWithGem annotationGem, Element element) {
Type annotationType = typeFactory.getType( getTypeMirror( annotationGem.value() ) );
List<ElementGem> eleGems = annotationGem.elements().get();
if ( isValid( annotationType, eleGems, element, annotationGem.mirror() ) ) {
return Optional.of( new Annotation( annotationType, convertToProperties( eleGems ) ) );
}
return Optional.empty();
}
private List<AnnotationElement> convertToProperties(List<ElementGem> eleGems) {
return eleGems.stream().map( gem -> convertToProperty( gem, typeFactory ) ).collect( Collectors.toList() );
}
private enum ConvertToProperty {
BOOLEAN(
AnnotationElementType.BOOLEAN,
(eleGem, typeFactory) -> eleGem.booleans().get(),
eleGem -> eleGem.booleans().hasValue()
),
BYTE(
AnnotationElementType.BYTE,
(eleGem, typeFactory) -> eleGem.bytes().get(),
eleGem -> eleGem.bytes().hasValue()
),
CHARACTER(
AnnotationElementType.CHARACTER,
(eleGem, typeFactory) -> eleGem.chars().get(),
eleGem -> eleGem.chars().hasValue()
),
CLASSES(
AnnotationElementType.CLASS,
(eleGem, typeFactory) -> {
return eleGem.classes().get().stream().map( typeFactory::getType ).collect( Collectors.toList() );
},
eleGem -> eleGem.classes().hasValue()
),
DOUBLE(
AnnotationElementType.DOUBLE,
(eleGem, typeFactory) -> eleGem.doubles().get(),
eleGem -> eleGem.doubles().hasValue()
),
ENUM(
AnnotationElementType.ENUM,
(eleGem, typeFactory) -> {
List<EnumAnnotationElementHolder> values = new ArrayList<>();
for ( String enumName : eleGem.enums().get() ) {
Type type = typeFactory.getType( eleGem.enumClass().get() );
values.add( new EnumAnnotationElementHolder( type, enumName ) );
}
return values;
},
eleGem -> eleGem.enums().hasValue() && eleGem.enumClass().hasValue()
),
FLOAT(
AnnotationElementType.FLOAT,
(eleGem, typeFactory) -> eleGem.floats().get(),
eleGem -> eleGem.floats().hasValue()
),
INT(
AnnotationElementType.INTEGER,
(eleGem, typeFactory) -> eleGem.ints().get(),
eleGem -> eleGem.ints().hasValue()
),
LONG(
AnnotationElementType.LONG,
(eleGem, typeFactory) -> eleGem.longs().get(),
eleGem -> eleGem.longs().hasValue()
),
SHORT(
AnnotationElementType.SHORT,
(eleGem, typeFactory) -> eleGem.shorts().get(),
eleGem -> eleGem.shorts().hasValue()
),
STRING(
AnnotationElementType.STRING,
(eleGem, typeFactory) -> eleGem.strings().get(),
eleGem -> eleGem.strings().hasValue()
);
private final AnnotationElementType type;
private final BiFunction<ElementGem, TypeFactory, List<? extends Object>> factory;
private final Predicate<ElementGem> usabilityChecker;
ConvertToProperty(AnnotationElementType type,
BiFunction<ElementGem, TypeFactory, List<? extends Object>> factory,
Predicate<ElementGem> usabilityChecker) {
this.type = type;
this.factory = factory;
this.usabilityChecker = usabilityChecker;
}
AnnotationElement toProperty(ElementGem eleGem, TypeFactory typeFactory) {
return new AnnotationElement(
type,
eleGem.name().get(),
factory.apply( eleGem, typeFactory )
);
}
boolean isUsable(ElementGem eleGem) {
return usabilityChecker.test( eleGem );
}
}
private AnnotationElement convertToProperty(ElementGem eleGem, TypeFactory typeFactory) {
for ( ConvertToProperty convertToJava : ConvertToProperty.values() ) {
if ( convertToJava.isUsable( eleGem ) ) {
return convertToJava.toProperty( eleGem, typeFactory );
}
}
return null;
}
private boolean isValid(Type annotationType, List<ElementGem> eleGems, Element element,
AnnotationMirror annotationMirror) {
boolean isValid = true;
if ( !annotationIsAllowed( annotationType, element, annotationMirror ) ) {
isValid = false;
}
List<ExecutableElement> annotationElements = methodsIn( annotationType.getTypeElement()
.getEnclosedElements() );
if ( !allRequiredElementsArePresent( annotationType, annotationElements, eleGems, element,
annotationMirror ) ) {
isValid = false;
}
if ( !allElementsAreKnownInAnnotation( annotationType, annotationElements, eleGems, element ) ) {
isValid = false;
}
if ( !allElementsAreOfCorrectType( annotationType, annotationElements, eleGems, element ) ) {
isValid = false;
}
if ( !enumConstructionIsCorrectlyUsed( eleGems, element ) ) {
isValid = false;
}
if ( !allElementsAreUnique( eleGems, element ) ) {
isValid = false;
}
return isValid;
}
private boolean allElementsAreUnique(List<ElementGem> eleGems, Element element) {
boolean isValid = true;
List<String> checkedElements = new ArrayList<>();
for ( ElementGem elementGem : eleGems ) {
String elementName = elementGem.name().get();
if ( checkedElements.contains( elementName ) ) {
isValid = false;
messager
.printMessage(
element,
elementGem.mirror(),
Message.ANNOTATE_WITH_DUPLICATE_PARAMETER,
elementName );
}
else {
checkedElements.add( elementName );
}
}
return isValid;
}
private boolean enumConstructionIsCorrectlyUsed(List<ElementGem> eleGems, Element element) {
boolean isValid = true;
for ( ElementGem elementGem : eleGems ) {
if ( elementGem.enums().hasValue() ) {
if ( elementGem.enumClass().getValue() == null ) {
isValid = false;
messager
.printMessage(
element,
elementGem.mirror(),
Message.ANNOTATE_WITH_ENUM_CLASS_NOT_DEFINED );
}
else {
Type type = typeFactory.getType( getTypeMirror( elementGem.enumClass() ) );
if ( type.isEnumType() ) {
List<String> enumConstants = type.getEnumConstants();
for ( String enumName : elementGem.enums().get() ) {
if ( !enumConstants.contains( enumName ) ) {
isValid = false;
messager
.printMessage(
element,
elementGem.mirror(),
elementGem.enums().getAnnotationValue(),
Message.ANNOTATE_WITH_ENUM_VALUE_DOES_NOT_EXIST,
type.describe(),
enumName );
}
}
}
}
}
else if ( elementGem.enumClass().getValue() != null ) {
isValid = false;
messager.printMessage( element, elementGem.mirror(), Message.ANNOTATE_WITH_ENUMS_NOT_DEFINED );
}
}
return isValid;
}
private boolean annotationIsAllowed(Type annotationType, Element element, AnnotationMirror annotationMirror) {
Target target = annotationType.getTypeElement().getAnnotation( Target.class );
if ( target == null ) {
return true;
}
Set<ElementType> annotationTargets = Stream.of( target.value() )
// The eclipse compiler returns null for some values
// Therefore, we filter out null values
.filter( Objects::nonNull )
.collect( Collectors.toCollection( () -> EnumSet.noneOf( ElementType.class ) ) );
boolean isValid = true;
if ( isTypeTarget( element ) && !annotationTargets.contains( ElementType.TYPE ) ) {
isValid = false;
messager.printMessage(
element,
annotationMirror,
Message.ANNOTATE_WITH_NOT_ALLOWED_ON_CLASS,
annotationType.describe()
);
}
if ( isMethodTarget( element ) && !annotationTargets.contains( ElementType.METHOD ) ) {
isValid = false;
messager.printMessage(
element,
annotationMirror,
Message.ANNOTATE_WITH_NOT_ALLOWED_ON_METHODS,
annotationType.describe()
);
}
return isValid;
}
private boolean isTypeTarget(Element element) {
return element.getKind().isInterface() || element.getKind().isClass();
}
private boolean isMethodTarget(Element element) {
return element.getKind() == ElementKind.METHOD;
}
private boolean allElementsAreKnownInAnnotation(Type annotationType, List<ExecutableElement> annotationParameters,
List<ElementGem> eleGems, Element element) {
Set<String> allowedAnnotationParameters = annotationParameters.stream()
.map( ee -> ee.getSimpleName().toString() )
.collect( Collectors.toSet() );
boolean isValid = true;
for ( ElementGem eleGem : eleGems ) {
if ( eleGem.name().isValid()
&& !allowedAnnotationParameters.contains( eleGem.name().get() ) ) {
isValid = false;
messager
.printMessage(
element,
eleGem.mirror(),
eleGem.name().getAnnotationValue(),
Message.ANNOTATE_WITH_UNKNOWN_PARAMETER,
eleGem.name().get(),
annotationType.describe(),
Strings.getMostSimilarWord( eleGem.name().get(), allowedAnnotationParameters )
);
}
}
return isValid;
}
private boolean allRequiredElementsArePresent(Type annotationType, List<ExecutableElement> annotationParameters,
List<ElementGem> elements, Element element,
AnnotationMirror annotationMirror) {
boolean valid = true;
for ( ExecutableElement annotationParameter : annotationParameters ) {
if ( annotationParameter.getDefaultValue() == null ) {
// Mandatory parameter, must be present in the elements
String parameterName = annotationParameter.getSimpleName().toString();
boolean elementGemDefined = false;
for ( ElementGem elementGem : elements ) {
if ( elementGem.isValid() && elementGem.name().get().equals( parameterName ) ) {
elementGemDefined = true;
break;
}
}
if ( !elementGemDefined ) {
valid = false;
messager
.printMessage(
element,
annotationMirror,
Message.ANNOTATE_WITH_MISSING_REQUIRED_PARAMETER,
parameterName,
annotationType.describe()
);
}
}
}
return valid;
}
private boolean allElementsAreOfCorrectType(Type annotationType, List<ExecutableElement> annotationParameters,
List<ElementGem> elements,
Element element) {
Map<String, ExecutableElement> annotationParametersByName =
annotationParameters.stream()
.collect( Collectors.toMap( ee -> ee.getSimpleName().toString(), Function.identity() ) );
boolean isValid = true;
for ( ElementGem eleGem : elements ) {
Type annotationParameterType = getAnnotationParameterType( annotationParametersByName, eleGem );
Type annotationParameterTypeSingular = getNonArrayType( annotationParameterType );
if ( annotationParameterTypeSingular == null ) {
continue;
}
if ( hasTooManyDifferentTypes( eleGem ) ) {
isValid = false;
messager.printMessage(
element,
eleGem.mirror(),
eleGem.name().getAnnotationValue(),
Message.ANNOTATE_WITH_TOO_MANY_VALUE_TYPES,
eleGem.name().get(),
annotationParameterType.describe(),
annotationType.describe()
);
}
else {
Map<Type, Integer> elementTypes = getParameterTypes( eleGem );
Set<ElementGem> reportedSizeError = new HashSet<>();
for ( Type eleGemType : elementTypes.keySet() ) {
if ( !sameTypeOrAssignableClass( annotationParameterTypeSingular, eleGemType ) ) {
isValid = false;
messager.printMessage(
element,
eleGem.mirror(),
eleGem.name().getAnnotationValue(),
Message.ANNOTATE_WITH_WRONG_PARAMETER,
eleGem.name().get(),
eleGemType.describe(),
annotationParameterType.describe(),
annotationType.describe()
);
}
else if ( !annotationParameterType.isArrayType()
&& elementTypes.get( eleGemType ) > 1
&& !reportedSizeError.contains( eleGem ) ) {
isValid = false;
messager.printMessage(
element,
eleGem.mirror(),
Message.ANNOTATE_WITH_PARAMETER_ARRAY_NOT_EXPECTED,
eleGem.name().get(),
annotationType.describe()
);
reportedSizeError.add( eleGem );
}
}
}
}
return isValid;
}
private boolean hasTooManyDifferentTypes( ElementGem eleGem ) {
return Arrays.stream( ConvertToProperty.values() )
.filter( anotationElement -> anotationElement.isUsable( eleGem ) )
.count() > 1;
}
private Type getNonArrayType(Type annotationParameterType) {
if ( annotationParameterType == null ) {
return null;
}
if ( annotationParameterType.isArrayType() ) {
return annotationParameterType.getComponentType();
}
return annotationParameterType;
}
private boolean sameTypeOrAssignableClass(Type annotationParameterType, Type eleGemType) {
return annotationParameterType.equals( eleGemType )
|| eleGemType.isAssignableTo( getTypeBound( annotationParameterType ) );
}
private Type getTypeBound(Type annotationParameterType) {
List<Type> typeParameters = annotationParameterType.getTypeParameters();
if ( typeParameters.size() != 1 ) {
return annotationParameterType;
}
return typeParameters.get( 0 ).getTypeBound();
}
private Map<Type, Integer> getParameterTypes(ElementGem eleGem) {
Map<Type, Integer> suppliedParameterTypes = new HashMap<>();
if ( eleGem.booleans().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( boolean.class ),
eleGem.booleans().get().size() );
}
if ( eleGem.bytes().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( byte.class ),
eleGem.bytes().get().size() );
}
if ( eleGem.chars().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( char.class ),
eleGem.chars().get().size() );
}
if ( eleGem.classes().hasValue() ) {
for ( TypeMirror mirror : eleGem.classes().get() ) {
suppliedParameterTypes.put(
typeFactory.getType( typeMirrorFromAnnotation( mirror ) ),
eleGem.classes().get().size()
);
}
}
if ( eleGem.doubles().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( double.class ),
eleGem.doubles().get().size() );
}
if ( eleGem.floats().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( float.class ),
eleGem.floats().get().size() );
}
if ( eleGem.ints().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( int.class ),
eleGem.ints().get().size() );
}
if ( eleGem.longs().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( long.class ),
eleGem.longs().get().size() );
}
if ( eleGem.shorts().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( short.class ),
eleGem.shorts().get().size() );
}
if ( eleGem.strings().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( String.class ),
eleGem.strings().get().size() );
}
if ( eleGem.enums().hasValue() && eleGem.enumClass().hasValue() ) {
suppliedParameterTypes.put(
typeFactory.getType( getTypeMirror( eleGem.enumClass() ) ),
eleGem.enums().get().size() );
}
return suppliedParameterTypes;
}
private Type getAnnotationParameterType(Map<String, ExecutableElement> annotationParameters,
ElementGem element) {
if ( annotationParameters.containsKey( element.name().get() ) ) {
return typeFactory.getType( annotationParameters.get( element.name().get() ).getReturnType() );
}
else {
return null;
}
}
private TypeMirror getTypeMirror(GemValue<TypeMirror> gemValue) {
return typeMirrorFromAnnotation( gemValue.getValue() );
}
private TypeMirror typeMirrorFromAnnotation(TypeMirror typeMirror) {
if ( typeMirror == null ) {
// When a class used in an annotation 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 );
}
return typeMirror;
}
}

View File

@ -6,9 +6,11 @@
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
@ -21,16 +23,13 @@ public class Annotation extends ModelElement {
private final Type type; private final Type type;
/** private List<AnnotationElement> properties;
* List of annotation attributes. Quite simplistic, but it's sufficient for now.
*/
private List<String> properties;
public Annotation(Type type) { public Annotation(Type type) {
this( type, Collections.emptyList() ); this( type, Collections.emptyList() );
} }
public Annotation(Type type, List<String> properties) { public Annotation(Type type, List<AnnotationElement> properties) {
this.type = type; this.type = type;
this.properties = properties; this.properties = properties;
} }
@ -41,10 +40,15 @@ public class Annotation extends ModelElement {
@Override @Override
public Set<Type> getImportTypes() { public Set<Type> getImportTypes() {
return Collections.singleton( type ); Set<Type> types = new HashSet<>();
for ( AnnotationElement prop : properties ) {
types.addAll( prop.getImportTypes() );
}
types.add( type );
return types;
} }
public List<String> getProperties() { public List<AnnotationElement> getProperties() {
return properties; return properties;
} }
} }

View File

@ -85,6 +85,7 @@ import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETE
*/ */
public class BeanMappingMethod extends NormalTypeMappingMethod { public class BeanMappingMethod extends NormalTypeMappingMethod {
private final List<Annotation> annotations;
private final List<PropertyMapping> propertyMappings; private final List<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter; private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final Map<String, List<PropertyMapping>> constructorMappingsByParameter; private final Map<String, List<PropertyMapping>> constructorMappingsByParameter;
@ -112,6 +113,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Set<Parameter> unprocessedSourceParameters = new HashSet<>(); private final Set<Parameter> unprocessedSourceParameters = new HashSet<>();
private final Set<String> existingVariableNames = new HashSet<>(); private final Set<String> existingVariableNames = new HashSet<>();
private final Map<String, Set<MappingReference>> unprocessedDefinedTargets = new LinkedHashMap<>(); private final Map<String, Set<MappingReference>> unprocessedDefinedTargets = new LinkedHashMap<>();
private final List<Annotation> annotations = new ArrayList<>();
private MappingReferences mappingReferences; private MappingReferences mappingReferences;
private MethodReference factoryMethod; private MethodReference factoryMethod;
@ -214,6 +216,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// If the return type cannot be constructed then no need to try to create mappings // If the return type cannot be constructed then no need to try to create mappings
return null; return null;
} }
AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder(
ctx.getElementUtils(),
ctx.getTypeFactory(),
ctx.getMessager() );
annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) );
/* the type that needs to be used in the mapping process as target */ /* the type that needs to be used in the mapping process as target */
Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct; Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;
@ -362,6 +370,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return new BeanMappingMethod( return new BeanMappingMethod(
method, method,
annotations,
existingVariableNames, existingVariableNames,
propertyMappings, propertyMappings,
factoryMethod, factoryMethod,
@ -1701,6 +1710,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
//CHECKSTYLE:OFF //CHECKSTYLE:OFF
private BeanMappingMethod(Method method, private BeanMappingMethod(Method method,
List<Annotation> annotations,
Collection<String> existingVariableNames, Collection<String> existingVariableNames,
List<PropertyMapping> propertyMappings, List<PropertyMapping> propertyMappings,
MethodReference factoryMethod, MethodReference factoryMethod,
@ -1722,6 +1732,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
); );
//CHECKSTYLE:ON //CHECKSTYLE:ON
this.annotations = annotations;
this.propertyMappings = propertyMappings; this.propertyMappings = propertyMappings;
this.returnTypeBuilder = returnTypeBuilder; this.returnTypeBuilder = returnTypeBuilder;
this.finalizerMethod = finalizerMethod; this.finalizerMethod = finalizerMethod;
@ -1760,6 +1771,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
this.subclassMappings = subclassMappings; this.subclassMappings = subclassMappings;
} }
public List<Annotation> getAnnotations() {
return annotations;
}
public List<PropertyMapping> getConstantMappings() { public List<PropertyMapping> getConstantMappings() {
return constantMappings; return constantMappings;
} }

View File

@ -10,15 +10,14 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Accessibility;
import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.internal.version.VersionInformation;
@ -220,7 +219,9 @@ public abstract class GeneratedType extends ModelElement {
} }
for ( Annotation annotation : annotations ) { for ( Annotation annotation : annotations ) {
addIfImportRequired( importedTypes, annotation.getType() ); for ( Type type : annotation.getImportTypes() ) {
addIfImportRequired( importedTypes, type );
}
} }
for ( Type extraImport : extraImportedTypes ) { for ( Type extraImport : extraImportedTypes ) {

View File

@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.model;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
@ -43,6 +42,7 @@ public class Mapper extends GeneratedType {
private String implPackage; private String implPackage;
private boolean customPackage; private boolean customPackage;
private boolean suppressGeneratorTimestamp; private boolean suppressGeneratorTimestamp;
private Set<Annotation> customAnnotations;
public Builder() { public Builder() {
super( Builder.class ); super( Builder.class );
@ -63,6 +63,11 @@ public class Mapper extends GeneratedType {
return this; return this;
} }
public Builder additionalAnnotations(Set<Annotation> customAnnotations) {
this.customAnnotations = customAnnotations;
return this;
}
public Builder decorator(Decorator decorator) { public Builder decorator(Decorator decorator) {
this.decorator = decorator; this.decorator = decorator;
return this; return this;
@ -105,6 +110,7 @@ public class Mapper extends GeneratedType {
definitionType, definitionType,
customPackage, customPackage,
customName, customName,
customAnnotations,
methods, methods,
options, options,
versionInformation, versionInformation,
@ -126,7 +132,7 @@ public class Mapper extends GeneratedType {
@SuppressWarnings( "checkstyle:parameternumber" ) @SuppressWarnings( "checkstyle:parameternumber" )
private Mapper(TypeFactory typeFactory, String packageName, String name, private Mapper(TypeFactory typeFactory, String packageName, String name,
Type mapperDefinitionType, Type mapperDefinitionType,
boolean customPackage, boolean customImplName, boolean customPackage, boolean customImplName, Set<Annotation> customAnnotations,
List<MappingMethod> methods, Options options, VersionInformation versionInformation, List<MappingMethod> methods, Options options, VersionInformation versionInformation,
boolean suppressGeneratorTimestamp, boolean suppressGeneratorTimestamp,
Accessibility accessibility, List<Field> fields, Constructor constructor, Accessibility accessibility, List<Field> fields, Constructor constructor,
@ -148,6 +154,7 @@ public class Mapper extends GeneratedType {
); );
this.customPackage = customPackage; this.customPackage = customPackage;
this.customImplName = customImplName; this.customImplName = customImplName;
customAnnotations.forEach( this::addAnnotation );
this.decorator = decorator; this.decorator = decorator;
} }

View File

@ -0,0 +1,128 @@
/*
* 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.internal.model.annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type;
/**
* @author Ben Zegveld
*/
public class AnnotationElement extends ModelElement {
public enum AnnotationElementType {
BOOLEAN, BYTE, CHARACTER, CLASS, DOUBLE, ENUM, FLOAT, INTEGER, LONG, SHORT, STRING
}
private final String elementName;
private final List<? extends Object> values;
private final AnnotationElementType type;
public AnnotationElement(AnnotationElementType type, List<? extends Object> values) {
this( type, null, values );
}
public AnnotationElement(AnnotationElementType type, String elementName, List<? extends Object> values) {
this.type = type;
this.elementName = elementName;
this.values = values;
}
public String getElementName() {
return elementName;
}
public List<? extends Object> getValues() {
return values;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> importTypes = null;
for ( Object value : values ) {
if ( value instanceof ModelElement ) {
if ( importTypes == null ) {
importTypes = new HashSet<>();
}
importTypes.addAll( ( (ModelElement) value ).getImportTypes() );
}
}
return importTypes == null ? Collections.emptySet() : importTypes;
}
public boolean isBoolean() {
return type == AnnotationElementType.BOOLEAN;
}
public boolean isByte() {
return type == AnnotationElementType.BYTE;
}
public boolean isCharacter() {
return type == AnnotationElementType.CHARACTER;
}
public boolean isClass() {
return type == AnnotationElementType.CLASS;
}
public boolean isDouble() {
return type == AnnotationElementType.DOUBLE;
}
public boolean isEnum() {
return type == AnnotationElementType.ENUM;
}
public boolean isFloat() {
return type == AnnotationElementType.FLOAT;
}
public boolean isInteger() {
return type == AnnotationElementType.INTEGER;
}
public boolean isLong() {
return type == AnnotationElementType.LONG;
}
public boolean isShort() {
return type == AnnotationElementType.SHORT;
}
public boolean isString() {
return type == AnnotationElementType.STRING;
}
@Override
public int hashCode() {
return Objects.hash( elementName, type, values );
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
AnnotationElement other = (AnnotationElement) obj;
return Objects.equals( elementName, other.elementName )
&& type == other.type
&& Objects.equals( values, other.values );
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.internal.model.annotation;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type;
public class EnumAnnotationElementHolder extends ModelElement {
private final Type enumClass;
private final String name;
public EnumAnnotationElementHolder(Type enumClass, String name) {
this.enumClass = enumClass;
this.name = name;
}
public Type getEnumClass() {
return enumClass;
}
public String getName() {
return name;
}
@Override
public Set<Type> getImportTypes() {
return enumClass.getImportTypes();
}
}

View File

@ -12,6 +12,8 @@ import java.util.List;
import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
/** /**
* A {@link ModelElementProcessor} which converts the given {@link Mapper} * A {@link ModelElementProcessor} which converts the given {@link Mapper}
@ -67,7 +69,10 @@ public class JakartaComponentProcessor extends AnnotationBasedComponentModelProc
private Annotation namedDelegate(Mapper mapper) { private Annotation namedDelegate(Mapper mapper) {
return new Annotation( return new Annotation(
getTypeFactory().getType( "jakarta.inject.Named" ), getTypeFactory().getType( "jakarta.inject.Named" ),
Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) Collections.singletonList(
new AnnotationElement( AnnotationElementType.STRING,
Collections.singletonList( mapper.getPackageName() + "." + mapper.getName() )
) )
); );
} }

View File

@ -12,6 +12,8 @@ import java.util.List;
import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper; 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 org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.AnnotationProcessingException;
@ -70,7 +72,11 @@ public class Jsr330ComponentProcessor extends AnnotationBasedComponentModelProce
private Annotation namedDelegate(Mapper mapper) { private Annotation namedDelegate(Mapper mapper) {
return new Annotation( return new Annotation(
getType( "Named" ), getType( "Named" ),
Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) Collections.singletonList(
new AnnotationElement(
AnnotationElementType.STRING,
Collections.singletonList( mapper.getPackageName() + "." + mapper.getName() )
) )
); );
} }

View File

@ -31,6 +31,7 @@ import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem;
import org.mapstruct.ap.internal.gem.MapperGem; import org.mapstruct.ap.internal.gem.MapperGem;
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder;
import org.mapstruct.ap.internal.model.BeanMappingMethod; import org.mapstruct.ap.internal.model.BeanMappingMethod;
import org.mapstruct.ap.internal.model.ContainerMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethod;
import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder; import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder;
@ -93,6 +94,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private AccessorNamingUtils accessorNaming; private AccessorNamingUtils accessorNaming;
private MappingBuilderContext mappingContext; private MappingBuilderContext mappingContext;
private AdditionalAnnotationsBuilder additionalAnnotationsBuilder;
@Override @Override
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) { public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
this.elementUtils = context.getElementUtils(); this.elementUtils = context.getElementUtils();
@ -103,6 +106,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
this.versionInformation = context.getVersionInformation(); this.versionInformation = context.getVersionInformation();
this.typeFactory = context.getTypeFactory(); this.typeFactory = context.getTypeFactory();
this.accessorNaming = context.getAccessorNaming(); this.accessorNaming = context.getAccessorNaming();
additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder( elementUtils, typeFactory, messager );
MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() );
List<MapperReference> mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions ); List<MapperReference> mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions );
@ -206,6 +211,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.implName( mapperOptions.implementationName() ) .implName( mapperOptions.implementationName() )
.implPackage( mapperOptions.implementationPackage() ) .implPackage( mapperOptions.implementationPackage() )
.suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() )
.additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) )
.build(); .build();
if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) {

View File

@ -8,13 +8,10 @@ package org.mapstruct.ap.internal.processor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
@ -54,9 +51,9 @@ import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.RepeatableAnnotations;
import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.TypeUtils;
import org.mapstruct.ap.spi.EnumTransformationStrategy; import org.mapstruct.ap.spi.EnumTransformationStrategy;
import org.mapstruct.tools.gem.Gem;
/** /**
* A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s
@ -68,8 +65,6 @@ import org.mapstruct.tools.gem.Gem;
*/ */
public class MethodRetrievalProcessor implements ModelElementProcessor<Void, List<SourceMethod>> { public class MethodRetrievalProcessor implements ModelElementProcessor<Void, List<SourceMethod>> {
private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation";
private static final String ORG_MAPSTRUCT_PKG = "org.mapstruct";
private static final String MAPPING_FQN = "org.mapstruct.Mapping"; private static final String MAPPING_FQN = "org.mapstruct.Mapping";
private static final String MAPPINGS_FQN = "org.mapstruct.Mappings"; private static final String MAPPINGS_FQN = "org.mapstruct.Mappings";
private static final String SUB_CLASS_MAPPING_FQN = "org.mapstruct.SubclassMapping"; private static final String SUB_CLASS_MAPPING_FQN = "org.mapstruct.SubclassMapping";
@ -280,8 +275,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
typeUtils, typeUtils,
typeFactory ); typeFactory );
RepeatableMappings repeatableMappings = new RepeatableMappings(); Set<MappingOptions> mappingOptions = getMappings( method, beanMappingOptions );
Set<MappingOptions> mappingOptions = repeatableMappings.getMappings( method, beanMappingOptions );
IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem( IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem(
IterableMappingGem.instanceOn( method ), IterableMappingGem.instanceOn( method ),
@ -593,7 +587,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
* @return The mappings for the given method, keyed by target property name * @return The mappings for the given method, keyed by target property name
*/ */
private Set<MappingOptions> getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { private Set<MappingOptions> getMappings(ExecutableElement method, BeanMappingOptions beanMapping) {
return new RepeatableMappings().getMappings( method, beanMapping ); return new RepeatableMappings( beanMapping ).getProcessedAnnotations( method );
} }
/** /**
@ -607,68 +601,84 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private Set<SubclassMappingOptions> getSubclassMappings(List<Parameter> sourceParameters, Type resultType, private Set<SubclassMappingOptions> getSubclassMappings(List<Parameter> sourceParameters, Type resultType,
ExecutableElement method, BeanMappingOptions beanMapping, ExecutableElement method, BeanMappingOptions beanMapping,
SubclassValidator validator) { SubclassValidator validator) {
return new RepeatableSubclassMappings( sourceParameters, resultType, validator ) return new RepeatableSubclassMappings( beanMapping, sourceParameters, resultType, validator )
.getMappings( method, beanMapping ); .getProcessedAnnotations( method );
} }
private class RepeatableMappings extends RepeatableMappingAnnotations<MappingGem, MappingsGem, MappingOptions> { private class RepeatableMappings extends RepeatableAnnotations<MappingGem, MappingsGem, MappingOptions> {
RepeatableMappings() { private BeanMappingOptions beanMappingOptions;
super( MAPPING_FQN, MAPPINGS_FQN );
RepeatableMappings(BeanMappingOptions beanMappingOptions) {
super( elementUtils, MAPPING_FQN, MAPPINGS_FQN );
this.beanMappingOptions = beanMappingOptions;
} }
@Override @Override
MappingGem singularInstanceOn(Element element) { protected MappingGem singularInstanceOn(Element element) {
return MappingGem.instanceOn( element ); return MappingGem.instanceOn( element );
} }
@Override @Override
MappingsGem multipleInstanceOn(Element element) { protected MappingsGem multipleInstanceOn(Element element) {
return MappingsGem.instanceOn( element ); return MappingsGem.instanceOn( element );
} }
@Override @Override
void addInstance(MappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, protected void addInstance(MappingGem gem, Element method, Set<MappingOptions> mappings) {
Set<MappingOptions> mappings) { MappingOptions.addInstance(
MappingOptions.addInstance( gem, method, beanMappingOptions, messager, typeUtils, mappings ); gem,
(ExecutableElement) method,
beanMappingOptions,
messager,
typeUtils,
mappings );
} }
@Override @Override
void addInstances(MappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, protected void addInstances(MappingsGem gem, Element method, Set<MappingOptions> mappings) {
Set<MappingOptions> mappings) { MappingOptions.addInstances(
MappingOptions.addInstances( gem, method, beanMappingOptions, messager, typeUtils, mappings ); gem,
(ExecutableElement) method,
beanMappingOptions,
messager,
typeUtils,
mappings );
} }
} }
private class RepeatableSubclassMappings private class RepeatableSubclassMappings
extends RepeatableMappingAnnotations<SubclassMappingGem, SubclassMappingsGem, SubclassMappingOptions> { extends RepeatableAnnotations<SubclassMappingGem, SubclassMappingsGem, SubclassMappingOptions> {
private final List<Parameter> sourceParameters; private final List<Parameter> sourceParameters;
private final Type resultType; private final Type resultType;
private SubclassValidator validator; private SubclassValidator validator;
private BeanMappingOptions beanMappingOptions;
RepeatableSubclassMappings(List<Parameter> sourceParameters, Type resultType, SubclassValidator validator) { RepeatableSubclassMappings(BeanMappingOptions beanMappingOptions, List<Parameter> sourceParameters,
super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN ); Type resultType, SubclassValidator validator) {
super( elementUtils, SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN );
this.beanMappingOptions = beanMappingOptions;
this.sourceParameters = sourceParameters; this.sourceParameters = sourceParameters;
this.resultType = resultType; this.resultType = resultType;
this.validator = validator; this.validator = validator;
} }
@Override @Override
SubclassMappingGem singularInstanceOn(Element element) { protected SubclassMappingGem singularInstanceOn(Element element) {
return SubclassMappingGem.instanceOn( element ); return SubclassMappingGem.instanceOn( element );
} }
@Override @Override
SubclassMappingsGem multipleInstanceOn(Element element) { protected SubclassMappingsGem multipleInstanceOn(Element element) {
return SubclassMappingsGem.instanceOn( element ); return SubclassMappingsGem.instanceOn( element );
} }
@Override @Override
void addInstance(SubclassMappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, protected void addInstance(SubclassMappingGem gem,
Element method,
Set<SubclassMappingOptions> mappings) { Set<SubclassMappingOptions> mappings) {
SubclassMappingOptions SubclassMappingOptions.addInstance(
.addInstance(
gem, gem,
method, (ExecutableElement) method,
beanMappingOptions, beanMappingOptions,
messager, messager,
typeUtils, typeUtils,
@ -679,12 +689,12 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
} }
@Override @Override
void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, protected void addInstances(SubclassMappingsGem gem,
Element method,
Set<SubclassMappingOptions> mappings) { Set<SubclassMappingOptions> mappings) {
SubclassMappingOptions SubclassMappingOptions.addInstances(
.addInstances(
gem, gem,
method, (ExecutableElement) method,
beanMappingOptions, beanMappingOptions,
messager, messager,
typeUtils, typeUtils,
@ -695,88 +705,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
} }
} }
private abstract class RepeatableMappingAnnotations<SINGULAR extends Gem, MULTIPLE extends Gem, OPTIONS> {
private final String singularFqn;
private final String multipleFqn;
RepeatableMappingAnnotations(String singularFqn, String multipleFqn) {
this.singularFqn = singularFqn;
this.multipleFqn = multipleFqn;
}
abstract SINGULAR singularInstanceOn(Element element);
abstract MULTIPLE multipleInstanceOn(Element element);
abstract void addInstance(SINGULAR gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
Set<OPTIONS> mappings);
abstract void addInstances(MULTIPLE gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
Set<OPTIONS> mappings);
/**
* Retrieves the mappings configured via {@code @Mapping} from the given method.
*
* @param method The method of interest
* @param beanMapping options coming from bean mapping method
* @return The mappings for the given method, keyed by target property name
*/
public Set<OPTIONS> getMappings(ExecutableElement method, BeanMappingOptions beanMapping) {
return getMappings( method, method, beanMapping, new LinkedHashSet<>(), new HashSet<>() );
}
/**
* Retrieves the mappings configured via {@code @Mapping} from the given method.
*
* @param method The method of interest
* @param element Element of interest: method, or (meta) annotation
* @param beanMapping options coming from bean mapping method
* @param mappingOptions LinkedSet of mappings found so far
* @return The mappings for the given method, keyed by target property name
*/
private Set<OPTIONS> getMappings(ExecutableElement method, Element element,
BeanMappingOptions beanMapping, LinkedHashSet<OPTIONS> mappingOptions,
Set<Element> handledElements) {
for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) {
Element lElement = annotationMirror.getAnnotationType().asElement();
if ( isAnnotation( lElement, singularFqn ) ) {
// although getInstanceOn does a search on annotation mirrors, the order is preserved
SINGULAR mapping = singularInstanceOn( element );
addInstance( mapping, method, beanMapping, mappingOptions );
}
else if ( isAnnotation( lElement, multipleFqn ) ) {
// although getInstanceOn does a search on annotation mirrors, the order is preserved
MULTIPLE mappings = multipleInstanceOn( element );
addInstances( mappings, method, beanMapping, mappingOptions );
}
else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK )
&& !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG )
&& !handledElements.contains( lElement ) ) {
// recur over annotation mirrors
handledElements.add( lElement );
getMappings( method, lElement, beanMapping, mappingOptions, handledElements );
}
}
return mappingOptions;
}
private boolean isAnnotationInPackage(Element element, String packageFQN) {
if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) {
return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() );
}
return false;
}
private boolean isAnnotation(Element element, String annotationFQN) {
if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) {
return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() );
}
return false;
}
}
/** /**
* Retrieves the mappings configured via {@code @ValueMapping} from the given * Retrieves the mappings configured via {@code @ValueMapping} from the given
* method. * method.

View File

@ -13,6 +13,8 @@ import java.util.List;
import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
/** /**
* A {@link ModelElementProcessor} which converts the given {@link Mapper} * A {@link ModelElementProcessor} which converts the given {@link Mapper}
@ -75,7 +77,11 @@ public class SpringComponentProcessor extends AnnotationBasedComponentModelProce
private Annotation qualifierDelegate() { private Annotation qualifierDelegate() {
return new Annotation( return new Annotation(
getTypeFactory().getType( "org.springframework.beans.factory.annotation.Qualifier" ), getTypeFactory().getType( "org.springframework.beans.factory.annotation.Qualifier" ),
Collections.singletonList( "\"delegate\"" ) ); Collections.singletonList(
new AnnotationElement(
AnnotationElementType.STRING,
Collections.singletonList( "delegate" )
) ) );
} }
private Annotation primary() { private Annotation primary() {

View File

@ -198,6 +198,20 @@ public enum Message {
MAPTOBEANMAPPING_WRONG_KEY_TYPE( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map<String, ???> but it was typed with %s.", Diagnostic.Kind.WARNING ), MAPTOBEANMAPPING_WRONG_KEY_TYPE( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map<String, ???> but it was typed with %s.", Diagnostic.Kind.WARNING ),
MAPTOBEANMAPPING_RAW_MAP( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map<String, ???> but it was raw.", Diagnostic.Kind.WARNING ), MAPTOBEANMAPPING_RAW_MAP( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map<String, ???> but it was raw.", Diagnostic.Kind.WARNING ),
ANNOTATE_WITH_MISSING_REQUIRED_PARAMETER( "Parameter \"%s\" is required for annotation \"%s\"." ),
ANNOTATE_WITH_UNKNOWN_PARAMETER( "Unknown parameter \"%s\" for annotation \"%s\". Did you mean \"%s\"?" ),
ANNOTATE_WITH_DUPLICATE_PARAMETER( "Parameter \"%s\" must not be defined more than once." ),
ANNOTATE_WITH_WRONG_PARAMETER( "Parameter \"%s\" is not of type \"%s\" but of type \"%s\" for annotation \"%s\"." ),
ANNOTATE_WITH_TOO_MANY_VALUE_TYPES( "Parameter \"%s\" has too many value types supplied, type \"%s\" is expected for annotation \"%s\"." ),
ANNOTATE_WITH_PARAMETER_ARRAY_NOT_EXPECTED( "Parameter \"%s\" does not accept multiple values for annotation \"%s\"." ),
ANNOTATE_WITH_NOT_ALLOWED_ON_CLASS( "Annotation \"%s\" is not allowed on classes." ),
ANNOTATE_WITH_NOT_ALLOWED_ON_METHODS( "Annotation \"%s\" is not allowed on methods." ),
ANNOTATE_WITH_ENUM_VALUE_DOES_NOT_EXIST( "Enum \"%s\" does not have value \"%s\"." ),
ANNOTATE_WITH_ENUM_CLASS_NOT_DEFINED( "enumClass needs to be defined when using enums." ),
ANNOTATE_WITH_ENUMS_NOT_DEFINED( "enums needs to be defined when using enumClass." ),
ANNOTATE_WITH_ANNOTATION_IS_NOT_REPEATABLE( "Annotation \"%s\" is not repeatable." ),
ANNOTATE_WITH_DUPLICATE( "Annotation \"%s\" is already present with the same elements configuration.", Diagnostic.Kind.WARNING ),
; ;
// CHECKSTYLE:ON // CHECKSTYLE:ON

View File

@ -0,0 +1,120 @@
/*
* 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.internal.util;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import org.mapstruct.tools.gem.Gem;
/**
* @author Ben Zegveld
*/
public abstract class RepeatableAnnotations<SINGULAR extends Gem, MULTIPLE extends Gem, OPTIONS> {
private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation";
private static final String ORG_MAPSTRUCT_PKG = "org.mapstruct";
private ElementUtils elementUtils;
private final String singularFqn;
private final String multipleFqn;
protected RepeatableAnnotations(ElementUtils elementUtils, String singularFqn, String multipleFqn) {
this.elementUtils = elementUtils;
this.singularFqn = singularFqn;
this.multipleFqn = multipleFqn;
}
/**
* @param element the element on which the Gem needs to be found
* @return the Gem found on the element.
*/
protected abstract SINGULAR singularInstanceOn(Element element);
/**
* @param element the element on which the Gems needs to be found
* @return the Gems found on the element.
*/
protected abstract MULTIPLE multipleInstanceOn(Element element);
/**
* @param gem the annotation gem to be processed
* @param source the source element where the request originated from
* @param mappings the collection of completed processing
*/
protected abstract void addInstance(SINGULAR gem, Element source, Set<OPTIONS> mappings);
/**
* @param gems the annotation gems to be processed
* @param source the source element where the request originated from
* @param mappings the collection of completed processing
*/
protected abstract void addInstances(MULTIPLE gems, Element source, Set<OPTIONS> mappings);
/**
* Retrieves the processed annotations.
*
* @param source The source element of interest
* @return The processed annotations for the given element
*/
public Set<OPTIONS> getProcessedAnnotations(Element source) {
return getMappings( source, source, new LinkedHashSet<>(), new HashSet<>() );
}
/**
* Retrieves the processed annotations.
*
* @param source The source element of interest
* @param element Element of interest: method, or (meta) annotation
* @param mappingOptions LinkedSet of mappings found so far
* @param handledElements The collection of already handled elements to handle recursion correctly.
* @return The processed annotations for the given element
*/
private Set<OPTIONS> getMappings(Element source, Element element,
LinkedHashSet<OPTIONS> mappingOptions,
Set<Element> handledElements) {
for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) {
Element lElement = annotationMirror.getAnnotationType().asElement();
if ( isAnnotation( lElement, singularFqn ) ) {
// although getInstanceOn does a search on annotation mirrors, the order is preserved
SINGULAR mapping = singularInstanceOn( element );
addInstance( mapping, source, mappingOptions );
}
else if ( isAnnotation( lElement, multipleFqn ) ) {
// although getInstanceOn does a search on annotation mirrors, the order is preserved
MULTIPLE mappings = multipleInstanceOn( element );
addInstances( mappings, source, mappingOptions );
}
else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK )
&& !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG )
&& !handledElements.contains( lElement ) ) {
// recur over annotation mirrors
handledElements.add( lElement );
getMappings( source, lElement, mappingOptions, handledElements );
}
}
return mappingOptions;
}
private boolean isAnnotationInPackage(Element element, String packageFQN) {
if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) {
return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() );
}
return false;
}
private boolean isAnnotation(Element element, String annotationFQN) {
if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) {
return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() );
}
return false;
}
}

View File

@ -6,4 +6,4 @@
--> -->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" -->
@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property>${property}<#if property_has_next>, </#if></#list>)</#if> @<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property><@includeModel object=property/><#if property_has_next>, </#if></#list>)</#if>

View File

@ -6,6 +6,9 @@
--> -->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.BeanMappingMethod" --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.BeanMappingMethod" -->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#if overridden>@Override</#if> <#if overridden>@Override</#if>
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> { <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#assign targetType = resultType /> <#assign targetType = resultType />

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
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.annotation.AnnotationElement" -->
<@compress single_line=true>
<#if elementName??>
${elementName} =
</#if>
<#if (values?size > 1) >
{
</#if>
<#-- rt and lt tags below are for formatting the arrays so that there are no spaces before ',' -->
<#list values as value>
<#if boolean>
${value?c}<#rt>
<#elseif byte>
${value}<#rt>
<#elseif character>
'${value}'<#rt>
<#elseif class>
<@includeModel object=value raw=true/>.class<#rt>
<#elseif double>
${value?c}<#rt>
<#elseif enum>
<@includeModel object=value/><#rt>
<#elseif float>
${value?c}f<#rt>
<#elseif integer>
${value?c}<#rt>
<#elseif long>
${value?c}L<#rt>
<#elseif short>
${value?c}<#rt>
<#elseif string>
"${value}"<#rt>
</#if>
<#if value_has_next>
, <#lt>
</#if>
</#list>
<#if (values?size > 1) >
}
</#if>
</@compress>

View File

@ -0,0 +1,9 @@
<#--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.annotation.EnumAnnotationElementHolder" -->
<@includeModel object=enumClass raw=true/>.${name}<#rt>

View File

@ -0,0 +1,10 @@
/*
* 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.annotatewith;
public enum AnnotateWithEnum {
EXISTING, OTHER_EXISTING
}

View File

@ -0,0 +1,548 @@
/*
* 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.annotatewith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.Mapper;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
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.GeneratedSource;
import org.mapstruct.factory.Mappers;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ben Zegveld
*/
@IssueKey("1574")
@WithClasses(AnnotateWithEnum.class)
public class AnnotateWithTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@ProcessorTest
@WithClasses({ DeprecateAndCustomMapper.class, CustomAnnotation.class })
public void mapperBecomesDeprecatedAndGetsCustomAnnotation() {
DeprecateAndCustomMapper mapper = Mappers.getMapper( DeprecateAndCustomMapper.class );
assertThat( mapper.getClass() ).hasAnnotations( Deprecated.class, CustomAnnotation.class );
}
@ProcessorTest
@WithClasses( { CustomNamedMapper.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void annotationWithValue() {
generatedSource.addComparisonToFixtureFor( CustomNamedMapper.class );
}
@ProcessorTest
@WithClasses( { MultipleArrayValuesMapper.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void annotationWithMultipleValues() {
generatedSource.addComparisonToFixtureFor( MultipleArrayValuesMapper.class );
}
@ProcessorTest
@WithClasses( { CustomNamedGenericClassMapper.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void annotationWithCorrectGenericClassValue() {
CustomNamedGenericClassMapper mapper = Mappers.getMapper( CustomNamedGenericClassMapper.class );
CustomAnnotationWithParams annotation = mapper.getClass().getAnnotation( CustomAnnotationWithParams.class );
assertThat( annotation ).isNotNull();
assertThat( annotation.stringParam() ).isEqualTo( "test" );
assertThat( annotation.genericTypedClass() ).isEqualTo( Mapper.class );
}
@ProcessorTest
@WithClasses( { AnnotationWithoutElementNameMapper.class, CustomAnnotation.class } )
public void annotateWithoutElementName() {
generatedSource
.forMapper( AnnotationWithoutElementNameMapper.class )
.content()
.contains( "@CustomAnnotation(value = \"value\")" );
}
@ProcessorTest
@WithClasses({ MetaAnnotatedMapper.class, ClassMetaAnnotation.class, CustomClassOnlyAnnotation.class })
public void metaAnnotationWorks() {
MetaAnnotatedMapper mapper = Mappers.getMapper( MetaAnnotatedMapper.class );
assertThat( mapper.getClass() ).hasAnnotation( CustomClassOnlyAnnotation.class );
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithMissingParameter.class,
line = 15,
message = "Parameter \"required\" is required for annotation \"AnnotationWithRequiredParameter\"."
)
}
)
@WithClasses({ ErroneousMapperWithMissingParameter.class, AnnotationWithRequiredParameter.class })
public void erroneousMapperWithMissingParameter() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithMethodOnInterface.class,
line = 15,
message = "Annotation \"CustomMethodOnlyAnnotation\" is not allowed on classes."
)
}
)
@WithClasses({ ErroneousMapperWithMethodOnInterface.class, CustomMethodOnlyAnnotation.class })
public void erroneousMapperWithMethodOnInterface() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithMethodOnClass.class,
line = 15,
message = "Annotation \"CustomMethodOnlyAnnotation\" is not allowed on classes."
)
}
)
@WithClasses({ ErroneousMapperWithMethodOnClass.class, CustomMethodOnlyAnnotation.class })
public void erroneousMapperWithMethodOnClass() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithAnnotationOnlyOnInterface.class,
line = 15,
message = "Annotation \"CustomAnnotationOnlyAnnotation\" is not allowed on classes."
)
}
)
@WithClasses({ ErroneousMapperWithAnnotationOnlyOnInterface.class, CustomAnnotationOnlyAnnotation.class })
public void erroneousMapperWithAnnotationOnlyOnInterface() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithAnnotationOnlyOnClass.class,
line = 15,
message = "Annotation \"CustomAnnotationOnlyAnnotation\" is not allowed on classes."
)
}
)
@WithClasses({ ErroneousMapperWithAnnotationOnlyOnClass.class, CustomAnnotationOnlyAnnotation.class })
public void erroneousMapperWithAnnotationOnlyOnClass() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithClassOnMethod.class,
line = 17,
message = "Annotation \"CustomClassOnlyAnnotation\" is not allowed on methods."
)
}
)
@WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class })
public void erroneousMapperWithClassOnMethod() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithUnknownParameter.class,
line = 17,
message = "Unknown parameter \"unknownParameter\" for annotation \"CustomAnnotation\"." +
" Did you mean \"value\"?"
)
}
)
@WithClasses({ ErroneousMapperWithUnknownParameter.class, CustomAnnotation.class })
public void erroneousMapperWithUnknownParameter() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithNonExistantEnum.class,
line = 17,
message = "Enum \"AnnotateWithEnum\" does not have value \"NON_EXISTANT\"."
)
}
)
@WithClasses( { ErroneousMapperWithNonExistantEnum.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void erroneousMapperWithNonExistantEnum() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithTooManyParameterValues.class,
line = 17,
message = "Parameter \"stringParam\" has too many value types supplied, type \"String\" is expected"
+ " for annotation \"CustomAnnotationWithParams\"."
)
}
)
@WithClasses( { ErroneousMapperWithTooManyParameterValues.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void erroneousMapperWithTooManyParameterValues() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 16,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"boolean\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 18,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"byte\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 20,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"char\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 22,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"CustomAnnotationWithParams\""
+ " but of type \"String\" for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 24,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"double\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 26,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"float\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 28,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"int\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 30,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"long\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 32,
alternativeLine = 43,
message = "Parameter \"stringParam\" is not of type \"short\" but of type \"String\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 35,
alternativeLine = 43,
message = "Parameter \"genericTypedClass\" is not of type \"String\" "
+ "but of type \"Class<? extends java.lang.annotation.Annotation>\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 36,
alternativeLine = 43,
message = "Parameter \"enumParam\" is not of type \"WrongAnnotateWithEnum\" "
+ "but of type \"AnnotateWithEnum\" for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 40,
alternativeLine = 43,
message = "Parameter \"genericTypedClass\" is not of type \"ErroneousMapperWithWrongParameter\" "
+ "but of type \"Class<? extends java.lang.annotation.Annotation>\" "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithWrongParameter.class,
line = 42,
alternativeLine = 43,
message = "Parameter \"value\" is not of type \"boolean\" "
+ "but of type \"String\" for annotation \"CustomAnnotation\"."
)
}
)
@WithClasses({
ErroneousMapperWithWrongParameter.class, CustomAnnotationWithParams.class,
CustomAnnotationWithParamsContainer.class, WrongAnnotateWithEnum.class, CustomAnnotation.class
})
public void erroneousMapperWithWrongParameter() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 17,
alternativeLine = 43,
message = "Parameter \"stringParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 18,
alternativeLine = 43,
message = "Parameter \"booleanParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 19,
alternativeLine = 32,
message = "Parameter \"byteParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 20,
alternativeLine = 32,
message = "Parameter \"charParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 21,
alternativeLine = 32,
message = "Parameter \"doubleParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 22,
alternativeLine = 32,
message = "Parameter \"floatParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 23,
alternativeLine = 32,
message = "Parameter \"intParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 24,
alternativeLine = 32,
message = "Parameter \"longParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 25,
alternativeLine = 32,
message = "Parameter \"shortParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 26,
alternativeLine = 32,
message = "Parameter \"genericTypedClass\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
),
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMultipleArrayValuesMapper.class,
line = 27,
alternativeLine = 32,
message = "Parameter \"enumParam\" does not accept multiple values "
+ "for annotation \"CustomAnnotationWithParams\"."
)
}
)
@WithClasses( { ErroneousMultipleArrayValuesMapper.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void erroneousMapperUsingMultipleValuesInsteadOfSingle() {
}
@ProcessorTest
@WithClasses( { MapperWithMissingAnnotationElementName.class,
CustomAnnotationWithTwoAnnotationElements.class } )
public void mapperWithMissingAnnotationElementNameShouldCompile() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithMissingEnumClass.class,
line = 17,
message = "enumClass needs to be defined when using enums."
)
}
)
@WithClasses( { ErroneousMapperWithMissingEnumClass.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void erroneousMapperWithMissingEnumClass() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithMissingEnums.class,
line = 17,
message = "enums needs to be defined when using enumClass."
)
}
)
@WithClasses( { ErroneousMapperWithMissingEnums.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void erroneousMapperWithMissingEnums() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithRepeatOfNotRepeatableAnnotation.class,
line = 16,
alternativeLine = 17,
message = "Annotation \"CustomAnnotation\" is not repeatable."
)
}
)
@WithClasses( { ErroneousMapperWithRepeatOfNotRepeatableAnnotation.class, CustomAnnotation.class } )
public void erroneousMapperWithRepeatOfNotRepeatableAnnotation() {
}
@ProcessorTest
@WithClasses( { MapperWithRepeatableAnnotation.class, CustomRepeatableAnnotation.class,
CustomRepeatableAnnotationContainer.class } )
public void mapperWithRepeatableAnnotationShouldCompile() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapperWithParameterRepeat.class,
line = 18,
message = "Parameter \"stringParam\" must not be defined more than once."
)
}
)
@WithClasses( { ErroneousMapperWithParameterRepeat.class, CustomAnnotationWithParamsContainer.class,
CustomAnnotationWithParams.class } )
public void erroneousMapperWithParameterRepeat() {
}
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.WARNING,
type = MapperWithIdenticalAnnotationRepeated.class,
line = 16,
alternativeLine = 17,
message = "Annotation \"CustomRepeatableAnnotation\" is already present "
+ "with the same elements configuration."
)
}
)
@WithClasses( { MapperWithIdenticalAnnotationRepeated.class, CustomRepeatableAnnotation.class,
CustomRepeatableAnnotationContainer.class } )
public void mapperWithIdenticalAnnotationRepeated() {
}
}

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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE, METHOD } )
public @interface AnnotationWithRequiredParameter {
String required();
}

View File

@ -0,0 +1,15 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
@Mapper
@AnnotateWith( value = CustomAnnotation.class, elements = @AnnotateWith.Element( strings = "value" ) )
public interface AnnotationWithoutElementNameMapper {
}

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.annotatewith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.AnnotateWith;
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE } )
@AnnotateWith( CustomClassOnlyAnnotation.class )
public @interface ClassMetaAnnotation {
}

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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE, METHOD } )
public @interface CustomAnnotation {
String value() default "";
}

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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME)
@Target({ ANNOTATION_TYPE })
public @interface CustomAnnotationOnlyAnnotation {
}

View File

@ -0,0 +1,64 @@
/*
* 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.annotatewith;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { METHOD, TYPE } )
@Repeatable( CustomAnnotationWithParamsContainer.class )
public @interface CustomAnnotationWithParams {
String stringParam();
Class<? extends Annotation> genericTypedClass() default CustomAnnotationWithParams.class;
AnnotateWithEnum enumParam() default AnnotateWithEnum.EXISTING;
byte byteParam() default 0x00;
char charParam() default 'a';
double doubleParam() default 0.0;
float floatParam() default 0.0f;
int intParam() default 0;
long longParam() default 0L;
short shortParam() default 0;
boolean booleanParam() default false;
short[] shortArray() default {};
byte[] byteArray() default {};
int[] intArray() default {};
long[] longArray() default {};
float[] floatArray() default {};
double[] doubleArray() default {};
char[] charArray() default {};
boolean[] booleanArray() default {};
String[] stringArray() default {};
Class<?>[] classArray() default {};
AnnotateWithEnum[] enumArray() 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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE, METHOD } )
public @interface CustomAnnotationWithParamsContainer {
CustomAnnotationWithParams[] value() default {};
}

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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE, METHOD } )
public @interface CustomAnnotationWithTwoAnnotationElements {
String value() default "";
boolean namedAnnotationElement() default false;
}

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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE } )
public @interface CustomClassOnlyAnnotation {
}

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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { METHOD } )
public @interface CustomMethodOnlyAnnotation {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@Element( name = "stringParam", strings = "test" ),
@Element( name = "genericTypedClass", classes = Mapper.class )
} )
public interface CustomNamedGenericClassMapper {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@Element( name = "stringArray", strings = "test" ),
@Element( name = "stringParam", strings = "test" ),
@Element( name = "booleanArray", booleans = true ),
@Element( name = "booleanParam", booleans = true ),
@Element( name = "byteArray", bytes = 0x10 ),
@Element( name = "byteParam", bytes = 0x13 ),
@Element( name = "charArray", chars = 'd' ),
@Element( name = "charParam", chars = 'a' ),
@Element( name = "enumArray", enumClass = AnnotateWithEnum.class, enums = "EXISTING" ),
@Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = "EXISTING" ),
@Element( name = "doubleArray", doubles = 0.3 ),
@Element( name = "doubleParam", doubles = 1.2 ),
@Element( name = "floatArray", floats = 0.3f ),
@Element( name = "floatParam", floats = 1.2f ),
@Element( name = "intArray", ints = 3 ),
@Element( name = "intParam", ints = 1 ),
@Element( name = "longArray", longs = 3L ),
@Element( name = "longParam", longs = 1L ),
@Element( name = "shortArray", shorts = 3 ),
@Element( name = "shortParam", shorts = 1 )
} )
public interface CustomNamedMapper {
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE, METHOD } )
@Repeatable( CustomRepeatableAnnotationContainer.class )
public @interface CustomRepeatableAnnotation {
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.annotatewith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention( RUNTIME )
@Target( { TYPE, METHOD } )
public @interface CustomRepeatableAnnotationContainer {
CustomRepeatableAnnotation[] 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( Deprecated.class )
@AnnotateWith( CustomAnnotation.class )
public interface DeprecateAndCustomMapper {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Filip Hrisafov
*/
@Mapper
@AnnotateWith( value = CustomAnnotationOnlyAnnotation.class )
public abstract class ErroneousMapperWithAnnotationOnlyOnClass {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Filip Hrisafov
*/
@Mapper
@AnnotateWith( value = CustomAnnotationOnlyAnnotation.class )
public interface ErroneousMapperWithAnnotationOnlyOnInterface {
}

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.ap.test.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
public interface ErroneousMapperWithClassOnMethod {
@AnnotateWith( value = CustomClassOnlyAnnotation.class )
Target toString(Source value);
class Source {
}
class Target {
}
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomMethodOnlyAnnotation.class )
public abstract class ErroneousMapperWithMethodOnClass {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomMethodOnlyAnnotation.class )
public interface ErroneousMapperWithMethodOnInterface {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = { @Element( name = "enumParam", enums = "EXISTING" ),
@Element( name = "stringParam", strings = "required" ) }
)
public interface ErroneousMapperWithMissingEnumClass {
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @Element( name = "stringParam", strings = "required", enumClass = AnnotateWithEnum.class )
)
public interface ErroneousMapperWithMissingEnums {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( AnnotationWithRequiredParameter.class )
public interface ErroneousMapperWithMissingParameter {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = { @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = "NON_EXISTANT" ),
@Element( name = "stringParam", strings = "required" ) }
)
public interface ErroneousMapperWithNonExistantEnum {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@Element( name = "stringParam", strings = "test" ),
@Element( name = "stringParam", strings = "otherValue" )
} )
public interface ErroneousMapperWithParameterRepeat {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotation.class )
@AnnotateWith( value = CustomAnnotation.class )
public interface ErroneousMapperWithRepeatOfNotRepeatableAnnotation {
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @Element( name = "stringParam", booleans = true, strings = "test" )
)
public interface ErroneousMapperWithTooManyParameterValues {
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotation.class,
elements = @Element( name = "unknownParameter", strings = "unknown" )
)
public interface ErroneousMapperWithUnknownParameter {
}

View File

@ -0,0 +1,45 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", booleans = true ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", bytes = 0x12 ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", chars = 'a' ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", classes = CustomAnnotationWithParams.class ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", doubles = 12.34 ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", floats = 12.34f ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", ints = 1234 ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", longs = 1234L ) )
@AnnotateWith( value = CustomAnnotationWithParams.class,
elements = @AnnotateWith.Element( name = "stringParam", shorts = 12 ) )
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@AnnotateWith.Element( name = "stringParam", strings = "correctValue" ),
@AnnotateWith.Element( name = "genericTypedClass", strings = "wrong" ),
@AnnotateWith.Element( name = "enumParam", enumClass = WrongAnnotateWithEnum.class, enums = "EXISTING" )
} )
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@AnnotateWith.Element( name = "stringParam", strings = "correctValue" ),
@AnnotateWith.Element( name = "genericTypedClass", classes = ErroneousMapperWithWrongParameter.class )
} )
@AnnotateWith( value = CustomAnnotation.class, elements = @AnnotateWith.Element( booleans = true ) )
public interface ErroneousMapperWithWrongParameter {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@Element( name = "stringParam", strings = { "test1", "test2" } ),
@Element( name = "booleanParam", booleans = { false, true } ),
@Element( name = "byteParam", bytes = { 0x08, 0x1f } ),
@Element( name = "charParam", chars = { 'b', 'c' } ),
@Element( name = "doubleParam", doubles = { 1.2, 3.4 } ),
@Element( name = "floatParam", floats = { 1.2f, 3.4f } ),
@Element( name = "intParam", ints = { 12, 34 } ),
@Element( name = "longParam", longs = { 12L, 34L } ),
@Element( name = "shortParam", shorts = { 12, 34 } ),
@Element( name = "genericTypedClass", classes = { Mapper.class, CustomAnnotationWithParams.class } ),
@Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = { "EXISTING", "OTHER_EXISTING" } )
} )
public interface ErroneousMultipleArrayValuesMapper {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "identical" ) )
@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "identical" ) )
public interface MapperWithIdenticalAnnotationRepeated {
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithTwoAnnotationElements.class, elements = {
@AnnotateWith.Element( strings = "unnamed annotation element" ),
@AnnotateWith.Element( name = "namedAnnotationElement", booleans = false )
} )
public abstract class MapperWithMissingAnnotationElementName {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomRepeatableAnnotation.class )
@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "different" ) )
public interface MapperWithRepeatableAnnotation {
}

View File

@ -0,0 +1,14 @@
/*
* 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.annotatewith;
import org.mapstruct.Mapper;
@ClassMetaAnnotation
@Mapper
public interface MetaAnnotatedMapper {
}

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.annotatewith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.AnnotateWith;
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.METHOD } )
@AnnotateWith( CustomMethodOnlyAnnotation.class )
public @interface MethodMetaAnnotation {
}

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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWith.Element;
import org.mapstruct.Mapper;
/**
* @author Ben Zegveld
*/
@Mapper
@AnnotateWith( value = CustomAnnotationWithParams.class, elements = {
@Element( name = "stringArray", strings = { "test1", "test2" } ),
@Element( name = "booleanArray", booleans = { false, true } ),
@Element( name = "byteArray", bytes = { 0x08, 0x1f } ),
@Element( name = "charArray", chars = { 'b', 'c' } ),
@Element( name = "doubleArray", doubles = { 1.2, 3.4 } ),
@Element( name = "floatArray", floats = { 1.2f, 3.4f } ),
@Element( name = "intArray", ints = { 12, 34 } ),
@Element( name = "longArray", longs = { 12L, 34L } ),
@Element( name = "shortArray", shorts = { 12, 34 } ),
@Element( name = "classArray", classes = { Mapper.class, CustomAnnotationWithParams.class } ),
@Element( name = "stringParam", strings = "required parameter" )
} )
public interface MultipleArrayValuesMapper {
}

View File

@ -0,0 +1,10 @@
/*
* 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.annotatewith;
public enum WrongAnnotateWithEnum {
EXISTING
}

View File

@ -0,0 +1,17 @@
/*
* 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.annotatewith;
import javax.annotation.processing.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-06-06T16:48:18+0200",
comments = "version: , compiler: javac, environment: Java 11.0.12 (Azul Systems, Inc.)"
)
@CustomAnnotationWithParams(stringArray = "test", stringParam = "test", booleanArray = true, booleanParam = true, byteArray = 16, byteParam = 19, charArray = 'd', charParam = 'a', enumArray = AnnotateWithEnum.EXISTING, enumParam = AnnotateWithEnum.EXISTING, doubleArray = 0.3, doubleParam = 1.2, floatArray = 0.300000011920929f, floatParam = 1.2000000476837158f, intArray = 3, intParam = 1, longArray = 3L, longParam = 1L, shortArray = 3, shortParam = 1)
public class CustomNamedMapperImpl implements CustomNamedMapper {
}

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.annotatewith;
import javax.annotation.processing.Generated;
import org.mapstruct.Mapper;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-06-06T16:48:23+0200",
comments = "version: , compiler: javac, environment: Java 11.0.12 (Azul Systems, Inc.)"
)
@CustomAnnotationWithParams(stringArray = { "test1", "test2" }, booleanArray = { false, true }, byteArray = { 8, 31 }, charArray = { 'b', 'c' }, doubleArray = { 1.2, 3.4 }, floatArray = { 1.2000000476837158f, 3.4000000953674316f }, intArray = { 12, 34 }, longArray = { 12L, 34L }, shortArray = { 12, 34 }, classArray = { Mapper.class, CustomAnnotationWithParams.class }, stringParam = "required parameter")
public class MultipleArrayValuesMapperImpl implements MultipleArrayValuesMapper {
}