diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
index 1095d4165..ce9b28842 100644
--- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
@@ -370,4 +370,55 @@ A nice example is to provide support for a custom transformation strategy.
----
include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation]
----
+====
+
+[[additional-supported-options-provider]]
+=== Additional Supported Options Provider
+
+SPI name: `org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider`
+
+MapStruct offers the ability to pass through declared compiler args (or "options") provided to the MappingProcessor
+to the individual SPIs, by implementing `AdditionalSupportedOptionsProvider` via the Service Provider Interface (SPI).
+
+.Custom Additional Supported Options Provider that declares `myorg.custom.defaultNullEnumConstant` as an option to pass through
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+include::{processor-ap-test}/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java[tag=documentation]
+----
+====
+
+The value of this option is provided by including an `arg` to the `compilerArgs` tag when defining your custom SPI
+implementation.
+
+.Example maven configuration with additional options
+====
+[source, maven, linenums]
+[subs="verbatim,attributes"]
+----
+
+
+
+ org.myorg
+ custom-spi-impl
+ ${project.version}
+
+
+
+ -Amyorg.custom.defaultNullEnumConstant=MISSING
+
+
+----
+====
+
+Your custom SPI implementations can then access this configured value via `MapStructProcessingEnvironment#getOptions()`.
+
+.Accessing your custom options
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+include::{processor-ap-test}/additionalsupportedoptions/UnknownEnumMappingStrategy.java[tag=documentation]
+----
====
\ No newline at end of file
diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
index 8c842f007..407166f6f 100644
--- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
@@ -8,6 +8,7 @@ package org.mapstruct.ap;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
@@ -33,17 +34,19 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind;
+import org.mapstruct.ap.internal.gem.MapperGem;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
+import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.option.Options;
-import org.mapstruct.ap.internal.gem.MapperGem;
-import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext;
import org.mapstruct.ap.internal.processor.ModelElementProcessor;
import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext;
import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.AnnotationProcessorContext;
import org.mapstruct.ap.internal.util.RoundContext;
+import org.mapstruct.ap.internal.util.Services;
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
import static javax.lang.model.element.ElementKind.CLASS;
@@ -113,6 +116,9 @@ public class MappingProcessor extends AbstractProcessor {
protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy";
protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy";
+ private final Set additionalSupportedOptions;
+ private final String additionalSupportedOptionsError;
+
private Options options;
private AnnotationProcessorContext annotationProcessorContext;
@@ -128,6 +134,21 @@ public class MappingProcessor extends AbstractProcessor {
*/
private Set deferredMappers = new HashSet<>();
+ public MappingProcessor() {
+ Set additionalSupportedOptions;
+ String additionalSupportedOptionsError;
+ try {
+ additionalSupportedOptions = resolveAdditionalSupportedOptions();
+ additionalSupportedOptionsError = null;
+ }
+ catch ( IllegalStateException ex ) {
+ additionalSupportedOptions = Collections.emptySet();
+ additionalSupportedOptionsError = ex.getMessage();
+ }
+ this.additionalSupportedOptions = additionalSupportedOptions;
+ this.additionalSupportedOptionsError = additionalSupportedOptionsError;
+ }
+
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init( processingEnv );
@@ -138,8 +159,13 @@ public class MappingProcessor extends AbstractProcessor {
processingEnv.getTypeUtils(),
processingEnv.getMessager(),
options.isDisableBuilders(),
- options.isVerbose()
+ options.isVerbose(),
+ resolveAdditionalOptions( processingEnv.getOptions() )
);
+
+ if ( additionalSupportedOptionsError != null ) {
+ processingEnv.getMessager().printMessage( Kind.ERROR, additionalSupportedOptionsError );
+ }
}
private Options createOptions() {
@@ -225,6 +251,17 @@ public class MappingProcessor extends AbstractProcessor {
return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
}
+ @Override
+ public Set getSupportedOptions() {
+ Set supportedOptions = super.getSupportedOptions();
+ if ( additionalSupportedOptions.isEmpty() ) {
+ return supportedOptions;
+ }
+ Set allSupportedOptions = new HashSet<>( supportedOptions );
+ allSupportedOptions.addAll( additionalSupportedOptions );
+ return allSupportedOptions;
+ }
+
/**
* Gets fresh copies of all mappers deferred from previous rounds (the originals may contain references to
* erroneous source/target type elements).
@@ -407,6 +444,35 @@ public class MappingProcessor extends AbstractProcessor {
);
}
+ /**
+ * Fetch the additional supported options provided by the SPI {@link AdditionalSupportedOptionsProvider}.
+ *
+ * @return the additional supported options
+ */
+ private static Set resolveAdditionalSupportedOptions() {
+ Set additionalSupportedOptions = null;
+ for ( AdditionalSupportedOptionsProvider optionsProvider :
+ Services.all( AdditionalSupportedOptionsProvider.class ) ) {
+ if ( additionalSupportedOptions == null ) {
+ additionalSupportedOptions = new HashSet<>();
+ }
+ Set providerOptions = optionsProvider.getAdditionalSupportedOptions();
+
+ for ( String providerOption : providerOptions ) {
+ // Ensure additional options are not in the mapstruct namespace
+ if ( providerOption.startsWith( "mapstruct" ) ) {
+ throw new IllegalStateException(
+ "Additional SPI options cannot start with \"mapstruct\". Provider " + optionsProvider +
+ " provided option " + providerOption );
+ }
+ additionalSupportedOptions.add( providerOption );
+ }
+
+ }
+
+ return additionalSupportedOptions == null ? Collections.emptySet() : additionalSupportedOptions;
+ }
+
private static class ProcessorComparator implements Comparator> {
@Override
@@ -425,4 +491,16 @@ public class MappingProcessor extends AbstractProcessor {
this.erroneousElement = erroneousElement;
}
}
+
+ /**
+ * Filters only the options belonging to the declared additional supported options.
+ *
+ * @param options all processor environment options
+ * @return filtered options
+ */
+ private Map resolveAdditionalOptions(Map options) {
+ return options.entrySet().stream()
+ .filter( entry -> additionalSupportedOptions.contains( entry.getKey() ) )
+ .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java
index 7421dfde7..81640848d 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java
@@ -55,8 +55,10 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
private boolean disableBuilder;
private boolean verbose;
+ private Map options;
+
public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder,
- boolean verbose) {
+ boolean verbose, Map options) {
astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList(
findAstModifyingAnnotationProcessors( messager ) );
this.elementUtils = elementUtils;
@@ -64,6 +66,7 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
this.messager = messager;
this.disableBuilder = disableBuilder;
this.verbose = verbose;
+ this.options = java.util.Collections.unmodifiableMap( options );
}
/**
@@ -270,4 +273,8 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
initialize();
return enumTransformationStrategies;
}
+
+ public Map getOptions() {
+ return this.options;
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java
index 2d37c46db..292512ff8 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java
@@ -18,6 +18,10 @@ public class Services {
private Services() {
}
+ public static Iterable all(Class serviceType) {
+ return ServiceLoader.load( serviceType, Services.class.getClassLoader() );
+ }
+
public static T get(Class serviceType, T defaultValue) {
Iterator services = ServiceLoader.load( serviceType, Services.class.getClassLoader() ).iterator();
diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java
new file mode 100644
index 000000000..3638f7032
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java
@@ -0,0 +1,23 @@
+/*
+ * 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.spi;
+
+import java.util.Set;
+
+/**
+ * Provider for any additional supported options required for custom SPI implementations.
+ * The resolved values are retrieved from {@link MapStructProcessingEnvironment#getOptions()}.
+ */
+public interface AdditionalSupportedOptionsProvider {
+
+ /**
+ * Returns the supported options required for custom SPI implementations.
+ *
+ * @return the additional supported options.
+ */
+ Set getAdditionalSupportedOptions();
+
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java
index 0471108a5..0cea4369e 100644
--- a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java
+++ b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java
@@ -7,6 +7,7 @@ package org.mapstruct.ap.spi;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
+import java.util.Map;
/**
* MapStruct will provide the implementations of its SPIs with on object implementing this interface so they can use
@@ -36,4 +37,12 @@ public interface MapStructProcessingEnvironment {
*/
Types getTypeUtils();
+ /**
+ * Returns the resolved options specified by the impl of
+ * {@link AdditionalSupportedOptionsProvider}.
+ *
+ * @return resolved options
+ */
+ Map getOptions();
+
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java
new file mode 100644
index 000000000..ff2c85139
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.additionalsupportedoptions;
+
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.mapstruct.ap.spi.EnumMappingStrategy;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.WithServiceImplementation;
+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.compilation.annotation.ProcessorOption;
+import org.mapstruct.ap.testutil.runner.Compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Execution( ExecutionMode.CONCURRENT )
+public class AdditionalSupportedOptionsProviderTest {
+
+ @ProcessorTest
+ @WithClasses({
+ Pet.class,
+ PetWithMissing.class,
+ UnknownEnumMappingStrategyMapper.class
+ })
+ @WithServiceImplementation(CustomAdditionalSupportedOptionsProvider.class)
+ @WithServiceImplementation(value = UnknownEnumMappingStrategy.class, provides = EnumMappingStrategy.class)
+ @ProcessorOption(name = "myorg.custom.defaultNullEnumConstant", value = "MISSING")
+ public void shouldUseConfiguredPrefix() {
+ assertThat( UnknownEnumMappingStrategyMapper.INSTANCE.map( null ) )
+ .isEqualTo( PetWithMissing.MISSING );
+ }
+
+ @ProcessorTest(Compiler.JDK) // The eclipse compiler does not parse the error message properly
+ @WithClasses({
+ EmptyMapper.class
+ })
+ @WithServiceImplementation(InvalidAdditionalSupportedOptionsProvider.class)
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ messageRegExp = "Additional SPI options cannot start with \"mapstruct\". Provider " +
+ "org.mapstruct.ap.test.additionalsupportedoptions.InvalidAdditionalSupportedOptionsProvider@.*" +
+ " provided option mapstruct.custom.test"
+ )
+ )
+ public void shouldFailWhenOptionsProviderUsesMapstructPrefix() {
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java
new file mode 100644
index 000000000..87a994bb4
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.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.additionalsupportedoptions;
+
+// tag::documentation[]
+import java.util.Collections;
+import java.util.Set;
+
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
+
+public class CustomAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider {
+
+ @Override
+ public Set getAdditionalSupportedOptions() {
+ return Collections.singleton( "myorg.custom.defaultNullEnumConstant" );
+ }
+
+}
+// end::documentation[]
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java
new file mode 100644
index 000000000..13a6f0aec
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java
@@ -0,0 +1,12 @@
+/*
+ * 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.additionalsupportedoptions;
+
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface EmptyMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java
new file mode 100644
index 000000000..1a387abba
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.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.additionalsupportedoptions;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
+
+public class InvalidAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider {
+
+ @Override
+ public Set getAdditionalSupportedOptions() {
+ return Collections.singleton( "mapstruct.custom.test" );
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java
new file mode 100644
index 000000000..8fe9b3955
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.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.additionalsupportedoptions;
+
+public enum Pet {
+
+ DOG,
+ CAT,
+ BEAR
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java
new file mode 100644
index 000000000..21d5f4a64
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.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.additionalsupportedoptions;
+
+public enum PetWithMissing {
+
+ DOG,
+ CAT,
+ BEAR,
+ MISSING
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java
new file mode 100644
index 000000000..ca4403925
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java
@@ -0,0 +1,29 @@
+/*
+ * 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.additionalsupportedoptions;
+
+// tag::documentation[]
+import javax.lang.model.element.TypeElement;
+
+import org.mapstruct.ap.spi.DefaultEnumMappingStrategy;
+import org.mapstruct.ap.spi.MapStructProcessingEnvironment;
+
+public class UnknownEnumMappingStrategy extends DefaultEnumMappingStrategy {
+
+ private String defaultNullEnumConstant;
+
+ @Override
+ public void init(MapStructProcessingEnvironment processingEnvironment) {
+ super.init( processingEnvironment );
+ defaultNullEnumConstant = processingEnvironment.getOptions().get( "myorg.custom.defaultNullEnumConstant" );
+ }
+
+ @Override
+ public String getDefaultNullEnumConstant(TypeElement enumType) {
+ return defaultNullEnumConstant;
+ }
+}
+// end::documentation[]
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java
new file mode 100644
index 000000000..be8c74e22
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.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.additionalsupportedoptions;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface UnknownEnumMappingStrategyMapper {
+
+ UnknownEnumMappingStrategyMapper INSTANCE = Mappers.getMapper( UnknownEnumMappingStrategyMapper.class );
+
+ PetWithMissing map(Pet pet);
+}