How to concatenate observable lists in JavaFX?
Asked Answered
S

6

7

By concatenation I mean obtaining a new list, which listens for changes in all concatenated parts.

What is the purpose of method FXCollections#concat(ObservableList<E>... lists)? If it just merges several lists, then I see no sense to have separate method for this.

And if regard as doing what I wish then it doesn't work:

import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

public class ConcatObservabeList {

   public static void main(String[] args) {

      ObservableList<Integer> list1 = FXCollections.observableArrayList();
      ObservableList<Integer> list2 = FXCollections.observableArrayList();


      ObservableList<Integer> concat = FXCollections.concat(list1, list2);
      concat.addListener(new ListChangeListener<Integer>() {
         public void onChanged(Change<? extends Integer> c) {
            System.out.println("changed");
         }
      });

      list1.add(12);


   }
}
Sabotage answered 30/5, 2016 at 11:34 Comment(4)
probably just a convenience - have a look at the code ;-) I suspect that it simply copies all content of all lists into its own backing structure (same as the observableList(List) doesHarmonyharmotome
@kleopatra: It look like it copies references, as suggested below.Autonomous
@Autonomous yeah, that was my guess, wasn't it :-)Harmonyharmotome
you might try to implement a custom TransformList (which might be what's done in the referenced library in one of the answers, didn't check that though) Curious: what exactly are you after?Harmonyharmotome
U
6

I was having the same need to concat/aggregate several ObservableLists into one list for a JavaFX LineChart. The examples I found here or on the github posted in another answer always copied all the entries from the sublists into the aggregated List on each change. For a list with many entries this seemed not very elegant.

I decided to implement my on version, which keeps track of the position of the sublists in the aggregated list and when elements in the sublists change, apply the same changes in the aggregated list. There is still room for improvement (not using a delegate List but extending a ObservableList directly, or firing the events from sublists up to the aggregated list and overriding the getters and iterators - help with that would be appreciated), but I thought I post my version here as it is, maybe it helps someone.

Code:

/**
 * This class aggregates several other Observed Lists (sublists), observes changes on those sublists and applies those same changes to the
 * aggregated list.
 * Inspired by:
 * - https://mcmap.net/q/1479617/-listchangelistener-waspermutated-block
 * - https://mcmap.net/q/1446695/-how-to-concatenate-observable-lists-in-javafx
 * - https://github.com/lestard/advanced-bindings/blob/master/src/main/java/eu/lestard/advanced_bindings/api/CollectionBindings.java
 * Posted result on: https://mcmap.net/q/1446695/-how-to-concatenate-observable-lists-in-javafx
 */
public class AggregatedObservableArrayList<T> {

    protected final List<ObservableList<T>> lists = new ArrayList<>();
    final private List<Integer> sizes = new ArrayList<>();
    final private List<InternalListModificationListener> listeners = new ArrayList<>();
    final protected ObservableList<T> aggregatedList = FXCollections.observableArrayList();

    public AggregatedObservableArrayList() {

    }

    /**
     * The Aggregated Observable List. This list is unmodifiable, because sorting this list would mess up the entire bookkeeping we do here.
     *
     * @return an unmodifiable view of the aggregatedList
     */
    public ObservableList<T> getAggregatedList() {
        return FXCollections.unmodifiableObservableList(aggregatedList);
    }

    public void appendList(@NotNull ObservableList<T> list) {
        assert !lists.contains(list) : "List is already contained: " + list;
        lists.add(list);
        final InternalListModificationListener listener = new InternalListModificationListener(list);
        list.addListener(listener);
        //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
        sizes.add(list.size());
        aggregatedList.addAll(list);
        listeners.add(listener);
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
    }

    public void prependList(@NotNull ObservableList<T> list) {
        assert !lists.contains(list) : "List is already contained: " + list;
        lists.add(0, list);
        final InternalListModificationListener listener = new InternalListModificationListener(list);
        list.addListener(listener);
        //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
        sizes.add(0, list.size());
        aggregatedList.addAll(0, list);
        listeners.add(0, listener);
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
    }

    public void removeList(@NotNull ObservableList<T> list) {
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
        final int index = lists.indexOf(list);
        if (index < 0) {
            throw new IllegalArgumentException("Cannot remove a list that is not contained: " + list + " lists=" + lists);
        }
        final int startIndex = getStartIndex(list);
        final int endIndex = getEndIndex(list, startIndex);
        // we want to find the start index of this list inside the aggregated List. End index will be start + size - 1.
        lists.remove(list);
        sizes.remove(index);
        final InternalListModificationListener listener = listeners.remove(index);
        list.removeListener(listener);
        aggregatedList.remove(startIndex, endIndex + 1); // end + 1 because end is exclusive
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
    }

    /**
     * Get the start index of this list inside the aggregated List.
     * This is a private function. we can safely asume, that the list is in the map.
     *
     * @param list the list in question
     * @return the start index of this list in the aggregated List
     */
    private int getStartIndex(@NotNull ObservableList<T> list) {
        int startIndex = 0;
        //System.out.println("=== searching startIndex of " + list);
        assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
        final int listIndex = lists.indexOf(list);
        for (int i = 0; i < listIndex; i++) {
            final Integer size = sizes.get(i);
            startIndex += size;
            //System.out.println(" startIndex = " + startIndex + " added=" + size);
        }
        //System.out.println("startIndex = " + startIndex);
        return startIndex;
    }

    /**
     * Get the end index of this list inside the aggregated List.
     * This is a private function. we can safely asume, that the list is in the map.
     *
     * @param list       the list in question
     * @param startIndex the start of the list (retrieve with {@link #getStartIndex(ObservableList)}
     * @return the end index of this list in the aggregated List
     */
    private int getEndIndex(@NotNull ObservableList<T> list, int startIndex) {
        assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
        final int index = lists.indexOf(list);
        return startIndex + sizes.get(index) - 1;
    }

    private class InternalListModificationListener implements ListChangeListener<T> {

        @NotNull
        private final ObservableList<T> list;

        public InternalListModificationListener(@NotNull ObservableList<T> list) {
            this.list = list;
        }

        /**
         * Called after a change has been made to an ObservableList.
         *
         * @param change an object representing the change that was done
         * @see Change
         */
        @Override
        public void onChanged(Change<? extends T> change) {
            final ObservableList<? extends T> changedList = change.getList();
            final int startIndex = getStartIndex(list);
            final int index = lists.indexOf(list);
            final int newSize = changedList.size();
            //System.out.println("onChanged for list=" + list + " aggregate=" + aggregatedList);
            while (change.next()) {
                final int from = change.getFrom();
                final int to = change.getTo();
                //System.out.println(" startIndex=" + startIndex + " from=" + from + " to=" + to);
                if (change.wasPermutated()) {
                    final ArrayList<T> copy = new ArrayList<>(aggregatedList.subList(startIndex + from, startIndex + to));
                    //System.out.println("  permutating sublist=" + copy);
                    for (int oldIndex = from; oldIndex < to; oldIndex++) {
                        int newIndex = change.getPermutation(oldIndex);
                        copy.set(newIndex - from, aggregatedList.get(startIndex + oldIndex));
                    }
                    //System.out.println("  permutating done sublist=" + copy);
                    aggregatedList.subList(startIndex + from, startIndex + to).clear();
                    aggregatedList.addAll(startIndex + from, copy);
                } else if (change.wasUpdated()) {
                    // do nothing
                } else {
                    if (change.wasRemoved()) {
                        List<? extends T> removed = change.getRemoved();
                        //System.out.println("  removed= " + removed);
                        // IMPORTANT! FROM == TO when removing items.
                        aggregatedList.remove(startIndex + from, startIndex + from + removed.size());
                    }
                    if (change.wasAdded()) {
                        List<? extends T> added = change.getAddedSubList();
                        //System.out.println("  added= " + added);
                        //add those elements to your data
                        aggregatedList.addAll(startIndex + from, added);
                    }
                }
            }
            // update the size of the list in the map
            //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
            sizes.set(index, newSize);
            //System.out.println("listSizesMap = " + sizes);
        }

    }

    public String dump(Function<T, Object> function) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        aggregatedList.forEach(el -> sb.append(function.apply(el)).append(","));
        final int length = sb.length();
        sb.replace(length - 1, length, "");
        sb.append("]");
        return sb.toString();
    }
}

