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

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

There is a newer version: 1.9.1
Show newest version
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.gui;

// Java collections are used for underlying data storage
import java.util.*;
// the core Glazed Lists packages
import ca.odell.glazedlists.*;
import ca.odell.glazedlists.event.*;

/**
 * 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 with this limitation 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 limitation, * 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. *
* *

Then some time later 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 so that no more concurrent changes can occur *
  2. All enqueued changes are combined into one 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 completed set of events 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 internal 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. * * @author Jesse Wilson */ public abstract class ThreadProxyEventList extends TransformedList { /** a local cache of the source list */ private List localCache = new ArrayList(); /** propagates events on the proxy thread */ private UpdateRunner updateRunner = new UpdateRunner(); /** whether the proxy thread has been scheduled */ private 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)}. */ public ThreadProxyEventList(EventList source) { super(source); // populate the initial cache value localCache.addAll(source); // handle my own events to update the internal state this.addListEventListener(updateRunner); // handle changes in the source event list source.addListEventListener(this); } /** {@inheritDoc} */ 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); } // add the changes for this event to our queue updates.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. */ protected abstract void schedule(Runnable runnable); /** {@inheritDoc} */ public final int size() { return localCache.size(); } /** {@inheritDoc} */ public final E get(int index) { return localCache.get(index); } /** {@inheritDoc} */ protected final boolean isWritable() { return true; } /** * Apply the {@link ListEvent} to the {@link List}. */ protected List applyChangeToCache(List 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; } /** * 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. */ public void run() { getReadWriteLock().writeLock().lock(); try { updates.commitEvent(); scheduled = false; } finally { getReadWriteLock().writeLock().unlock(); } } /** * Update local state as a consequence of the change event. */ public void listChanged(ListEvent listChanges) { localCache = applyChangeToCache(source, listChanges, localCache); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy