blob: 6782f2afa616618ed87aa3ad3dc8ca90974cb33c [file] [log] [blame]
/**
* Copyright (C) 2008 Google Inc.
*
* 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.
*/
package com.google.inject.grapher;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.internal.Sets;
import com.google.inject.spi.BindingTargetVisitor;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;
/**
* Root class for graphing an {@link Injector}. Bound in {@link GrapherModule}.
* <p>
* Use {@link #of(Injector)} to specify the {@link Injector} to use, and
* {@link graph()} to graph the {@link Injector} using the currently-bound
* {@link Renderer}.
* <p>
* By default, this will graph the entire {@link Injector}. Use
* {@link #rootedAt(Class...)} to specify an initial set of {@link Class}es to
* use, and this will graph their transitive bindings and dependencies.
*
* @author phopkins@gmail.com (Pete Hopkins)
*/
public class InjectorGrapher {
private static final Key<Logger> loggerKey = Key.get(Logger.class);
private final BindingTargetVisitor<Object, Collection<Key<?>>> keyVisitor;
private final BindingTargetVisitor<Object, Void> graphingVisitor;
private final Renderer renderer;
private Injector injector;
private Set<Key<?>> root;
@Inject
public InjectorGrapher(BindingTargetVisitor<Object, Collection<Key<?>>> keyVisitor,
BindingTargetVisitor<Object, Void> graphingVisitor, Renderer renderer) {
this.keyVisitor = keyVisitor;
this.graphingVisitor = graphingVisitor;
this.renderer = renderer;
}
/**
* Sets the {@link Injector} to graph.
*/
public InjectorGrapher of(Injector injector) {
this.injector = injector;
return this;
}
/**
* Sets an initial group of {@link Class}es to use as the starting point for
* the graph. The graph will be of these classes and their transitive
* dependencies and bindings.
*/
public InjectorGrapher rootedAt(Class<?>... classes) {
this.root = Sets.newHashSet();
for (Class<?> clazz : classes) {
this.root.add(Key.get(clazz));
}
return this;
}
/**
* Renders a graph with the bound {@link Renderer}. The {@link Injector}
* must have already been specified with {@link #of(Injector)}.
*/
public void graph() throws IOException {
processBindings();
renderer.render();
}
/**
* Tests for {@link Key}s that we don't want to include by default in the
* graph. They're left out of the initial set, but will appear if depended
* upon by other classes. Leaves out Guice classes (such as the
* {@link Injector}) and the {@link Logger}.
*/
private boolean skipKey(Key<?> key) {
return key.getTypeLiteral().getRawType().getPackage() == Guice.class.getPackage()
|| loggerKey.equals(key);
}
/**
* Takes the set of starting {@link Binding}s, which comes either from the
* {@link Injector} or from {@link #rootedAt(Class...)}, and applies the
* {@link #graphingVisitor} to them. Uses the {@link #keyVisitor} to build
* out the set of {@link Key}s so that the graph covers the transitive
* dependencies and bindings.
*/
private void processBindings() {
Set<Key<?>> keys = Sets.newLinkedHashSet();
Set<Key<?>> visitedKeys = Sets.newHashSet();
// build up the root set from the Injector if it wasn't specified
if (root == null) {
for (Key<?> key : injector.getBindings().keySet()) {
if (!skipKey(key)) {
keys.add(key);
}
}
} else {
keys.addAll(root);
}
while (!keys.isEmpty()) {
Iterator<Key<?>> iterator = keys.iterator();
Key<?> key = iterator.next();
iterator.remove();
if (visitedKeys.contains(key)) {
continue;
}
Binding<?> binding = injector.getBinding(key);
visitedKeys.add(key);
binding.acceptTargetVisitor(graphingVisitor);
// find the dependencies and make sure that they get visited
Collection<Key<?>> newKeys = binding.acceptTargetVisitor(keyVisitor);
if (newKeys != null) {
keys.addAll(newKeys);
}
}
}
}