1 /* 2 * Copyright (C) 2017 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // This must be in the dagger.internal.codegen package since Dagger doesn't expose its APIs publicly 18 // https://github.com/google/dagger/issues/773 could present an opportunity to put this somewhere in 19 // the regular kythe/java tree. 20 package dagger.internal.codegen.kythe; 21 22 import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; 23 24 import com.google.auto.service.AutoService; 25 import com.google.common.collect.Iterables; 26 import com.google.devtools.kythe.analyzers.base.EntrySet; 27 import com.google.devtools.kythe.analyzers.base.FactEmitter; 28 import com.google.devtools.kythe.analyzers.base.KytheEntrySets; 29 import com.google.devtools.kythe.analyzers.java.Plugin; 30 import com.google.devtools.kythe.proto.Storage.VName; 31 import com.sun.tools.javac.code.Symbol; 32 import com.sun.tools.javac.tree.JCTree.JCClassDecl; 33 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 34 import com.sun.tools.javac.util.Context; 35 import dagger.BindsInstance; 36 import dagger.Component; 37 import dagger.internal.codegen.binding.Binding; 38 import dagger.internal.codegen.binding.BindingDeclaration; 39 import dagger.internal.codegen.binding.BindingGraphFactory; 40 import dagger.internal.codegen.binding.BindingNode; 41 import dagger.internal.codegen.binding.ComponentDescriptorFactory; 42 import dagger.internal.codegen.binding.ModuleDescriptor; 43 import dagger.internal.codegen.javac.JavacPluginModule; 44 import dagger.internal.codegen.validation.InjectBindingRegistryModule; 45 import dagger.model.BindingGraph; 46 import dagger.model.BindingGraph.DependencyEdge; 47 import dagger.model.BindingGraph.Edge; 48 import dagger.model.BindingGraph.Node; 49 import dagger.model.DependencyRequest; 50 import dagger.producers.ProductionComponent; 51 import java.util.Optional; 52 import java.util.logging.Logger; 53 import javax.inject.Inject; 54 import javax.inject.Singleton; 55 import javax.lang.model.element.Element; 56 57 /** 58 * A plugin which emits nodes and edges for <a href="https://github.com/google/dagger">Dagger</a> 59 * specific code. 60 */ 61 @AutoService(Plugin.class) 62 public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { 63 // TODO(ronshapiro): use flogger 64 private static final Logger logger = Logger.getLogger(DaggerKythePlugin.class.getCanonicalName()); 65 private FactEmitter emitter; 66 @Inject ComponentDescriptorFactory componentDescriptorFactory; 67 @Inject BindingGraphFactory bindingGraphFactory; 68 69 @Override visitClassDef(JCClassDecl tree, Void p)70 public Void visitClassDef(JCClassDecl tree, Void p) { 71 if (tree.sym != null 72 && isAnyAnnotationPresent(tree.sym, Component.class, ProductionComponent.class)) { 73 addNodesForGraph( 74 bindingGraphFactory.create( 75 componentDescriptorFactory.rootComponentDescriptor(tree.sym), false)); 76 } 77 return super.visitClassDef(tree, p); 78 } 79 addNodesForGraph(dagger.internal.codegen.binding.BindingGraph graph)80 private void addNodesForGraph(dagger.internal.codegen.binding.BindingGraph graph) { 81 addDependencyEdges(graph.topLevelBindingGraph()); 82 83 // TODO(bcorso): Convert these to use the new BindingGraph 84 addModuleEdges(graph); 85 addChildComponentEdges(graph); 86 } 87 addDependencyEdges(BindingGraph graph)88 private void addDependencyEdges(BindingGraph graph) { 89 for (DependencyEdge dependencyEdge : graph.dependencyEdges()) { 90 DependencyRequest dependency = dependencyEdge.dependencyRequest(); 91 Node node = graph.network().incidentNodes(dependencyEdge).target(); 92 addEdgesForDependencyRequest(dependency, (BindingNode) node, graph); 93 } 94 } 95 96 /** 97 * Add {@code /inject/satisfiedby} edges from {@code dependency}'s {@link 98 * DependencyRequest#requestElement()} to any {@link BindingDeclaration#bindingElement() binding 99 * elements} that satisfy the request. 100 * 101 * <p>This collapses requests for synthetic bindings so that a request for a multibound key 102 * points to all of the contributions for the multibound object. It does so by recursively calling 103 * this method, with each dependency's key as the {@code targetKey}. 104 */ addEdgesForDependencyRequest( DependencyRequest dependency, BindingNode bindingNode, BindingGraph graph)105 private void addEdgesForDependencyRequest( 106 DependencyRequest dependency, BindingNode bindingNode, BindingGraph graph) { 107 if (!dependency.requestElement().isPresent()) { 108 return; 109 } 110 Binding binding = bindingNode.delegate(); 111 if (binding.bindingElement().isPresent()) { 112 addDependencyEdge(dependency, binding); 113 } else { 114 for (Edge outEdge : graph.network().outEdges(bindingNode)) { 115 if (outEdge instanceof DependencyEdge) { 116 Node outNode = graph.network().incidentNodes(outEdge).target(); 117 addEdgesForDependencyRequest(dependency, (BindingNode) outNode, graph); 118 } 119 } 120 } 121 for (BindingDeclaration bindingDeclaration : 122 Iterables.concat( 123 bindingNode.multibindingDeclarations(), 124 bindingNode.optionalBindingDeclarations())) { 125 addDependencyEdge(dependency, bindingDeclaration); 126 } 127 } 128 addDependencyEdge( DependencyRequest dependency, BindingDeclaration bindingDeclaration)129 private void addDependencyEdge( 130 DependencyRequest dependency, BindingDeclaration bindingDeclaration) { 131 Element requestElement = dependency.requestElement().get(); 132 Element bindingElement = bindingDeclaration.bindingElement().get(); 133 Optional<VName> requestElementNode = jvmNode(requestElement, "request element"); 134 Optional<VName> bindingElementNode = jvmNode(bindingElement, "binding element"); 135 emitEdge(requestElementNode, "/inject/satisfiedby", bindingElementNode); 136 // TODO(ronshapiro): emit facts about the component that satisfies the edge 137 } 138 addModuleEdges(dagger.internal.codegen.binding.BindingGraph graph)139 private void addModuleEdges(dagger.internal.codegen.binding.BindingGraph graph) { 140 Optional<VName> componentNode = jvmNode(graph.componentTypeElement(), "component"); 141 for (ModuleDescriptor module : graph.componentDescriptor().modules()) { 142 Optional<VName> moduleNode = jvmNode(module.moduleElement(), "module"); 143 emitEdge(componentNode, "/inject/installsmodule", moduleNode); 144 } 145 graph.subgraphs().forEach(this::addModuleEdges); 146 } 147 addChildComponentEdges(dagger.internal.codegen.binding.BindingGraph graph)148 private void addChildComponentEdges(dagger.internal.codegen.binding.BindingGraph graph) { 149 Optional<VName> componentNode = jvmNode(graph.componentTypeElement(), "component"); 150 for (dagger.internal.codegen.binding.BindingGraph subgraph : graph.subgraphs()) { 151 Optional<VName> subcomponentNode = 152 jvmNode(subgraph.componentTypeElement(), "child component"); 153 emitEdge(componentNode, "/inject/childcomponent", subcomponentNode); 154 } 155 graph.subgraphs().forEach(this::addChildComponentEdges); 156 } 157 jvmNode(Element element, String name)158 private Optional<VName> jvmNode(Element element, String name) { 159 Optional<VName> jvmNode = kytheGraph.getJvmNode((Symbol) element).map(KytheNode::getVName); 160 if (!jvmNode.isPresent()) { 161 logger.warning(String.format("Missing JVM node for %s: %s", name, element)); 162 } 163 return jvmNode; 164 } 165 emitEdge(Optional<VName> source, String edgeName, Optional<VName> target)166 private void emitEdge(Optional<VName> source, String edgeName, Optional<VName> target) { 167 source.ifPresent( 168 s -> target.ifPresent(t -> new EntrySet.Builder(s, edgeName, t).build().emit(emitter))); 169 } 170 171 @Override run( JCCompilationUnit compilationUnit, KytheEntrySets entrySets, KytheGraph kytheGraph)172 public void run( 173 JCCompilationUnit compilationUnit, KytheEntrySets entrySets, KytheGraph kytheGraph) { 174 if (bindingGraphFactory == null) { 175 emitter = entrySets.getEmitter(); 176 DaggerDaggerKythePlugin_PluginComponent.builder() 177 .context(kytheGraph.getJavaContext()) 178 .build() 179 .inject(this); 180 } 181 super.run(compilationUnit, entrySets, kytheGraph); 182 } 183 184 @Singleton 185 @Component(modules = {InjectBindingRegistryModule.class, JavacPluginModule.class}) 186 interface PluginComponent { inject(DaggerKythePlugin plugin)187 void inject(DaggerKythePlugin plugin); 188 189 @Component.Builder 190 interface Builder { 191 @BindsInstance context(Context context)192 Builder context(Context context); 193 build()194 PluginComponent build(); 195 } 196 } 197 } 198