source.ca.odell.glazedlists.FunctionList Maven / Gradle / Ivy
Show all versions of glazedlists_java15 Show documentation
/* 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.List;
import java.util.RandomAccess;
/**
* This List is meant to simplify the task of transforming each element of a
* source list to an element stored at the same index in this FunctionList.
* The logic of precisely how to transform the source elements is contained
* within a {@link Function} that must be supplied at the time of construction
* but can be changed afterward using {@link #setForwardFunction}. This
* {@link Function} is called the forward function because it creates elements
* in this {@link FunctionList} from elements that have been added or mutated
* within the source list. The forward function may be an implementation of
* either {@link Function} or {@link AdvancedFunction}.
*
* An optional reverse {@link Function} which is capable of mapping the
* elements of this FunctionList back to the corresponding source element may
* be supplied in order to use the following mutating methods:
*
*
* - {@link #add(Object)}
*
- {@link #add(int, Object)}
*
- {@link #set(int, Object)}
*
*
* If the reverse {@link Function} is not supplied then callers of those
* methods will receive an {@link IllegalStateException} explaining that those
* operations are not available without the reverse {@link Function}.
*
* If specified, the reverse {@link Function} should do its best to
* maintain the invariant:
*
*
o.equals(reverseFunction.evaluate(forwardFunction.evaluate(o)))
* for any o that is non-null.
*
*
Note: if two source elements share the same identity
* (i.e. source.get(i) == source.get(j) when i != j), it is up to author of the
* {@link Function} to decide if and how to
* preserve the relationship of their identities after their transformation.
*
* Warning: This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
*
* EventList Overview
* Writable: yes
* Concurrency: thread ready, not thread safe
* Performance: reads: O(1), writes O(1) amortized
* Memory:
* Unit Tests: FunctionList
* Issues:
* 282
*
*
*/
public final class FunctionList extends TransformedList implements RandomAccess {
private final List sourceElements;
/** A list of the Objects produced by running the source elements through the {@link #forward} Function. */
private final List mappedElements;
/** The Function that maps source elements to FunctionList elements. */
private AdvancedFunction forward;
/** The Function that maps FunctionList elements back to source elements. It may be null. */
private Function reverse;
/**
* Construct a {@link FunctionList} which stores the result of transforming
* each source element using the given forward {@link Function}. No reverse
* {@link Function} will be specified implying that {@link #add(Object)},
* {@link #add(int, Object)} and {@link #set(int, Object)} will throw
* {@link IllegalArgumentException} if they are called.
*
* @param source the EventList to decorate with a function transformation
* @param forward the function to execute on each source element
*/
public FunctionList(EventList source, Function forward) {
this(source, forward, null);
}
/**
* Construct a {@link FunctionList} which stores the result of transforming
* each source element using the given forward {@link Function}. If the
* reverse {@link Function} is not null, {@link #add(Object)},
* {@link #add(int, Object)} and {@link #set(int, Object)} will execute
* as expected.
*
* Note: an {@link AdvancedFunction} can be specified for the forward
* {@link Function} which allows the implementor a chance to examine the
* prior value that was mapped to a source element when it must be remapped
* due to a modification (from a call to {@link List#set}).
*
* @param source the EventList to decorate with a function transformation
* @param forward the function to execute on each source element
* @param reverse the function to map elements of FunctionList back to
* element values in the source list
*/
public FunctionList(EventList source, Function forward, Function reverse) {
super(source);
updateForwardFunction(forward);
setReverseFunction(reverse);
// save a reference to the source elements
this.sourceElements = new ArrayList(source);
// map all of the elements within source
this.mappedElements = new ArrayList(source.size());
for (int i = 0, n = source.size(); i < n; i++) {
this.mappedElements.add(forward(source.get(i)));
}
source.addListEventListener(this);
}
/**
* A convenience method to map a source element to a {@link FunctionList}
* element using the forward {@link Function}.
*
* @param s the source element to be transformed
* @return the result of transforming the source element
*/
private E forward(S s) {
return forward.evaluate(s);
}
/**
* A convenience method to remap a source element to a {@link FunctionList}
* element using the forward {@link Function}.
*
* @param e the last prior result of transforming the source element
* @param s the source element to be transformed
* @return the result of transforming the source element
*/
private E forward(E e, S s) {
return forward.reevaluate(s, e);
}
/**
* A convenience method to map a {@link FunctionList} element to a source
* element using the reverse {@link Function}.
*
* @param e the {@link FunctionList} element to be transformed
* @return the result of transforming the {@link FunctionList} element
*/
private S reverse(E e) {
if (reverse == null)
throw new IllegalStateException("A reverse mapping function must be specified to support this List operation");
return reverse.evaluate(e);
}
/**
* Changes the {@link Function} that evaluates source elements to produce
* mapped elements. Calling this method with a different
* forward
Function will cause all elements in this
* FunctionList to be reevaluated.
*
* Callers of this method typically also want to update the reverse
* function using {@link #setReverseFunction} if one exists.
*/
public void setForwardFunction(Function forward) {
updateForwardFunction(forward);
updates.beginEvent(true);
// remap all of the elements within source
for (int i = 0, n = source.size(); i < n; i++) {
final E oldValue = this.mappedElements.set(i, forward(source.get(i)));
updates.elementUpdated(i, oldValue);
}
updates.commitEvent();
}
/**
* A convenience method to run a null check on the given
* forward
Function and to wrap it in a delegating
* implementation of the {@link AdvancedFunction} interface as needed.
*/
private void updateForwardFunction(Function forward) {
if (forward == null)
throw new IllegalArgumentException("forward Function may not be null");
// wrap the forward function in an adapter to the AdvancedFunction interface if necessary
if (forward instanceof AdvancedFunction)
this.forward = (AdvancedFunction) forward;
else
this.forward = new AdvancedFunctionAdapter(forward);
}
/**
* Returns the {@link Function} which maps source elements to elements
* stored within this {@link FunctionList}. The {@link Function} is
* guaranteed to be non-null.
*/
public Function getForwardFunction() {
// unwrap the forward function from an AdvancedFunctionAdapter if necessary
if (forward instanceof AdvancedFunctionAdapter)
return ((AdvancedFunctionAdapter) forward).getDelegate();
else
return forward;
}
/**
* Changes the {@link Function} that evaluates FunctionList elements to
* produce the original source element with which it corresponds. The
* reverse Function will be used in all subsequent calls to:
*
*
* - {@link #add(Object)}
*
- {@link #add(int, Object)}
*
- {@link #set(int, Object)}
*
*
* This method should typically be called at the same time the forward
* function is changed using {@link #setForwardFunction}.
*/
public void setReverseFunction(Function reverse) {
this.reverse = reverse;
}
/**
* Returns the {@link Function} which maps elements stored within this
* {@link FunctionList} back to elements within the source list or
* null
if no such {@link Function} was specified.
*/
public Function getReverseFunction() {
return reverse;
}
/** {@inheritDoc} */
protected boolean isWritable() {
return true;
}
/** {@inheritDoc} */
public void listChanged(ListEvent listChanges) {
updates.beginEvent(true);
if (listChanges.isReordering()) {
final int[] reorderMap = listChanges.getReorderMap();
final List originalMappedElements = new ArrayList(mappedElements);
for (int i = 0; i < reorderMap.length; i++) {
final int sourceIndex = reorderMap[i];
mappedElements.set(i, originalMappedElements.get(sourceIndex));
}
updates.reorder(reorderMap);
} else {
while (listChanges.next()) {
final int changeIndex = listChanges.getIndex();
final int changeType = listChanges.getType();
if (changeType == ListEvent.INSERT) {
final S newValue = source.get(changeIndex);
final E newValueTransformed = forward(newValue);
sourceElements.add(changeIndex, newValue);
mappedElements.add(changeIndex, newValueTransformed);
updates.elementInserted(changeIndex, newValueTransformed);
} else if (changeType == ListEvent.UPDATE) {
final E oldValueTransformed = get(changeIndex);
final S newValue = source.get(changeIndex);
final E newValueTransformed = forward(oldValueTransformed, newValue);
sourceElements.set(changeIndex, newValue);
mappedElements.set(changeIndex, newValueTransformed);
updates.elementUpdated(changeIndex, oldValueTransformed, newValueTransformed);
} else if (changeType == ListEvent.DELETE) {
final S oldValue = sourceElements.remove(changeIndex);
final E oldValueTransformed = mappedElements.remove(changeIndex);
forward.dispose(oldValue, oldValueTransformed);
updates.elementDeleted(changeIndex, oldValueTransformed);
}
}
}
updates.commitEvent();
}
/** {@inheritDoc} */
public E get(int index) {
return mappedElements.get(index);
}
/** {@inheritDoc} */
public E remove(int index) {
final E removed = get(index);
source.remove(index);
return removed;
}
/** {@inheritDoc} */
public E set(int index, E value) {
final E updated = get(index);
source.set(index, reverse(value));
return updated;
}
/** {@inheritDoc} */
public void add(int index, E value) {
source.add(index, reverse(value));
}
/**
* A Function encapsulates the logic for transforming a list element into
* any kind of Object. Implementations should typically create and return
* new objects, though it is permissible to return the original value
* unchanged (i.e. the Identity Function).
*/
public interface Function {
/**
* Transform the given sourceValue
into any kind of Object.
*
* @param sourceValue the Object to transform
* @return the transformed version of the object
*/
public B evaluate(A sourceValue);
}
/**
* An AdvancedFunction is an extension of the simple Function interface
* which provides more hooks in the lifecycle of the transformation of a
* source element. Specifically, it includes:
*
*
* - {@link #reevaluate} which is called when an element is mutated
* in place and thus run through this mapping function again. It
* provides access to the previous value returned from this function
* in case it is of use when remapping the same element.
*
*
- {@link #dispose} which is called when an element is removed from
* the FunctionList and is meant to be location that cleans up any
* resources the Function may have allocated. (like Listeners for
* example)
*
*
* If neither of these extensions to FunctionList are useful, users are
* encouraged to implement only the Function interface for their forward
* function.
*/
public interface AdvancedFunction extends Function {
/**
* Evaluate the sourceValue
again to produce the
* corresponding value in the FunctionList. The last
* transformedValue
is provided as a reference when
* evaluating a sourceValue
that has previously been
* evaluated.
*
* @param sourceValue the Object to transform (again)
* @param transformedValue the Object produced by this function the
* last time it evaluated sourceValue
* @return the transformed version of the sourceValue
*/
public B reevaluate(A sourceValue, B transformedValue);
/**
* Perform any necessary resource cleanup on the given
* sourceValue
and transformedValue
as they
* are removed from the FunctionList such as installed listeners.
*
* @param sourceValue the Object that was transformed
* @param transformedValue the Object that resulted from the last
* transformation
*/
public void dispose(A sourceValue, B transformedValue);
}
/**
* This class adapts an implementation of the simple {@link Function}
* interface to the {@link AdvancedFunction} interface. This is purely to
* ease the implementation of FunctionList since it can treat all forward
* functions as though they are AdvancedFunctions which means less casting.
*/
private static final class AdvancedFunctionAdapter implements AdvancedFunction {
private final Function delegate;
/**
* Adapt the given delegate
to the
* {@link AdvancedFunction} interface.
*/
public AdvancedFunctionAdapter(Function delegate) {
this.delegate = delegate;
}
/**
* Defers to the delegate.
*/
public B evaluate(A sourceValue) {
return delegate.evaluate(sourceValue);
}
/**
* Defers to the delegate's {@link Function#evaluate} method.
*/
public B reevaluate(A sourceValue, B transformedValue) {
return evaluate(sourceValue);
}
public void dispose(A sourceValue, B transformedValue) {
// do nothing
}
public Function getDelegate() {
return delegate;
}
}
}