【问题标题】:How to write a filtered IObservableList如何编写过滤后的 IObservableList
【发布时间】:2012-04-19 06:15:58
【问题描述】:

通过一些 API 调用,我从 Eclipse 数据绑定框架中返回了 IObservableList<E>。我希望根据在元素类型E 上定义的一些谓词,从中派生另一个IObservableList<E>。派生列表应根据原始列表的变化动态更新。

我怎样才能最好地实现它?我考虑过继承DecoratingObservableList,但不知道如何使用它。

当然,我可以自己实现整个IObservableList 接口,但我想知道周围是否有其他实用程序类可供我使用。

【问题讨论】:

  • 人们看到你的问题基于“java”标签不会意识到你在说什么。明确提及 Eclipse 数据绑定。

标签: java data-binding filter eclipse-databinding


【解决方案1】:

我认为扩展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();}
}

【讨论】:

  • 这似乎没有按预期工作。例如,当从包装列表中添加或删除过滤掉的元素时,它仍然会触发事件。
  • 当然,它只适用于迭代。如果您需要添加/删除,如果我有时间我会看看我能做些什么。
  • @Jean-PhilippePellet 但是等一下,您希望如何阻止包装列表中的事件?如果您在包装列表中注册了一个侦听器并直接向其中添加元素,它将触发事件。如果您要求自动更改原始列表的行为,而不是在其之上应用过滤行为,那么这不是您的解决方案。事实上,我不知道你将如何做到这一点。
  • @Jean-PhilippePellet 也许,根据您的具体用法,您可以直接向包装列表添加/删除,但您肯定需要将侦听器附加到装饰列表。在这种情况下,可以安排适当过滤事件。
  • 我当然会把它们附在装饰清单上。但我认为您的解决方案会将包装列表中的每个事件转发给装饰列表的侦听器。
【解决方案2】:

这是一个只读实现。

几个注意事项:

  • 这取决于基本的 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();
    }
}

【讨论】:

  • 这看起来不错!我很快就会仔细研究一下。
  • 其实我看到两个问题: 1.这段代码并没有改变过滤后的ListDiffEntry的位置,所以他们很可能在过滤后的列表中出错; 2. 对于每一个ListChangeEvent,你再次遍历整个装饰列表,而它可以简单地从获得的差异中修改以提高效率。
  • @Jean-PhilippePellet 1. 哎呀,没有意识到结构有索引字段。今天晚上我会讲到的。 2. 你需要它有那么高的性能吗?这是可行的,但与影响底层证券的过滤列表中的变异器一样难以正确处理。索引映射看起来很痛苦。
  • 是的,索引映射很痛苦,而这正是我想要解决的问题。我也许应该在问题中清楚地提到这一点……
【解决方案3】:

您可能想查看GlazedLists 的源代码,它具有可过滤、可观察的列表。

【讨论】:

  • 因为这是给 Swing 的,而且问题是关于 JFace 的,所以这几乎没有用处。
  • @Marko Topolnik 实际上这是不正确的。 Glazed Lists 也可以与 SWT 一起使用:hexapixel.com/2009/01/02/glazed-lists-swt-tables-true
  • @ulmangt 嗯...“当我们使用 SWT 时,我们必须自己完成几乎所有的工作”
  • 也许是在 GaledLists 提供的具体类之上实现 IObservableList
  • 是的,看过该页面后,这似乎是一种获取过滤列表的方法——但如果如您所说,您需要装饰现有的ObservableList,那么这是一个糟糕的选择。
猜你喜欢
  • 1970-01-01
  • 2011-08-03
  • 1970-01-01
  • 2018-05-31
  • 2013-10-03
  • 1970-01-01
  • 2010-09-22
  • 2010-12-08
  • 1970-01-01
相关资源
最近更新 更多