blob: d479cfd03ce633ca3b768c8edca3160e0b5ce424 [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.auto.common.MoreElements.isAnnotationPresent;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.getOnlyElement;
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.ConfigurationAnnotations.getComponentAnnotation;
import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies;
import static dagger.internal.codegen.ConfigurationAnnotations.getModuleAnnotation;
import static dagger.internal.codegen.DaggerTypes.hasTypeVariable;
import static dagger.internal.codegen.InjectionAnnotations.getQualifier;
import static dagger.internal.codegen.KytheFormatting.formatAnnotation;
import static dagger.internal.codegen.KytheFormatting.formatKey;
import static javax.lang.model.element.ElementKind.CONSTRUCTOR;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
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.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symbol;
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.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.util.Context;
import dagger.Binds;
import dagger.BindsInstance;
import dagger.BindsOptionalOf;
import dagger.Component;
import dagger.MembersInjector;
import dagger.Provides;
import dagger.internal.codegen.MembersInjectionBinding.InjectionSite;
import dagger.model.DependencyRequest;
import dagger.model.Key;
import dagger.multibindings.Multibinds;
import dagger.producers.Produces;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* 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;
@Inject BindingFactory bindingFactory;
@Inject DelegateDeclaration.Factory delegateDeclarationFactory;
@Inject MultibindingDeclaration.Factory multibindingDeclarationFactory;
@Inject SubcomponentDeclaration.Factory subcomponentDeclarationFactory;
@Inject KeyFactory keyFactory;
@Inject DaggerTypes types;
@Inject DaggerElements elements;
@Override
public Void visitClassDef(JCClassDecl tree, Void p) {
TypeElement type = MoreElements.asType(getElement(tree));
bindingGraphFactory.create(type).ifPresent(this::addNodesForGraph);
if (getModuleAnnotation(type).isPresent()) {
subcomponentDeclarationFactory.forModule(type).forEach(this::addBindingDeclarationEdge);
}
Optional<AnnotationMirror> componentAnnotation = getComponentAnnotation(type);
if (componentAnnotation.isPresent()) {
addBindingAndDependencyEdges(bindingFactory.componentBinding(type));
for (TypeMirror dependency : getComponentDependencies(componentAnnotation.get())) {
ComponentRequirement requirement = ComponentRequirement.forDependency(dependency);
addBindingAndDependencyEdges(bindingFactory.componentDependencyBinding(requirement));
}
}
MembersInjectionBinding membersInjectionBinding =
bindingFactory.membersInjectionBinding(
MoreTypes.asDeclared(type.asType()), Optional.empty());
for (InjectionSite injectionSite : membersInjectionBinding.injectionSites()) {
// ignore inherited injection sites
if (injectionSite.element().getEnclosingElement().equals(type)) {
injectionSite.dependencies().forEach(this::addDependencyEdge);
}
}
if (!membersInjectionBinding.injectionSites().isEmpty()) {
Key membersInjectorKey =
Key.builder(types.wrapType(membersInjectionBinding.key().type(), MembersInjector.class))
.build();
addBindingDeclarationEdge(membersInjectorKey, type);
}
return super.visitClassDef(tree, p);
}
@Override
public Void visitMethodDef(JCMethodDecl tree, Void p) {
ExecutableElement element = MoreElements.asExecutable(getElement(tree));
if (isAnnotationPresent(element, Inject.class) && element.getKind().equals(CONSTRUCTOR)) {
addBindingAndDependencyEdges(bindingFactory.injectionBinding(element, Optional.empty()));
} else {
TypeElement enclosingType = MoreElements.asType(element.getEnclosingElement());
if (isAnnotationPresent(element, Provides.class)){
addBindingAndDependencyEdges(bindingFactory.providesMethodBinding(element, enclosingType));
} else if (isAnnotationPresent(element, Produces.class)) {
addBindingAndDependencyEdges(bindingFactory.producesMethodBinding(element, enclosingType));
} else if (isAnnotationPresent(element, Binds.class)) {
DelegateDeclaration delegateDeclaration =
delegateDeclarationFactory.create(element, enclosingType);
addBindingDeclarationEdge(delegateDeclaration);
addDependencyEdge(delegateDeclaration.delegateRequest());
} else if (isAnnotationPresent(element, Multibinds.class)) {
addBindingDeclarationEdge(
multibindingDeclarationFactory.forMultibindsMethod(element, enclosingType));
} else if (isAnnotationPresent(element, BindsOptionalOf.class)) {
Key key = keyFactory.forBindsOptionalOfMethod(element, enclosingType);
for (Class<?> optionalClass : optionalClasses()) {
Key wrappedOptionalKey =
key.toBuilder().type(types.wrapType(key.type(), optionalClass)).build();
addBindingDeclarationEdge(wrappedOptionalKey, element);
}
} else if (isAnnotationPresent(element, BindsInstance.class)) {
VariableElement parameter = getOnlyElement(element.getParameters());
Key key = Key.builder(parameter.asType()).qualifier(getQualifier(parameter)).build();
addBindingDeclarationEdge(key, element);
}
}
return super.visitMethodDef(tree, p);
}
private void addNodesForGraph(BindingGraph graph) {
for (ComponentDescriptor.ComponentMethodDescriptor componentMethod :
graph.componentDescriptor().componentMethods()) {
componentMethod.dependencyRequest().ifPresent(this::addDependencyEdge);
}
}
private void addBindingAndDependencyEdges(Binding binding) {
addBindingDeclarationEdge(binding);
binding.dependencies().forEach(this::addDependencyEdge);
}
private void addBindingDeclarationEdge(BindingDeclaration declaration) {
addBindingDeclarationEdge(declaration.key(), declaration.bindingElement().get());
}
/**
* Adds a {@code defines/binding} edge between {@code bindingElement} and the node for {@code
* key}.
*/
private void addBindingDeclarationEdge(Key key, Element bindingElement) {
if (hasTypeVariable(key.type())) {
return;
}
EntrySet bindingAnchor =
entrySets.newAnchorAndEmit(fileVName, bindingElementSpan(bindingElement), null);
entrySets.emitEdge(bindingAnchor, DEFINES_BINDING, keyNode(key));
}
private Span bindingElementSpan(Element bindingElement) {
Name name =
bindingElement.getKind().equals(ElementKind.CONSTRUCTOR)
? bindingElement.getEnclosingElement().getSimpleName()
: bindingElement.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 addDependencyEdge(DependencyRequest dependencyRequest) {
if (!dependencyRequest.requestElement().isPresent()
|| hasTypeVariable(dependencyRequest.key().type())) {
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;
}
private ImmutableSet<Class<?>> optionalClasses() {
// TODO(user): Can the plugin infrastructure be modified to guarantee certain types are
// always available to plugins via kytheGraph.getNode() even if they haven't been scanned yet?
return ImmutableSet.of(java.util.Optional.class, com.google.common.base.Optional.class)
.stream()
.filter(this::isClassAvailable)
.collect(toImmutableSet());
}
private boolean isClassAvailable(Class<?> clazz) {
return kytheGraph
.getNode((Symbol) elements.getTypeElement(clazz.getCanonicalName()))
.isPresent();
}
private Element getElement(Tree tree) {
return trees.getElement(trees.getPath(compilationUnit, tree));
}
@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));
DaggerDaggerKythePlugin_PluginComponent.builder()
.types(JavacTypes.instance(javaContext))
.elements(JavacElements.instance(javaContext))
.build()
.inject(this);
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);
}
@Singleton
@Component
interface PluginComponent {
void inject(DaggerKythePlugin plugin);
@Component.Builder
interface Builder {
@BindsInstance Builder types(Types types);
@BindsInstance Builder elements(Elements elements);
PluginComponent build();
}
}
}