Skip to content

Commit

Permalink
Better support for ObservableList changes:
Browse files Browse the repository at this point in the history
* Add ListChange, TransientListChange and MaterializedListChange interfaces.
  These provide a simpler model for list changes than
  javafx.collections.ListChangeListener.Change.
* Expose ListChangeAccumulator.
* Add EventStreams.simpleChangesOf(ObservableList) factory method.
  • Loading branch information
TomasMikula committed Nov 12, 2014
1 parent 3fbd6af commit 7f4b77a
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 273 deletions.
16 changes: 16 additions & 0 deletions reactfx/src/main/java/org/reactfx/EventStreams.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer;
import org.reactfx.util.TransientListChange;
import org.reactfx.util.Tuple4;
import org.reactfx.util.Tuple5;
import org.reactfx.util.Tuple6;
Expand Down Expand Up @@ -160,6 +161,21 @@ protected Subscription subscribeToInputs() {
};
}

public static <T> EventStream<TransientListChange<T>> simpleChangesOf(ObservableList<T> list) {
return new LazilyBoundStream<TransientListChange<T>>() {
@Override
protected Subscription subscribeToInputs() {
ListChangeListener<T> listener = c -> {
while(c.next()) {
emit(TransientListChange.fromCurrentStateOf(c));
}
};
list.addListener(listener);
return () -> list.removeListener(listener);
}
};
}

