/* * Copyright 2010 the original author or authors. * * 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 org.gradle.internal.event; import com.google.common.collect.Lists; import org.gradle.internal.dispatch.Dispatch; import org.gradle.internal.dispatch.MethodInvocation; import org.gradle.internal.dispatch.ProxyDispatchAdapter; import org.gradle.internal.dispatch.ReflectionDispatch; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; @SuppressWarnings({"unchecked"}) public class DefaultListenerManager implements ListenerManager { private final Map<Object, ListenerDetails> allListeners = new LinkedHashMap<Object, ListenerDetails>(); private final Map<Object, ListenerDetails> allLoggers = new LinkedHashMap<Object, ListenerDetails>(); private final Map<Class<?>, EventBroadcast> broadcasters = new HashMap<Class<?>, EventBroadcast>(); private final Object lock = new Object(); private final DefaultListenerManager parent; public DefaultListenerManager() { this(null); } private DefaultListenerManager(DefaultListenerManager parent) { this.parent = parent; } public void addListener(Object listener) { ListenerDetails details = null; synchronized (lock) { if (!allListeners.containsKey(listener)) { details = new ListenerDetails(listener); allListeners.put(listener, details); } } if (details != null) { details.useAsListener(); } } public void removeListener(Object listener) { ListenerDetails details; synchronized (lock) { details = allListeners.remove(listener); if (details != null) { details.disconnect(); } } if (details != null) { details.remove(); } } public void useLogger(Object logger) { ListenerDetails details = null; synchronized (lock) { if (!allLoggers.containsKey(logger)) { details = new ListenerDetails(logger); allLoggers.put(logger, details); } } if (details != null) { details.useAsLogger(); } } public <T> T getBroadcaster(Class<T> listenerClass) { return getBroadcasterInternal(listenerClass).getBroadcaster(); } public <T> ListenerBroadcast<T> createAnonymousBroadcaster(Class<T> listenerClass) { ListenerBroadcast<T> broadcast = new ListenerBroadcast(listenerClass); broadcast.add(getBroadcasterInternal(listenerClass).getDispatch(true)); return broadcast; } private <T> EventBroadcast<T> getBroadcasterInternal(Class<T> listenerClass) { synchronized (lock) { EventBroadcast<T> broadcaster = broadcasters.get(listenerClass); if (broadcaster == null) { broadcaster = new EventBroadcast<T>(listenerClass); broadcasters.put(listenerClass, broadcaster); for (ListenerDetails listener : allListeners.values()) { broadcaster.maybeAdd(listener); } for (ListenerDetails logger : allLoggers.values()) { broadcaster.maybeSetLogger(logger); } } return broadcaster; } } public ListenerManager createChild() { return new DefaultListenerManager(this); } /** * Manages the listeners and state for a given listener type */ private class EventBroadcast<T> implements Dispatch<MethodInvocation> { private final Class<T> type; private final ListenerDispatch dispatch; private final ListenerDispatch dispatchNoLogger; private volatile ProxyDispatchAdapter<T> source; private final Set<ListenerDetails> listeners = new LinkedHashSet<ListenerDetails>(); private final List<Runnable> queuedOperations = new LinkedList<Runnable>(); private final ReentrantLock broadcasterLock = new ReentrantLock(); private ListenerDetails logger; private Dispatch<MethodInvocation> parentDispatch; private List<Dispatch<MethodInvocation>> allWithLogger = Collections.emptyList(); private List<Dispatch<MethodInvocation>> allWithNoLogger = Collections.emptyList(); EventBroadcast(Class<T> type) { this.type = type; dispatch = new ListenerDispatch(type, true); dispatchNoLogger = new ListenerDispatch(type, false); if (parent != null) { parentDispatch = parent.getBroadcasterInternal(type).getDispatch(true); invalidateDispatchCache(); } } @Override public void dispatch(MethodInvocation message) { dispatch.dispatch(message); } Dispatch<MethodInvocation> getDispatch(boolean includeLogger) { return includeLogger ? dispatch : dispatchNoLogger; } T getBroadcaster() { if (source == null) { synchronized (this) { if (source == null) { source = new ProxyDispatchAdapter<T>(this, type); } } } return source.getSource(); } private void invalidateDispatchCache() { ensureAllWithLoggerInitialized(); ensureAllWithoutLoggerInitialized(); } void maybeAdd(final ListenerDetails listener) { if (type.isInstance(listener.listener)) { if (broadcasterLock.tryLock()) { try { listeners.add(listener); invalidateDispatchCache(); } finally { broadcasterLock.unlock(); } } else { synchronized (queuedOperations) { queuedOperations.add(new Runnable() { @Override public void run() { listeners.add(listener); } }); } } } } void maybeRemove(final ListenerDetails listener) { if (broadcasterLock.tryLock()) { try { if (listeners.remove(listener)) { invalidateDispatchCache(); } } finally { broadcasterLock.unlock(); } } else { synchronized (queuedOperations) { queuedOperations.add(new Runnable() { @Override public void run() { listeners.remove(listener); } }); } } } void maybeSetLogger(final ListenerDetails candidate) { if (type.isInstance(candidate.listener)) { if (broadcasterLock.tryLock()) { try { doSetLogger(candidate); invalidateDispatchCache(); } finally { broadcasterLock.unlock(); } } else { synchronized (queuedOperations) { queuedOperations.add(new Runnable() { @Override public void run() { doSetLogger(candidate); } }); } } } } private void doSetLogger(ListenerDetails candidate) { if (logger == null && parent != null) { parentDispatch = parent.getBroadcasterInternal(type).getDispatch(false); } logger = candidate; } private List<Dispatch<MethodInvocation>> startNotification(boolean includeLogger) { takeOwnership(); // Take a snapshot while holding lock List<Dispatch<MethodInvocation>> result = includeLogger ? allWithLogger : allWithNoLogger; doStartNotification(result); return result; } private void doStartNotification(List<Dispatch<MethodInvocation>> result) { for (Dispatch<MethodInvocation> dispatch : result) { if (dispatch instanceof ListenerDetails) { ListenerDetails listenerDetails = (ListenerDetails) dispatch; listenerDetails.startNotification(); } } } private void ensureAllWithoutLoggerInitialized() { if (parentDispatch == null && listeners.isEmpty()) { allWithNoLogger = Collections.emptyList(); } else { List<Dispatch<MethodInvocation>> dispatchers = new ArrayList<Dispatch<MethodInvocation>>(); if (parentDispatch != null) { dispatchers.add(parentDispatch); } dispatchers.addAll(listeners); allWithNoLogger = dispatchers; } } private void ensureAllWithLoggerInitialized() { if (logger == null && parentDispatch == null && listeners.isEmpty()) { allWithLogger = Collections.emptyList(); } else { allWithLogger = buildAllWithLogger(); } } private void takeOwnership() { // Mark this listener type as being notified if (broadcasterLock.isHeldByCurrentThread()) { throw new IllegalStateException(String.format("Cannot notify listeners of type %s as these listeners are already being notified.", type.getSimpleName())); } broadcasterLock.lock(); } private List<Dispatch<MethodInvocation>> buildAllWithLogger() { List<Dispatch<MethodInvocation>> result = new ArrayList<Dispatch<MethodInvocation>>(); if (logger != null) { result.add(logger); } if (parentDispatch != null) { result.add(parentDispatch); } result.addAll(listeners); return result; } private void endNotification(List<Dispatch<MethodInvocation>> dispatchers) { for (Dispatch<MethodInvocation> dispatcher : dispatchers) { if (dispatcher instanceof ListenerDetails) { ListenerDetails listener = (ListenerDetails) dispatcher; listener.endNotification(); } } try { synchronized (queuedOperations) { if (!queuedOperations.isEmpty()) { for (Runnable queuedOperation : queuedOperations) { queuedOperation.run(); } invalidateDispatchCache(); } } } finally { broadcasterLock.unlock(); } } private class ListenerDispatch extends AbstractBroadcastDispatch<T> { private final boolean includeLogger; ListenerDispatch(Class<T> type, boolean includeLogger) { super(type); this.includeLogger = includeLogger; } @Override public void dispatch(MethodInvocation invocation) { List<Dispatch<MethodInvocation>> dispatchers = startNotification(includeLogger); try { if (!dispatchers.isEmpty()) { dispatch(invocation, dispatchers.iterator()); } } finally { endNotification(dispatchers); } } } } /** * Holds state about a particular listener */ private class ListenerDetails implements Dispatch<MethodInvocation> { final Object listener; final Dispatch<MethodInvocation> dispatch; final AtomicBoolean removed = new AtomicBoolean(); final ReentrantLock notifyingLock = new ReentrantLock(); public ListenerDetails(Object listener) { this.listener = listener; this.dispatch = new ReflectionDispatch(listener); } void disconnect() { removed.set(true); } @Override public void dispatch(MethodInvocation message) { if (!removed.get()) { dispatch.dispatch(message); } } void startNotification() { notifyingLock.lock(); } void endNotification() { notifyingLock.unlock(); } void remove() { // block until the listener has finished notifying. notifyingLock.lock(); try { for (EventBroadcast<?> broadcaster : Lists.newArrayList(broadcasters.values())) { broadcaster.maybeRemove(this); } } finally { notifyingLock.unlock(); } } void useAsLogger() { for (EventBroadcast<?> broadcaster : broadcasters.values()) { broadcaster.maybeSetLogger(this); } } void useAsListener() { for (EventBroadcast<?> broadcaster : broadcasters.values()) { broadcaster.maybeAdd(this); } } } }