How do I make my ImmutableList sortable in its Obs

2019-07-08 09:46发布

问题:

I have my own implementation of ObservableList called ObservableImmutableList. It wraps a Guava ImmutableList with the ObservableList interface so I can fully utilize ImmutableList easily with JavaFX. There is nothing mutable about it except it allows another ImmutableList to be swapped in as the backing list using the set() method (and notify all listeners/bindings of the change).

However, one problem I ran into is the ObservableImmutableList cannot be sorted. I cannot extend or implement SortedList, so how do I implement it into this class?

I tried to create a method asSorted() which returns the ObservableImmutableList wrapped in a SortedList. But it also would not work. Is there some kind of simple abstract layer I can use to keep the list Immutable but allow the values to be resorted in the abstracted layer?

public final class ObservableImmutableList<T> implements ObservableList<T> {
    private volatile ImmutableList<T> backingList;
    private final LazyProperty<SortedList<T>> sortedList = LazyProperty.forSupplier(() -> new SortedList<>(this));
    private final CopyOnWriteArrayList<ListChangeListener<? super T>> listeners = new CopyOnWriteArrayList<>();
    private final CopyOnWriteArrayList<InvalidationListener> invalidationListeners = new CopyOnWriteArrayList<>();

    private final ObjectProperty<ObservableList<T>> property;

    private ObservableImmutableList(ImmutableList<T> immutableList) {
        this.backingList = immutableList;
        this.property = new SimpleObjectProperty<ObservableList<T>>(this);
    }

    public static <T> ObservableImmutableList<T> of(ImmutableList<T> immutableList) {
        return new ObservableImmutableList<T>(immutableList);
    }
    public SortedList<T> asSorted() { 
        return sortedList.get();
    }
    public void set(ImmutableList<T> immutableList) { 

        this.property.setValue(this);

        final ImmutableList<T> oldList = this.backingList;
        final ImmutableList<T> newList = immutableList;
        listeners.forEach(l -> l.onChanged(new Change<T>(this) {
            private int changeNum = 0;
            @Override
            public boolean next() {
                changeNum++;
                return changeNum <= 2 ? true : false;
            }
            @Override
            public boolean wasUpdated() {
                return true;
            }
            @Override
            public void reset() {
                // TODO Auto-generated method stub

            }
            @Override
            public int getFrom() {
                return 0;
            }
            @Override
            public int getTo() {
                return changeNum == 1 ? oldList.size() - 1 : newList.size() - 1;
            }
            @Override
            public List<T> getRemoved() {
                return changeNum == 1 ? oldList : ImmutableList.of();
            }
            @Override
            public List<T> getAddedSubList() { 
                return changeNum == 1 ? ImmutableList.of() : newList;
            }
            @Override
            protected int[] getPermutation() {
                int[] permutations = new int[changeNum == 1 ? oldList.size() : newList.size()];
                for (int i = 0; i < permutations.length; i++) { 
                    permutations[i] = i;
                }
                return permutations;
            }

        }));
        this.backingList = immutableList;

        invalidationListeners.forEach(l -> l.invalidated(this));

    }

    public ImmutableList<T> get() { 
        return backingList;
    }
    public ObjectProperty<ObservableList<T>> asProperty() { 
        return property;
    }

    @Override
    public int size() {
        return backingList.size();
    }

    @Override
    public boolean isEmpty() {
        return backingList.isEmpty();
    }

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

    @Override
    public Iterator<T> iterator() {
        return backingList.iterator();
    }

    @Override
    public Object[] toArray() {
        return backingList.toArray();
    }

    @Override
    public <B> B[] toArray(B[] a) {
        return backingList.toArray(a);
    }

    @Override @Deprecated
    public boolean add(T e) {
        return backingList.add(e);
    }

    @Override @Deprecated
    public boolean remove(Object o) {
        return backingList.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return backingList.containsAll(c);
    }

