mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#3071 Support defining custom processor options by custom SPI
This commit is contained in:
parent
2f78d3f4e2
commit
931591a385
@ -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"]
|
||||
----
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.myorg</groupId>
|
||||
<artifactId>custom-spi-impl</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>-Amyorg.custom.defaultNullEnumConstant=MISSING</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
----
|
||||
====
|
||||
|
||||
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]
|
||||
----
|
||||
====
|
@ -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<String> additionalSupportedOptions;
|
||||
private final String additionalSupportedOptionsError;
|
||||
|
||||
private Options options;
|
||||
|
||||
private AnnotationProcessorContext annotationProcessorContext;
|
||||
@ -128,6 +134,21 @@ public class MappingProcessor extends AbstractProcessor {
|
||||
*/
|
||||
private Set<DeferredMapper> deferredMappers = new HashSet<>();
|
||||
|
||||
public MappingProcessor() {
|
||||
Set<String> 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<String> getSupportedOptions() {
|
||||
Set<String> supportedOptions = super.getSupportedOptions();
|
||||
if ( additionalSupportedOptions.isEmpty() ) {
|
||||
return supportedOptions;
|
||||
}
|
||||
Set<String> 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<String> resolveAdditionalSupportedOptions() {
|
||||
Set<String> additionalSupportedOptions = null;
|
||||
for ( AdditionalSupportedOptionsProvider optionsProvider :
|
||||
Services.all( AdditionalSupportedOptionsProvider.class ) ) {
|
||||
if ( additionalSupportedOptions == null ) {
|
||||
additionalSupportedOptions = new HashSet<>();
|
||||
}
|
||||
Set<String> 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<ModelElementProcessor<?, ?>> {
|
||||
|
||||
@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<String, String> resolveAdditionalOptions(Map<String, String> options) {
|
||||
return options.entrySet().stream()
|
||||
.filter( entry -> additionalSupportedOptions.contains( entry.getKey() ) )
|
||||
.collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +55,10 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
|
||||
private boolean disableBuilder;
|
||||
private boolean verbose;
|
||||
|
||||
private Map<String, String> options;
|
||||
|
||||
public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder,
|
||||
boolean verbose) {
|
||||
boolean verbose, Map<String, String> 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<String, String> getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ public class Services {
|
||||
private Services() {
|
||||
}
|
||||
|
||||
public static <T> Iterable<T> all(Class<T> serviceType) {
|
||||
return ServiceLoader.load( serviceType, Services.class.getClassLoader() );
|
||||
}
|
||||
|
||||
public static <T> T get(Class<T> serviceType, T defaultValue) {
|
||||
|
||||
Iterator<T> services = ServiceLoader.load( serviceType, Services.class.getClassLoader() ).iterator();
|
||||
|
@ -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<String> getAdditionalSupportedOptions();
|
||||
|
||||
}
|
@ -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<String, String> getOptions();
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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<String> getAdditionalSupportedOptions() {
|
||||
return Collections.singleton( "myorg.custom.defaultNullEnumConstant" );
|
||||
}
|
||||
|
||||
}
|
||||
// end::documentation[]
|
@ -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 {
|
||||
}
|
@ -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<String> getAdditionalSupportedOptions() {
|
||||
return Collections.singleton( "mapstruct.custom.test" );
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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[]
|
@ -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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user