Skip to content

Commit 7f4b77a

Browse files
committed
Better support for ObservableList changes:
* 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.
1 parent 3fbd6af commit 7f4b77a

File tree

7 files changed

+319
-273
lines changed

7 files changed

+319
-273
lines changed

reactfx/src/main/java/org/reactfx/EventStreams.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.reactfx.util.FxTimer;
3030
import org.reactfx.util.Timer;
31+
import org.reactfx.util.TransientListChange;
3132
import org.reactfx.util.Tuple4;
3233
import org.reactfx.util.Tuple5;
3334
import org.reactfx.util.Tuple6;
@@ -160,6 +161,21 @@ protected Subscription subscribeToInputs() {
160161
};
161162
}
162163

164+
public static <T> EventStream<TransientListChange<T>> simpleChangesOf(ObservableList<T> list) {
165+
return new LazilyBoundStream<TransientListChange<T>>() {
166+
@Override
167+
protected Subscription subscribeToInputs() {
168+
ListChangeListener<T> listener = c -> {
169+
while(c.next()) {
170+
emit(TransientListChange.fromCurrentStateOf(c));
171+
}
172+
};
173+
list.addListener(listener);
174+
return () -> list.removeListener(listener);
175+
}
176+
};
177+
}
178+
163179
public static <T> EventStream<SetChangeListener.Change<? extends T>> changesOf(ObservableSet<T> set) {
164180
return new LazilyBoundStream<SetChangeListener.Change<? extends T>>() {
165181
@Override

reactfx/src/main/java/org/reactfx/inhibeans/collection/ObservableListWrapper.java

Lines changed: 12 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.reactfx.inhibeans.collection;
22

3-
import java.util.ArrayList;
4-
import java.util.Arrays;
53
import java.util.Collection;
64
import java.util.Iterator;
75
import java.util.List;
@@ -12,7 +10,9 @@
1210
import javafx.collections.ListChangeListener.Change;
1311

1412
import org.reactfx.Guard;
13+
import org.reactfx.util.ListChangeAccumulator;
1514
import org.reactfx.util.ListHelper;
15+
import org.reactfx.util.TransientListChange;
1616

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

2323
private boolean blocked;
24-
private final List<SingleListChange<E>> pendingChanges = new ArrayList<>();
24+
private final ListChangeAccumulator<E> pendingChanges = new ListChangeAccumulator<>();
2525

2626
ObservableListWrapper(javafx.collections.ObservableList<E> delegate) {
2727
this.delegate = delegate;
@@ -47,17 +47,16 @@ public Guard block() {
4747
private void release() {
4848
blocked = false;
4949
if(!pendingChanges.isEmpty()) {
50-
Change<E> change = squash(pendingChanges);
51-
pendingChanges.clear();
50+
Change<E> change = squash(pendingChanges.fetch());
5251
notifyListeners(change);
5352
}
5453
}
5554

56-
private Change<E> squash(List<SingleListChange<E>> changeList) {
55+
private Change<E> squash(List<TransientListChange<? extends E>> changeList) {
5756
return new Change<E>(this) {
5857
@SuppressWarnings("unchecked")
59-
private final SingleListChange<E>[] changes =
60-
(SingleListChange<E>[]) changeList.toArray(new SingleListChange<?>[changeList.size()]);
58+
private final TransientListChange<? extends E>[] changes =
59+
(TransientListChange<? extends E>[]) changeList.toArray(new TransientListChange<?>[changeList.size()]);
6160

6261
private int current = -1;
6362

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

6968
@Override
7069
protected int[] getPermutation() {
71-
throw new AssertionError("Unreachable code");
72-
}
73-
74-
@Override
75-
public boolean wasPermutated() {
76-
return changes[current].isPermutation();
77-
}
78-
79-
@Override
80-
public int getPermutation(int i) {
81-
return changes[current].getPermutation(i);
70+
return new int[0]; // not a permutation
8271
}
8372

8473
@Override
74+
@SuppressWarnings("unchecked")
8575
public List<E> getRemoved() {
86-
return changes[current].getRemoved();
76+
// cast is safe, because the list is unmodifiable
77+
return (List<E>) changes[current].getRemoved();
8778
}
8879

8980
@Override
@@ -110,127 +101,8 @@ public void reset() {
110101

111102
private void incorporateChange(Change<? extends E> change) {
112103
while(change.next()) {
113-
int from = change.getFrom();
114-
if(change.wasPermutated()) {
115-
int len = change.getTo() - from;
116-
int[] permutation = new int[len];
117-
List<E> replaced = new ArrayList<>(len);
118-
for(int i = 0; i < len; ++i) {
119-
int pi = change.getPermutation(from + i);
120-
permutation[i] = pi - from;
121-
replaced.add(delegate.get(pi));
122-
}
123-
incorporateChange(new Permutation<>(from, permutation, replaced));
124-
} else {
125-
@SuppressWarnings("unchecked") // cast is safe because the list is unmodifiable
126-
List<E> removed = (List<E>) change.getRemoved();
127-
incorporateChange(new Replacement<>(from, removed, change.getAddedSize()));
128-
}
129-
}
130-
}
131-
132-
private void incorporateChange(SingleListChange<E> change) {
133-
if(pendingChanges.isEmpty()) {
134-
pendingChanges.add(change);
135-
} else {
136-
// find first and last overlapping change
137-
int from = change.getFrom();
138-
int to = from + change.getOldLength();
139-
int firstOverlapping = 0;
140-
for(; firstOverlapping < pendingChanges.size(); ++firstOverlapping) {
141-
if(pendingChanges.get(firstOverlapping).getTo() >= from) {
142-
break;
143-
}
144-
}
145-
int lastOverlapping = pendingChanges.size() - 1;
146-
for(; lastOverlapping >= 0; --lastOverlapping) {
147-
if(pendingChanges.get(lastOverlapping).getFrom() <= to) {
148-
break;
149-
}
150-
}
151-
152-
// offset changes farther in the list
153-
int diff = change.getTo() - change.getFrom() - change.getOldLength();
154-
offsetPendingChanges(lastOverlapping + 1, diff);
155-
156-
// combine overlapping changes into one
157-
if(lastOverlapping < firstOverlapping) { // no overlap
158-
pendingChanges.add(firstOverlapping, change);
159-
} else { // overlaps one or more former changes
160-
List<SingleListChange<E>> overlapping = pendingChanges.subList(firstOverlapping, lastOverlapping + 1);
161-
SingleListChange<E> joined = join(overlapping, change.getReplaced(), change.getFrom());
162-
SingleListChange<E> newChange = combine(joined, change);
163-
overlapping.clear();
164-
pendingChanges.add(firstOverlapping, newChange);
165-
}
166-
}
167-
}
168-
169-
private void offsetPendingChanges(int from, int offset) {
170-
pendingChanges.subList(from, pendingChanges.size())
171-
.replaceAll(change -> change.offset(offset));
172-
}
173-
174-
private SingleListChange<E> join(List<SingleListChange<E>> changes, List<E> gone, int goneOffset) {
175-
if(changes.size() == 1) {
176-
return changes.get(0);
177-
}
178-
179-
List<E> removed = new ArrayList<>();
180-
SingleListChange<E> prev = changes.get(0);
181-
int from = prev.getFrom();
182-
removed.addAll(prev.getReplaced());
183-
for(int i = 1; i < changes.size(); ++i) {
184-
SingleListChange<E> ch = changes.get(i);
185-
removed.addAll(gone.subList(prev.getTo() - goneOffset, ch.getFrom() - goneOffset));
186-
removed.addAll(ch.getReplaced());
187-
prev = ch;
188-
}
189-
return new Replacement<>(from, removed, prev.getTo() - from);
190-
}
191-
192-
private SingleListChange<E> combine(
193-
SingleListChange<E> former,
194-
SingleListChange<E> latter) {
195-
196-
if(latter.getFrom() >= former.getFrom() && latter.getFrom() + latter.getOldLength() <= former.getTo()) {
197-
// latter is within former
198-
List<E> removed = former.getReplaced();
199-
int addedSize = former.getNewLength() - latter.getOldLength() + latter.getNewLength();
200-
return new Replacement<>(former.getFrom(), removed, addedSize);
201-
} else if(latter.getFrom() <= former.getFrom() && latter.getFrom() + latter.getOldLength() >= former.getTo()) {
202-
// former is within latter
203-
List<E> removed = concat(
204-
latter.getReplaced().subList(0, former.getFrom() - latter.getFrom()),
205-
former.getReplaced(),
206-
latter.getReplaced().subList(former.getTo() - latter.getFrom(), latter.getOldLength()));
207-
int addedSize = latter.getNewLength();
208-
return new Replacement<>(latter.getFrom(), removed, addedSize);
209-
} else if(latter.getFrom() >= former.getFrom()) {
210-
// latter overlaps to the right
211-
List<E> removed = concat(
212-
former.getReplaced(),
213-
latter.getReplaced().subList(former.getTo() - latter.getFrom(), latter.getOldLength()));
214-
int addedSize = latter.getFrom() - former.getFrom() + latter.getNewLength();
215-
return new Replacement<>(former.getFrom(), removed, addedSize);
216-
} else {
217-
// latter overlaps to the left
218-
List<E> removed = concat(
219-
latter.getReplaced().subList(0, former.getFrom() - latter.getFrom()),
220-
former.getReplaced());
221-
int addedSize = former.getTo() - (latter.getFrom() + latter.getOldLength()) + latter.getNewLength();
222-
return new Replacement<>(latter.getFrom(), removed, addedSize);
223-
}
224-
}
225-
226-
@SafeVarargs
227-
private static <T> List<T> concat(List<T>... lists) {
228-
int n = Arrays.asList(lists).stream().mapToInt(List::size).sum();
229-
List<T> res = new ArrayList<>(n);
230-
for(List<T> l: lists) {
231-
res.addAll(l);
104+
pendingChanges.add(TransientListChange.fromCurrentStateOf(change));
232105
}
233-
return res;
234106
}
235107

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

reactfx/src/main/java/org/reactfx/inhibeans/collection/SingleListChange.java

Lines changed: 0 additions & 133 deletions
This file was deleted.

0 commit comments

Comments
 (0)