2012-04-05 10 views
5

De alguna llamada API, obtengo un IObservableList<E>, desde el marco de enlace de datos de Eclipse. Me gustaría derivar otro IObservableList<E> de este de acuerdo con algún predicado definido en el tipo de elemento E. La lista derivada se debe actualizar dinámicamente de acuerdo con los cambios en la lista original.Cómo escribir una IObservableList filtrada

¿Cómo puedo implementarlo mejor? Consideré la creación de subclases DecoratingObservableList, pero no pude encontrar la manera de usarlo.

Por supuesto, podría implementar toda la interfaz IObservableList, pero me preguntaba si no habría otras clases de utilidad que pudiera utilizar.

+0

Las personas que vean su pregunta basada en la etiqueta "java" no se darán cuenta de lo que está hablando. Mencione Eclipse Databinding explícitamente. –

Respuesta

3

Creo que extender DecoratingObservableList es un buen comienzo. También sugiero que se concentre exactamente en el uso previsto en lugar de implementar inmediatamente toda la API. Por ejemplo, ¿necesita escribir de forma aleatoria a través del set? De lo contrario, no se moleste en implementarlo. Esto cubre una vista de sólo lectura de un mutable ObservableList, la cartografía de los eventos de cambio de la lista decoradas para los eventos de cambio correspondientes en la lista filtrada:

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

Esto no parece funcionar como se esperaba. Por ejemplo, todavía activará eventos cuando los elementos filtrados se agreguen o eliminen de la lista envuelta. –

+0

Por supuesto, solo funciona para iteración. Si necesita agregar/eliminar, veré lo que puedo hacer si encuentro algo de tiempo. –

+0

@ Jean-PhilippePellet Pero espere un segundo, ¿cómo espera bloquear los eventos de la lista envuelta? Si ha registrado un oyente en la lista completa y agrega elementos directamente a él, lanzará eventos. Si es su requerimiento cambiar automágicamente el comportamiento de la lista original y no aplicar un comportamiento filtrado sobre ella, entonces esta no es la solución para usted. De hecho, no tengo idea de cómo podrá lograr eso. –

1

Es posible que desee mirar el código fuente de GlazedLists que tiene filtrables, listas observables.

+0

Dado que esto es para Swing y la pregunta es sobre JFace, esto no va a ser útil. –

+1

@Marko Topolnik En realidad eso no es correcto. Las listas transparentes también se pueden usar con SWT: http://hexapixel.com/2009/01/02/glazed-lists-swt-tables-true – ulmangt

+0

@ulmangt Hmm ... "como usamos SWT, tenemos que hacer bastante tanto todo el trabajo nosotros mismos " –

3

Aquí hay una implementación de solo lectura.

Un par de advertencias:

  • Esto depende de la DecoratingObservableList base de disparar eventos en los cambios.
  • El iterador no estará activo si la lista decorada cambia después de la hora de creación del iterador, funciona de esa manera como CopyOnWriteArrayList. Podría decirse que debería arrojar allí ConcurrentModificationExceptions.

Si tiene que ser editable, ¿puede definir cómo desea hacer la asignación de índice, y quizás especificar si va a permitir elementos no únicos en la Lista?

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

¡Esto se ve bastante bien! Lo echaré un vistazo más de cerca en breve. –

+0

En realidad, veo dos problemas: 1. este código no cambia la posición del filtrado 'ListDiffEntry', por lo que es probable que salga mal en la lista filtrada; 2. para cada uno de los '' ListChangeEvent', se ejecuta nuevamente a través de la lista completa decorada, mientras que podría ser simplemente modificado a partir de los diffs obtenidos para mayor eficiencia. –

+0

@ Jean-PhilippePellet 1. Vaya, no me di cuenta de que la estructura tenía campos indexados. Voy a llegar a eso esta tarde. 2. ¿necesitas que sea tan eficiente? Es factible, pero es tan difícil de corregir como los mutadores en la lista filtrada que afectan al subyacente. El mapeo de índice parece ser un dolor. – sharakan

Cuestiones relacionadas