    @Override @Deprecated
    public boolean addAll(Collection<? extends T> c) {
        return backingList.addAll(c);
    }

    @Override @Deprecated
    public boolean addAll(int index, Collection<? extends T> c) {
        return backingList.addAll(index, c);
    }

    @Override @Deprecated
    public boolean removeAll(Collection<?> c) {
        return backingList.removeAll(c);
    }

    @Override @Deprecated
    public boolean retainAll(Collection<?> c) {
        return backingList.retainAll(c);
    }

    @Override @Deprecated
    public void clear() {
        backingList.clear();
    }

    @Override
    public T get(int index) {
        return backingList.get(index);
    }

    @Override @Deprecated
    public T set(int index, T element) {
        return backingList.set(index, element);
    }

    @Override @Deprecated
    public void add(int index, T element) {
        backingList.add(index, element);
    }

    @Override @Deprecated
    public T remove(int index) {
        return backingList.remove(index);
    }

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

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

    @Override
    public ListIterator<T> listIterator() {
        return backingList.listIterator();
    }

    @Override
    public ListIterator<T> listIterator(int index) {
        return backingList.listIterator(index);
    }

    @Override
    public ImmutableList<T> subList(int fromIndex, int toIndex) {
        return backingList.subList(fromIndex, toIndex);
    }

    @Override
    public void addListener(InvalidationListener listener) {
        invalidationListeners.add(listener);
    }

    @Override
    public void removeListener(InvalidationListener listener) {
        invalidationListeners.remove(listener);
    }

    @Override
    public void addListener(ListChangeListener<? super T> listener) {
        listeners.add(listener);
    }

    @Override
    public void removeListener(ListChangeListener<? super T> listener) {
        listeners.remove(listener);
    }

    @Override @Deprecated
    public boolean addAll(T... elements) {
        return backingList.addAll(ImmutableList.copyOf(elements));
    }

    @Override @Deprecated
    public boolean setAll(T... elements) {
        return false;
    }

    @Override @Deprecated
    public boolean setAll(Collection<? extends T> col) {
        return false;
    }

    @Override @Deprecated
    public boolean removeAll(T... elements) {
        return backingList.removeAll(ImmutableList.copyOf(elements));
    }

    @Override @Deprecated
    public boolean retainAll(T... elements) {
        return false;
    }

    @Override @Deprecated
    public void remove(int from, int to) {
    }

    @Override
    public String toString() { 
        return backingList.toString();
    }

}

回答1:

The part you asked about: a SortedList is just a wrapper around its backing list, it does not change it in any way. So you can wrap any list - mutable or not - into a SortedList and use that in the TableView. In the example of your previous question:

ObservableList<NumericCombo> immutable = 
        FXCollections.unmodifiableObservableList(values);
 //       new ImmutableObservableList<>(values);

TableView<NumericCombo> tableView = new TableView<>();
SortedList sorted = new SortedList(immutable);
tableView.setItems(sorted);
sorted.comparatorProperty().bind(tableView.comparatorProperty());

The part you did not ask about (how to get the notifications right).

  • don't implement ObservableList from scratch, instead go from ObservableListBase
  • when swapping out the backing list, call the support methods provided by super to notify the listeners

A simple example:

public class ImmutableObservableList<E> extends ObservableListBase<E> {

    private List<E> backing;

    public ImmutableObservableList(List<E> backingList) {
        this.backing = backingList;
    }

    public void setBackingList(List<E> backingList) {
        beginChange();
        if (this.backing != null) {
            nextRemove(0, this.backing);
        }
        this.backing = backingList;
        if (backingList != null) {
            nextAdd(0, backingList.size());
        }
        endChange();
    }
    @Override
    public E get(int index) {
        if (backing == null) throw new IndexOutOfBoundsException();
        return backing.get(index);
    }

    @Override
    public int size() {
        return backing != null ? backing.size() : 0;
    }

}