198 lines
8.0 KiB
Java
198 lines
8.0 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|
|
}
|