All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mapstruct.ap.MappingProcessor Maven / Gradle / Ivy

/*
 * 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;

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;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind;

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.spi.TypeHierarchyErroneousException;

import static javax.lang.model.element.ElementKind.CLASS;

/**
 * A JSR 269 annotation {@link Processor} which generates the implementations for mapper interfaces (interfaces
 * annotated with {@code @Mapper}).
 * 

* Implementation notes: *

* The generation happens by incrementally building up a model representation of each mapper to be generated (a * {@link Mapper} object), which is then written into the resulting Java source file. *

* The model instantiation and processing happens in several phases/passes by applying a sequence of * {@link ModelElementProcessor}s. The processors to apply are retrieved using the Java service loader mechanism and are * processed in order of their {@link ModelElementProcessor#getPriority() priority}. The general processing flow is * this: *

    *
  • retrieve mapping methods
  • *
  • create the {@code Mapper} model
  • *
  • perform enrichments and modifications (e.g. add annotations for dependency injection)
  • *
  • if no error occurred, write out the model into Java source files
  • *
*

* For reading annotation attributes, gems as generated with help of Gem Tools. These gems allow comfortable access to annotations and * their attributes without depending on their class objects. *

* The creation of Java source files is done using the FreeMarker template engine. * Each node of the mapper model has a corresponding FreeMarker template file which provides the Java representation of * that element and can include sub-elements via a custom FreeMarker directive. That way writing out a root node of the * model ({@code Mapper}) will recursively include all contained sub-elements (such as its methods, their property * mappings etc.). * * @author Gunnar Morling */ @SupportedAnnotationTypes("org.mapstruct.Mapper") @SupportedOptions({ MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, MappingProcessor.UNMAPPED_TARGET_POLICY, MappingProcessor.UNMAPPED_SOURCE_POLICY, MappingProcessor.DEFAULT_COMPONENT_MODEL, MappingProcessor.DEFAULT_INJECTION_STRATEGY, MappingProcessor.DISABLE_BUILDERS, MappingProcessor.VERBOSE }) public class MappingProcessor extends AbstractProcessor { /** * Whether this processor claims all processed annotations exclusively or not. */ private static final boolean ANNOTATIONS_CLAIMED_EXCLUSIVELY = false; protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp"; protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = "mapstruct.suppressGeneratorVersionInfoComment"; protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; protected static final String UNMAPPED_SOURCE_POLICY = "mapstruct.unmappedSourcePolicy"; protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy"; protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; protected static final String VERBOSE = "mapstruct.verbose"; private Options options; private AnnotationProcessorContext annotationProcessorContext; /** * Any mappers for which an implementation cannot be generated in the current round because they have source/target * types with incomplete hierarchies (as super-types are to be generated by other processors). They will be * processed in subsequent rounds. *

* If the hierarchy of a mapper's source/target types is never completed (i.e. the missing super-types are not * generated by other processors), this mapper will not be generated; That's fine, the compiler will raise an error * due to the inconsistent Java types used as source or target anyways. */ private Set deferredMappers = new HashSet<>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init( processingEnv ); options = createOptions(); annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), processingEnv.getMessager(), options.isDisableBuilders(), options.isVerbose() ); } private Options createOptions() { String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); return new Options( Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, unmappedSourcePolicy != null ? ReportingPolicyGem.valueOf( unmappedSourcePolicy.toUpperCase() ) : null, processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), Boolean.valueOf( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) ) ); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(final Set annotations, final RoundEnvironment roundEnvironment) { // nothing to do in the last round if ( !roundEnvironment.processingOver() ) { RoundContext roundContext = new RoundContext( annotationProcessorContext ); // process any mappers left over from previous rounds Set deferredMappers = getAndResetDeferredMappers(); processMapperElements( deferredMappers, roundContext ); // get and process any mappers from this round Set mappers = getMappers( annotations, roundEnvironment ); processMapperElements( mappers, roundContext ); } else if ( !deferredMappers.isEmpty() ) { // If the processing is over and there are deferred mappers it means something wrong occurred and // MapStruct didn't generate implementations for those for ( DeferredMapper deferredMapper : deferredMappers ) { TypeElement deferredMapperElement = deferredMapper.deferredMapperElement; Element erroneousElement = deferredMapper.erroneousElement; String erroneousElementName; if ( erroneousElement instanceof QualifiedNameable ) { erroneousElementName = ( (QualifiedNameable) erroneousElement ).getQualifiedName().toString(); } else { erroneousElementName = erroneousElement != null ? erroneousElement.getSimpleName().toString() : null; } // When running on Java 8 we need to fetch the deferredMapperElement again. // Otherwise the reporting will not work properly deferredMapperElement = annotationProcessorContext.getElementUtils() .getTypeElement( deferredMapperElement.getQualifiedName() ); processingEnv.getMessager() .printMessage( Kind.ERROR, "No implementation was created for " + deferredMapperElement.getSimpleName() + " due to having a problem in the erroneous element " + erroneousElementName + "." + " Hint: this often means that some other annotation processor was supposed to" + " process the erroneous element. You can also enable MapStruct verbose mode by setting" + " -Amapstruct.verbose=true as a compilation argument.", deferredMapperElement ); } } return ANNOTATIONS_CLAIMED_EXCLUSIVELY; } /** * Gets fresh copies of all mappers deferred from previous rounds (the originals may contain references to * erroneous source/target type elements). */ private Set getAndResetDeferredMappers() { Set deferred = new HashSet<>( deferredMappers.size() ); for ( DeferredMapper deferredMapper : deferredMappers ) { TypeElement element = deferredMapper.deferredMapperElement; deferred.add( processingEnv.getElementUtils().getTypeElement( element.getQualifiedName() ) ); } deferredMappers.clear(); return deferred; } private Set getMappers(final Set annotations, final RoundEnvironment roundEnvironment) { Set mapperTypes = new HashSet<>(); for ( TypeElement annotation : annotations ) { //Indicates that the annotation's type isn't on the class path of the compiled //project. Let the compiler deal with that and print an appropriate error. if ( annotation.getKind() != ElementKind.ANNOTATION_TYPE ) { continue; } try { Set annotatedMappers = roundEnvironment.getElementsAnnotatedWith( annotation ); for (Element mapperElement : annotatedMappers) { TypeElement mapperTypeElement = asTypeElement( mapperElement ); // on some JDKs, RoundEnvironment.getElementsAnnotatedWith( ... ) returns types with // annotations unknown to the compiler, even though they are not declared Mappers if ( mapperTypeElement != null && MapperGem.instanceOn( mapperTypeElement ) != null ) { mapperTypes.add( mapperTypeElement ); } } } catch ( Throwable t ) { // whenever that may happen, but just to stay on the save side handleUncaughtError( annotation, t ); continue; } } return mapperTypes; } private void processMapperElements(Set mapperElements, RoundContext roundContext) { for ( TypeElement mapperElement : mapperElements ) { try { // create a new context for each generated mapper in order to have imports of referenced types // correctly managed; // note that this assumes that a new source file is created for each mapper which must not // necessarily be the case, e.g. in case of several mapper interfaces declared as inner types // of one outer interface List tst = mapperElement.getEnclosedElements(); ProcessorContext context = new DefaultModelElementProcessorContext( processingEnv, options, roundContext, getDeclaredTypesNotToBeImported( mapperElement ), mapperElement ); processMapperTypeElement( context, mapperElement ); } catch ( TypeHierarchyErroneousException thie ) { TypeMirror erroneousType = thie.getType(); Element erroneousElement = erroneousType != null ? roundContext.getAnnotationProcessorContext() .getTypeUtils() .asElement( erroneousType ) : null; if ( options.isVerbose() ) { processingEnv.getMessager().printMessage( Kind.NOTE, "MapStruct: referred types not available (yet), deferring mapper: " + mapperElement ); } deferredMappers.add( new DeferredMapper( mapperElement, erroneousElement ) ); } catch ( Throwable t ) { handleUncaughtError( mapperElement, t ); break; } } } private Map getDeclaredTypesNotToBeImported(TypeElement element) { return element.getEnclosedElements().stream() .filter( e -> CLASS.equals( e.getKind() ) ) .map( Element::getSimpleName ) .map( Name::toString ) .collect( Collectors.toMap( k -> k, v -> element.getQualifiedName().toString() + "." + v ) ); } private void handleUncaughtError(Element element, Throwable thrown) { StringWriter sw = new StringWriter(); thrown.printStackTrace( new PrintWriter( sw ) ); String reportableStacktrace = sw.toString().replace( System.lineSeparator( ), " " ); processingEnv.getMessager().printMessage( Kind.ERROR, "Internal error in the mapping processor: " + reportableStacktrace, element ); } /** * Applies all registered {@link ModelElementProcessor}s to the given mapper * type. * * @param context The processor context. * @param mapperTypeElement The mapper type element. */ private void processMapperTypeElement(ProcessorContext context, TypeElement mapperTypeElement) { Object model = null; for ( ModelElementProcessor processor : getProcessors() ) { try { model = process( context, processor, mapperTypeElement, model ); } catch ( AnnotationProcessingException e ) { processingEnv.getMessager() .printMessage( Kind.ERROR, e.getMessage(), e.getElement(), e.getAnnotationMirror(), e.getAnnotationValue() ); break; } } } private R process(ProcessorContext context, ModelElementProcessor processor, TypeElement mapperTypeElement, Object modelElement) { @SuppressWarnings("unchecked") P sourceElement = (P) modelElement; return processor.process( context, mapperTypeElement, sourceElement ); } /** * Retrieves all model element processors, ordered by their priority value * (with the method retrieval processor having the lowest priority value (1) * and the code generation processor the highest priority value. * * @return A list with all model element processors. */ private Iterable> getProcessors() { // TODO Re-consider which class loader to use in case processors are // loaded from other modules, too @SuppressWarnings("rawtypes") Iterator processorIterator = ServiceLoader.load( ModelElementProcessor.class, MappingProcessor.class.getClassLoader() ) .iterator(); List> processors = new ArrayList<>(); while ( processorIterator.hasNext() ) { processors.add( processorIterator.next() ); } Collections.sort( processors, new ProcessorComparator() ); return processors; } private TypeElement asTypeElement(Element element) { return element.accept( new ElementKindVisitor6() { @Override public TypeElement visitTypeAsInterface(TypeElement e, Void p) { return e; } @Override public TypeElement visitTypeAsClass(TypeElement e, Void p) { return e; } }, null ); } private static class ProcessorComparator implements Comparator> { @Override public int compare(ModelElementProcessor o1, ModelElementProcessor o2) { return Integer.compare( o1.getPriority(), o2.getPriority() ); } } private static class DeferredMapper { private final TypeElement deferredMapperElement; private final Element erroneousElement; private DeferredMapper(TypeElement deferredMapperElement, Element erroneousElement) { this.deferredMapperElement = deferredMapperElement; this.erroneousElement = erroneousElement; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy