/*******************************************************************************
* 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();
}
}
}