blob: 4c6f85e2159ebcab1a91489093c957e5565f8701 [file] [log] [blame]
/*
* 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 <a href="https://github.com/google/dagger">Dagger</a>
* specific code.
*/
@AutoService(Plugin.class)
public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> {
// 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.
*
* <p>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<VName> requestElementNode = jvmNode(requestElement, "request element");
Optional<VName> 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<VName> componentNode = jvmNode(graph.componentTypeElement(), "component");
for (ModuleDescriptor module : graph.componentDescriptor().modules()) {
Optional<VName> 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<VName> componentNode = jvmNode(graph.componentTypeElement(), "component");
for (dagger.internal.codegen.binding.BindingGraph subgraph : graph.subgraphs()) {
Optional<VName> subcomponentNode =
jvmNode(subgraph.componentTypeElement(), "child component");
emitEdge(componentNode, "/inject/childcomponent", subcomponentNode);
}
graph.subgraphs().forEach(this::addChildComponentEdges);
}
private Optional<VName> jvmNode(Element element, String name) {
Optional<VName> 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<VName> source, String edgeName, Optional<VName> 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();
}
}
}