Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a filtered IObservableList

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.

like image 443
Jean-Philippe Pellet Avatar asked Apr 05 '12 13:04

Jean-Philippe Pellet


3 Answers

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();}
}
like image 166
Marko Topolnik Avatar answered Oct 16 '22 14:10

Marko Topolnik


Here's a read-only implementation.

A couple of caveats:

  • This depends on the base DecoratingObservableList firing events on edits.
  • The iterator won't be live if the decorated list changes after iterator creation time, it works like a CopyOnWriteArrayList that way. Arguably it should throw ConcurrentModificationExceptions there.

If it has to be writeable, can you define how you want to do index mapping, and perhaps specify whether you're going to allow non-unique items in the List?

package filteredobservablelist;

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

import org.eclipse.core.databinding.observable.list.DecoratingObservableList;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.list.ListDiff;
import org.eclipse.core.databinding.observable.list.ListDiffEntry;

public class FilteredObservableList extends DecoratingObservableList {

    private static final class FilteredListDiff extends ListDiff {

        private final List<ListDiffEntry> filteredDiffs;

        private FilteredListDiff(List<ListDiffEntry> filteredDiffs) {
            this.filteredDiffs = filteredDiffs;
        }

        @Override
        public ListDiffEntry[] getDifferences() {
            return filteredDiffs.toArray(new ListDiffEntry[filteredDiffs.size()]);
        }
    }

    public interface Predicate {

        boolean evaluate(Object element);

    }

    private final Predicate predicate;
    private List<Object> filteredList;

    public FilteredObservableList(IObservableList decorated, boolean disposeDecoratedOnDispose, Predicate predicate) {
        super(decorated, disposeDecoratedOnDispose);

        this.predicate = predicate;
        rebuildCache();
    }

    @Override
    protected void handleListChange(final ListChangeEvent event) {
        final List<ListDiffEntry> filteredDiffs = new ArrayList<ListDiffEntry>(event.diff.getDifferences().length);
        for (ListDiffEntry element : event.diff.getDifferences()) {
            if (predicate.evaluate(element.getElement())) {
                filteredDiffs.add(element);
            }
        }

        rebuildCache();

        if (!filteredDiffs.isEmpty()) {
            fireListChange(new FilteredListDiff(filteredDiffs));
        }
    }

    private void rebuildCache() {
        filteredList = new ArrayList<Object>();
        for (Object element : getDecorated()) {
            if (predicate.evaluate(element)) {
                filteredList.add(element);
            }
        }
    }

    @Override
    public boolean contains(Object o) {
        return filteredList.contains(o);
    }

    @Override
    public boolean containsAll(Collection c) {
        return filteredList.containsAll(c);
    }

    @Override
    public Object get(int index) {
        getterCalled();
        return filteredList.get(index);
    }

    @Override
    public int indexOf(Object o) {
        getterCalled();
        return filteredList.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        getterCalled();
        return filteredList.lastIndexOf(o);
    }

    @Override
    public List subList(int fromIndex, int toIndex) {
        getterCalled();
        return this.filteredList.subList(fromIndex, toIndex);
    }

    @Override
    public IObservableList getDecorated() {
        return (IObservableList) super.getDecorated();
    }

    @Override
    public Iterator iterator() {
        return listIterator();
    }

    @Override
    public ListIterator listIterator() {
        return this.listIterator(0);
    }

    @Override
    public ListIterator listIterator(int index) {
        getterCalled();

        final ListIterator iterator = filteredList.listIterator(index);

        return new ListIterator() {

            @Override
            public boolean hasNext() {
                getterCalled();
                return iterator.hasNext();
            }

            @Override
            public boolean hasPrevious() {
                getterCalled();
                return iterator.hasPrevious();
            }

            @Override
            public Object next() {
                getterCalled();
                return iterator.next();
            }

            @Override
            public int nextIndex() {
                getterCalled();
                return iterator.nextIndex();
            }

            @Override
            public Object previous() {
                getterCalled();
                return iterator.previous();
            }

            @Override
            public int previousIndex() {
                getterCalled();
                return iterator.previousIndex();
            }

            @Override
            public void add(Object o) {
                throw new UnsupportedOperationException();
            }

            @Override
            public void set(Object o) {
                throw new UnsupportedOperationException();
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    @Override
    public Object move(int oldIndex, int newIndex) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object remove(int index) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object set(int index, Object element) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void add(int index, Object o) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean add(Object o) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(Collection c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(int index, Collection c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(Object o) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeAll(Collection c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean retainAll(Collection c) {
        throw new UnsupportedOperationException();
    }
}
like image 30
sharakan Avatar answered Oct 16 '22 14:10

sharakan


You might want to look at the source code of GlazedLists which has filterable, observable lists.

like image 44
Wim Deblauwe Avatar answered Oct 16 '22 15:10

Wim Deblauwe