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

ca.odell.glazedlists.FunctionList 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;

// the Glazed Lists' change objects
import ca.odell.glazedlists.event.ListEvent;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 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 tranform the source elements is contained
 * within a {@link Function} that must be supplied at the time of construction
 * and can never be changed. 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 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. * *

* * * * * * * *
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 { 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 final AdvancedFunction forward; /** The Function that maps FunctionList elements back to source elements. It may be null. */ private final 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 * correctly. * *

Note: a {@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); if (forward == null) throw new IllegalArgumentException("forward Function may not be null"); // wrap the forward function in an adapter to the AdvancedFunction interface it is isn't yet if (forward instanceof AdvancedFunction) this.forward = (AdvancedFunction) forward; else this.forward = new AdvancedFunctionAdapter(forward); this.reverse = 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 (Iterator iter = source.iterator(); iter.hasNext();) { this.mappedElements.add(this.forward(iter.next())); } 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 this.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 this.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 (this.reverse == null) throw new IllegalStateException("A reverse mapping function must be specified to support this List operation"); return this.reverse.evaluate(e); } /** * 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 (this.forward instanceof AdvancedFunctionAdapter) return ((AdvancedFunctionAdapter) this.forward).getDelegate(); else return this.forward; } /** * Returns the {@link Function} which maps elements stored within this * {@link FunctionList} to elements within the source list or * null if no such {@link Function} was specified at * construction. */ public Function getReverseFunction() { return this.reverse; } /** {@inheritDoc} */ protected boolean isWritable() { return true; } /** {@inheritDoc} */ public void listChanged(ListEvent listChanges) { while (listChanges.next()) { final int changeIndex = listChanges.getIndex(); final int changeType = listChanges.getType(); if (changeType == ListEvent.INSERT) { final S inserted = source.get(changeIndex); this.sourceElements.add(changeIndex, inserted); this.mappedElements.add(changeIndex, this.forward(inserted)); } else if (changeType == ListEvent.UPDATE) { final S updated = source.get(changeIndex); this.sourceElements.set(changeIndex, updated); this.mappedElements.set(changeIndex, this.forward(this.get(changeIndex), updated)); } else if (changeType == ListEvent.DELETE) { final S deletedSource = this.sourceElements.remove(changeIndex); final E deletedTransform = this.mappedElements.remove(changeIndex); this.forward.dispose(deletedSource, deletedTransform); } } listChanges.reset(); updates.forwardEvent(listChanges); } /** {@inheritDoc} */ public E get(int index) { return this.mappedElements.get(index); } /** {@inheritDoc} */ public E remove(int index) { final E removed = this.get(index); source.remove(index); return removed; } /** {@inheritDoc} */ public E set(int index, E value) { final E updated = this.get(index); source.set(index, this.reverse(value)); return updated; } /** {@inheritDoc} */ public void add(int index, E value) { source.add(index, this.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 * resource 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. For example, an 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 this.delegate.evaluate(sourceValue); } /** * Defers to the delegate's {@link Function#evaluate} method. */ public B reevaluate(A sourceValue, B transformedValue) { return this.evaluate(sourceValue); } public void dispose(A sourceValue, B transformedValue) { // do nothing } public Function getDelegate() { return this.delegate; } } }