diff --git a/src/main/java/org/fxmisc/easybind/ConcatList.java b/src/main/java/org/fxmisc/easybind/ConcatList.java new file mode 100644 index 0000000..ff4abef --- /dev/null +++ b/src/main/java/org/fxmisc/easybind/ConcatList.java @@ -0,0 +1,91 @@ +package org.fxmisc.easybind; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableListBase; + +class ConcatList extends ObservableListBase { + private final List> sourceLists; + + public ConcatList(List> sourceLists) { + assert sourceLists != null; + this.sourceLists = sourceLists; + for (ObservableList source : sourceLists) + source.addListener(this::onSourceListChanged); + } + + private void onSourceListChanged(ListChangeListener.Change change) { + ObservableList source = change.getList(); + int indexOffset = 0; + for (int i = 0; sourceLists.get(i) != source; ++i) + indexOffset += sourceLists.get(i).size(); + + beginChange(); + while (change.next()) { + if (change.wasPermutated()) { + int rangeSize = change.getTo() - change.getFrom(); + int[] permutation = new int[rangeSize]; + for (int i = 0; i < rangeSize; ++i) + permutation[i] = change.getPermutation(i + change.getFrom()) + indexOffset; + nextPermutation(change.getFrom() + indexOffset, change.getTo() + indexOffset, permutation); + } else if (change.wasUpdated()) { + for (int i = change.getFrom(); i < change.getTo(); ++i) + nextUpdate(i+indexOffset); + } else if (change.wasAdded()) { + nextAdd(change.getFrom()+indexOffset, change.getTo()+indexOffset); + } else + nextRemove(change.getFrom()+indexOffset, change.getRemoved()); + } + endChange(); + } + + @Override + public E get(int index) { + if (index < 0) + throw new IndexOutOfBoundsException("List index must be >= 0. Was " + index); + + for (ObservableList source : sourceLists) { + if (index < source.size()) + return source.get(index); + index -= source.size(); + } + throw new IndexOutOfBoundsException("Index too large."); + } + + @Override + public Iterator iterator() { + return new Iterator() { + Iterator> sourceIterator = sourceLists.iterator(); + Iterator currentIterator = null; + + @Override + public boolean hasNext() { + while (currentIterator == null || !currentIterator.hasNext()) + if (sourceIterator.hasNext()) + currentIterator = sourceIterator.next().iterator(); + else + return false; + return true; + } + + @Override + public E next() { + while (currentIterator == null || !currentIterator.hasNext()) + if (sourceIterator.hasNext()) + currentIterator = sourceIterator.next().iterator(); + else + throw new NoSuchElementException(); + return currentIterator.next(); + } + }; + } + + @Override + public int size() { + return sourceLists.stream().mapToInt(ObservableList::size).sum(); + } +} diff --git a/src/main/java/org/fxmisc/easybind/EasyBind.java b/src/main/java/org/fxmisc/easybind/EasyBind.java index 2fc189f..5a02cd4 100644 --- a/src/main/java/org/fxmisc/easybind/EasyBind.java +++ b/src/main/java/org/fxmisc/easybind/EasyBind.java @@ -1,5 +1,6 @@ package org.fxmisc.easybind; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.function.BiFunction; @@ -116,6 +117,16 @@ public static ObservableList map( return new MappedList<>(sourceList, f); } + public static ObservableList concat( + List> sources) { + return new ConcatList<>(sources); + } + + public static ObservableList concat( + ObservableList... sources) { + return new ConcatList<>(Arrays.asList(sources)); + } + public static MonadicBinding combine( ObservableValue src1, ObservableValue src2, diff --git a/src/test/java/org/fxmisc/easybind/ConcatListTest.java b/src/test/java/org/fxmisc/easybind/ConcatListTest.java new file mode 100644 index 0000000..8a1cb1b --- /dev/null +++ b/src/test/java/org/fxmisc/easybind/ConcatListTest.java @@ -0,0 +1,45 @@ +package org.fxmisc.easybind; + +import static org.junit.Assert.*; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.junit.Test; + +public class ConcatListTest { + @Test + public void test() { + ObservableList a = FXCollections.observableArrayList("zero", "one", "two"); + ObservableList b = FXCollections.observableArrayList("three", "four", "five"); + ObservableList c = EasyBind.concat(a, b); + ObservableList d = EasyBind.concat(a, a); + + StringBinding bindOne = Bindings.stringValueAt(c, 1); + StringBinding bindFour = Bindings.stringValueAt(c, 4); + + assertEquals(6, c.size()); + assertEquals("one", bindOne.get()); + assertEquals("five", c.get(5)); + + a.remove(1); + assertEquals(5, c.size()); + assertEquals("three", c.get(2)); + assertEquals("two", bindOne.get()); + assertEquals("five", bindFour.get()); + + b.add(1, "x"); + assertEquals(6, c.size()); + assertEquals("x", c.get(3)); + assertEquals("four", bindFour.get()); + + a.set(0, "null"); + assertEquals("null", c.get(0)); + + assertEquals(4, d.size()); + assertEquals("two", d.get(1)); + assertEquals("null", d.get(2)); + } +}