package co.codewizards.cloudstore.local; import static co.codewizards.cloudstore.core.util.AssertUtil.*; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.repo.local.LocalRepoManager; import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerCloseEvent; import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerCloseListener; class LocalRepoManagerInvocationHandler implements InvocationHandler { private static final Logger logger = LoggerFactory.getLogger(LocalRepoManagerInvocationHandler.class); final LocalRepoManagerImpl localRepoManagerImpl; // package-protected for our test private final AtomicBoolean open = new AtomicBoolean(true); private final List<LocalRepoManagerCloseListener> localRepoManagerCloseListeners = new CopyOnWriteArrayList<LocalRepoManagerCloseListener>(); private volatile Throwable proxyCreatedStackTraceException = new Exception("proxyCreatedStackTraceException").fillInStackTrace(); private static final Set<String> methodsAllowedOnClosedProxy = new HashSet<String>(Arrays.asList( "close", "finalize", "isOpen", "equals", "hashCode", "toString")); public LocalRepoManagerInvocationHandler(final LocalRepoManagerImpl localRepoManagerImpl) { this.localRepoManagerImpl = assertNotNull(localRepoManagerImpl, "localRepoManagerImpl"); } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { final LocalRepoManager localRepoManagerProxy = (LocalRepoManager) proxy; boolean proxyClosedInThisInvocation = false; if (!methodsAllowedOnClosedProxy.contains(method.getName())) assertOpen(); if ("close".equals(method.getName())) { proxyClosedInThisInvocation = close(localRepoManagerProxy, method, args); if (!proxyClosedInThisInvocation) // Multiple invocations of close() should have no effect. return null; } else if ("isOpen".equals(method.getName())) return isOpen(localRepoManagerProxy, method, args); // Do *not* delegate. else if ("addLocalRepoManagerCloseListener".equals(method.getName())) addLocalRepoManagerCloseListener(localRepoManagerProxy, method, args); else if ("removeLocalRepoManagerCloseListener".equals(method.getName())) removeLocalRepoManagerCloseListener(localRepoManagerProxy, method, args); else if ("finalize".equals(method.getName())) { finalize(localRepoManagerProxy, method, args); return null; // NEVER delegating finalize } final Object result = method.invoke(localRepoManagerImpl, args); if (proxyClosedInThisInvocation) firePostClose(localRepoManagerProxy); return result; } private void assertOpen() { if (!open.get()) { throw new IllegalStateException("This LocalRepoManager (proxy) is already closed!"); } } private boolean close(final LocalRepoManager localRepoManagerProxy, final Method method, final Object[] args) { proxyCreatedStackTraceException = null; if (open.compareAndSet(true, false)) { firePreClose(localRepoManagerProxy); return true; } return false; } private void finalize(final LocalRepoManager localRepoManagerProxy, final Method method, final Object[] args) { if (proxyCreatedStackTraceException != null) { logger.warn("finalize: Detected forgotten close() invocation!", proxyCreatedStackTraceException); } close(localRepoManagerProxy, method, args); } private void firePreClose(final LocalRepoManager localRepoManagerProxy) { final LocalRepoManagerCloseEvent event = new LocalRepoManagerCloseEvent(localRepoManagerProxy, localRepoManagerProxy, false); for (final LocalRepoManagerCloseListener listener : localRepoManagerCloseListeners) { listener.preClose(event); } } private void firePostClose(final LocalRepoManager localRepoManagerProxy) { final LocalRepoManagerCloseEvent event = new LocalRepoManagerCloseEvent(localRepoManagerProxy, localRepoManagerProxy, false); for (final LocalRepoManagerCloseListener listener : localRepoManagerCloseListeners) { listener.postClose(event); } } private boolean isOpen(final LocalRepoManager localRepoManagerProxy, final Method method, final Object[] args) { return open.get(); } private void addLocalRepoManagerCloseListener(final LocalRepoManager localRepoManagerProxy, final Method method, final Object[] args) { if (args == null || args.length != 1) throw new IllegalArgumentException("args == null || args.length != 1"); if (!(args[0] instanceof LocalRepoManagerCloseListener)) throw new IllegalArgumentException("args[0] is not an instance of LocalRepoManagerCloseListener"); localRepoManagerCloseListeners.add((LocalRepoManagerCloseListener) args[0]); } private void removeLocalRepoManagerCloseListener(final LocalRepoManager localRepoManagerProxy, final Method method, final Object[] args) { if (args == null || args.length != 1) throw new IllegalArgumentException("args == null || args.length != 1"); if (!(args[0] instanceof LocalRepoManagerCloseListener)) throw new IllegalArgumentException("args[0] is not an instance of LocalRepoManagerCloseListener"); localRepoManagerCloseListeners.remove(args[0]); } }