Как написать отфильтрованный IObservableList

От какого-то вызова API я получаю обратно IObservableList<E> из среды привязки данных Eclipse. Я хотел бы получить другое IObservableList<E> из этого согласно некоторому предикату, определенному на типе элемента E, Производный список должен динамически обновляться в соответствии с изменениями в исходном списке.

Как я могу лучше всего реализовать это? Я рассмотрел подкласс DecoratingObservableList, но не мог понять, как его использовать.

Конечно, я мог бы реализовать себя весь IObservableList интерфейс, но мне было интересно, если бы не было других полезных классов вокруг, которые я мог бы использовать.

3 ответа

Решение

Я думаю, что расширение DecoratingObservableList хорошее начало Я бы также предложил сконцентрироваться именно на предполагаемом использовании, а не на немедленной реализации всего API. Например, вам нужна запись в случайном порядке через set? Если нет, то не беспокойтесь о его реализации. Это покрывает доступное только для чтения представление изменяемого ObservableList, сопоставляя события изменения оформленного списка с соответствующими событиями изменения в отфильтрованном списке:

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();}
}

Вот реализация только для чтения.

Несколько предостережений:

  • Это зависит от базовых событий запуска DecoratingObservableList при редактировании.
  • Итератор не будет активным, если оформленный список изменится после времени создания итератора, он работает как CopyOnWriteArrayList сюда. Возможно, это должно бросить ConcurrentModificationExceptions там.

Если это должно быть доступно для записи, можете ли вы определить, как вы хотите сделать отображение индекса, и, возможно, указать, собираетесь ли вы разрешать неуникальные элементы в списке?

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();
    }
}

Возможно, вы захотите взглянуть на исходный код GlazedLists, который имеет фильтруемые, наблюдаемые списки.

Другие вопросы по тегам