jUnit Test:

/**
 * Testing the AggregatedObservableArrayList
 */
public class AggregatedObservableArrayListTest {


    @Test
    public void testObservableValue() {
        final AggregatedObservableArrayList<IntegerProperty> aggregatedWrapper = new AggregatedObservableArrayList<>();
        final ObservableList<IntegerProperty> aggregatedList = aggregatedWrapper.getAggregatedList();
        aggregatedList.addListener((Observable observable) -> {
            System.out.println("observable = " + observable);
        });

        final ObservableList<IntegerProperty> list1 = FXCollections.observableArrayList();
        final ObservableList<IntegerProperty> list2 = FXCollections.observableArrayList();
        final ObservableList<IntegerProperty> list3 = FXCollections.observableArrayList();

        list1.addAll(new SimpleIntegerProperty(1), new SimpleIntegerProperty(2), new SimpleIntegerProperty(3), new SimpleIntegerProperty(4),
                     new SimpleIntegerProperty(5));
        list2.addAll(new SimpleIntegerProperty(10), new SimpleIntegerProperty(11), new SimpleIntegerProperty(12), new SimpleIntegerProperty(13),
                     new SimpleIntegerProperty(14), new SimpleIntegerProperty(15));
        list3.addAll(new SimpleIntegerProperty(100), new SimpleIntegerProperty(110), new SimpleIntegerProperty(120), new SimpleIntegerProperty(130),
                     new SimpleIntegerProperty(140), new SimpleIntegerProperty(150));

        // adding list 1 to aggregate
        aggregatedWrapper.appendList(list1);
        assertEquals("wrong content", "[1,2,3,4,5]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // removing elems from list1
        list1.remove(2, 4);
        assertEquals("wrong content", "[1,2,5]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // adding second List
        aggregatedWrapper.appendList(list2);
        assertEquals("wrong content", "[1,2,5,10,11,12,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // removing elems from second List
        list2.remove(1, 3);
        assertEquals("wrong content", "[1,2,5,10,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // replacing element in first list
        list1.set(1, new SimpleIntegerProperty(3));
        assertEquals("wrong content", "[1,3,5,10,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // adding third List
        aggregatedWrapper.appendList(list3);
        assertEquals("wrong content", "[1,3,5,10,13,14,15,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // emptying second list
        list2.clear();
        assertEquals("wrong content", "[1,3,5,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // adding new elements to second list
        list2.addAll(new SimpleIntegerProperty(203), new SimpleIntegerProperty(202), new SimpleIntegerProperty(201));
        assertEquals("wrong content", "[1,3,5,203,202,201,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // sorting list2. this results in permutation
        list2.sort((o1, o2) -> o1.getValue().compareTo(o2.getValue()));
        assertEquals("wrong content", "[1,3,5,201,202,203,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // removing list2 completely
        aggregatedWrapper.removeList(list2);
        assertEquals("wrong content", "[1,3,5,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // updating one integer value in list 3
        SimpleIntegerProperty integer = (SimpleIntegerProperty) list3.get(0);
        integer.set(1);
        assertEquals("wrong content", "[1,3,5,1,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // prepending list 2 again
        aggregatedWrapper.prependList(list2);
        assertEquals("wrong content", "[201,202,203,1,3,5,1,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

    }

}
Upstairs answered 10/10, 2016 at 13:55 Comment(0)
A
3

A ListChangeListener added to an ObservableList sees certain specific changes made to the list as a whole. The overhead of also listening to any ancestor lists is considerable, as seen in the API cited here. Because FXCollections.concat() simply copies references from the source lists to the destination's backing list, a listener added to concat will see changes made to concat; it will not see changes to list1 or list2.

If you don't need to create a new ObservableList for some other reason, aggregate the lists in a way that allows you to add the same listener to each.

Console:

changed { [42] added at 0 }

Code:

import java.util.ArrayList;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

/**
 * @see https://mcmap.net/q/1446695/-how-to-concatenate-observable-lists-in-javafx
 */
public class ObservableListAggregate {

    public static void main(String[] args) {
        ObservableList<Integer> list1 = FXCollections.observableArrayList();
        ObservableList<Integer> list2 = FXCollections.observableArrayList();
        Aggregate<ObservableList<Integer>> aggregate = new Aggregate(list1, list2);
        aggregate.addListener(new ListChangeListener<ObservableList<Integer>>() {
            @Override
            public void onChanged(ListChangeListener.Change<? extends ObservableList<Integer>> c) {
                System.out.println("changed " + c);
            }
        });
        list1.add(42);
    }

    private static class Aggregate<T> {

        List<ObservableList<T>> lists = new ArrayList<>();

        public Aggregate(ObservableList<T>... lists) {
            for (ObservableList<T> list : lists) {
                this.lists.add(list);
            }
        }

        public final void addListener(ListChangeListener<? super T> listener) {
            for (ObservableList<T> list : lists) {
                list.addListener(listener);
            }
        }

        public final void removeListener(ListChangeListener<? super T> listener) {
            for (ObservableList<T> list : lists) {
                list.removeListener(listener);
            }
        }
    }
}

To see changes to the individual list elements, use an ObservableList<Observable>, such as ObservableList<IntegerProperty>. In the example below, note that ip, added to list1, is the same IntegerProperty later modified in concat.

Console:

IntegerProperty [value: 42]
concat changed { [IntegerProperty [value: 2147483647]] added at 1 }

Code:

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

public class ConcatObservabeList {

    public static void main(String[] args) {

        ObservableList<IntegerProperty> list1 = FXCollections.observableArrayList();
        IntegerProperty ip = new SimpleIntegerProperty(0);
        ip.addListener(System.out::println);
        list1.add(ip);
        ObservableList<IntegerProperty> list2 = FXCollections.observableArrayList();
        ObservableList<IntegerProperty> concat = FXCollections.concat(list1, list2);
        concat.get(0).setValue(42);
        concat.addListener(new ListChangeListener<IntegerProperty>() {
            @Override
            public void onChanged(ListChangeListener.Change<? extends IntegerProperty> c) {
                System.out.println("concat changed " + c);
            }
        });
        concat.add(new SimpleIntegerProperty(Integer.MAX_VALUE));
    }
}
Autonomous answered 30/5, 2016 at 13:43 Comment(6)
hmm .. you might consider to clarify that adding an new item to one of the source list will not trigger the changeListener on the concatenated list (which I think was the question - could be wrong, though, brain working sub-optimally on vacation :-)Harmonyharmotome
@kleopatra: Your point is well-taken; I'm waiting for a response from Dims.Autonomous
It is interesting aspect you noted. Indeed, concat-ed list seems to hold the very same instances, that member lists do. This is strengthen an impression, that FXCollection#concat method is wrongly designed.Sabotage
Dims: I must demur; observers should observe the list to which they are added as listeners; the overhead of also listening to ancestors would grow combinatorially, as seen in the API cited here; I would reiterate @kleopatra's question—What problem are you trying to solve?Autonomous
@Sabotage actually, I agree that the method is an api design accident: the accidental aspect being that it requires a list of type ObservableList when its implementation only makes use of a simple List (in copying all content into an Arraylist and then calling observableList(copiedAll). This typing requrement itself might lead to the (otherwise unsupported) expectation that those lists are observed. lAlso the javadoc is clearly written by a <censored>.. But again: it's not rocket science: simply implement your own (on vacation without a suitable IDE, would show you how to otherwise :-)Harmonyharmotome
@kleopatra: I've added a simple aggregation example above; as always, critical review welcome.Autonomous
B
2

I stumbled across the same problem, because the method FXCollections.concat(...) did not change on changes of the source lists, which was a requirement for my use case.

Since the other answers to this question seem like a little overkill to me, I'll add my own solution, which has two major limitations:

  • The resulting ObservableList will be read-only to the contents of the source lists.
  • The resulting ObservableList will be a new instance on each change, so that possible UI elements based on them will be newly created on every change.

As any ListBinding computes an ObservableList as its value and implements (and therefor is a) ObservableList at the same time, it can be easily used to enhance the default concat method to update on changes of the source lists:

@SafeVarargs
public static <T> ObservableList<T> concat(ObservableList<T>... sources) {
    return new ListBinding<T>() {

        {
            bind(sources);
        }

        @Override
        protected ObservableList<T> computeValue() {
            return FXCollections.concat(sources);
        }
    };
}
Baggott answered 18/7, 2018 at 23:59 Comment(1)
This is what I really needed! ThanksLeeannaleeanne
C
1

I also had the same problem. Finally I made a list of ObservableList:

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;

/**
 * Read only view on a list of {@link ObservableList}
 *
 * @author Marcel Heckel
 */
public class CompositeObservableList<T> extends AbstractList<T> implements ObservableList<T>
{
    protected List<ObservableList<T>>             lists;

    protected List<ListChangeListener<? super T>> listChangeListeners          = new ArrayList<>();

    protected List<InvalidationListener>          invalidationListeners        = new ArrayList<>();

    protected InvalidationListener                internalInvalidationListener = this::invalidated;

    protected ListChangeListener<T>               internalListChangeListener   = this::onChanged;

    protected WeakInvalidationListener            weakInvalidationListener     = new WeakInvalidationListener(
        internalInvalidationListener);

    protected WeakListChangeListener<T>           weakListChangeListener       = new WeakListChangeListener<>(
        internalListChangeListener);

    public CompositeObservableList()
    {
        this.lists = new ArrayList<>();
    }

    public CompositeObservableList(List<ObservableList<T>> lists)
    {
        this.lists = lists;

        for (ObservableList<T> l : lists)
        {
            l.addListener(weakInvalidationListener);
            l.addListener(weakListChangeListener);
        }
    }

    public void addObservableList(ObservableList<T> l)
    {
        lists.add(l);
        l.addListener(weakInvalidationListener);
        l.addListener(weakListChangeListener);
    }

    /** remove listeners and clears the internal list */
    public void clearLists()
    {
        for (ObservableList<T> l : lists)
        {
            l.removeListener(weakInvalidationListener);
            l.removeListener(weakListChangeListener);
        }
        lists.clear();
    }

    ///////////////////////////////////////
    // listeners

    private void invalidated(Observable observable)
    {
        for (InvalidationListener l : invalidationListeners)
            l.invalidated(CompositeObservableList.this);
    }

    private void onChanged(ListChangeListener.Change<? extends T> c)
    {
        int idx = getStartIndexOfListReference(c.getList());
        assert (idx >= 0);
        if (idx < 0)
            return;

        c = new IndexOffsetChange<>(CompositeObservableList.this, idx, c);

        for (ListChangeListener<? super T> l : listChangeListeners)
        {
            l.onChanged(c);
        }
    }

    private int getStartIndexOfListReference(ObservableList<?> l)
    {
        int startIndex = 0;
        for (int i = 0; i < lists.size(); i++ )
        {
            if (l == lists.get(i))
                return startIndex;
            startIndex += l.size();
        }
        return -1;
    }

    ////////////////////////////////////////

    @Override
    public int size()
    {
        int size = 0;
        for (Collection<T> c : lists)
            size += c.size();
        return size;
    }

    @Override
    public boolean isEmpty()
    {
        for (Collection<T> c : lists)
            if ( !c.isEmpty())
                return false;
        return true;
    }

    @Override
    public boolean contains(Object obj)
    {
        for (Collection<T> c : lists)
            if (c.contains(obj))
                return true;
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c)
    {
        for (Object ele : c)
        {
            if ( !this.contains(ele))
                return false;
        }
        return true;
    }

    @Override
    public int indexOf(Object o)
    {
        int index = 0;
        for (List<T> l : lists)
        {
            int i = l.indexOf(o);
            if (i >= 0)
                return index + i;
            index += l.size();
        }
        return -1;
    }

    @Override
    public T get(int index)
    {
        if (index < 0)
            throw new IndexOutOfBoundsException("index: " + index + " - size: " + size());

        for (List<T> l : lists)
        {
            if (l.size() > index)
                return l.get(index);
            index -= l.size();
        }
        throw new IndexOutOfBoundsException("index: " + index + " - size: " + size());
    }

    @Override
    public Iterator<T> iterator()
    {
        return new Iterator<T>()
            {
                Iterator<T>                 currentIterator = null;

                Iterator<ObservableList<T>> listsIterator   = lists.iterator();

                @Override
                public boolean hasNext()
                {
                    while (true)
                    {
                        if (currentIterator != null && currentIterator.hasNext())
                            return true;
                        if ( !listsIterator.hasNext())
                            return false;
                        currentIterator = listsIterator.next().iterator();
                    }
                }

                @Override
                public T next()
                {
                    if ( !hasNext())
                        throw new NoSuchElementException();
                    return currentIterator.next();
                }

                @Override
                public void remove()
                {
                    throw new UnsupportedOperationException();
                }

            };
    }

    // editing methods

    @Override
    public boolean add(T obj)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(Object obj)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(Collection<? extends T> c)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeAll(Collection<?> c)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean retainAll(final Collection<?> c)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear()
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> c)
    {

        throw new UnsupportedOperationException();
    }

    @Override
    public T set(int index, T element)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public void add(int index, T element)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public T remove(int index)
    {
        throw new UnsupportedOperationException();
    }

    // editing methods of ObservableList list

    @Override
    public boolean addAll(T... elements)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean setAll(T... elements)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean setAll(Collection<? extends T> c)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeAll(T... elements)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean retainAll(T... elements)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public void remove(int from, int to)
    {
        throw new UnsupportedOperationException();
    }

    /////////////////////////////

    @Override
    public void addListener(InvalidationListener listener)
    {
        invalidationListeners.add(listener);
    }

    @Override
    public void removeListener(InvalidationListener listener)
    {
        invalidationListeners.remove(listener);
    }

    @Override
    public void addListener(ListChangeListener<? super T> listener)
    {
        listChangeListeners.add(listener);

    }

    @Override
    public void removeListener(ListChangeListener<? super T> listener)
    {
        listChangeListeners.remove(listener);
    }

    ////////////////

    private static class IndexOffsetChange<T> extends ListChangeListener.Change<T>
    {
        private final int                                    indexOffset;

        private final ListChangeListener.Change<? extends T> delegate;

        public IndexOffsetChange(ObservableList<T> list, final int indexOffset,
            ListChangeListener.Change<? extends T> c)
        {
            super(list);
            this.indexOffset = indexOffset;
            this.delegate = c;
        }

        @Override
        public boolean next()
        {
            return delegate.next();
        }

        @Override
        public void reset()
        {
            delegate.reset();
        }

        @Override
        public int getFrom()
        {
            return delegate.getFrom() + indexOffset;
        }

        @Override
        public int getTo()
        {
            return delegate.getTo() + indexOffset;
        }

        @Override
        public boolean wasPermutated()
        {
            return delegate.wasPermutated();
        }

        @Override
        public int getPermutation(int i)
        {
            return indexOffset + super.getPermutation(i - indexOffset);
        }

        @Override
        protected int[] getPermutation()
        {
            return null;
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<T> getAddedSubList()
        {
            return (List<T>) delegate.getAddedSubList();
        }

        @Override
        public int getAddedSize()
        {
            return delegate.getAddedSize();
        }

        @Override
        public boolean wasAdded()
        {
            return delegate.wasAdded();
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<T> getRemoved()
        {
            return (List<T>) delegate.getRemoved();
        }

        @Override
        public int getRemovedSize()
        {
            return delegate.getRemovedSize();
        }

        @Override
        public boolean wasRemoved()
        {
            return delegate.wasRemoved();
        }

        @Override
        public boolean wasReplaced()
        {
            return delegate.wasReplaced();
        }

        @Override
        public boolean wasUpdated()
        {
            return delegate.wasUpdated();
        }

    }

}
Channa answered 16/3, 2018 at 20:37 Comment(0)
O
0

I had the same issue and have created a helper for this use case in the library advanced-bindings (javadoc of the helper method).

Odericus answered 30/5, 2016 at 17:3 Comment(5)
Note that link-only answers are discouraged, SO answers should be the end-point of a search for a solution (vs. yet another stopover of references, which tend to get stale over time). Please consider adding a stand-alone synopsis here, keeping the link as a reference.Harmonyharmotome
I have to agree with @kleopatra; the overhead of the approach cited is a considerable; a synopsis would be useful.Autonomous
I'm not sure the result of CollectionBindings.concat() can even be called an ObservableList, as it is cleared with each change to any of the lists.Autonomous
@Autonomous I think it's covered by the contract, though very ill-behaved as it does't report the changes in the most narrow manner but concats (haha) every type of change into a clear/multiple addsHarmonyharmotome
You are right. The implementation isn't really good and the behaviour of change events can be a problem. I was posting it here because maybe it would be still useful for some people. It was useful for me at least in some use cases. But maybe I will fix it and replace it with a "real" implementation that behaves as expected in all use cases.Odericus
O
0

If you're fine with using a library, I recommend EasyBind:

ObservableList<String> listA = ...;
ObservableList<String> listB = ...;
ObservableList<String> combinedList = EasyBind.concat(listA, listB);

If you don't like adding a new dependency, @Lukas Körfer's answer using ListBinding is the best you'll get.

Oxen answered 29/7, 2020 at 23:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.