All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.odell.glazedlists.matchers.ThreadedMatcherEditor Maven / Gradle / Ivy

/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.matchers;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * A MatcherEditor which decorates a source MatcherEditor with functionality.
 * Specifically, this MatcherEditor is meant to act as a buffer for
 * MatcherEvents to smooth out disparities between the rate at which
 * MatcherEvents are produced by the source MatcherEditor and the rate at
 * which they are consumed by registered MatcherEditorListeners. 

* * Internally, a {@link ThreadedMatcherEditor} enqueues MatcherEvents as they * they are received from the source MatcherEditor. The MatcherEvents on the * queue are fired by another Thread as rapidly as the MatcherEditorListeners * can consume them. Two methods exist on this class which enable subclasses * to customize aspects of processing queued MatcherEvents: * *

    *
  1. {@link #executeMatcherEventQueueRunnable(Runnable)} is consulted when * a Thread must be selected to execute the given Runnable which will * drain the queue of MatcherEvents. Subclasses may override to * customize which Thread is used. * *
  2. {@link #coalesceMatcherEvents(List)} is used to compress * many enqueued MatcherEvents into a single representative * MatcherEvent. This implies a contract between all registered * MatcherEditorListeners and this {@link ThreadedMatcherEditor} that * guarantees that processing the coalesced MatcherEvent is equivalent * to processing all MatcherEvents sequentially. *
* * Typical usage patterns of ThreadedMatcherEditor resemble: * *
 *   MatcherEditor threadedMatcherEditor = new ThreadedMatcherEditor(new AnyMatcherEditor());
 *   FilterList filterList = new FilterList(new BasicEventList(), threadedMatcherEditor);
 * 
* * @author James Lemieux */ public class ThreadedMatcherEditor extends AbstractMatcherEditorListenerSupport { /** The underlying MatcherEditor whose MatcherEvents are being queued and fired on an alternate Thread. */ private final MatcherEditor source; /** * The LinkedList acting as a queue of MatcherEditor.Event in the order in which they are received * from {@link #source}. We take great care to ensure that the queue's monitor is held before it * is queried or mutated. */ private final List> matcherEventQueue = new LinkedList>(); /** * The MatcherEditorListener which reacts to MatcherEvents from the {@link #source} * by enqueuing them for firing on another Thread at some later time. */ private MatcherEditor.Listener queuingMatcherEditorListener = new QueuingMatcherEditorListener(); /** * true indicates a Thread is currently executing the * {@link #drainMatcherEventQueueRunnable} to drain the {@link #matcherEventQueue}. */ private boolean isDrainingQueue = false; /** * The {@link Runnable} containing the logic to drain the queue of MatcherEvents until it is empty. * The Runnable is executed on a Thread using {@link #executeMatcherEventQueueRunnable(Runnable)}. */ private Runnable drainMatcherEventQueueRunnable = new DrainMatcherEventQueueRunnable(); /** * Creates a ThreadedMatcherEditor which wraps the given source. * MatcherEvents fired from the source will be enqueued within * this MatcherEditor until they are processed on an alternate Thread. * * @param source the MatcherEditor to wrap with buffering functionality * @throws NullPointerException if source is null */ public ThreadedMatcherEditor(MatcherEditor source) { if (source == null) throw new NullPointerException("source may not be null"); this.source = source; this.source.addMatcherEditorListener(this.queuingMatcherEditorListener); } /** * Returns the current Matcher specified by the source {@link MatcherEditor}. * * @return the current Matcher specified by the source {@link MatcherEditor} */ public Matcher getMatcher() { return this.source.getMatcher(); } /** * This method implements the strategy for coalescing many queued * MatcherEvents into a single representative MatcherEvent. Listeners which * process the MatcherEvent returned from this method should match the state * that would exist if each of the matcherEvents were fired * sequentially. In general, any group of matcherEvents can be * succesfully coalesced as a single MatcherEvent with a type of * changed, however, this method's default implementation * uses a few heuristics to do more intelligent coalescing in order to * gain speed improvements: * *
    *
  1. if matcherEvents ends in a MatcherEvent which is a * {@link MatcherEditor.Event#MATCH_ALL} or {@link MatcherEditor.Event#MATCH_NONE} * type, the last MatcherEvent is returned, regardless of previous * MatcherEvents

    * *

  2. if matcherEvents only contains a series of * monotonically constraining MatcherEvents, the final MatcherEvent * is returned

    * *

  3. if matcherEvents only contains a series of * monotonically relaxing MatcherEvents, the final MatcherEvent is * returned

    * *

  4. if matcherEvents contains both constraining and * relaxing MatcherEvents, the final MatcherEvent is returned with * its type as {@link MatcherEditor.Event#CHANGED} *
* * Note that 1, 2, and 3 above merely represent * safe optimizations of the type of MatcherEvent that can be returned. * It could also have been returned as a MatcherEvent with a type of * {@link MatcherEditor.Event#CHANGED} and be assumed to work correctly, though * potentially less efficiently, since it is a more generic type of change. *

* * Subclasses with the ability to fire precise MatcherEvents with fine grain * types (i.e. relaxed or constrained) when * coalescing matcherEvents in situations not recounted above * may do so by overiding this method. * * @param matcherEvents an array of MatcherEvents recorded in the order * they were received from the source MatcherEditor * @return a single MatcherEvent which, when fired, will result in the * same state as if all matcherEvents had been fired * sequentially */ protected Event coalesceMatcherEvents(List> matcherEvents) { boolean changeType = false; // fetch the last matcher event - it is the basis of the MatcherEvent which must be returned // all that remains is to determine the type of the MatcherEvent to return final Event lastMatcherEvent = matcherEvents.get(matcherEvents.size()-1); final int lastMatcherEventType = lastMatcherEvent.getType(); // if the last MatcherEvent is a MATCH_ALL or MATCH_NONE type, we can safely return it immediately if (lastMatcherEventType != Event.MATCH_ALL && lastMatcherEventType != Event.MATCH_NONE) { // otherwise determine if any constraining and/or relaxing MatcherEvents exist boolean constrained = false; boolean relaxed = false; for (Iterator> i = matcherEvents.iterator(); i.hasNext(); ) { switch (i.next().getType()) { case Event.MATCH_ALL: relaxed = true; break; case Event.MATCH_NONE: constrained = true; break; case Event.RELAXED: relaxed = true; break; case Event.CONSTRAINED: constrained = true; break; case Event.CHANGED: constrained = relaxed = true; break; } } changeType = constrained && relaxed; } // if both constraining and relaxing MatcherEvents exist, ensure we must return a CHANGED MatcherEvent // otherwise the last MatcherEvent must represent the coalesced MatcherEvent return new MatcherEditor.Event(this, changeType ? Event.CHANGED : lastMatcherEventType, lastMatcherEvent.getMatcher()); } /** * This method executes the given runnable on a Thread. The * particular Thread chosen to execute the Runnable is left as an * implementation detail. By default, a new Thread named * MatcherQueueThread is constructed to execute the * runnable each time this method is called. Subclasses may * override this method to use any Thread selection strategy they wish. * * @param runnable a Runnable to execute on an alternate Thread */ protected void executeMatcherEventQueueRunnable(Runnable runnable) { new Thread(runnable, "MatcherQueueThread").start(); } /** * This MatcherEditorListener enqueues each MatcherEvent it receives in the * order it is received and then schedules a Runnable to drain the queue of * MatcherEvents as soon as possible. */ private class QueuingMatcherEditorListener implements MatcherEditor.Listener { public void changedMatcher(Event matcherEvent) { synchronized(matcherEventQueue) { matcherEventQueue.add(matcherEvent); // if necessary, start a Thread to drain the queue if (!isDrainingQueue) { isDrainingQueue = true; executeMatcherEventQueueRunnable(drainMatcherEventQueueRunnable); } } } } /** * This Runnable contains logic which continues to process batches of * MatcherEvents from the matcherEventQueue until the queue is empty. Each * batch of MatcherEvents includes all MatcherEvents available at the time * the queue is inspected. The MatcherEvents are then coalesced and the * resulting singular MatcherEvent is fired to MatcherEditorListeners * attached to this ThreadedMatcherEditor on a different Thread. When the * fire method returns, the queue is drained again if it has accumulated * MatcherEvents otherwise the DrainMatcherEventQueueRunnable exits. */ private class DrainMatcherEventQueueRunnable implements Runnable { public void run() { while (true) { // acquire the monitor that guards assigning the drainMatcherEventQueueRunnable // to a processing Thread as well as exiting the drainMatcherEventQueueRunnable final Event matcherEvent; synchronized (matcherEventQueue) { // if no work exists in the queue, exit the Runnable if (matcherEventQueue.isEmpty()) { // no matter the circumstance for us exiting the Runnable, // ensure we indicate we are no longer draining the queue isDrainingQueue = false; return; } // fetch a copy of all MatcherEvents currently in the queue matcherEvent = coalesceMatcherEvents(matcherEventQueue); matcherEventQueue.clear(); } try { // coalesce all of the current MatcherEvents to a single representative MatcherEvent // and fire the single coalesced MatcherEvent fireChangedMatcher(matcherEvent); } catch(Error e) { synchronized(matcherEventQueue) { isDrainingQueue = false; } throw e; } catch(RuntimeException e) { synchronized(matcherEventQueue) { isDrainingQueue = false; } throw e; } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy