/* * Copyright (C) 2017 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This must be in the dagger.internal.codegen package since Dagger doesn't expose its APIs publicly // https://github.com/google/dagger/issues/773 could present an opportunity to put this somewhere in // the regular kythe/java tree. package dagger.internal.codegen.kythe; import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; import com.google.auto.service.AutoService; import com.google.common.collect.Iterables; import com.google.devtools.kythe.analyzers.base.EntrySet; import com.google.devtools.kythe.analyzers.base.FactEmitter; import com.google.devtools.kythe.analyzers.base.KytheEntrySets; import com.google.devtools.kythe.analyzers.java.Plugin; import com.google.devtools.kythe.proto.Storage.VName; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; import dagger.BindsInstance; import dagger.Component; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingDeclaration; import dagger.internal.codegen.binding.BindingGraphFactory; import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.ComponentDescriptorFactory; import dagger.internal.codegen.binding.ModuleDescriptor; import dagger.internal.codegen.javac.JavacPluginModule; import dagger.internal.codegen.validation.InjectBindingRegistryModule; import dagger.model.BindingGraph; import dagger.model.BindingGraph.DependencyEdge; import dagger.model.BindingGraph.Edge; import dagger.model.BindingGraph.Node; import dagger.model.DependencyRequest; import dagger.producers.ProductionComponent; import java.util.Optional; import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Singleton; import javax.lang.model.element.Element; /** * A plugin which emits nodes and edges for Dagger * specific code. */ @AutoService(Plugin.class) public class DaggerKythePlugin extends Plugin.Scanner { // TODO(ronshapiro): use flogger private static final Logger logger = Logger.getLogger(DaggerKythePlugin.class.getCanonicalName()); private FactEmitter emitter; @Inject ComponentDescriptorFactory componentDescriptorFactory; @Inject BindingGraphFactory bindingGraphFactory; @Override public Void visitClassDef(JCClassDecl tree, Void p) { if (tree.sym != null && isAnyAnnotationPresent(tree.sym, Component.class, ProductionComponent.class)) { addNodesForGraph( bindingGraphFactory.create( componentDescriptorFactory.rootComponentDescriptor(tree.sym), false)); } return super.visitClassDef(tree, p); } private void addNodesForGraph(dagger.internal.codegen.binding.BindingGraph graph) { addDependencyEdges(graph.topLevelBindingGraph()); // TODO(bcorso): Convert these to use the new BindingGraph addModuleEdges(graph); addChildComponentEdges(graph); } private void addDependencyEdges(BindingGraph graph) { for (DependencyEdge dependencyEdge : graph.dependencyEdges()) { DependencyRequest dependency = dependencyEdge.dependencyRequest(); Node node = graph.network().incidentNodes(dependencyEdge).target(); addEdgesForDependencyRequest(dependency, (BindingNode) node, graph); } } /** * Add {@code /inject/satisfiedby} edges from {@code dependency}'s {@link * DependencyRequest#requestElement()} to any {@link BindingDeclaration#bindingElement() binding * elements} that satisfy the request. * *

This collapses requests for synthetic bindings so that a request for a multibound key * points to all of the contributions for the multibound object. It does so by recursively calling * this method, with each dependency's key as the {@code targetKey}. */ private void addEdgesForDependencyRequest( DependencyRequest dependency, BindingNode bindingNode, BindingGraph graph) { if (!dependency.requestElement().isPresent()) { return; } Binding binding = bindingNode.delegate(); if (binding.bindingElement().isPresent()) { addDependencyEdge(dependency, binding); } else { for (Edge outEdge : graph.network().outEdges(bindingNode)) { if (outEdge instanceof DependencyEdge) { Node outNode = graph.network().incidentNodes(outEdge).target(); addEdgesForDependencyRequest(dependency, (BindingNode) outNode, graph); } } } for (BindingDeclaration bindingDeclaration : Iterables.concat( bindingNode.multibindingDeclarations(), bindingNode.optionalBindingDeclarations())) { addDependencyEdge(dependency, bindingDeclaration); } } private void addDependencyEdge( DependencyRequest dependency, BindingDeclaration bindingDeclaration) { Element requestElement = dependency.requestElement().get(); Element bindingElement = bindingDeclaration.bindingElement().get(); Optional requestElementNode = jvmNode(requestElement, "request element"); Optional bindingElementNode = jvmNode(bindingElement, "binding element"); emitEdge(requestElementNode, "/inject/satisfiedby", bindingElementNode); // TODO(ronshapiro): emit facts about the component that satisfies the edge } private void addModuleEdges(dagger.internal.codegen.binding.BindingGraph graph) { Optional componentNode = jvmNode(graph.componentTypeElement(), "component"); for (ModuleDescriptor module : graph.componentDescriptor().modules()) { Optional moduleNode = jvmNode(module.moduleElement(), "module"); emitEdge(componentNode, "/inject/installsmodule", moduleNode); } graph.subgraphs().forEach(this::addModuleEdges); } private void addChildComponentEdges(dagger.internal.codegen.binding.BindingGraph graph) { Optional componentNode = jvmNode(graph.componentTypeElement(), "component"); for (dagger.internal.codegen.binding.BindingGraph subgraph : graph.subgraphs()) { Optional subcomponentNode = jvmNode(subgraph.componentTypeElement(), "child component"); emitEdge(componentNode, "/inject/childcomponent", subcomponentNode); } graph.subgraphs().forEach(this::addChildComponentEdges); } private Optional jvmNode(Element element, String name) { Optional jvmNode = kytheGraph.getJvmNode((Symbol) element).map(KytheNode::getVName); if (!jvmNode.isPresent()) { logger.warning(String.format("Missing JVM node for %s: %s", name, element)); } return jvmNode; } private void emitEdge(Optional source, String edgeName, Optional target) { source.ifPresent( s -> target.ifPresent(t -> new EntrySet.Builder(s, edgeName, t).build().emit(emitter))); } @Override public void run( JCCompilationUnit compilationUnit, KytheEntrySets entrySets, KytheGraph kytheGraph) { if (bindingGraphFactory == null) { emitter = entrySets.getEmitter(); DaggerDaggerKythePlugin_PluginComponent.builder() .context(kytheGraph.getJavaContext()) .build() .inject(this); } super.run(compilationUnit, entrySets, kytheGraph); } @Singleton @Component(modules = {InjectBindingRegistryModule.class, JavacPluginModule.class}) interface PluginComponent { void inject(DaggerKythePlugin plugin); @Component.Builder interface Builder { @BindsInstance Builder context(Context context); PluginComponent build(); } } }