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.
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();}
}
Here's a read-only implementation.
A couple of caveats:
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();
}
}
You might want to look at the source code of GlazedLists which has filterable, observable lists.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With