package tc.oc.commons.core.inject; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import com.google.inject.MembersInjector; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; /** * Detects repeated member injection of the same object * * Guice does nothing to prevent {@link MembersInjector#injectMembers(Object)} * from being called on the same object multiple times, which can easily have * unwanted side-effects. * * This module installs a custom universal {@link MembersInjector} that can * detect repeat application to the same object. To do this, it must hold a * weak reference to every injected object indefinitely, so this should not * be used in a production environment. */ public class RepeatInjectionDetector extends SingletonManifest { @Override protected void configure() { final ReferenceQueue<Object> queue = new ReferenceQueue<>(); final Set<Object> injected = Collections.newSetFromMap(new IdentityHashMap<>()); final AtomicBoolean running = new AtomicBoolean(true); final Thread reaper = new Thread(() -> { while(running.get()) { try { final Object instance = queue.remove().get(); if(instance != null) synchronized(injected) { injected.remove(instance); } } catch(InterruptedException e) { // ignored } } }); reaper.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { running.set(false); reaper.interrupt(); })); bindListener(com.google.inject.matcher.Matchers.any(), new TypeListener() { @Override public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { encounter.register(new MembersInjector<I>() { @Override public void injectMembers(I instance) { final boolean added; synchronized(injected) { added = injected.add(instance); } if(added) { new WeakReference<>(instance, queue); } else { throw new ProvisionException("Multiple injections for " + instance); } } }); } }); } }