Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListChangeListener wasPermutated block

The JavaDoc for the ListChangeListener provides a template for handling changes. I don't know how to handle permutations, however. For every index, I can find out where the new index of the item is, but I don't know what to do with it. This is a bit of a puzzle that is independent of programming language. An ObservableList can only add(), remove(), set(), and also has an iterator.

If I have an original list [1,2,3], and bind a list[] to it, the bound list[1,2,3] needs to match it. If the original list gets its comparator swapped so that the original list now reads [3,2,1], how do I make the bound list follow along?

/**
 * Binds a source list's elements to a destination list. Any changes made in
 * the source list will reflect in the destination list.
 *
 * @param <SRC> The source list's object type.
 * @param <DEST> The destination list's object type.
 * @param dest The destination list that will be bound to the src list.
 * @param src The source list to watch for changes, and propagate up to the
 * destination list.
 * @param transformer A function that will transform a source list data
 * type, A, into a destination list data type, B.
 */
public static <SRC, DEST> void bindLists(
        ObservableList<DEST> dest, ObservableList<SRC> src, Function<? super SRC, ? extends DEST> transformer) {
    /*Add the initial data into the destination list.*/
    for (SRC a : src) {
        dest.add(transformer.apply(a));
    }
    /*Watch for future data to add to the destination list. Also watch for removal
     of data form the source list to remove its respective item in the destination
     list.*/
    src.addListener((ListChangeListener.Change<? extends SRC> c) -> {
        while (c.next()) {
            if (c.wasPermutated()) {
                /*How do you handle permutations? Do you remove and then add, 
                 or add and then remove, or use set, or use a copy arraylist 
                 and set the right indices? Removing/adding causes concurrent modifications.*/
                for (int oldIndex = c.getFrom(); oldIndex < c.getTo(); oldIndex++) {
                    int newIndex = c.getPermutation(oldIndex);
                    dest.remove(oldIndex);
                    dest.add(newIndex, dest.get(oldIndex));
                }
            } else if (c.wasUpdated()) {

            } else {
                /*Respond to removed data.*/
                for (SRC item : c.getRemoved()) {
                    int from = c.getFrom();
                    dest.remove(from);
                }
                /*Respond to added data.*/
                for (SRC item : c.getAddedSubList()) {
                    int indexAdded = src.indexOf(item);
                    dest.add(indexAdded, transformer.apply(item));
                }
            }
        }
    });
}
like image 760
Toni_Entranced Avatar asked Sep 06 '14 23:09

Toni_Entranced


2 Answers

For the permutation case, I wouldn't bother trying to use add() and remove() to do handle it. This will cause the indexes to shift around and will make things confusing (at least to me).

Conceptually what you get is a range of elements affected, and an array containing some numbers that indicate where each element was moved. I think you understand that much. In your code you have,

    newIndex = getPermutation(oldIndex);

which means that the element was at oldIndex needs to be moved to newIndex. The wrinkle is that if you just make the move directly, you might be overwriting an element that hasn't been moved yet. I think the simplest way to deal with this is to make a copy of the affected subrange and then just step through the permutation array and move elements from the copy into their new positions. The code to do this is:

    int from = c.getFrom();
    int to = c.getTo();
    List<DEST> copy = new ArrayList<>(dest.subList(from, to));
    for (int oldIndex = from; oldIndex < to; oldIndex++) {
        int newIndex = c.getPermutation(oldIndex);
        dest.set(newIndex, copy.get(oldIndex - from));
    }

This is a permutation, so every element ends up somewhere, and none are added or deleted. This implies that you don't have to copy the list range, and that you could move elements one at a time following the chain of moves while using only a single element of temporary space. There might be multiple cycles of chains, so you'd have to detect and handle that too. That sounds pretty complex. I'll leave that for another answerer. :-) For my money, copying the affected range is simple and easy to understand.

The permutation and updated change modes aren't triggered by normal list actions. If you look at javafx.collections.ObservableListBase you can see a protocol that a list implementation can use to build up information about a specific change. If the implementation supplies the right information to nextPermutation or nextUpdate methods, it will trigger these more other change modes. I'm not sure what might trigger them in JavaFX. For example, the Node.toFront() and Node.toBack() methods to change node stacking order potentially could generate permutation changes, but they don't seem to. I don't know of anything that would generate an update change either.

Semantically, I think an update change implies that the elements in that range of the list have changed but that the length of the list stays the same. This is in contrast to the "replaced" change mode, where a range of elements might be replaced with a different number of elements. It could also be that the update change means that the elements themselves haven't been replaced -- that is, the list contents haven't changed -- merely that the elements' internal states have changed.

like image 132
Stuart Marks Avatar answered Nov 19 '22 04:11

Stuart Marks


Note that what you are doing here is implemented in EasyBind. See how to map and bind a list.

Essentially, this is how you implement your bindLists method:

public static <SRC, DEST> void bindLists(
        ObservableList<DEST> dest, ObservableList<SRC> src,
        Function<? super SRC, ? extends DEST> transformer) {

    EasyBind.listBind(dest, EasyBind.map(src, transformer));
}
like image 22
Tomas Mikula Avatar answered Nov 19 '22 03:11

Tomas Mikula