public static <T> EventStream<SetChangeListener.Change<? extends T>> changesOf(ObservableSet<T> set) {
return new LazilyBoundStream<SetChangeListener.Change<? extends T>>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.reactfx.inhibeans.collection;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
Expand All @@ -12,7 +10,9 @@
import javafx.collections.ListChangeListener.Change;

import org.reactfx.Guard;
import org.reactfx.util.ListChangeAccumulator;
import org.reactfx.util.ListHelper;
import org.reactfx.util.TransientListChange;

class ObservableListWrapper<E> implements ObservableList<E> {
private final javafx.collections.ObservableList<E> delegate;
Expand All @@ -21,7 +21,7 @@ class ObservableListWrapper<E> implements ObservableList<E> {
private ListHelper<ListChangeListener<? super E>> listListeners = null;

private boolean blocked;
private final List<SingleListChange<E>> pendingChanges = new ArrayList<>();
private final ListChangeAccumulator<E> pendingChanges = new ListChangeAccumulator<>();

ObservableListWrapper(javafx.collections.ObservableList<E> delegate) {
this.delegate = delegate;
Expand All @@ -47,17 +47,16 @@ public Guard block() {
private void release() {
blocked = false;
if(!pendingChanges.isEmpty()) {
Change<E> change = squash(pendingChanges);
pendingChanges.clear();
Change<E> change = squash(pendingChanges.fetch());
notifyListeners(change);
}
}

private Change<E> squash(List<SingleListChange<E>> changeList) {
private Change<E> squash(List<TransientListChange<? extends E>> changeList) {
return new Change<E>(this) {
@SuppressWarnings("unchecked")
private final SingleListChange<E>[] changes =
(SingleListChange<E>[]) changeList.toArray(new SingleListChange<?>[changeList.size()]);
private final TransientListChange<? extends E>[] changes =
(TransientListChange<? extends E>[]) changeList.toArray(new TransientListChange<?>[changeList.size()]);

private int current = -1;

Expand All @@ -68,22 +67,14 @@ public int getFrom() {

@Override
protected int[] getPermutation() {
throw new AssertionError("Unreachable code");
}

@Override
public boolean wasPermutated() {
return changes[current].isPermutation();
}

@Override
public int getPermutation(int i) {
return changes[current].getPermutation(i);
return new int[0]; // not a permutation
}

@Override
@SuppressWarnings("unchecked")
public List<E> getRemoved() {
return changes[current].getRemoved();
// cast is safe, because the list is unmodifiable
return (List<E>) changes[current].getRemoved();
}

@Override
Expand All @@ -110,127 +101,8 @@ public void reset() {

private void incorporateChange(Change<? extends E> change) {
while(change.next()) {
int from = change.getFrom();
if(change.wasPermutated()) {
int len = change.getTo() - from;
int[] permutation = new int[len];
List<E> replaced = new ArrayList<>(len);
for(int i = 0; i < len; ++i) {
int pi = change.getPermutation(from + i);
permutation[i] = pi - from;
replaced.add(delegate.get(pi));
}
incorporateChange(new Permutation<>(from, permutation, replaced));
} else {
@SuppressWarnings("unchecked") // cast is safe because the list is unmodifiable
List<E> removed = (List<E>) change.getRemoved();
incorporateChange(new Replacement<>(from, removed, change.getAddedSize()));
}
}
}

private void incorporateChange(SingleListChange<E> change) {
if(pendingChanges.isEmpty()) {
pendingChanges.add(change);
} else {
// find first and last overlapping change
int from = change.getFrom();
int to = from + change.getOldLength();
int firstOverlapping = 0;
for(; firstOverlapping < pendingChanges.size(); ++firstOverlapping) {
if(pendingChanges.get(firstOverlapping).getTo() >= from) {
break;
}
}
int lastOverlapping = pendingChanges.size() - 1;
for(; lastOverlapping >= 0; --lastOverlapping) {
if(pendingChanges.get(lastOverlapping).getFrom() <= to) {
break;
}
}

// offset changes farther in the list
int diff = change.getTo() - change.getFrom() - change.getOldLength();
offsetPendingChanges(lastOverlapping + 1, diff);

// combine overlapping changes into one
if(lastOverlapping < firstOverlapping) { // no overlap
pendingChanges.add(firstOverlapping, change);
} else { // overlaps one or more former changes
List<SingleListChange<E>> overlapping = pendingChanges.subList(firstOverlapping, lastOverlapping + 1);
SingleListChange<E> joined = join(overlapping, change.getReplaced(), change.getFrom());
SingleListChange<E> newChange = combine(joined, change);
overlapping.clear();
pendingChanges.add(firstOverlapping, newChange);
}
}
}

private void offsetPendingChanges(int from, int offset) {
pendingChanges.subList(from, pendingChanges.size())
.replaceAll(change -> change.offset(offset));
}

private SingleListChange<E> join(List<SingleListChange<E>> changes, List<E> gone, int goneOffset) {
if(changes.size() == 1) {
return changes.get(0);
}

List<E> removed = new ArrayList<>();
SingleListChange<E> prev = changes.get(0);
int from = prev.getFrom();
removed.addAll(prev.getReplaced());
for(int i = 1; i < changes.size(); ++i) {
SingleListChange<E> ch = changes.get(i);
removed.addAll(gone.subList(prev.getTo() - goneOffset, ch.getFrom() - goneOffset));
removed.addAll(ch.getReplaced());
prev = ch;
}
return new Replacement<>(from, removed, prev.getTo() - from);
}

private SingleListChange<E> combine(
SingleListChange<E> former,
SingleListChange<E> latter) {

if(latter.getFrom() >= former.getFrom() && latter.getFrom() + latter.getOldLength() <= former.getTo()) {
// latter is within former
List<E> removed = former.getReplaced();
int addedSize = former.getNewLength() - latter.getOldLength() + latter.getNewLength();
return new Replacement<>(former.getFrom(), removed, addedSize);
} else if(latter.getFrom() <= former.getFrom() && latter.getFrom() + latter.getOldLength() >= former.getTo()) {
// former is within latter
List<E> removed = concat(
latter.getReplaced().subList(0, former.getFrom() - latter.getFrom()),
former.getReplaced(),
latter.getReplaced().subList(former.getTo() - latter.getFrom(), latter.getOldLength()));
int addedSize = latter.getNewLength();
return new Replacement<>(latter.getFrom(), removed, addedSize);
} else if(latter.getFrom() >= former.getFrom()) {
// latter overlaps to the right
List<E> removed = concat(
former.getReplaced(),
latter.getReplaced().subList(former.getTo() - latter.getFrom(), latter.getOldLength()));
int addedSize = latter.getFrom() - former.getFrom() + latter.getNewLength();
return new Replacement<>(former.getFrom(), removed, addedSize);
} else {
// latter overlaps to the left
List<E> removed = concat(
latter.getReplaced().subList(0, former.getFrom() - latter.getFrom()),
former.getReplaced());
int addedSize = former.getTo() - (latter.getFrom() + latter.getOldLength()) + latter.getNewLength();
return new Replacement<>(latter.getFrom(), removed, addedSize);
}
}

@SafeVarargs
private static <T> List<T> concat(List<T>... lists) {
int n = Arrays.asList(lists).stream().mapToInt(List::size).sum();
List<T> res = new ArrayList<>(n);
for(List<T> l: lists) {
res.addAll(l);
pendingChanges.add(TransientListChange.fromCurrentStateOf(change));
}
return res;
}

private void notifyListeners(Change<? extends E> change) {
Expand Down

This file was deleted.

Loading

0 comments on commit 7f4b77a

Please sign in to comment.