From 849085e02647d5d0cc63c9c2da98c8e959f4e6e2 Mon Sep 17 00:00:00 2001
From: Zegveld <41897697+Zegveld@users.noreply.github.com>
Date: Sat, 20 Aug 2022 12:59:38 +0200
Subject: [PATCH] #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.
---
.../main/java/org/mapstruct/AnnotateWith.java | 176 +++++
.../java/org/mapstruct/AnnotateWiths.java | 31 +
.../src/main/java/org/mapstruct/NullEnum.java | 15 +
.../chapter-3-defining-a-mapper.asciidoc | 40 ++
.../ap/internal/gem/GemGenerator.java | 5 +
.../model/AdditionalAnnotationsBuilder.java | 602 ++++++++++++++++++
.../ap/internal/model/Annotation.java | 18 +-
.../ap/internal/model/BeanMappingMethod.java | 15 +
.../ap/internal/model/GeneratedType.java | 7 +-
.../mapstruct/ap/internal/model/Mapper.java | 11 +-
.../model/annotation/AnnotationElement.java | 128 ++++
.../EnumAnnotationElementHolder.java | 35 +
.../processor/JakartaComponentProcessor.java | 7 +-
.../processor/Jsr330ComponentProcessor.java | 8 +-
.../processor/MapperCreationProcessor.java | 6 +
.../processor/MethodRetrievalProcessor.java | 198 ++----
.../processor/SpringComponentProcessor.java | 8 +-
.../mapstruct/ap/internal/util/Message.java | 14 +
.../internal/util/RepeatableAnnotations.java | 120 ++++
.../ap/internal/model/Annotation.ftl | 2 +-
.../ap/internal/model/BeanMappingMethod.ftl | 3 +
.../model/annotation/AnnotationElement.ftl | 48 ++
.../EnumAnnotationElementHolder.ftl | 9 +
.../test/annotatewith/AnnotateWithEnum.java | 10 +
.../test/annotatewith/AnnotateWithTest.java | 548 ++++++++++++++++
.../AnnotationWithRequiredParameter.java | 19 +
.../AnnotationWithoutElementNameMapper.java | 15 +
.../annotatewith/ClassMetaAnnotation.java | 20 +
.../test/annotatewith/CustomAnnotation.java | 19 +
.../CustomAnnotationOnlyAnnotation.java | 18 +
.../CustomAnnotationWithParams.java | 64 ++
.../CustomAnnotationWithParamsContainer.java | 19 +
...omAnnotationWithTwoAnnotationElements.java | 20 +
.../CustomClassOnlyAnnotation.java | 18 +
.../CustomMethodOnlyAnnotation.java | 18 +
.../CustomNamedGenericClassMapper.java | 22 +
.../test/annotatewith/CustomNamedMapper.java | 40 ++
.../CustomRepeatableAnnotation.java | 21 +
.../CustomRepeatableAnnotationContainer.java | 19 +
.../DeprecateAndCustomMapper.java | 19 +
...oneousMapperWithAnnotationOnlyOnClass.java | 18 +
...usMapperWithAnnotationOnlyOnInterface.java | 18 +
.../ErroneousMapperWithClassOnMethod.java | 27 +
.../ErroneousMapperWithMethodOnClass.java | 18 +
.../ErroneousMapperWithMethodOnInterface.java | 18 +
.../ErroneousMapperWithMissingEnumClass.java | 22 +
.../ErroneousMapperWithMissingEnums.java | 21 +
.../ErroneousMapperWithMissingParameter.java | 18 +
.../ErroneousMapperWithNonExistantEnum.java | 22 +
.../ErroneousMapperWithParameterRepeat.java | 22 +
...erWithRepeatOfNotRepeatableAnnotation.java | 19 +
...neousMapperWithTooManyParameterValues.java | 21 +
.../ErroneousMapperWithUnknownParameter.java | 21 +
.../ErroneousMapperWithWrongParameter.java | 45 ++
.../ErroneousMultipleArrayValuesMapper.java | 31 +
...MapperWithIdenticalAnnotationRepeated.java | 19 +
...apperWithMissingAnnotationElementName.java | 21 +
.../MapperWithRepeatableAnnotation.java | 19 +
.../annotatewith/MetaAnnotatedMapper.java | 14 +
.../annotatewith/MethodMetaAnnotation.java | 20 +
.../MultipleArrayValuesMapper.java | 31 +
.../annotatewith/WrongAnnotateWithEnum.java | 10 +
.../annotatewith/CustomNamedMapperImpl.java | 17 +
.../MultipleArrayValuesMapperImpl.java | 18 +
64 files changed, 2774 insertions(+), 151 deletions(-)
create mode 100644 core/src/main/java/org/mapstruct/AnnotateWith.java
create mode 100644 core/src/main/java/org/mapstruct/AnnotateWiths.java
create mode 100644 core/src/main/java/org/mapstruct/NullEnum.java
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java
create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl
create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java
create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java
create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java
diff --git a/core/src/main/java/org/mapstruct/AnnotateWith.java b/core/src/main/java/org/mapstruct/AnnotateWith.java
new file mode 100644
index 000000000..090a7bc0d
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/AnnotateWith.java
@@ -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.
+ *
+ * Examples based on the spring framework annotations.
+ *
+ * Marking a class as `Lazy`:
+ *
+ *
+ * @AnnotateWith( value = Lazy.class )
+ * @Mapper
+ * public interface FooMapper {
+ * // mapper code
+ * }
+ *
+ *
+ * The following code would be generated:
+ *
+ *
+ * @Lazy
+ * public class FooMapperImpl implements FooMapper {
+ * // mapper code
+ * }
+ *
+ * Setting the profile on the generated implementation:
+ *
+ *
+ * @AnnotateWith( value = Profile.class, elements = @AnnotateWith.Element( strings = "prod" ) )
+ * @Mapper
+ * public interface FooMapper {
+ * // mapper code
+ * }
+ *
+ *
+ * The following code would be generated:
+ *
+ *
+ * @Profile( value = "prod" )
+ * public class FooMapperImpl implements FooMapper {
+ * // mapper code
+ * }
+ *
+ *
+ * @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 {};
+
+ }
+}
diff --git a/core/src/main/java/org/mapstruct/AnnotateWiths.java b/core/src/main/java/org/mapstruct/AnnotateWiths.java
new file mode 100644
index 000000000..5e86ad9a1
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/AnnotateWiths.java
@@ -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();
+}
diff --git a/core/src/main/java/org/mapstruct/NullEnum.java b/core/src/main/java/org/mapstruct/NullEnum.java
new file mode 100644
index 000000000..ac39b3485
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/NullEnum.java
@@ -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 {
+}
diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
index ab3366643..e4a77f539 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -721,3 +721,43 @@ i.e. You can map from `Map` 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
+ }
+}
+----
+====
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java
index 70098c952..cbb59f4e5 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java
@@ -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)
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java
new file mode 100644
index 000000000..f28cdfef9
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java
@@ -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 {
+ 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 mappings) {
+ buildAnnotation( gem, source ).ifPresent( t -> addAndValidateMapping( mappings, source, gem, t ) );
+ }
+
+ @Override
+ protected void addInstances(AnnotateWithsGem gem, Element source, Set mappings) {
+ for ( AnnotateWithGem annotateWithGem : gem.value().get() ) {
+ buildAnnotation(
+ annotateWithGem,
+ source ).ifPresent( t -> addAndValidateMapping( mappings, source, annotateWithGem, t ) );
+ }
+ }
+
+ private void addAndValidateMapping(Set 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 buildAnnotation(AnnotateWithGem annotationGem, Element element) {
+ Type annotationType = typeFactory.getType( getTypeMirror( annotationGem.value() ) );
+ List eleGems = annotationGem.elements().get();
+ if ( isValid( annotationType, eleGems, element, annotationGem.mirror() ) ) {
+ return Optional.of( new Annotation( annotationType, convertToProperties( eleGems ) ) );
+ }
+ return Optional.empty();
+ }
+
+ private List convertToProperties(List 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 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> factory;
+ private final Predicate usabilityChecker;
+
+ ConvertToProperty(AnnotationElementType type,
+ BiFunction> factory,
+ Predicate 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 eleGems, Element element,
+ AnnotationMirror annotationMirror) {
+ boolean isValid = true;
+ if ( !annotationIsAllowed( annotationType, element, annotationMirror ) ) {
+ isValid = false;
+ }
+
+ List 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 eleGems, Element element) {
+ boolean isValid = true;
+ List 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 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 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 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 annotationParameters,
+ List eleGems, Element element) {
+ Set 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 annotationParameters,
+ List 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 annotationParameters,
+ List elements,
+ Element element) {
+ Map 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 elementTypes = getParameterTypes( eleGem );
+ Set 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 typeParameters = annotationParameterType.getTypeParameters();
+ if ( typeParameters.size() != 1 ) {
+ return annotationParameterType;
+ }
+ return typeParameters.get( 0 ).getTypeBound();
+ }
+
+ private Map getParameterTypes(ElementGem eleGem) {
+ Map 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 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 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 ""
+ // 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;
+ }
+
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java
index 405958ccd..866012c51 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java
@@ -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 properties;
+ private List properties;
public Annotation(Type type) {
this( type, Collections.emptyList() );
}
- public Annotation(Type type, List properties) {
+ public Annotation(Type type, List properties) {
this.type = type;
this.properties = properties;
}
@@ -41,10 +40,15 @@ public class Annotation extends ModelElement {
@Override
public Set getImportTypes() {
- return Collections.singleton( type );
+ Set types = new HashSet<>();
+ for ( AnnotationElement prop : properties ) {
+ types.addAll( prop.getImportTypes() );
+ }
+ types.add( type );
+ return types;
}
- public List getProperties() {
+ public List getProperties() {
return properties;
}
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
index d17718370..e748c173a 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
@@ -85,6 +85,7 @@ import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETE
*/
public class BeanMappingMethod extends NormalTypeMappingMethod {
+ private final List annotations;
private final List propertyMappings;
private final Map> mappingsByParameter;
private final Map> constructorMappingsByParameter;
@@ -112,6 +113,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Set unprocessedSourceParameters = new HashSet<>();
private final Set existingVariableNames = new HashSet<>();
private final Map> unprocessedDefinedTargets = new LinkedHashMap<>();
+ private final List 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 annotations,
Collection existingVariableNames,
List 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 getAnnotations() {
+ return annotations;
+ }
+
public List getConstantMappings() {
return constantMappings;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java
index 134ab081d..f8f65c927 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java
@@ -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 ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java
index fa15cd8ed..9b7729e8f 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java
@@ -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 customAnnotations;
public Builder() {
super( Builder.class );
@@ -63,6 +63,11 @@ public class Mapper extends GeneratedType {
return this;
}
+ public Builder additionalAnnotations(Set 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 customAnnotations,
List methods, Options options, VersionInformation versionInformation,
boolean suppressGeneratorTimestamp,
Accessibility accessibility, List 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;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java
new file mode 100644
index 000000000..6ecde886b
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java
@@ -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 getImportTypes() {
+ Set 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 );
+ }
+
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java
new file mode 100644
index 000000000..de87f3252
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java
@@ -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 getImportTypes() {
+ return enumClass.getImportTypes();
+ }
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java
index 5161ea689..999c0a51d 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java
@@ -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() )
+ ) )
);
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java
index 6a6cc1658..00ba72a07 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java
@@ -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() )
+ ) )
);
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java
index d8acb1039..c05d2d18b 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java
@@ -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 sourceModel) {
this.elementUtils = context.getElementUtils();
@@ -103,6 +106,8 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions );
@@ -206,6 +211,7 @@ public class MapperCreationProcessor implements ModelElementProcessor> {
- 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 mappingOptions = repeatableMappings.getMappings( method, beanMappingOptions );
+ Set mappingOptions = getMappings( method, beanMappingOptions );
IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem(
IterableMappingGem.instanceOn( method ),
@@ -593,7 +587,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor 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 getSubclassMappings(List 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 {
- RepeatableMappings() {
- super( MAPPING_FQN, MAPPINGS_FQN );
+ private class RepeatableMappings extends RepeatableAnnotations {
+ 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 mappings) {
- MappingOptions.addInstance( gem, method, beanMappingOptions, messager, typeUtils, mappings );
+ protected void addInstance(MappingGem gem, Element method, Set mappings) {
+ MappingOptions.addInstance(
+ gem,
+ (ExecutableElement) method,
+ beanMappingOptions,
+ messager,
+ typeUtils,
+ mappings );
}
@Override
- void addInstances(MappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
- Set mappings) {
- MappingOptions.addInstances( gem, method, beanMappingOptions, messager, typeUtils, mappings );
+ protected void addInstances(MappingsGem gem, Element method, Set mappings) {
+ MappingOptions.addInstances(
+ gem,
+ (ExecutableElement) method,
+ beanMappingOptions,
+ messager,
+ typeUtils,
+ mappings );
}
}
private class RepeatableSubclassMappings
- extends RepeatableMappingAnnotations {
+ extends RepeatableAnnotations {
private final List sourceParameters;
private final Type resultType;
private SubclassValidator validator;
+ private BeanMappingOptions beanMappingOptions;
- RepeatableSubclassMappings(List sourceParameters, Type resultType, SubclassValidator validator) {
- super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN );
+ RepeatableSubclassMappings(BeanMappingOptions beanMappingOptions, List 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 mappings) {
- SubclassMappingOptions
- .addInstance(
- gem,
- method,
- beanMappingOptions,
- messager,
- typeUtils,
- mappings,
- sourceParameters,
- resultType,
- validator );
+ protected void addInstance(SubclassMappingGem gem,
+ Element method,
+ Set mappings) {
+ SubclassMappingOptions.addInstance(
+ gem,
+ (ExecutableElement) method,
+ beanMappingOptions,
+ messager,
+ typeUtils,
+ mappings,
+ sourceParameters,
+ resultType,
+ validator );
}
@Override
- void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
- Set mappings) {
- SubclassMappingOptions
- .addInstances(
- gem,
- method,
- beanMappingOptions,
- messager,
- typeUtils,
- mappings,
- sourceParameters,
- resultType,
- validator );
- }
- }
-
- private abstract class RepeatableMappingAnnotations {
-
- 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 mappings);
-
- abstract void addInstances(MULTIPLE gem, ExecutableElement method, BeanMappingOptions beanMappingOptions,
- Set 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 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 getMappings(ExecutableElement method, Element element,
- BeanMappingOptions beanMapping, LinkedHashSet mappingOptions,
- Set 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 mappings) {
+ SubclassMappingOptions.addInstances(
+ gem,
+ (ExecutableElement) method,
+ beanMappingOptions,
+ messager,
+ typeUtils,
+ mappings,
+ sourceParameters,
+ resultType,
+ validator );
}
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java
index f7aa9b4fe..4efc537c3 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java
@@ -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() {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
index 4e1c0302f..92951ad52 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
@@ -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 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 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
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java
new file mode 100644
index 000000000..accae7771
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java
@@ -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 {
+ 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 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 mappings);
+
+ /**
+ * Retrieves the processed annotations.
+ *
+ * @param source The source element of interest
+ * @return The processed annotations for the given element
+ */
+ public Set 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 getMappings(Element source, Element element,
+ LinkedHashSet mappingOptions,
+ Set 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;
+ }
+}
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl
index 4391bcb97..eb67d732f 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl
@@ -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>
\ No newline at end of file
+@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property><@includeModel object=property/><#if property_has_next>, #if>#list>)#if>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl
index 81e3b61fd..1b402a57f 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl
@@ -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 />
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl
new file mode 100644
index 000000000..21c1977f5
--- /dev/null
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl
@@ -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>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl
new file mode 100644
index 000000000..145b93315
--- /dev/null
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl
@@ -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>
\ No newline at end of file
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java
new file mode 100644
index 000000000..b2627fd8c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java
@@ -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
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java
new file mode 100644
index 000000000..c2055b4c5
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java
@@ -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() {
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java
new file mode 100644
index 000000000..183b86592
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java
@@ -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();
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java
new file mode 100644
index 000000000..8bb83ba56
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java
new file mode 100644
index 000000000..057cb384f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java
new file mode 100644
index 000000000..71ee6f12e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java
@@ -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 "";
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java
new file mode 100644
index 000000000..52d3e386a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java
new file mode 100644
index 000000000..bc25302fc
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java
@@ -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 {};
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java
new file mode 100644
index 000000000..51ed62885
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java
@@ -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 {};
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java
new file mode 100644
index 000000000..fb8c5c69f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java
@@ -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;
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java
new file mode 100644
index 000000000..858c8fb52
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java
new file mode 100644
index 000000000..1dfe8e734
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java
new file mode 100644
index 000000000..a320d56bb
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java
new file mode 100644
index 000000000..32241ab4a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java
new file mode 100644
index 000000000..d7c0c8510
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java
@@ -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 "";
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java
new file mode 100644
index 000000000..8de3a8bf0
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java
@@ -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 {};
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java
new file mode 100644
index 000000000..809ad5702
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java
new file mode 100644
index 000000000..d617e8d0f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java
new file mode 100644
index 000000000..4e914b30a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java
new file mode 100644
index 000000000..d3d53c910
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java
@@ -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 {
+
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java
new file mode 100644
index 000000000..262880d6f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java
new file mode 100644
index 000000000..75f903909
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java
new file mode 100644
index 000000000..6905a363f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java
new file mode 100644
index 000000000..fc7096d29
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java
new file mode 100644
index 000000000..40ffa5309
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java
new file mode 100644
index 000000000..1ed85b990
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java
new file mode 100644
index 000000000..d75c7a4ee
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java
new file mode 100644
index 000000000..be9596ea4
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java
new file mode 100644
index 000000000..4b6c20c39
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java
new file mode 100644
index 000000000..c705dc88f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java
new file mode 100644
index 000000000..3251122b2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java
new file mode 100644
index 000000000..f6bf7629c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java
new file mode 100644
index 000000000..0959ee305
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java
new file mode 100644
index 000000000..ed9523c54
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java
new file mode 100644
index 000000000..b66ec6826
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java
new file mode 100644
index 000000000..2dddb17f8
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java
new file mode 100644
index 000000000..a93d54c28
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java
new file mode 100644
index 000000000..2ccdbb0ea
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java
@@ -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 {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java
new file mode 100644
index 000000000..677a6cc6d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java
@@ -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
+}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java
new file mode 100644
index 000000000..cb01cfec8
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java
@@ -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 {
+}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java
new file mode 100644
index 000000000..fe9bb93f1
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java
@@ -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 {
+}