blob: 26e0a8ce410695bed19afe311face55a2950a185 [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 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.model.JavacElements;
import com.sun.tools.javac.model.JavacTypes;
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.model.DependencyRequest;
import dagger.model.Key;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.lang.model.element.Element;
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 static final Logger logger = Logger.getLogger(DaggerKythePlugin.class.getCanonicalName());
private FactEmitter emitter;
@Inject KytheBindingGraphFactory bindingGraphFactory;
@Override
public Void visitClassDef(JCClassDecl tree, Void p) {
Optional.ofNullable(tree.sym)
.flatMap(bindingGraphFactory::create)
.ifPresent(this::addNodesForGraph);
return super.visitClassDef(tree, p);
}
private void addNodesForGraph(BindingGraph graph) {
for (ResolvedBindings resolvedBinding : graph.resolvedBindings()) {
for (Binding binding : resolvedBinding.bindings()) {
for (DependencyRequest dependency : binding.explicitDependencies()) {
addEdgesForDependencyRequest(dependency, dependency.key(), graph);
}
}
}
for (ComponentDescriptor.ComponentMethodDescriptor componentMethod :
graph.componentDescriptor().componentMethods()) {
componentMethod
.dependencyRequest()
.ifPresent(request -> addEdgesForDependencyRequest(request, request.key(), graph));
}
graph.subgraphs().forEach(this::addNodesForGraph);
}
/**
* 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, Key targetKey, BindingGraph graph) {
if (!dependency.requestElement().isPresent()) {
return;
}
ResolvedBindings resolvedBindings = graph.resolvedBindings(dependency.kind(), targetKey);
for (Binding binding : resolvedBindings.bindings()) {
if (binding.bindingElement().isPresent()) {
addDependencyEdge(dependency, binding);
} else {
for (DependencyRequest subsequentDependency : binding.explicitDependencies()) {
addEdgesForDependencyRequest(dependency, subsequentDependency.key(), graph);
}
}
}
for (BindingDeclaration bindingDeclaration :
Iterables.concat(
resolvedBindings.multibindingDeclarations(),
resolvedBindings.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);
Optional<VName> bindingElementNode = jvmNode(bindingElement);
if (requestElementNode.isPresent() && bindingElementNode.isPresent()) {
new EntrySet.Builder(
requestElementNode.get(), "/inject/satisfiedby", bindingElementNode.get())
.build()
.emit(emitter);
// TODO(ronshapiro): emit facts about the component that satisfies the edge
} else {
List<String> missingNodes = new ArrayList<>();
if (!requestElementNode.isPresent()) {
missingNodes.add("requestElement: " + requestElement);
}
if (!bindingElementNode.isPresent()) {
missingNodes.add("bindingElement: " + bindingElement);
}
// TODO(ronshapiro): use Flogger
logger.warning(String.format("Missing JVM nodes: %s ", missingNodes));
}
}
private Optional<VName> jvmNode(Element element) {
return kytheGraph.getJvmNode((Symbol) element).map(KytheNode::getVName);
}
@Override
public void run(
JCCompilationUnit compilationUnit, KytheEntrySets entrySets, KytheGraph kytheGraph) {
if (bindingGraphFactory == null) {
Context javaContext = kytheGraph.getJavaContext();
emitter = entrySets.getEmitter();
DaggerDaggerKythePlugin_PluginComponent.builder()
.types(JavacTypes.instance(javaContext))
.elements(JavacElements.instance(javaContext))
.compilerOptions(KytheBindingGraphFactory.createCompilerOptions())
.build()
.inject(this);
}
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);
@BindsInstance
Builder compilerOptions(CompilerOptions compilerOptions);
PluginComponent build();
}
}
}