#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.
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 org.mapstruct.AfterMapping;
import org.mapstruct.AnnotateWith;
import org.mapstruct.AnnotateWiths;
import org.mapstruct.BeanMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Builder;
@ -43,6 +45,9 @@ import org.mapstruct.tools.gem.GemDefinition;
*
* @author Gunnar Morling
*/
@GemDefinition(AnnotateWith.class)
@GemDefinition(AnnotateWith.Element.class)
@GemDefinition(AnnotateWiths.class)
@GemDefinition(Mapper.class)
@GemDefinition(Mapping.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;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
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.Type;
@ -21,16 +23,13 @@ public class Annotation extends ModelElement {
private final Type type;
/**
* List of annotation attributes. Quite simplistic, but it's sufficient for now.
*/
private List<String> properties;
private List<AnnotationElement> properties;
public Annotation(Type type) {
this( type, Collections.emptyList() );
}
public Annotation(Type type, List<String> properties) {
public Annotation(Type type, List<AnnotationElement> properties) {
this.type = type;
this.properties = properties;
}
@ -41,10 +40,15 @@ public class Annotation extends ModelElement {
@Override
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;
}
}

View File

@ -85,6 +85,7 @@ import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETE
*/
public class BeanMappingMethod extends NormalTypeMappingMethod {
private final List<Annotation> annotations;
private final List<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter;
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<String> existingVariableNames = new HashSet<>();
private final Map<String, Set<MappingReference>> unprocessedDefinedTargets = new LinkedHashMap<>();
private final List<Annotation> annotations = new ArrayList<>();
private MappingReferences mappingReferences;
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
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 */
Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;
@ -362,6 +370,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return new BeanMappingMethod(
method,
annotations,
existingVariableNames,
propertyMappings,
factoryMethod,
@ -1701,6 +1710,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
//CHECKSTYLE:OFF
private BeanMappingMethod(Method method,
List<Annotation> annotations,
Collection<String> existingVariableNames,
List<PropertyMapping> propertyMappings,
MethodReference factoryMethod,
@ -1722,6 +1732,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
);
//CHECKSTYLE:ON
this.annotations = annotations;
this.propertyMappings = propertyMappings;
this.returnTypeBuilder = returnTypeBuilder;
this.finalizerMethod = finalizerMethod;
@ -1760,6 +1771,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
this.subclassMappings = subclassMappings;
}
public List<Annotation> getAnnotations() {
return annotations;
}
public List<PropertyMapping> getConstantMappings() {
return constantMappings;
}

View File

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

View File

@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.model;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@ -43,6 +42,7 @@ public class Mapper extends GeneratedType {
private String implPackage;
private boolean customPackage;
private boolean suppressGeneratorTimestamp;
private Set<Annotation> customAnnotations;
public Builder() {
super( Builder.class );
@ -63,6 +63,11 @@ public class Mapper extends GeneratedType {
return this;
}
public Builder additionalAnnotations(Set<Annotation> customAnnotations) {
this.customAnnotations = customAnnotations;
return this;
}
public Builder decorator(Decorator decorator) {
this.decorator = decorator;
return this;
@ -105,6 +110,7 @@ public class Mapper extends GeneratedType {
definitionType,
customPackage,
customName,
customAnnotations,
methods,
options,
versionInformation,
@ -126,7 +132,7 @@ public class Mapper extends GeneratedType {
@SuppressWarnings( "checkstyle:parameternumber" )
private Mapper(TypeFactory typeFactory, String packageName, String name,
Type mapperDefinitionType,
boolean customPackage, boolean customImplName,
boolean customPackage, boolean customImplName, Set<Annotation> customAnnotations,
List<MappingMethod> methods, Options options, VersionInformation versionInformation,
boolean suppressGeneratorTimestamp,
Accessibility accessibility, List<Field> fields, Constructor constructor,
@ -148,6 +154,7 @@ public class Mapper extends GeneratedType {
);
this.customPackage = customPackage;
this.customImplName = customImplName;
customAnnotations.forEach( this::addAnnotation );
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.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
/**
* A {@link ModelElementProcessor} which converts the given {@link Mapper}
@ -67,7 +69,10 @@ public class JakartaComponentProcessor extends AnnotationBasedComponentModelProc
private Annotation namedDelegate(Mapper mapper) {
return new Annotation(
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.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.util.AnnotationProcessingException;
@ -70,7 +72,11 @@ public class Jsr330ComponentProcessor extends AnnotationBasedComponentModelProce
private Annotation namedDelegate(Mapper mapper) {
return new Annotation(
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.MappingInheritanceStrategyGem;
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.ContainerMappingMethod;
import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder;
@ -93,6 +94,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private AccessorNamingUtils accessorNaming;
private MappingBuilderContext mappingContext;
private AdditionalAnnotationsBuilder additionalAnnotationsBuilder;
@Override
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
this.elementUtils = context.getElementUtils();
@ -103,6 +106,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
this.versionInformation = context.getVersionInformation();
this.typeFactory = context.getTypeFactory();
this.accessorNaming = context.getAccessorNaming();
additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder( elementUtils, typeFactory, messager );
MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() );
List<MapperReference> mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions );
@ -206,6 +211,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.implName( mapperOptions.implementationName() )
.implPackage( mapperOptions.implementationPackage() )
.suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() )
.additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) )
.build();
if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) {

View File

@ -8,13 +8,10 @@ package org.mapstruct.ap.internal.processor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
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.ExecutableElement;
import javax.lang.model.element.Modifier;
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.FormattingMessager;
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.spi.EnumTransformationStrategy;
import org.mapstruct.tools.gem.Gem;
/**
* 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>> {
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 MAPPINGS_FQN = "org.mapstruct.Mappings";
private static final String SUB_CLASS_MAPPING_FQN = "org.mapstruct.SubclassMapping";
@ -280,8 +275,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
typeUtils,
typeFactory );
RepeatableMappings repeatableMappings = new RepeatableMappings();
Set<MappingOptions> mappingOptions = repeatableMappings.getMappings( method, beanMappingOptions );
Set<MappingOptions> mappingOptions = getMappings( method, beanMappingOptions );
IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem(
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
*/
private Set<MappingOptions> getMappings(ExecutableElement method, BeanMappingOptions beanMapping) {
return new RepeatableMappings().getMappings( method, beanMapping );
return new RepeatableMappings( beanMapping ).getProcessedAnnotations( method );
}
/**
@ -607,173 +601,107 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private Set<SubclassMappingOptions> getSubclassMappings(List<Parameter> sourceParameters, Type resultType,
ExecutableElement method, BeanMappingOptions beanMapping,
SubclassValidator validator) {
return new RepeatableSubclassMappings( sourceParameters, resultType, validator )
.getMappings( method, beanMapping );
return new RepeatableSubclassMappings( beanMapping, sourceParameters, resultType, validator )
.getProcessedAnnotations( method );
}
private class RepeatableMappings extends RepeatableMappingAnnotations<MappingGem, MappingsGem, MappingOptions> {
RepeatableMappings() {
super( MAPPING_FQN, MAPPINGS_FQN );
private class RepeatableMappings extends RepeatableAnnotations<MappingGem, MappingsGem, MappingOptions> {
private BeanMappingOptions beanMappingOptions;
RepeatableMappings(BeanMappingOptions beanMappingOptions) {
super( elementUtils, MAPPING_FQN, MAPPINGS_FQN );
this.beanMappingOptions = beanMappingOptions;
}
@Override
MappingGem singularInstanceOn(Element element) {
protected MappingGem singularInstanceOn(Element element) {
return MappingGem.instanceOn( element );
}
@Override
MappingsGem multipleInstanceOn(Element element) {
protected MappingsGem multipleInstanceOn(Element element) {
return MappingsGem.instanceOn( element );
}
@Override
void addInstance(MappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
Set<MappingOptions> mappings) {
MappingOptions.addInstance( gem, method, beanMappingOptions, messager, typeUtils, mappings );
protected void addInstance(MappingGem gem, Element method, Set<MappingOptions> mappings) {
MappingOptions.addInstance(
gem,
(ExecutableElement) method,
beanMappingOptions,
messager,
typeUtils,
mappings );
}
@Override
void addInstances(MappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
Set<MappingOptions> mappings) {
MappingOptions.addInstances( gem, method, beanMappingOptions, messager, typeUtils, mappings );
protected void addInstances(MappingsGem gem, Element method, Set<MappingOptions> mappings) {
MappingOptions.addInstances(
gem,
(ExecutableElement) method,
beanMappingOptions,
messager,
typeUtils,
mappings );
}
}
private class RepeatableSubclassMappings
extends RepeatableMappingAnnotations<SubclassMappingGem, SubclassMappingsGem, SubclassMappingOptions> {
extends RepeatableAnnotations<SubclassMappingGem, SubclassMappingsGem, SubclassMappingOptions> {
private final List<Parameter> sourceParameters;
private final Type resultType;
private SubclassValidator validator;
private BeanMappingOptions beanMappingOptions;
RepeatableSubclassMappings(List<Parameter> sourceParameters, Type resultType, SubclassValidator validator) {
super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN );
RepeatableSubclassMappings(BeanMappingOptions beanMappingOptions, List<Parameter> sourceParameters,
Type resultType, SubclassValidator validator) {
super( elementUtils, SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN );
this.beanMappingOptions = beanMappingOptions;
this.sourceParameters = sourceParameters;
this.resultType = resultType;
this.validator = validator;
}
@Override
SubclassMappingGem singularInstanceOn(Element element) {
protected SubclassMappingGem singularInstanceOn(Element element) {
return SubclassMappingGem.instanceOn( element );
}
@Override
SubclassMappingsGem multipleInstanceOn(Element element) {
protected SubclassMappingsGem multipleInstanceOn(Element element) {
return SubclassMappingsGem.instanceOn( element );
}
@Override
void addInstance(SubclassMappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
Set<SubclassMappingOptions> mappings) {
SubclassMappingOptions
.addInstance(
gem,
method,
beanMappingOptions,
messager,
typeUtils,
mappings,
sourceParameters,
resultType,
validator );
protected void addInstance(SubclassMappingGem gem,
Element method,
Set<SubclassMappingOptions> mappings) {
SubclassMappingOptions.addInstance(
gem,
(ExecutableElement) method,
beanMappingOptions,
messager,
typeUtils,
mappings,
sourceParameters,
resultType,
validator );
}
@Override
void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
Set<SubclassMappingOptions> mappings) {
SubclassMappingOptions
.addInstances(
gem,
method,
beanMappingOptions,
messager,
typeUtils,
mappings,
sourceParameters,
resultType,
validator );
}
}
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;
protected void addInstances(SubclassMappingsGem gem,
Element method,
Set<SubclassMappingOptions> mappings) {
SubclassMappingOptions.addInstances(
gem,
(ExecutableElement) method,
beanMappingOptions,
messager,
typeUtils,
mappings,
sourceParameters,
resultType,
validator );
}
}

View File

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

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" -->
@<@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" -->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#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/> {
<#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 {
}