From 3568fa65d4b0c8960e4cd74f46d46d19feedfca9 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 6 Apr 2016 02:23:16 +0200 Subject: [PATCH 1/3] Add EasyBind.concat(List>) Creates a new list that combines the values of the given lists. Unlike FXCollections.concat(), updates to the source lists propagate to the combined list. --- .../java/org/fxmisc/easybind/ConcatList.java | 92 +++++++++++++++++++ .../java/org/fxmisc/easybind/EasyBind.java | 11 +++ 2 files changed, 103 insertions(+) create mode 100644 src/main/java/org/fxmisc/easybind/ConcatList.java 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..1a52b16 --- /dev/null +++ b/src/main/java/org/fxmisc/easybind/ConcatList.java @@ -0,0 +1,92 @@ +package org.fxmisc.easybind; + +import java.util.Arrays; +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 ObservableConcatList(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..b92cddd 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, From ccf76e1a14e51f64555b37375c8360bc57f32703 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 6 Apr 2016 12:44:45 +0200 Subject: [PATCH 2/3] Fix compiler errors --- src/main/java/org/fxmisc/easybind/ConcatList.java | 3 +-- src/main/java/org/fxmisc/easybind/EasyBind.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/fxmisc/easybind/ConcatList.java b/src/main/java/org/fxmisc/easybind/ConcatList.java index 1a52b16..ff4abef 100644 --- a/src/main/java/org/fxmisc/easybind/ConcatList.java +++ b/src/main/java/org/fxmisc/easybind/ConcatList.java @@ -1,6 +1,5 @@ package org.fxmisc.easybind; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -12,7 +11,7 @@ class ConcatList extends ObservableListBase { private final List> sourceLists; - public ObservableConcatList(List> sourceLists) { + public ConcatList(List> sourceLists) { assert sourceLists != null; this.sourceLists = sourceLists; for (ObservableList source : sourceLists) diff --git a/src/main/java/org/fxmisc/easybind/EasyBind.java b/src/main/java/org/fxmisc/easybind/EasyBind.java index b92cddd..5a02cd4 100644 --- a/src/main/java/org/fxmisc/easybind/EasyBind.java +++ b/src/main/java/org/fxmisc/easybind/EasyBind.java @@ -118,12 +118,12 @@ public static ObservableList map( } public static ObservableList concat( - List> sources) { + List> sources) { return new ConcatList<>(sources); } public static ObservableList concat( - ObservableList... sources) { + ObservableList... sources) { return new ConcatList<>(Arrays.asList(sources)); } From 5074448f0499c8541d2b728dff2537defb75a8f5 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 6 Apr 2016 12:45:10 +0200 Subject: [PATCH 3/3] add some tests for concatenating lists --- .../org/fxmisc/easybind/ConcatListTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/test/java/org/fxmisc/easybind/ConcatListTest.java 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)); + } +}