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

ca.odell.glazedlists.impl.gui.ThreadProxyEventList Maven / Gradle / Ivy

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

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventAssembler;
import ca.odell.glazedlists.event.ListEventListener;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.RandomAccess;

/**
 * An {@link EventList} that only forwards its events on a proxy thread,
 * regardless of the thread of their origin.
 *
 * 

While the proxy thread is not executing, any number of events may arrive. * This means that multiple changes may have occurred before the proxy can * notify its listeners. One problem created by this scenario is that some of * these changes may contradict one another. For example, an inserted item may * be later removed before either event is processed. To overcome this problem, * this class uses the change-contradiction resolving logic of * {@link ListEventAssembler}. * *

The flow of events is as follows: *

    *
  1. Any thread makes a change to the source list and calls * {@link ThreadProxyEventList#listChanged(ListEvent)}. *
  2. The event is enqueued and the proxy thread is notified that it has a * task to process in the future. *
* *

At some point in the future, after one or more events have been enqueued, * the proxy thread executes and processes its queue: *

    *
  1. First it acquires a lock to prevent further concurrent changes *
  2. All enqueued changes are combined into a single change. Currently this * implementation does a best effort on conflict resolution. It is * limited in its handling of reordering events, as caused by changing a * {@link SortedList} {@link Comparator}. *
  3. The single, combined event is fired. *
  4. The first listener is the {@link ThreadProxyEventList} itself. It listens * to its own event because this event will be free of conflicts. It applies * the changes to its own private copy of the data. *
  5. All other listeners are notified of the change. *
  6. The lock is released. *
* *

The {@link ThreadProxyEventList} keeps a private copy of the elements of the * source {@link EventList}. This enables interested classes to read a consistent * (albeit potentially out of date) view of the data at all times. * *

Important: ThreadProxyEventList * relies heavily on its ability to pause changes to its source EventList * while it is updating its private copy of the source data. It does this by * acquiring the writeLock for the list pipeline. This implies that * ALL code which accesses the pipeline must be thread-safe * (or the acquisition of the writeLock will be meaningless). See * {@link EventList} for an example of thread safe code. * * @author Jesse Wilson */ public abstract class ThreadProxyEventList extends TransformedList implements RandomAccess { /** a local cache of the source list */ private List localCache = new ArrayList(); /** propagates events on the proxy thread */ private UpdateRunner updateRunner = new UpdateRunner(); /** propagates events immediately. The source events might be fired later */ private final ListEventAssembler cacheUpdates = new ListEventAssembler(this, ListEventAssembler.createListEventPublisher()); /** whether the proxy thread has been scheduled */ private volatile boolean scheduled = false; /** * Create a {@link ThreadProxyEventList} which delivers changes to the * given source on a particular {@link Thread}, called the * proxy {@link Thread} of a subclasses choosing. The {@link Thread} used * depends on the implementation of {@link #schedule(Runnable)}. * * @param source the {@link EventList} for which to proxy events */ public ThreadProxyEventList(EventList source) { super(source); // populate the initial cache value localCache.addAll(source); // handle my own events to update the internal state cacheUpdates.addListEventListener(updateRunner); // handle changes in the source event list source.addListEventListener(this); } /** {@inheritDoc} */ @Override public final void listChanged(ListEvent listChanges) { // if we've haven't scheduled a commit, we need to begin a new event if(!scheduled) { updates.beginEvent(true); cacheUpdates.beginEvent(true); } // add the changes for this event to our queue updates.forwardEvent(listChanges); cacheUpdates.forwardEvent(listChanges); // commit the event on the appropriate thread if(!scheduled) { scheduled = true; schedule(updateRunner); } } /** * Schedule the specified runnable to be executed on the proxy thread. * * @param runnable a unit of work to be executed on the proxy thread */ protected abstract void schedule(Runnable runnable); /** {@inheritDoc} */ @Override public final int size() { return localCache.size(); } /** {@inheritDoc} */ @Override public final E get(int index) { return localCache.get(index); } /** {@inheritDoc} */ @Override protected final boolean isWritable() { return true; } /** * Apply the {@link ListEvent} to the {@link List}. * * @param source the EventList whose changes are being proxied to another thread * @param listChanges the list of changes from the source to be applied * @param localCache a private snapshot of the source which * is now out of sync with that source list and will be repaired * @return a new List to serve as the up-to-date local cache */ private List applyChangeToCache(EventList source, ListEvent listChanges, List localCache) { List result = new ArrayList(source.size()); // cacheOffset is the running index delta between localCache and result int resultIndex = 0; int cacheOffset = 0; while(true) { // find the next change (or the end of the list) int changeIndex; int changeType; if(listChanges.next()) { changeIndex = listChanges.getIndex(); changeType = listChanges.getType(); } else { changeIndex = source.size(); changeType = -1; } // perform all the updates before this change for(; resultIndex < changeIndex; resultIndex++) { result.add(resultIndex, localCache.get(resultIndex + cacheOffset)); } // perform this change if(changeType == ListEvent.DELETE) { cacheOffset++; } else if(changeType == ListEvent.UPDATE) { result.add(resultIndex, source.get(changeIndex)); resultIndex++; } else if(changeType == ListEvent.INSERT) { result.add(resultIndex, source.get(changeIndex)); resultIndex++; cacheOffset--; } else if(changeType == -1) { break; } } return result; } /** {@inheritDoc} */ @Override public void dispose() { super.dispose(); cacheUpdates.removeListEventListener(updateRunner); } /** * Updates the internal data using the proxy thread. * * @author Jesse Wilson */ private class UpdateRunner implements Runnable, ListEventListener { /** * When run, this combines all events thus far and forwards them. * *

If a reordering event is being forwarded, the reordering may be lost * if it arrives simultaneously with another event. This is somewhat of a * hack for the time being. Hopefully later we can refine this so that a * new event is created with these changes properly. */ @Override public void run() { getReadWriteLock().writeLock().lock(); try { // We need to apply the changes to the local cache immediately, // before forwarding the event downstream to other listeners. // This is necessary so that intermediate states in this list // are visible to larger list changes (such as clearing tables, // see bug 447) cacheUpdates.commitEvent(); updates.commitEvent(); } finally { scheduled = false; getReadWriteLock().writeLock().unlock(); } } /** * Update local state as a consequence of the change event. */ @Override public void listChanged(ListEvent listChanges) { localCache = applyChangeToCache(source, listChanges, localCache); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy