/*
* Copyright 2012 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.gwt.inject.rebind.output;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.inject.rebind.GinScope;
import com.google.gwt.inject.rebind.GinjectorBindings;
import com.google.gwt.inject.rebind.RootBindings;
import com.google.gwt.inject.rebind.binding.Binding;
import com.google.gwt.inject.rebind.binding.Dependency;
import com.google.gwt.inject.rebind.binding.ExposedChildBinding;
import com.google.gwt.inject.rebind.binding.ParentBinding;
import com.google.gwt.inject.rebind.reflect.FieldLiteral;
import com.google.gwt.inject.rebind.reflect.MethodLiteral;
import com.google.gwt.inject.rebind.util.GuiceUtil;
import com.google.gwt.inject.rebind.util.MemberCollector;
import com.google.gwt.inject.rebind.util.PrettyPrinter;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.InjectionPoint;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Utility class that determines which bindings from a {@link GinjectorBindings}
* hierarchy should actually be written in the generated code.
*
* <p>This eliminates a significant amount of code that we know will never be
* invoked, based on our high-level knowledge of the generated code. This is
* preferable to relying entirely on GWT's reachability analysis, which is
* (understandably) imperfect and sometimes makes different judgements as we
* restructure the generated code files.
*/
final class ReachabilityAnalyzer {
private Set<Binding> reachable = null;
private Map<GinjectorBindings, Set<TypeLiteral<?>>> reachableMemberInjects = null;
private final GuiceUtil guiceUtil;
private final TreeLogger logger;
private final MemberCollector memberCollector;
private final GinjectorBindings rootBindings;
@Inject
public ReachabilityAnalyzer(
GuiceUtil guiceUtil,
Provider<MemberCollector> memberCollectorProvider,
@RootBindings GinjectorBindings rootBindings,
TreeLogger logger) {
this.guiceUtil = guiceUtil;
this.logger = logger;
this.memberCollector = memberCollectorProvider.get();
this.rootBindings = rootBindings;
this.memberCollector.setMethodFilter(MemberCollector.ALL_METHOD_FILTER);
}
/**
* Tests whether the given binding is reachable from a true root.
*
* <p>A true root is a key that the user can actually create. In contrast,
* the dependency tree we generate earlier in the process is rooted at keys
* that must exist semantically, even if they are never created.
*
* <p>More specifically, the following keys are true roots:
*
* <ul>
* <li>Every non-Void key that is the return value of an injector method.
* <li>Every key that is a parameter to an injector method.
* <li>Every key that is injected as an eager singleton.
* <li>Every key that is a parameter to a static injection method on a class
* for which static injection was requested.
* <li>Every key that is the type of a statically injected field of a class
* for which static injection was requested.
* </ul>
*/
boolean isReachable(Binding binding) {
if (reachable == null) {
computeReachable();
}
return reachable.contains(binding);
}
boolean isReachableMemberInject(GinjectorBindings bindings, TypeLiteral<?> type) {
if (reachableMemberInjects == null) {
computeReachable();
}
return getReachableMemberInjects(bindings).contains(type);
}
private void computeReachable() {
reachable = new LinkedHashSet<Binding>();
reachableMemberInjects = new LinkedHashMap<GinjectorBindings, Set<TypeLiteral<?>>>();
logger.log(TreeLogger.DEBUG, "Begin reachability analysis");
// Note on implementation: for simplicity, we use a Binding as the node of
// the graph that we run reachability on. This would be incoherent before
// binding resolution (when we didn't know which bindings existed), but now
// that all the bindings are created, every key that we can inject has a
// unique Binding object. Since the caller of this routine is interested in
// determining which bindings to output, it's more convenient to just work
// at the binding level.
traceGinjectorMethods();
traceEagerSingletons();
traceStaticInjections();
logger.log(TreeLogger.DEBUG, "End reachability analysis");
}
/** Traces out bindings that are reachable from a GInjector method. */
private void traceGinjectorMethods() {
TypeLiteral<?> ginjectorInterface = rootBindings.getGinjectorInterface();
for (MethodLiteral<?, Method> method
: memberCollector.getMethods(ginjectorInterface)) {
if (!guiceUtil.isMemberInject(method)) {
// It's a constructor method, so just trace to the key that's
// constructed.
Key<?> key = guiceUtil.getKey(method);
PrettyPrinter.log(logger, TreeLogger.DEBUG,
"ROOT -> %s:%s [%s]", rootBindings, key, method);
traceKey(key, rootBindings);
} else {
Key<?> sourceKey = guiceUtil.getKey(method);
getReachableMemberInjects(rootBindings).add(sourceKey.getTypeLiteral());
for (Dependency dependency
: guiceUtil.getMemberInjectionDependencies(sourceKey, sourceKey.getTypeLiteral())) {
Key<?> targetKey = dependency.getTarget();
PrettyPrinter.log(
logger, TreeLogger.DEBUG, "ROOT -> %s:%s [%s]", rootBindings, targetKey, method);
traceKey(targetKey, rootBindings);
}
}
}
}
/** Traces out bindings that are reachable from an eager singleton. */
private void traceEagerSingletons() {
doTraceEagerSingletons(rootBindings);
}
private void doTraceEagerSingletons(GinjectorBindings bindings) {
for (Map.Entry<Key<?>, Binding> entry : bindings.getBindings()) {
Key<?> key = entry.getKey();
Binding binding = entry.getValue();
GinScope scope = bindings.determineScope(key);
if (scope == GinScope.EAGER_SINGLETON) {
PrettyPrinter.log(logger, TreeLogger.DEBUG,
"ROOT -> %s:%s [eager singleton: %s]", bindings, key, binding);
traceKey(key, bindings);
}
}
for (GinjectorBindings child : bindings.getChildren()) {
doTraceEagerSingletons(child);
}
}
/**
* Traces out bindings that are reachable from statically injected fields and
* methods.
*/
private void traceStaticInjections() {
doTraceStaticInjections(rootBindings);
}
private void doTraceStaticInjections(GinjectorBindings bindings) {
for (Class<?> klass : bindings.getStaticInjectionRequests()) {
traceStaticInjectionsFor(klass, bindings);
}
for (GinjectorBindings child : bindings.getChildren()) {
doTraceStaticInjections(child);
}
}
private void traceStaticInjectionsFor(Class<?> klass, GinjectorBindings bindings) {
TypeLiteral<?> type = TypeLiteral.get(klass);
for (InjectionPoint injectionPoint : InjectionPoint.forStaticMethodsAndFields(klass)) {
Member member = injectionPoint.getMember();
if (member instanceof Method) {
Method methodRaw = (Method) member;
TypeLiteral<?> declaringClass = TypeLiteral.get(methodRaw.getDeclaringClass());
MethodLiteral<?, ?> method = MethodLiteral.get(methodRaw, declaringClass);
for (Key<?> key : method.getParameterKeys()) {
PrettyPrinter.log(logger, TreeLogger.DEBUG, "ROOT -> %s:%s [static injection: %s]",
bindings, key, method);
traceKey(key, bindings);
}
} else if (member instanceof Field) {
Field fieldRaw = (Field) member;
TypeLiteral<?> declaringClass = TypeLiteral.get(fieldRaw.getDeclaringClass());
FieldLiteral<?> field = FieldLiteral.get(fieldRaw, declaringClass);
Key<?> key = guiceUtil.getKey(field);
PrettyPrinter.log(logger, TreeLogger.DEBUG, "ROOT -> %s:%s [static injection: %s]",
bindings, key, field);
traceKey(key, bindings);
}
}
}
/**
* Marks the binding of the given key in the given {@link GinjectorBindings}
* as reachable, and traces out its dependencies.
*/
private void traceKey(Key<?> key, GinjectorBindings bindings) {
Binding binding = bindings.getBinding(key);
// Make sure the binding is present: optional bindings might be missing.
if (binding != null) {
if (!reachable.add(binding)) {
// The binding was already marked as reachable.
return;
}
getReachableMemberInjects(bindings).addAll(binding.getMemberInjectRequests());
for (Dependency dependency : binding.getDependencies()) {
if (dependency.getSource().equals(key)) {
Key<?> target = dependency.getTarget();
PrettyPrinter.log(logger, TreeLogger.DEBUG, "%s:%s -> %s:%s [%s]",
bindings, key, bindings, dependency.getTarget(), binding);
traceKey(target, bindings);
}
}
// Special cases: parent / child bindings induce dependencies between
// GinjectorBindings objects, which can't be represented in the standard
// dependency graph.
if (binding instanceof ParentBinding) {
ParentBinding parentBinding = (ParentBinding) binding;
PrettyPrinter.log(logger, TreeLogger.DEBUG, "%s:%s -> %s:%s [inherited]",
bindings, key, parentBinding.getParentBindings(), key);
traceKey(key, parentBinding.getParentBindings());
} else if (binding instanceof ExposedChildBinding) {
ExposedChildBinding exposedChildBinding = (ExposedChildBinding) binding;
PrettyPrinter.log(logger, TreeLogger.DEBUG, "%s:%s -> %s:%s [exposed]",
bindings, key, exposedChildBinding.getChildBindings(), key);
traceKey(key, exposedChildBinding.getChildBindings());
}
}
}
private Set<TypeLiteral<?>> getReachableMemberInjects(GinjectorBindings bindings) {
Set<TypeLiteral<?>> result = reachableMemberInjects.get(bindings);
if (result == null) {
result = new LinkedHashSet<TypeLiteral<?>>();
reachableMemberInjects.put(bindings, result);
}
return result;
}
}