ca.odell.glazedlists.SequenceList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glazedlists_java15 Show documentation
Show all versions of glazedlists_java15 Show documentation
Event-driven lists for dynamically filtered and sorted tables
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import ca.odell.glazedlists.event.ListEvent;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.RandomAccess;
/**
* A SequenceList contains values in adjacent indices which occur at predictable
* intervals from each other. A simple SequenceList could be:
* {-10, -5, 0, 5, 10, 15}
*
* while a more sophisticated example could be:
* {Jun 1, Jul 1, Aug 1, Sep 1, Oct 1}
*
* As long as the values can be ordered via a {@link Comparator} and a
* {@link Sequencer} can be implemented to reliably produce the next or previous
* value in a sequence using only some value from the source list.
*
* SequenceList is a readonly list; calling any write method on this list
* will produce an {@link UnsupportedOperationException}.
*
*
The start and end values of the sequence are the smallest sequence values
* which maintain the invariant that:
* sequence start <= each value in the source list <= sequence end
*
*
Warning: This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
*
* EventList Overview
* Writable: no
* Concurrency: thread ready, not thread safe
* Performance: reads: O(1)
* Memory: O(N)
* Unit Tests: SequenceListTest
* Issues: N/A
*
*
* @author James Lemieux
*/
public final class SequenceList extends TransformedList implements RandomAccess {
/** The values participating in the sequence. */
private final List sequence = new ArrayList();
/** The comparator that defines the order of the source and sequence values. */
private final Comparator super E> comparator;
/**
* The object containing the logic which produces next and previous
* sequence values by inspecting any source value.
*/
private final Sequencer sequencer;
/**
* Constructs a SequenceList containing a sequence of values produced by
* the sequencer
which cover the range of values contained
* within the source
.
*
* @param source the raw values to build a sequence around
* @param sequencer the logic to produce sequence values relative to a value
*/
public SequenceList(EventList source, Sequencer sequencer) {
this(source, sequencer, (Comparator) GlazedLists.comparableComparator());
}
/**
* Constructs a SequenceList containing a sequence of values produced by
* the sequencer
which cover the range of values contained
* within the source
. The given comparator
* determines the order of the sequence values.
*
* @param source the raw values to build a sequence around
* @param sequencer the logic to produce sequence values relative to a value
* @param comparator determines the order of the sequence values
*/
public SequenceList(EventList source, Sequencer sequencer, Comparator super E> comparator) {
this(new SortedList(source, comparator), sequencer, comparator);
}
private SequenceList(SortedList source, Sequencer sequencer, Comparator super E> comparator) {
super(source);
if (sequencer == null)
throw new IllegalArgumentException("sequencer may not be null");
if (comparator == null)
throw new IllegalArgumentException("comparator may not be null");
this.sequencer = sequencer;
this.comparator = comparator;
this.updateSequence();
source.addListEventListener(this);
}
/**
* @return false; SequenceList is readonly
*/
@Override
protected boolean isWritable() {
return false;
}
/** {@inheritDoc} */
@Override
public int size() {
return this.sequence.size();
}
/** {@inheritDoc} */
@Override
public E get(int index) {
return this.sequence.get(index);
}
/**
* A Sequencer defines the logic required to calculate the previous and
* next sequence values given any value. It is important to note that the
* arguments passed to {@link #previous} and {@link #next} will not always
* be sequence values themselves. For example if a Sequencer is contains
* logic to produce a sequence of numbers evenly divisible by 2, it must
* handle returning the next and previous even number relative to
* any integer. So the Sequencer logic must produce:
*
*
* previous(5)
returns 4
* previous(6)
returns 4
* next(5)
returns 6
* next(4)
returns 6
*
*/
public interface Sequencer {
/**
* Given a sequencable value
, produce the previous value
* in the sequence such that value
is now included in the
* sequence.
*
* @param value a sequencable value
* @return the previous value in the sequence such that value
* would be included within the bounds of the sequence
*/
public E previous(E value);
/**
* Given a sequencable value
, produce the next value
* in the sequence such that value
is now included in the
* sequence.
*
* @param value a sequencable value
* @return the next value in the sequence such that value
* would be included within the bounds of the sequence
*/
public E next(E value);
}
/**
* Returns true if value
is exactly a sequence value
* (i.e. could be stored at some index within this {@link SequenceList}.
*/
private boolean isSequenceValue(E value) {
final E sequencedValue = sequencer.previous(sequencer.next(value));
return comparator.compare(value, sequencedValue) == 0;
}
/**
* Returns the previous value in the sequence defined by this list or
* value
itself if it is a sequence value.
*
* @param value the value relative to which the previous sequence value is returned
* @return the previous sequence value relative to the given value
*/
public E getPreviousSequenceValue(E value) {
// if value is already a sequence value, return it
if (isSequenceValue(value))
return value;
// ask the sequencer for the previous value
return sequencer.previous(value);
}
/**
* Returns the next value in the sequence defined by this list or
* value
itself if it is a sequence value.
*
* @param value the value relative to which the next sequence value is returned
* @return the next sequence value relative to the given value
*/
public E getNextSequenceValue(E value) {
// if value is already a sequence value, return it
if (isSequenceValue(value))
return value;
// ask the sequencer for the next value
return sequencer.next(value);
}
/** {@inheritDoc} */
@Override
public void listChanged(ListEvent listChanges) {
this.updateSequence();
}
/**
* A convenience method to update the sequence to minimally cover the
* underlying SortedList.
*/
private void updateSequence() {
updates.beginEvent();
// check for the special case when the underlying list has been completely cleared
if (source.isEmpty()) {
while (!sequence.isEmpty()) {
updates.elementDeleted(0, sequence.remove(0));
}
} else {
// seed this SequenceList with the initial two values
if (this.isEmpty()) {
final E value = source.get(0);
final E previousSequenceValue = getPreviousSequenceValue(value);
final E nextSequenceValue = getNextSequenceValue(value);
sequence.add(0, previousSequenceValue);
updates.elementInserted(0, previousSequenceValue);
sequence.add(1, nextSequenceValue);
updates.elementInserted(1, nextSequenceValue);
}
// add the necessary leading sequence values
final E firstSourceValue = source.get(0);
while (comparator.compare(firstSourceValue, get(0)) == -1) {
E element = sequencer.previous(get(0));
sequence.add(0, element);
updates.elementInserted(0, element);
}
// remove the unnecessary leading sequence values
while (comparator.compare(get(1), firstSourceValue) == -1) {
E oldValue = sequence.remove(0);
updates.elementDeleted(0, oldValue);
}
// add the necessary trailing sequence values
final E lastSourceValue = source.get(source.size()-1);
while (comparator.compare(lastSourceValue, get(size()-1)) == 1) {
E element = sequencer.next(get(size() - 1));
int index = size();
sequence.add(index, element);
updates.elementInserted(index, element);
}
// remove the unnecessary trailing sequence values
while (comparator.compare(get(size()-2), lastSourceValue) == 1) {
final int lastIndex = size()-1;
updates.elementDeleted(lastIndex, sequence.remove(lastIndex));
}
}
updates.commitEvent();
}
}