How to write a filtered IObservableList

data-binding, eclipse-databinding, filter, java

Solution

I think extending `DecoratingObservableList` is a good start. I would also suggest concentrating on exactly the intended usage instead of immediately implementing the entire API. For example, do you need random-acces writing via `set`? If not, then don't bother implementing it. This covers a read-only view of a mutable `ObservableList`, mapping the change events of the decorated list to the appropriate change events on the filtered list:

public class FilteredObservableList<E> extends DecoratingObservableList
{
  private final IObservableList decorated;
  private final Predicate pred;
  private final List<E> filtered = new ArrayList();

  public FilteredObservableList(
      IObservableList decorated, Predicate pred, boolean disposeDecoratedOnDispose)
  {
    super(decorated, disposeDecoratedOnDispose);
    this.decorated = decorated;
    this.pred = pred;
    for (Object o : decorated) filtered.add(pred.eval(o)? (E) o : null);
  }

  @Override protected void handleListChange(ListChangeEvent event) {
    final List<ListDiffEntry> diffs = new ArrayList();
    final List<Integer> mapping = new ArrayList();
    int i = 0;
    for (E e : filtered) mapping.add(e != null? i++ : i);
    event.diff.accept(new ListDiffVisitor() {
      @Override public void handleAdd(int index, Object element) {
        final boolean passes = pred.eval(element);
        filtered.add(index, passes? (E) element : null);
        final Integer outInd = mapping.get(index);
        mapping.add(index, outInd);
        if (passes) {
          diffs.add(new FilteredDiffEntry(outInd, true, element));
          for (int i = index + 1; i < mapping.size(); i++)
            mapping.set(i, mapping.get(i) + 1);
        }
      }
      @Override public void handleRemove(int index, Object element) {
        final boolean passes = filtered.get(index) != null;
        filtered.remove(index);
        final int outInd = mapping.get(index);
        mapping.remove(index);
        if (passes) {
          diffs.add(new FilteredDiffEntry(outInd, false, element));
          for (int i = index; i < mapping.size(); i++)
            mapping.set(i, mapping.get(i)-1);
        }
      }
    });
    if (!diffs.isEmpty()) {
      final ListDiffEntry[] difAry = diffs.toArray(new ListDiffEntry[diffs.size()]);
      fireListChange(new ListDiff() {
        @Override public ListDiffEntry[] getDifferences() { return difAry; }
      });
    }
  }

  public ListIterator<E> listIterator() {
    getterCalled();
    final Iterator<E> it = decorated.iterator();
    return new ListIterator<E>() {
      E next;
      boolean nextReady;
      public boolean hasNext() {
        getterCalled();
        if (nextReady) return true;
        while (it.hasNext()) {
          next = it.next();
          if (next != null) { nextReady = true; break; }
        }
        return nextReady;
      }
      public E next() {
        getterCalled();
        if (hasNext()) { nextReady = false; return next; }
        else throw new NoSuchElementException();
      }
      public void add(Object o) { throw new UnsupportedOperationException(); }
      public boolean hasPrevious() { throw new UnsupportedOperationException(); }
      public int nextIndex() { throw new UnsupportedOperationException(); }
      public E previous() { throw new UnsupportedOperationException(); }
      public int previousIndex() { throw new UnsupportedOperationException(); }
      public void remove() { throw new UnsupportedOperationException(); }
      public void set(Object o) { throw new UnsupportedOperationException(); }
    };
  }

  public interface Predicate { boolean eval(Object o); }

  private static final class FilteredDiffEntry extends ListDiffEntry {
    private final int pos;
    private final boolean isAdd;
    private final Object el;
    FilteredDiffEntry(int pos, boolean isAdd, Object el) {
      this.pos = pos; this.isAdd = isAdd; this.el = el;
    }
    @Override public int getPosition() { return pos; }
    @Override public boolean isAddition() { return isAdd; }
    @Override public Object getElement() { return el; }
  }

  @Override public Object move(int _, int __) { throw new UnsupportedOperationException(); }
  @Override public Object remove(int _) { throw new UnsupportedOperationException(); }
  @Override public Object set(int _, Object __) { throw new UnsupportedOperationException(); }
  @Override public void add(int _, Object __) { throw new UnsupportedOperationException(); }
  @Override public boolean add(Object _) { throw new UnsupportedOperationException(); }
  @Override public boolean addAll(Collection _) { throw new UnsupportedOperationException(); }
  @Override public boolean addAll(int _, Collection __) {
    throw new UnsupportedOperationException();
  }
  @Override public void clear() { throw new UnsupportedOperationException(); }
  @Override public boolean remove(Object _) { throw new UnsupportedOperationException(); }
  @Override public boolean removeAll(Collection _) { throw new UnsupportedOperationException();}
  @Override public boolean retainAll(Collection _) { throw new UnsupportedOperationException();}
}

Problem

From some API call, I am getting back an `IObservableList<E>`, from the Eclipse Databinding framework. I'd wish to derive another `IObservableList<E>` from this one according to some predicate defined on the element type `E`. The derived list should be dynamically updated according to changes in the original list. How can I best implement that? I've considered subclassing `DecoratingObservableList`, but couldn't figure out how to use it. Of course, I could implement myself the whole `IObservableList` interface, but I was wondering if there were not other utility classes around that I could use.

Original source