gt: 				if (!it.next().isAlive()) it.remove();
context: package org.exist.fluent;
  
  import java.lang.ref.*;
  import java.util.*;
  
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.exist.collections.Collection;
  import org.exist.collections.triggers.CollectionTrigger;
  import org.exist.collections.triggers.DocumentTrigger;
  import org.exist.collections.triggers.SAXTrigger;
  import org.exist.collections.triggers.TriggerException;
  import org.exist.dom.persistent.DocumentImpl;
  import org.exist.storage.DBBroker;
  import org.exist.storage.txn.Txn;
  import org.exist.xmldb.XmldbURI;
  
  /**
   * Internal class not for public use; needs to be public due to external instantiation requirements.
   * Mediates between native eXist triggers and db listeners.
   *
   * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
   */
  public class ListenerManager {
  	
  	static String getTriggerConfigXml() {
  		return "<triggers><trigger class='org.exist.fluent.ListenerManager$TriggerDispatcher'/></triggers>";
  	}
  	
  	static class EventKey implements Comparable<EventKey> {
  		final String path;
  		final Trigger trigger;
  		EventKey(String path, Trigger trigger) {
  			this.path = Database.normalizePath(path);
  			this.trigger = trigger;
  		}
  		EventKey(String path, int existEventCode, boolean before) {
  			this(path, toTrigger(existEventCode, before));
  		}
  		private static Trigger toTrigger(int code, boolean before) {
  			switch(code) {
  				case org.exist.collections.triggers.Trigger.STORE_DOCUMENT_EVENT:
  					return before ? Trigger.BEFORE_STORE : Trigger.AFTER_STORE;
  				case org.exist.collections.triggers.Trigger.CREATE_COLLECTION_EVENT:
  					return before ? Trigger.BEFORE_CREATE : Trigger.AFTER_CREATE;
  				case org.exist.collections.triggers.Trigger.UPDATE_DOCUMENT_EVENT:
  					return before ? Trigger.BEFORE_UPDATE : Trigger.AFTER_UPDATE;
  				case org.exist.collections.triggers.Trigger.RENAME_DOCUMENT_EVENT:
  				case org.exist.collections.triggers.Trigger.RENAME_COLLECTION_EVENT:
  					return before ? Trigger.BEFORE_RENAME : Trigger.AFTER_RENAME;
  				case org.exist.collections.triggers.Trigger.MOVE_DOCUMENT_EVENT:
  				case org.exist.collections.triggers.Trigger.MOVE_COLLECTION_EVENT:
  					return before ? Trigger.BEFORE_MOVE : Trigger.AFTER_MOVE;
  				case org.exist.collections.triggers.Trigger.REMOVE_DOCUMENT_EVENT:
  				case org.exist.collections.triggers.Trigger.REMOVE_COLLECTION_EVENT:
  					return before ? Trigger.BEFORE_REMOVE : Trigger.AFTER_REMOVE;
  				default:
  					throw new IllegalArgumentException("unknown exist trigger code " + code);
  			}
  		}
  		@Override
  		public boolean equals(Object o) {
  			if (o instanceof EventKey) {
  				EventKey that = (EventKey) o;
  				return
  					path.equals(that.path) &&
  					trigger == that.trigger; 
  			}
  			return false;
  		}
  		@Override
  		public int hashCode() {
  			return path.hashCode() * 37 + trigger.hashCode();
  		}
  		public int compareTo(EventKey that) {
  			int r = this.trigger.compareTo(that.trigger);
  			if (r == 0) r = that.path.compareTo(this.path);	// reverse order on purpose
  			return r;
  		}
  		boolean matchesAsPrefix(EventKey that) {
  			return
  				this.trigger == that.trigger &&
  				that.path.startsWith(this.path) &&
  				(this.path.equals("/") || this.path.length() == that.path.length() || that.path.charAt(this.path.length()) == '/');
  		}
  	}
  	
  	private static class ListenerWrapper {
  		final Reference<Listener> refListener;
  		final Resource origin;
  		ListenerWrapper(Listener listener, Resource origin) {
  			this.refListener = new WeakReference<Listener>(listener);
  			this.origin = origin;
  		}
  		private Document wrap(DocumentImpl doc) {
  			return doc == null ? null : Document.newInstance(doc, origin);
  		}
  		private Folder wrap(org.exist.collections.Collection col) {
  			if (col == null) return null;
  			return new Folder(col.getURI().getCollectionPath(), false, origin);
  		}
  		boolean sameOrNull(Listener listener) {
  			Listener x = refListener.get();
  			return x == null || x == listener;
  		}
  		boolean isAlive() {
  			return refListener.get() != null;
  		}
  		void fireDocumentEvent(EventKey key, DocumentImpl doc) {
  			Listener listener = refListener.get();
  			if (listener instanceof Document.Listener) ((Document.Listener) listener).handle(new Document.Event(key, wrap(doc)));
  		}
  		void fireFolderEvent(EventKey key, Collection col) {
  			Listener listener = refListener.get();
  			if (listener instanceof Folder.Listener) ((Folder.Listener) listener).handle(new Folder.Event(key, wrap(col)));
  		}
  	}
  	
  	enum Depth {
  		/**
  		 * Targets matching the given path exactly.
  		 */
  		ZERO,
  		/**
  		 * Targets one level below the given path (i.e. inside the given folder).
  		 */
  		ONE,
  		/**
  		 * Targets matching or at any level below the given path.
  		 */
  		MANY}
  
  	private final Map<EventKey,List<ListenerWrapper>>[] listenerMaps;
  	
  	@SuppressWarnings("unchecked")
  	private ListenerManager() {
  		listenerMaps = new Map[Depth.values().length];
  		listenerMaps[Depth.ZERO.ordinal()] = Collections.synchronizedMap(new HashMap<EventKey,List<ListenerWrapper>>());
  		listenerMaps[Depth.ONE.ordinal()] = Collections.synchronizedMap(new HashMap<EventKey,List<ListenerWrapper>>());
  		listenerMaps[Depth.MANY.ordinal()] = Collections.synchronizedSortedMap(new TreeMap<EventKey,List<ListenerWrapper>>());
  	}
  	
  	private void checkListenerType(Listener listener) {
  		if (!(listener instanceof Document.Listener || listener instanceof Folder.Listener))
  			throw new IllegalArgumentException("invalid listener type " + listener.getClass().getName());
  	}
  	
  	void add(String pathPrefix, Depth depth, Set<Trigger> triggers, Listener listener, Resource origin) {
  		checkListenerType(listener);
  		if (triggers.isEmpty()) throw new IllegalArgumentException("cannot add listener with empty set of triggers");
  		for (Trigger trigger : triggers) {
  			EventKey key = new EventKey(pathPrefix, trigger);
  			List<ListenerWrapper> list;
  			synchronized(listenerMaps[depth.ordinal()]) {
  				list = listenerMaps[depth.ordinal()].get(key);
  				if (list == null) {
  					list = new LinkedList<ListenerWrapper>();
  					listenerMaps[depth.ordinal()].put(key, list);
  				}
  			}
  			synchronized(list) {
  				list.add(new ListenerWrapper(listener, origin));
  			}
  		}
  	}
  
  	void remove(String pathPrefix, Depth depth, Listener listener) {
  		remove(pathPrefix, listenerMaps[depth.ordinal()], listener);
  	}
  	
  	void remove(Listener listener) {
  		for (Map<EventKey,List<ListenerWrapper>> map : listenerMaps) remove(null, map, listener);
  	}
  	
  	private void remove(String path, Map<EventKey,List<ListenerWrapper>> map, Listener listener) {
  		checkListenerType(listener);
  		synchronized(map) {
  			for (Iterator<Map.Entry<EventKey,List<ListenerWrapper>>> it = map.entrySet().iterator(); it.hasNext(); ) {
  				Map.Entry<EventKey,List<ListenerWrapper>> entry = it.next();
  				if (path != null && !entry.getKey().path.equals(path)) continue;
  				synchronized(entry.getValue()) {
  					for (Iterator<ListenerWrapper> it2 = entry.getValue().iterator(); it2.hasNext(); ) {
  						if (it2.next().sameOrNull(listener)) it2.remove();
  					}
  				}
  				if (entry.getValue().isEmpty()) it.remove();
  			}
  		}
  	}
  	
  	void fire(EventKey key, DocumentImpl doc) {
  		fire(key, doc, null, true);
  	}
  		
  	void fire(EventKey key, org.exist.collections.Collection col) {
  		fire(key, null, col, false);
  	}
  		
  	private void fire(EventKey key, DocumentImpl doc, org.exist.collections.Collection col, boolean documentEvent) {
  		fire(listenerMaps[Depth.ZERO.ordinal()].get(key), key, doc, col, documentEvent);
  		
  		int k = key.path.lastIndexOf('/');
  		assert k != -1;
  		if (k > 0) {
  			EventKey trimmedKey = new EventKey(key.path.substring(0, k), key.trigger);
  			fire(listenerMaps[Depth.ONE.ordinal()].get(trimmedKey), key, doc, col, documentEvent);
  		}
  		
  		SortedMap<EventKey,List<ListenerWrapper>> map =
  				(SortedMap<EventKey,List<ListenerWrapper>>) listenerMaps[Depth.MANY.ordinal()];
  		SortedMap<EventKey,List<ListenerWrapper>> tailMap;
  		synchronized(map) {
  			tailMap = new TreeMap<EventKey,List<ListenerWrapper>>(map.tailMap(key));
  		}
  		for (Map.Entry<EventKey,List<ListenerWrapper>> entry : tailMap.entrySet()) {
  			EventKey target = entry.getKey();
  			if (!target.matchesAsPrefix(key)) break;
  			fire(entry.getValue(), key, doc, col, documentEvent);
  		}
  	}
  	
  	private void fire(List<ListenerWrapper> list, EventKey key, DocumentImpl doc, org.exist.collections.Collection col, boolean documentEvent) {
  		if (list == null) return;
  		List<ListenerWrapper> listCopy;
  		synchronized(list) {
  			for (Iterator<ListenerWrapper> it = list.iterator(); it.hasNext(); ) {
  