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

ca.odell.glazedlists.FunctionList Maven / Gradle / Ivy

/* 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} */ @Override protected boolean isWritable() { return true; } /** {@inheritDoc} */ @Override 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} */ @Override public E get(int index) { return mappedElements.get(index); } /** {@inheritDoc} */ @Override public E remove(int index) { final E removed = get(index); source.remove(index); return removed; } /** {@inheritDoc} */ @Override public E set(int index, E value) { final E updated = get(index); source.set(index, reverse(value)); return updated; } /** {@inheritDoc} */ @Override 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; } } }