package org.limewire.inject; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.atomic.AtomicReference; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.Scope; import com.google.inject.Scopes; import com.google.inject.Singleton; import com.google.inject.spi.BindingScopingVisitor; /** * A factory for creating Providers that will enforce extreme laziness of * creating the implementation class. The implementation class can be * injected directly, and it will not be constructed until at least * one method is called on the implementation. * <p> * This enforces that all bindings are done on implementations scoped * to a {@code @}{@link Singleton} or {@code @}{@link LazySingleton}, * to ensure that methods are called on the same underlying instance * of the implementation through repeated calls. */ public class LazyBinder<T> implements Provider<T> { private final AtomicReference<T> providee = new AtomicReference<T>(); private final Class<T> expected; private final Class<? extends T> implClass; private Injector injector; /** * Constructs a new Provider that will ensure the implementation class is * created only after at least one method is called. */ public static <T> Provider<T> newLazyProvider(Class<T> expected, Class<? extends T> implClass) { if (!expected.isInterface()) { throw new RuntimeException("Expected class must be an interface"); } return new LazyBinder<T>(expected, implClass); } private LazyBinder(Class<T> expected, Class<? extends T> implClass) { this.expected = expected; this.implClass = implClass; } @SuppressWarnings("unused") @Inject private void registerAndCheckTypes(Injector injector) { this.injector = injector; injector.getBinding(implClass).acceptScopingVisitor(new BindingScopingVisitor<Void>() { public Void visitEagerSingleton() { return null; } public Void visitNoScoping() { throw new RuntimeException("Class: " + implClass + " must be in scope @Singleton or @LazySingleton or @EagerSingleton"); }; public Void visitScope(Scope scope) { if(scope != Scopes.SINGLETON && scope != MoreScopes.LAZY_SINGLETON && scope != MoreScopes.EAGER_SINGLETON) { throw new RuntimeException("Class: " + implClass + " must be in scope @Singleton or @LazySingleton or @EagerSingleton"); } return null; }; public Void visitScopeAnnotation( Class<? extends Annotation> scopeAnnotation) { throw new RuntimeException("Wasn't expecting this"); }; }); } @Override public T get() { // Keep only one LazyT. T got = providee.get(); if(got != null) { return got; } else { providee.compareAndSet(null, createProxy(expected, injector.getProvider(implClass))); return providee.get(); } } private static <T> T createProxy(Class<T> expected, final Provider<? extends T> provider) { ClassLoader classLoader = expected.getClassLoader(); return expected.cast(Proxy.newProxyInstance(classLoader, new Class[] { expected }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return method.invoke(provider.get(), args); } catch(InvocationTargetException ite) { throw ite.getTargetException(); } } })); } }