Skip to content

Instantly share code, notes, and snippets.

@alangpierce
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alangpierce/a35374aeb133caea27bc to your computer and use it in GitHub Desktop.
Save alangpierce/a35374aeb133caea27bc to your computer and use it in GitHub Desktop.
Java class that walks the Dagger dependencies in the KA code and generates a .dot file to use for a visual representation.
package org.khanacademy.android.graphgen;
import org.khanacademy.android.dependencies.components.ApplicationComponent;
import dagger.Component;
import dagger.Provides;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
/**
* Hacked-together code to generate a DAG from the KA Android codebase. It does runtime reflection
* to walk the Dagger bindings and build the graph, and writes the result as a .dot file.
*
* Note that this code doesn't come close to supporting all of Dagger 2's features, and relies on the
* fact that the current KA code only uses a small subset of what Dagger 2 offers. For example, this
* code assumes that dependencies can be identified by a Class<?>, but Dagger 2 includes type parameters
* in the dependency name and supports @Named and any other qualifiers.
*
* Usage:
* If you haven't done so, run "brew install graphviz".
* Run this class. It generates an android.dot file.
* In a shell, run "cat android.dot | dot -Tpdf > android.pdf" This creates an android.pdf.
*/
public class GraphGenerator {
private final Set<Class<?>> seenClasses = new HashSet<>();
private final Set<Class<?>> seenProvides = new HashSet<>();
private final PrintWriter writer;
public GraphGenerator(PrintWriter writer) {
this.writer = writer;
}
public static void main(String[] args) throws FileNotFoundException {
PrintWriter writer = new PrintWriter("/Users/apierce/graphviz/android.dot");
writer.println("digraph G {");
GraphGenerator graphGenerator = new GraphGenerator(writer);
graphGenerator.walkComponent(ApplicationComponent.class);
graphGenerator.emitAllClassInfo();
writer.println("}");
writer.flush();
System.out.println("Done!");
}
public void walkComponent(Class<?> componentClass) {
Component componentAnnotation = componentClass.getAnnotation(Component.class);
for (Class<?> module : componentAnnotation.modules()) {
walkModule(module);
}
for (Method method : componentClass.getDeclaredMethods()) {
if (method.getParameterTypes().length == 0) {
walkClass(method.getReturnType());
} else {
walkClass(method.getParameterTypes()[0]);
}
}
}
public void walkModule(Class<?> module) {
for (Method method : module.getDeclaredMethods()) {
if (method.getAnnotation(Provides.class) != null) {
walkProvidesMethod(method);
}
}
writer.println(" subgraph cluster_" + module.getSimpleName() + " {");
for (Method method : module.getDeclaredMethods()) {
if (method.getAnnotation(Provides.class) != null) {
emitModuleMembership(module, method.getReturnType());
walkProvidesMethod(method);
}
}
writer.println(" }");
writer.printf(" \"%s\" [shape=box fontsize=24]\n", module.getSimpleName());
}
private void emitModuleMembership(Class<?> module, Class<?> returnType) {
writer.printf(" \"%s\" -> \"%s\";\n", module.getSimpleName(), returnType.getName());
}
private void walkProvidesMethod(Method method) {
Class<?> returnType = method.getReturnType();
if (seenProvides.contains(returnType)) {
return;
}
seenProvides.add(returnType);
walkClass(returnType);
for (Class<?> parameterType : method.getParameterTypes()) {
walkDependency(returnType, parameterType);
}
}
public void walkDependency(Class<?> fromClass, Class<?> toClass) {
emitEdge(fromClass, toClass);
walkClass(toClass);
}
public void walkClass(Class<?> dependencyClass) {
if (seenClasses.contains(dependencyClass)) {
return;
}
seenClasses.add(dependencyClass);
for (Constructor<?> constructor : dependencyClass.getDeclaredConstructors()) {
if (constructor.getAnnotation(Inject.class) != null) {
for (Class<?> parameterType : constructor.getParameterTypes()) {
walkDependency(dependencyClass, parameterType);
}
}
}
for (Field field : dependencyClass.getDeclaredFields()) {
if (field.getAnnotation(Inject.class) != null) {
walkDependency(dependencyClass, field.getType());
}
}
for (Method method : dependencyClass.getDeclaredMethods()) {
if (method.getAnnotation(Inject.class) != null) {
for (Class<?> parameterType : method.getParameterTypes()) {
walkDependency(dependencyClass, parameterType);
}
}
}
}
public void emitEdge(Class<?> fromClass, Class<?> toClass) {
writer.printf(" \"%s\" -> \"%s\";%n", fromClass.getName(), toClass.getName());
}
public void emitAllClassInfo() {
for (Class<?> seenClass : seenClasses) {
emitClassInfo(seenClass);
}
}
private void emitClassInfo(Class<?> classToEmit) {
int classSize = computeClassSize(classToEmit);
writer.printf(" \"%s\" [", classToEmit.getName());
if (classToEmit.getName().startsWith("org.khanacademy.core")) {
writer.printf("penwidth=%s ", Math.log10(classSize));
writer.printf("label=\"%s\" ", classToEmit.getSimpleName());
writer.printf("fillcolor=lightblue ");
writer.printf("style=filled ");
writer.printf("fontsize=18 ");
} else if (classToEmit.getName().startsWith("org.khanacademy.android")) {
writer.printf("penwidth=%s ", Math.log10(classSize));
writer.printf("label=\"%s\" ", classToEmit.getSimpleName());
writer.printf("fontsize=18 ");
} else {
writer.printf("label=\"%s\\n%s\" ",
classToEmit.getPackage().getName(), classToEmit.getSimpleName());
writer.printf("fillcolor=lightyellow ");
writer.printf("style=filled ");
}
writer.println("];");
}
public int computeClassSize(Class<?> classToEmit) {
InputStream stream = classToEmit.getClassLoader()
.getResourceAsStream(classToEmit.getName().replace(".", "/") + ".class");
int len = 0;
try {
while (stream.read() != -1) {
len++;
}
} catch (IOException e) {
e.printStackTrace();
}
return len;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment