blob: f3d3849c0aac376ea06f9e4ee54401d2cb2baea7 [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;
import static com.google.devtools.kythe.analyzers.base.EdgeKind.DEFINES_BINDING;
import static com.google.devtools.kythe.analyzers.base.EdgeKind.REF;
import static dagger.internal.codegen.KytheFormatting.formatAnnotation;
import static dagger.internal.codegen.KytheFormatting.formatKey;
import com.google.auto.common.MoreElements;
import com.google.auto.service.AutoService;
import com.google.devtools.kythe.analyzers.base.CorpusPath;
import com.google.devtools.kythe.analyzers.base.EdgeKind;
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.google.devtools.kythe.util.Span;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.model.JavacTypes;
import com.sun.tools.javac.tree.JCTree;
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.model.DependencyRequest;
import dagger.model.Key;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
/**
* 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> {
private JavacTrees trees;
private FactEmitter emitter;
private JCCompilationUnit compilationUnit;
private VName fileVName;
private KytheBindingGraphFactory bindingGraphFactory;
private CorpusPath corpusPath;
private KeyVNameFactory keys;
@Override
public Void visitClassDef(JCClassDecl tree, Void p) {
TypeElement type = MoreElements.asType(trees.getElement(trees.getPath(compilationUnit, tree)));
bindingGraphFactory.create(type).ifPresent(this::addNodesForGraph);
return super.visitClassDef(tree, p);
}
private void addNodesForGraph(BindingGraph graph) {
for (ResolvedBindings resolvedBindings : graph.resolvedBindings()) {
for (Binding binding : resolvedBindings.bindings()) {
addBindingDeclarationNode(binding);
binding.dependencies().forEach(this::addDependencyNode);
}
resolvedBindings.multibindingDeclarations().forEach(this::addBindingDeclarationNode);
resolvedBindings.subcomponentDeclarations().forEach(this::addBindingDeclarationNode);
resolvedBindings
.optionalBindingDeclarations()
.forEach(declaration -> addBindingDeclarationNode(declaration, resolvedBindings.key()));
}
for (ComponentDescriptor.ComponentMethodDescriptor componentMethod :
graph.componentDescriptor().componentMethods()) {
componentMethod.dependencyRequest().ifPresent(this::addDependencyNode);
}
}
private void addBindingDeclarationNode(BindingDeclaration declaration) {
addBindingDeclarationNode(declaration, declaration.key());
}
/**
* Adds a {@code defines/binding} edge between {@code declaration}'s {@link
* BindingDeclaration#bindingElement()} and the node for {@code key}.
*
* <p>{@link BindingDeclaration#key()} is not used directly, since {@link
* OptionalBindingDeclaration}s' keys are the unwrapped {@code Optional} types and can apply to
* either {@code java.util.Optional} or {@code com.google.common.base.Optional}.
*/
private void addBindingDeclarationNode(BindingDeclaration declaration, Key key) {
if (!declaration.bindingElement().isPresent()) {
return;
}
EntrySet bindingAnchor =
entrySets.newAnchorAndEmit(fileVName, bindingElementSpan(declaration), null);
entrySets.emitEdge(bindingAnchor, DEFINES_BINDING, keyNode(key));
}
private Span bindingElementSpan(BindingDeclaration declaration) {
Element bindingElement = declaration.bindingElement().get();
Name name =
bindingElement.getKind().equals(ElementKind.METHOD)
? bindingElement.getSimpleName()
: declaration.bindingTypeElement().get().getSimpleName();
return span(name, trees.getTree(bindingElement));
}
/**
* Adds a {@code ref} edge between {@code dependencyRequest} and it's {@link
* DependencyRequest#key() key's} node.
*/
private void addDependencyNode(DependencyRequest dependencyRequest) {
if (!dependencyRequest.requestElement().isPresent()) {
return;
}
EntrySet dependencyRequestAnchor =
entrySets.newAnchorAndEmit(fileVName, dependencyRequestSpan(dependencyRequest), null);
entrySets.emitEdge(dependencyRequestAnchor, REF, keyNode(dependencyRequest.key()));
}
private Span dependencyRequestSpan(DependencyRequest dependency) {
Element requestElement = dependency.requestElement().get();
return span(requestElement.getSimpleName(), trees.getTree(requestElement));
}
private Span span(Name name, JCTree tree) {
return kytheGraph.findIdentifier(name, tree.getPreferredPosition()).get();
}
private EntrySet keyNode(Key key) {
EntrySet keyNode = newNode("key", formatKey(key));
entrySets.emitEdge(keyNode.getVName(), EdgeKind.PARAM, keys.vname(key), 0);
key.qualifier()
.ifPresent(
qualifier -> {
entrySets.emitEdge(
keyNode.getVName(), EdgeKind.PARAM, qualifierNode(qualifier).getVName(), 1);
});
return keyNode;
}
private EntrySet qualifierNode(AnnotationMirror qualifier) {
return newNode("qualifier", formatAnnotation(qualifier));
}
private EntrySet newNode(String nodeKind, String format) {
EntrySet node = entrySets
.newNode("inject/" + nodeKind)
.setCorpusPath(corpusPath)
.setSignature(String.format("inject_%s:%s", nodeKind, format))
.build();
node.emit(emitter);
return node;
}
@Override
public void run(
JCCompilationUnit compilationUnit, KytheEntrySets entrySets, KytheGraph kytheGraph) {
if (bindingGraphFactory == null) {
Context javaContext = kytheGraph.getJavaContext();
trees = JavacTrees.instance(javaContext);
emitter = entrySets.getEmitter();
bindingGraphFactory =
new KytheBindingGraphFactory(
JavacTypes.instance(javaContext), JavacElements.instance(javaContext));
keys = new KeyVNameFactory(kytheGraph, entrySets, emitter);
}
this.compilationUnit = compilationUnit;
fileVName = kytheGraph.getNode(compilationUnit).get().getVName();
corpusPath = new CorpusPath(fileVName.getCorpus(), "", "");
super.run(compilationUnit, entrySets, kytheGraph);
}
}