blob: 74cb6af5137e35c0ca05d3ccb91bd05fa2e85fbe [file] [log] [blame]
package com.google.inject.lifecycle;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.internal.Lists;
import com.google.inject.internal.Maps;
import com.google.inject.internal.Preconditions;
import com.google.inject.matcher.Matcher;
import static com.google.inject.matcher.Matchers.any;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;
/** @author dhanji@google.com (Dhanji R. Prasanna) */
@Singleton class BroadcastingLifecycle implements Lifecycle {
private final Injector injector;
private final List<Class<?>> callableClasses;
// @GuardedBy(this)
private Map<Class<?>, List<Key<?>>> callableKeys;
private volatile boolean started = false;
@Inject
public BroadcastingLifecycle(Injector injector, @ListOfMatchers List<Class<?>> callableClasses) {
this.injector = injector;
this.callableClasses = callableClasses;
// Self start. Eventually we may want to move this to a hook-in from Guice-core.
start();
}
public void start() {
if (started) {
// throw? log warning?
return;
}
// OK to start the startables now.
// Guaranteed to return in order of module binding..
Map<Key<?>, Binding<?>> allBindings = injector.getBindings();
List<Binding<Startable>> startables = Lists.newArrayList();
Map<Class<?>, List<Key<?>>> callableKeys = Maps.newLinkedHashMap();
// Do not collapse into loop below (in synchronized block). Time complexity is still linear.
for (Binding<?> binding : allBindings.values()) {
Class<?> bindingType = binding.getKey().getTypeLiteral().getRawType();
// inner loop N*M complexity
for (Class<?> callable : callableClasses) {
if (callable.isAssignableFrom(bindingType)) {
// we don't want to instantiate these right now...
List<Key<?>> list = callableKeys.get(callable);
// Multimap put.
if (null == list) {
list = Lists.newArrayList();
callableKeys.put(callable, list);
}
list.add(binding.getKey());
}
}
// check startables now.
if (Startable.class.isAssignableFrom(bindingType)) {
// First make sure this is a singleton.
Preconditions.checkState(Scopes.isSingleton(binding),
"Egregious error, all Startables must be scopes as singletons!");
//noinspection unchecked
startables.add((Binding<Startable>) binding);
}
}
synchronized (this) {
for (Binding<Startable> binding : startables) {
// Go go zilla go! (sequential startup)
injector.getInstance(binding.getKey()).start();
}
// Safely publish keymap.
this.callableKeys = callableKeys;
// success!
started = true;
}
}
public <T> T broadcast(Class<T> clazz) {
return broadcast(clazz, any());
}
public <T> T broadcast(Class<T> clazz, Matcher<? super T> matcher) {
final List<T> ts = Lists.newArrayList();
for (Key<?> key : callableKeys.get(clazz)) {
// Should this get instancing happen during method call?
@SuppressWarnings("unchecked") // Guarded by getInstance
T t = (T) injector.getInstance(key);
if (matcher.matches(t)) {
ts.add(t);
}
}
@SuppressWarnings("unchecked") T caster = (T) Proxy
.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// propagate the method call with the same arg list to all instances.
for (T t : ts) {
method.invoke(t, objects);
}
// We can't return from multiple instances, so just return null.
return null;
}
});
return caster;
}
}