org.reactfx.collection.ListChangeAccumulator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of reactfx Show documentation
Show all versions of reactfx Show documentation
Reactive event streams for JavaFX
package org.reactfx.collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.collections.ListChangeListener;
import org.reactfx.util.Lists;
public final class ListChangeAccumulator implements ListModificationSequence {
private QuasiListChangeImpl modifications = new QuasiListChangeImpl<>();
public ListChangeAccumulator() {}
public ListChangeAccumulator(QuasiListChange change) {
modifications = new QuasiListChangeImpl<>(change);
}
@Override
public ListChangeAccumulator asListChangeAccumulator() {
return this;
}
@Override
public QuasiListChange asListChange() {
return fetch();
}
@Override
public List> getModifications() {
return Collections.unmodifiableList(modifications);
}
public boolean isEmpty() {
return modifications.isEmpty();
}
public QuasiListChange fetch() {
QuasiListChange res = modifications;
modifications = new QuasiListChangeImpl<>();
return res;
}
public ListChangeAccumulator drop(int n) {
modifications.subList(0, n).clear();
return this;
}
public ListChangeAccumulator add(QuasiListModification mod) {
if(modifications.isEmpty()) {
modifications.add(mod);
} else {
// find first and last overlapping modification
int from = mod.getFrom();
int to = from + mod.getRemovedSize();
int firstOverlapping = 0;
for(; firstOverlapping < modifications.size(); ++firstOverlapping) {
if(modifications.get(firstOverlapping).getTo() >= from) {
break;
}
}
int lastOverlapping = modifications.size() - 1;
for(; lastOverlapping >= 0; --lastOverlapping) {
if(modifications.get(lastOverlapping).getFrom() <= to) {
break;
}
}
// offset modifications farther in the list
int diff = mod.getTo() - mod.getFrom() - mod.getRemovedSize();
offsetPendingModifications(lastOverlapping + 1, diff);
// combine overlapping modifications into one
if(lastOverlapping < firstOverlapping) { // no overlap
modifications.add(firstOverlapping, mod);
} else { // overlaps one or more former modifications
List> overlapping = modifications.subList(firstOverlapping, lastOverlapping + 1);
QuasiListModification joined = join(overlapping, mod.getRemoved(), mod.getFrom());
QuasiListModification newMod = combine(joined, mod);
overlapping.clear();
modifications.add(firstOverlapping, newMod);
}
}
return this;
}
public ListChangeAccumulator add(QuasiListChange change) {
for(QuasiListModification mod: change) {
add(mod);
}
return this;
}
public ListChangeAccumulator add(ListChangeListener.Change change) {
while(change.next()) {
add(QuasiListModification.fromCurrentStateOf(change));
}
return this;
}
private void offsetPendingModifications(int from, int offset) {
modifications.subList(from, modifications.size())
.replaceAll(mod -> new QuasiListModificationImpl<>(
mod.getFrom() + offset,
mod.getRemoved(),
mod.getAddedSize()));
}
private static QuasiListModification join(
List> mods,
List gone,
int goneOffset) {
if(mods.size() == 1) {
return mods.get(0);
}
List> removedLists = new ArrayList<>(2*mods.size() - 1);
QuasiListModification prev = mods.get(0);
int from = prev.getFrom();
removedLists.add(prev.getRemoved());
for(int i = 1; i < mods.size(); ++i) {
QuasiListModification m = mods.get(i);
removedLists.add(gone.subList(prev.getTo() - goneOffset, m.getFrom() - goneOffset));
removedLists.add(m.getRemoved());
prev = m;
}
List removed = Lists.concatView(removedLists);
return new QuasiListModificationImpl<>(from, removed, prev.getTo() - from);
}
private static QuasiListModification combine(
QuasiListModification former,
QuasiListModification latter) {
if(latter.getFrom() >= former.getFrom() && latter.getFrom() + latter.getRemovedSize() <= former.getTo()) {
// latter is within former
List removed = former.getRemoved();
int addedSize = former.getAddedSize() - latter.getRemovedSize() + latter.getAddedSize();
return new QuasiListModificationImpl<>(former.getFrom(), removed, addedSize);
} else if(latter.getFrom() <= former.getFrom() && latter.getFrom() + latter.getRemovedSize() >= former.getTo()) {
// former is within latter
List removed = Lists.concatView(
latter.getRemoved().subList(0, former.getFrom() - latter.getFrom()),
former.getRemoved(),
latter.getRemoved().subList(former.getTo() - latter.getFrom(), latter.getRemovedSize()));
return new QuasiListModificationImpl<>(latter.getFrom(), removed, latter.getAddedSize());
} else if(latter.getFrom() >= former.getFrom()) {
// latter overlaps to the right
List removed = Lists.concatView(
former.getRemoved(),
latter.getRemoved().subList(former.getTo() - latter.getFrom(), latter.getRemovedSize()));
return new QuasiListModificationImpl<>(former.getFrom(), removed, latter.getTo() - former.getFrom());
} else {
// latter overlaps to the left
List removed = Lists.concatView(
latter.getRemoved().subList(0, former.getFrom() - latter.getFrom()),
former.getRemoved());
int addedSize = former.getTo() - latter.getRemovedSize() + latter.getAddedSize() - latter.getFrom();
return new QuasiListModificationImpl<>(latter.getFrom(), removed, addedSize);
}
}
}