/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.dlect.events; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nonnull; /** * * @author lee */ public class BaseEventAdapter implements EventAdapter { private EventAdapter parentAdapter; private final transient Set<EventListener> anyClassListeners; private final transient Multimap<Class<?>, EventListener> specificClassEventListeners; /** * Creates a new event adapter. */ public BaseEventAdapter() { this.anyClassListeners = Sets.newHashSet(); this.specificClassEventListeners = HashMultimap.create(); } /** * Creates a new event adapter using the lists provided. This method can be used to create a linked event adapter as * the given collections are not copied or checked. * * @param anyClassListeners The set to hold all the listeners who are listening to every event that passes * this adapter. * @param specificClassEventListeners The multimap to hold the listeners who are listening to specific classes that * pass through this adapter. */ public BaseEventAdapter(Set<EventListener> anyClassListeners, Multimap<Class<?>, EventListener> specificClassEventListeners) { this.anyClassListeners = anyClassListeners; this.specificClassEventListeners = specificClassEventListeners; } @Override public boolean addListener(@Nonnull EventListener listener, Class<?>... listensTo) { if (listener == null) { throw new IllegalArgumentException("Null listener given to listen to " + Arrays.toString(listensTo)); } synchronized (anyClassListeners) { if (anyClassListeners.contains(listener)) { // The listener is already listening to all events. return false; } else if (listensTo == null || listensTo.length == 0) { // Remove the listener from the classFiltered multimap to save time on event firing. removeListener(listener); return anyClassListeners.add(listener); } else { int size = specificClassEventListeners.size(); for (Class<?> clz : listensTo) { specificClassEventListeners.put(clz, listener); } /* * If the size increased; then one of the additions was successful. This will not overlap with other * adds as this is in a synchronised block. */ return size != specificClassEventListeners.size(); } } } /** * This method checks for a loop contained in the given event adapter if it were added to {@code this}. This will * throw an {@link IllegalArgumentException} if a cycle is found. * * @param e */ protected void checkForParentLoop(EventAdapter e) { Set<EventAdapter> adapters = Sets.newLinkedHashSet(); // Linked to keep order if there is a problem. adapters.add(this); EventAdapter parent = e; while (parent != null) { if (adapters.contains(parent)) { throw new IllegalArgumentException("Encountered existing adapters. Adapter: " + parent + "; Parents(This is first): " + adapters); } adapters.add(parent); parent = parent.getParentAdapter(); } } /** * {@inheritDoc } * * @param e */ @Override public void fireEvent(@Nonnull Event e) { if (e == null) { throw new IllegalArgumentException("Event is null!"); } Set<EventListener> listeners = Sets.newHashSet(); synchronized (anyClassListeners) { listeners.addAll(anyClassListeners); listeners.addAll(specificClassEventListeners.get(e.getSourceClass())); } /* * Taken a copy of the listeners; don't care if anyone changes them; plus doing this outside the synchronised * block will allow other events to fire whilst this is processing. */ for (EventListener el : listeners) { el.processEvent(e); } if (parentAdapter != null) { parentAdapter.fireEvent(e); } } @Override public EventAdapter getParentAdapter() { return this.parentAdapter; } @Override public void setParentAdapter(EventAdapter e) { if (e != null) { checkForParentLoop(e); } this.parentAdapter = e; } @Override public boolean removeListener(@Nonnull EventListener l) { synchronized (anyClassListeners) { boolean removed = anyClassListeners.remove(l); Collection<Entry<Class<?>, EventListener>> entries = specificClassEventListeners.entries(); int size = entries.size(); for (Iterator<Entry<Class<?>, EventListener>> it = entries.iterator(); it.hasNext();) { Entry<Class<?>, EventListener> entry = it.next(); if (l.equals(entry.getValue())) { it.remove(); } } // If it was removed from the anyList or if the specificClassList changed size(an element was removed). return removed || size != entries.size(); } } }