/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * 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 com.analog.lyric.dimple.events; import java.lang.reflect.Modifier; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import net.jcip.annotations.ThreadSafe; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.collect.ReleasableIterator; import com.analog.lyric.collect.Supers; import com.analog.lyric.dimple.environment.DimpleEnvironment; import com.analog.lyric.util.misc.Internal; import com.google.common.collect.MapMaker; /** * Manages dispatching of {@link DimpleEvent}s. * * @since 0.06 * @author Christopher Barber */ @ThreadSafe public class DimpleEventListener implements IDimpleEventListener { /*--------------------- * Public nested types */ /** * Describes a particular event handler setting. * <p> * @since 0.06 * @see IHandlersForSource * @see DimpleEventListener#register(IDimpleEventHandler, Class, boolean, IDimpleEventSource) */ public interface IHandlerEntry { /** * The base class of the events registered for handling by {@link #eventHandler()}. * * @since 0.06 */ public Class<? extends DimpleEvent> eventClass(); /** * The handler instance registered for events of specified {@link #eventClass}. * * @since 0.06 */ public IDimpleEventHandler<?> eventHandler(); /** * The event source whose events will be handled by {@link #eventHandler()}. * * @since 0.06 */ public IDimpleEventSource eventSource(); /** * Indicates whether the handler is registered to handle all subclass events * or just ones with the exact specified {@link #eventClass}. * * @since 0.06 */ public boolean handleSubclasses(); } /** * Describes association of registered event handlers with a particular event source. * * @since 0.06 * @see DimpleEventListener#allHandlerPerSource() */ public interface IHandlersForSource { /** * Event source associated with {@link #handlerEntries()}. * * @since 0.06 */ public IDimpleEventSource eventSource(); /** * Immutable list of handler entries associated with {@link #eventSource()}. * * @since 0.06 */ public List<IHandlerEntry> handlerEntries(); } /*------- * State */ private final ConcurrentMap<IDimpleEventSource, Entry[]> _handlersForSource; @Deprecated private static final AtomicReference<DimpleEventListener> _defaultListener = new AtomicReference<DimpleEventListener>(); /*-------------- * Construction */ /** * Constructs a new empty event listener. */ public DimpleEventListener() { // We use ConcurrentMap so that readers don't have to lock it, but force updates to be single // threaded with a lock this so the concurrency level is set to one to minimize overhead. // Use weak keys to prevent listener from keeping event sources in memory if no one else is using them. _handlersForSource = new MapMaker().concurrencyLevel(1).weakKeys().makeMap(); } /** * @deprecated As of release 0.07 the default listener is no longer used and will be removed * in a future release. */ @Deprecated public static DimpleEventListener getDefault() { DimpleEventListener listener; while ((listener = _defaultListener.get()) == null) { _defaultListener.compareAndSet(null, new DimpleEventListener()); } return listener; } /** * @deprecated As of release 0.07 the default listener is no longer used and will be removed * in a future release. */ @Deprecated public boolean isDefault() { return this == _defaultListener.get(); } /** * @deprecated As of release 0.07 the default listener is no longer used and will be removed * in a future release. */ @Deprecated public static @Nullable DimpleEventListener setDefault(@Nullable DimpleEventListener listener) { return _defaultListener.getAndSet(listener); } /*------------------------------ * IDimpleEventListener methods */ @Override public boolean isListeningFor(Class<? extends DimpleEvent> eventClass, IDimpleEventSource on) { boolean isListening = false; final ReleasableIterator<IDimpleEventSource> sources = eventSources(on); outer: while (sources.hasNext()) { final IDimpleEventSource source = sources.next(); Entry[] entries = _handlersForSource.get(source); if (entries != null) { for (Entry entry : entries) { if (entry.canHandleEvent(eventClass)) { isListening = !entry._handler.isBlocker(); break outer; } } } } sources.release(); return isListening; } /** * {@inheritDoc} * <p> * This method will visit each handler in order listed by the {@link #getHandlersFor} method * for the event's concrete class and its source attribute and invoke it's {@code handleEvent} * method. If the event is marked as consumed then no further handlers will be tried. * <p> * @since 0.06 * @see DimpleEvent#consumed() * @see DimpleEvent#getSource() * @see IDimpleEventHandler#handleEvent(DimpleEvent) */ @Override public boolean raiseEvent(DimpleEvent event) { final ReleasableIterator<IDimpleEventSource> sources = eventSources(event.getSource()); boolean handlerFound = false; outer: while (sources.hasNext()) { final IDimpleEventSource source = sources.next(); Entry[] entries = _handlersForSource.get(source); if (entries != null) { for (Entry entry : entries) { handlerFound |= entry.handleEvent(event); if (event.consumed()) { break outer; } } } } sources.release(); return handlerFound; } /*------------------------------------ * Static DimpleEventListener methods */ /** * Determine if event source has listener active for given event type. * * @param source non-null source of event. * @param eventClass non-null type of event listened for. * @return true if {@code source} has an event listener that is listening for * the given {@code eventClass}. * @since 0.06 */ public static boolean sourceHasListenerFor(IDimpleEventSource source, Class<? extends DimpleEvent> eventClass) { IDimpleEventListener listener = source.getEventListener(); return listener != null && listener.isListeningFor(eventClass, source); } /*----------------------------- * DimpleEventListener methods */ /** * Lists all handlers currently registered with this listener for each source. * <p> * The underlying iterators do not support removal. * <p> * @since 0.06 */ public Iterable<IHandlersForSource> allHandlerPerSource() { return new HandlersForSourceIterable(); } /** * Block specified events from percolating past specified source. * <p> * Simply invokes {@link #register(IDimpleEventHandler, Class, boolean, IDimpleEventSource)} with * {@link DimpleEventBlocker} as the handler. * @since 0.06 */ public void block(Class<? extends DimpleEvent> eventClass, boolean blockSubclasses, IDimpleEventSource target) { register(DimpleEventBlocker.INSTANCE, eventClass, blockSubclasses, target); } /** * Returns list of handler entries registered to handle the given event. * <p> * The entries are given in the order in which they would be invoked when calling {@link #raiseEvent(DimpleEvent)} * when passed an event with given {@code eventClass} and {@code eventSource}. * This may include more entries than would be invoked by {@code raiseEvent} since that method stops processing * if the event is consumed by a handler. * <p> * The list is constructed by checking very source in {@link #eventSources(IDimpleEventSource)} starting * with given {@code eventSource} in order and adding entries matching given {@code eventClass} in topological * order from subclass to superclass. The list is terminated if a blocking handler is found. * * @return newly allocated mutable list * @see IDimpleEventHandler#isBlocker() * @since 0.06 */ public List<IHandlerEntry> getHandlersFor(Class<? extends DimpleEvent> eventClass, IDimpleEventSource eventSource) { final List<IHandlerEntry> handlers = new ArrayList<IHandlerEntry>(); final ReleasableIterator<IDimpleEventSource> sources = eventSources(eventSource); outer: while (sources.hasNext()) { final IDimpleEventSource source = sources.next(); Entry[] entries = _handlersForSource.get(source); if (entries != null) { for (Entry entry : entries) { if (entry.canHandleEvent(eventClass)) { handlers.add(entry); if (entry.eventHandler().isBlocker()) { break outer; } } } } } sources.release(); return handlers; } /** * Iterates over sources of {@code event} in hierarchical order from child to parent. * <p> * Specifically, this is equivalent to the ordering that would be produced by: * <p> * <pre> * void addSources(List<IDimpleEventSource> sources, IDimpleEventSource source) * { * source.add(source); * if (source.getModelEventSource() != source) * { * source.add(source.getModelEventSource()); * } * if (source.getEventParent() != null) * { * addSources(source.getEventParent()); * } * } * </pre> * <p> * Iterator is not thread-safe. Expected to be used for a single function call. * * @since 0.06 */ public EventSourceIterator eventSources(@Nullable IDimpleEventSource source) { return EventSourceIterator.create(source); } /** * True if no handlers are registered with this listener. * * @since 0.06 */ public boolean isEmpty() { return _handlersForSource.isEmpty(); } /** * Notifies all registered sources that listener has changed. * <p> * Invokes {@link IDimpleEventSource#notifyListenerChanged()} on every source with a registered * handler. This is used by {@link DimpleEnvironment} when changing the environment's listener. * <p> * @since 0.07 */ @Internal public synchronized void notifyListenerChanged() { for (IDimpleEventSource source : _handlersForSource.keySet()) { source.notifyListenerChanged(); } } /** * Registers an event handler for given event class on given event source. * <p> * @param handler non-null handler * @param eventClass is the class of events to be handled * @param handleSubclasses specifies whether the handler should be advertised as listening for instances * of subclasses of the {@code eventClass}. * @param target is the object to be monitored for events. Note that children of this object may also * @since 0.06 * @see #register(IDimpleEventHandler, Class, IDimpleEventSource) * @see #raiseEvent(DimpleEvent) */ public <Event extends DimpleEvent> void register( IDimpleEventHandler<? super Event> handler, Class<Event> eventClass, boolean handleSubclasses, IDimpleEventSource target) { Objects.requireNonNull(handler); Objects.requireNonNull(eventClass); Objects.requireNonNull(target); final Entry entry = new Entry(handler, eventClass, handleSubclasses, target); synchronized(this) { Entry[] entries = _handlersForSource.get(target); if (entries == null) { entries = new Entry[] { entry }; } else { final int size = entries.length; int i = Arrays.binarySearch(entries, entry, EntryEventClassOrder.COMPARATOR); if (i >= 0) { // A match is found. Search linearly for an exact match. for (; i < size && !entry.equals(entries[i]); ++i) {} } if (0 <= i && i < size) { // An entry already exists for this class/handler combination; so replace it. entries = entries.clone(); entries[i] = entry; } else { entries = Arrays.copyOf(entries, size + 1); if (i < 0) { // Insert new entry in middle. i = -(i+1); System.arraycopy(entries, i, entries, i + 1, size - i); } entries[i] = entry; } } _handlersForSource.put(target, entries); } } /** * Registers an event handler for given event class on given event source. * <p> * This simply invokes {@link #register(IDimpleEventHandler, Class, boolean, IDimpleEventSource)} * passing true for the {@code handleSubclasses} argument if {@code eventClass} is abstract. * <p> * @param handler non-null handler * @param eventClass is the class of events to be handled * @param target is the object to be monitored for events. Note that children of this object may also * @since 0.06 * @see #raiseEvent(DimpleEvent) */ public <Event extends DimpleEvent> void register( IDimpleEventHandler<? super Event> handler, Class<Event> eventClass, IDimpleEventSource target) { register(handler, eventClass, Modifier.isAbstract(eventClass.getModifiers()), target); } /** * Unblocks specified events from percolating past specified source. * <p> * Simply invokes {@link #unregister(IDimpleEventHandler, Class, IDimpleEventSource)} with * {@link DimpleEventBlocker} as the handler. Note that the {@code eventClass} and {@code target} * must exactly match that used with previous {@link #block(Class, boolean, IDimpleEventSource)} call. * * @return false if no matching blocker was found. * @since 0.06 */ public boolean unblock(Class<? extends DimpleEvent> eventClass, IDimpleEventSource target) { return unregister(DimpleEventBlocker.INSTANCE, eventClass, target); } /** * Removes previously registered handler for given event class and event source. * * @return false if no handler matching the arguments was found. * @since 0.06 */ public boolean unregister( IDimpleEventHandler<?> handler, Class<? extends DimpleEvent> eventClass, IDimpleEventSource target) { boolean found = false; synchronized(this) { Entry[] entries = _handlersForSource.get(target); if (entries != null) { final Entry entry = new Entry(handler, eventClass, false, target); int i = Arrays.binarySearch(entries, entry, EntryEventClassOrder.COMPARATOR); if (i >= 0) { for (int size = entries.length; i < size; ++i) { if (entry.equals(entries[i])) { if (--size == 0) { _handlersForSource.remove(target); } else { final Entry[] newEntries = new Entry[size]; System.arraycopy(entries, 0, newEntries, 0, i); System.arraycopy(entries, i + 1, newEntries, i, size - i); _handlersForSource.put(target, newEntries); } found = true; break; } } } } } return found; } /** * Unregister all event handlers for given source. * <p> * This does not remove handlers registered with the source's children. * * @return true if any handlers were unregistered. * * @since 0.06 */ public boolean unregisterSource(IDimpleEventSource source) { boolean found = false; synchronized(this) { found = null != _handlersForSource.remove(source); } return found; } /** * Unregisters all handlers. * <p> * After invoking this {@link #allHandlerPerSource()} will be empty. * * @since 0.06 */ public void unregisterAll() { synchronized(this) { _handlersForSource.clear(); } } /** * Unregisters all instances of given handler. * <p> * @param handler is the handler instance that will be removed. * @return the number of entries that were removed from the listener. * @since 0.06 */ public int unregisterAll(IDimpleEventHandler<?> handler) { int nRemoved = 0; synchronized(this) { final List<IHandlerEntry> entriesToRemove = new ArrayList<IHandlerEntry>(); for (IHandlersForSource handlersForSource : allHandlerPerSource()) { for (IHandlerEntry entry : handlersForSource.handlerEntries()) { if (entry.eventHandler() == handler) { entriesToRemove.add(entry); } } } for (IHandlerEntry entry : entriesToRemove) { unregister(handler, entry.eventClass(), entry.eventSource()); ++nRemoved; } } return nRemoved; } /*----------------------- * Private inner classes */ /** * Entry containing handler registered for given event class for a given target. */ private static class Entry implements IHandlerEntry { /*------- * State */ private final IDimpleEventSource _eventSource; private final Class<? extends DimpleEvent> _eventClass; private final boolean _handleSubclasses; private final IDimpleEventHandler<DimpleEvent> _handler; private final int _eventClassDepth; /*-------------- * Construction */ @SuppressWarnings("unchecked") private Entry(IDimpleEventHandler<?> handler, Class<? extends DimpleEvent> eventClass, boolean handleSubclasses, IDimpleEventSource eventSource) { _eventSource = eventSource; _eventClass = eventClass; _handleSubclasses = handleSubclasses; _handler = (IDimpleEventHandler<DimpleEvent>) handler; _eventClassDepth = Supers.numberOfSuperClasses(eventClass); } /*---------------- * Object methods */ /** * True if other entry has same {@link #eventClass}, {@link #eventSource}, and {@link #eventHandler}. */ @Override public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (!(other instanceof Entry)) { return false; } Entry that = (Entry)other; return this._eventSource == that._eventSource && this._eventClass == that._eventClass && this._handler == that._handler; } @Override public int hashCode() { return _eventSource.hashCode() * 3 + _eventClass.hashCode() * 5 + _handler.hashCode(); } /*---------------- * IHandlerEntry */ @Override public final Class<? extends DimpleEvent> eventClass() { return _eventClass; } @Override public final IDimpleEventHandler<?> eventHandler() { return _handler; } @Override public final IDimpleEventSource eventSource() { return _eventSource; } @Override public final boolean handleSubclasses() { return _handleSubclasses; } /*--------- * Private */ private boolean canHandleEvent(Class<? extends DimpleEvent> eventClass) { return _handleSubclasses ? _eventClass.isAssignableFrom(eventClass) : _eventClass.equals(eventClass); } private boolean handleEvent(DimpleEvent event) { final boolean handle = canHandleEvent(event.getClass()); if (handle) { _handler.handleEvent(event); } return handle; } } /** * Produces an ordering based on the depth of the event class in the class hierarchy with * subclasses coming before superclasses. Ties are broken using the event class names, * which ensures that multiple entries for the same class will be next to each other. */ @NonNullByDefault(false) private static enum EntryEventClassOrder implements Comparator<Entry> { COMPARATOR; @Override public int compare(Entry entry1, Entry entry2) { int diff = entry2._eventClassDepth - entry1._eventClassDepth; if (diff == 0) { diff = entry1._eventClass.getName().compareTo(entry2._eventClass.getName()); } return diff; } } private static final class HandlerEntryList extends AbstractList<IHandlerEntry> { private final IHandlerEntry[] _entries; private HandlerEntryList(IHandlerEntry[] entries) { _entries = entries; } @Override public IHandlerEntry get(int index) { return _entries[index]; } @Override public int size() { return _entries.length; } } private static final class HandlersForSource implements IHandlersForSource { private final IDimpleEventSource _eventSource; private final List<IHandlerEntry> _handlerEntries; private HandlersForSource(Map.Entry<IDimpleEventSource, Entry[]> entry) { _eventSource = entry.getKey(); _handlerEntries = new HandlerEntryList(entry.getValue()); } @Override public IDimpleEventSource eventSource() { return _eventSource; } @Override public List<IHandlerEntry> handlerEntries() { return _handlerEntries; } } private final class HandlersForSourceIterator implements Iterator<IHandlersForSource> { private Iterator<Map.Entry<IDimpleEventSource, Entry[]>> _iterator = _handlersForSource.entrySet().iterator(); @Override public boolean hasNext() { return _iterator.hasNext(); } @Override public HandlersForSource next() { return new HandlersForSource(_iterator.next()); } @Override public void remove() { throw new UnsupportedOperationException("Iterator.remove"); } } private final class HandlersForSourceIterable implements Iterable<IHandlersForSource> { @Override public Iterator<IHandlersForSource> iterator() { return new HandlersForSourceIterator(); } } }