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

io.morethan.daggerdoc.DaggerDocProcessor Maven / Gradle / Ivy

package io.morethan.daggerdoc;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;

import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import dagger.Component;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import io.morethan.daggerdoc.Util.ProvidesParamEvaluator;
import io.morethan.daggerdoc.model.DependencyGraph;
import io.morethan.daggerdoc.model.NodeType;

/**
 * Main annotation processor that generates a {@link DependencyGraph} out of the processed sources. Implementations of
 * {@link ResultWriter} are then responsible for converting the {@link DependencyGraph} to anything meaningful!
 */
@AutoService(Processor.class)
public class DaggerDocProcessor extends AbstractProcessor {

    private static final String WRITERS_OPTION = "writers";

    private final DependencyGraph.Builder _graphBuilder = DependencyGraph.builder();
    private List _resultWriters;

    public DaggerDocProcessor() {
        // default constructor called by regular compilation
    }

    @VisibleForTesting
    public DaggerDocProcessor(ResultWriter resultWriter) {
        _resultWriters = new ArrayList<>();
        _resultWriters.add(resultWriter);
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set getSupportedAnnotationTypes() {
        Set annotations = new HashSet<>();
        annotations.add("dagger.Component");
        annotations.add("dagger.Module");
        return annotations;
    }

    @Override
    public Set getSupportedOptions() {
        Verify.verifyNotNull(_resultWriters);
        ImmutableSet.Builder supportedOptions = ImmutableSet.builder();
        supportedOptions.add(WRITERS_OPTION);
        for (ResultWriter resultWriter : _resultWriters) {
            supportedOptions.addAll(resultWriter.supportedOptions());
        }
        return supportedOptions.build();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        if (_resultWriters == null) {
            _resultWriters = loadResultWriters(processingEnv.getOptions(), processingEnv.getMessager());
        }
    }

    private static List loadResultWriters(Map options, Messager messager) {
        List resultWriters = ImmutableList.copyOf(ServiceLoader.load(ResultWriter.class, DaggerDocProcessor.class.getClassLoader()));
        Preconditions.checkState(resultWriters.size() > 0, "No instances of '%s' found!", ResultWriter.class.getName());

        String writersOption = options.get(WRITERS_OPTION);
        if (writersOption != null) {
            Set pickedWriters = ImmutableSet.copyOf(Splitter.on(',').omitEmptyStrings().split(writersOption));
            if (pickedWriters.size() > 0) {
                resultWriters = resultWriters.stream().filter(writer -> pickedWriters.contains(writer.id())).collect(Collectors.toList());
            }
        }
        messager.printMessage(Kind.NOTE, "Activated result writers (writers=" + writersOption + "):", null);
        for (ResultWriter resultWriter : resultWriters) {
            messager.printMessage(Kind.NOTE, "\t" + resultWriter.getClass().getName(), null);
        }
        return resultWriters;
    }

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {

        // Inspect components
        for (Element componentClass : roundEnv.getElementsAnnotatedWith(Component.class)) {
            _graphBuilder.addNode(componentClass, NodeType.COMPONENT, Optional.empty());
            Util.consumeAnnotationValues(componentClass, Component.class, "modules", entry -> {
                Util.consumeClassValues(entry.getValue(), typeMirror -> {
                    _graphBuilder.addDependency(componentClass, typeMirror);
                });
            });
        }

        // Inspect modules
        for (Element moduleClass : roundEnv.getElementsAnnotatedWith(Module.class)) {
            Optional moduleCategory = Optional.empty();
            ModuleDoc annotation = moduleClass.getAnnotation(ModuleDoc.class);
            if (annotation != null && !Strings.isNullOrEmpty(annotation.category())) {
                moduleCategory = Optional.of(annotation.category());
            }
            _graphBuilder.addNode(moduleClass, NodeType.MODULE, moduleCategory);
            Util.consumeAnnotationValues(moduleClass, Module.class, "includes", entry -> {
                Util.consumeClassValues(entry.getValue(), typeMirror -> {
                    _graphBuilder.addDependency(moduleClass, typeMirror);
                });
            });

            // Inspect the @Provides methods of the module
            moduleClass.getEnclosedElements().stream()
                    .filter(elem -> elem.getKind() == ElementKind.METHOD && elem.getAnnotation(Provides.class) != null)
                    .map(method -> (ExecutableElement) method)
                    .forEach(providesMethod -> {
                        // Multi-binding contributors
                        if (providesMethod.getAnnotation(IntoSet.class) != null) {
                            TypeMirror returnType = providesMethod.getReturnType();
                            _graphBuilder.addMultibindingLink(moduleClass, returnType);
                        }

                        // Multi-binding consumers
                        ProvidesParamEvaluator typeVisitor = new ProvidesParamEvaluator(processingEnv, _graphBuilder, moduleClass);
                        for (Element methodParam : providesMethod.getParameters()) {
                            methodParam.asType().accept(typeVisitor, null);
                        }
                    });
        }

        if (roundEnv.processingOver()) {
            for (ResultWriter resultWriter : _resultWriters) {
                resultWriter.write(processingEnv, _graphBuilder.build());
            }
        }
        return false;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy