ListChangeListener

Gaja Sutra gajasutra at gmail.com
Fri Dec 9 06:28:55 PST 2011


> Unfortunately we will not be able to implement your proposal for some time. ObservableList is an interface and we will not be able to make changes until JDK 8. Even with virtual extension methods in place, we will probably not be able to add the addListener method to ObservableList, because there is no default implementation as far as I can tell.
Yes, this is clearly not possible now with good compatibility, but I 
have not found problem before (sorry for not trying to write an 
ObservableList before). In Java8, the default implementation can be: 
wrap interface in an adaptor for current listener, like Daniel 
(Zwolenski) say.

> The other issue is, that you will have a hard time to convince people in the JavaFX team. One of the high level goals for the JavaFX API is to use SAMs as much as possible to benefit from lambda expressions later. You may have noticed that all listeners, event handlers, callbacks etc. in the JavaFX library are SAMs. Convincing people to break this pattern with the ListChangeHandler will be tough. :-)
Yes, but others listeners are on simple properties, not on collections 
(MapChangeListener seem to have the same problem than ListChangeListener 
for me, even if I doesn't have tried to write one). This is not a simple 
event handler, because you iterate on each operation of the change 
(because it listen a collection).

> Now what we can do now and what we should do is improve the documentation. From my experience most ListChangeListeners are actually quite simple, because usually you do not have to test the operation type. For example if you loop over all removed elements, this will also work for an add-operation, because the list of removed elements will be empty. But AFAIK this is not documented so far, I can imagine we need a tutorial or somebody should blog about some common cases and how you can implement them with ListChangeListeners.
After thinking, I think there is a bigger problem with current design: 
impossibility of evolving Change class with new change type from 
improving performance when many listeners uses this change type and can 
have specialized code for this change type.
Contrary to current Change object, the two stream API will be easier to 
extends with new change type (IterableChangeBuilder need only adding a 
method, ListChangeHandler will only need another method (if it is an 
abstract class it will need to have a default implementation splitting 
it in existing operations, or if it is an interface to have a Java8 
extension method with default pointing to same code than abstract class 
default implementation).
By example adding a new change type, like swap between two items without 
change on items between them, will be impossible on Change (need code 
change in all listeners to add a new test wasSwap(): then swap will 
always be broken using current simpler operations (like add+remove or 
big permute concerning items between the two) by IterableChangeBuilder 
constructing Change and knowledge of swap will be lost in Change 
instance. It will be impossible to have a specialized handler for swap 
(like one doing less work if size of list is unchanged (*), or if some 
items in the middle are unchanged (#)). After some thinking, I think 
only the streams API can be evolved by adding new change operations in 
future (but only with abstract class currently or interface using Java8 
virtual extension methods).

(*) Like a ReadOnlyIntegerProperty with the size of ObservableList 
(possible useful addition), if swap is expanded in add+remove.
(#) Like a FilteredList observing items in the middle of swap but not 
the two swapped items, if swap is expanded in a big permute.
> Maybe you bumped into some use cases that we are not aware of. Could you please give us some examples? That would be very helpful to understand the difficulties you have encountered.
Yes. my biggest problem is with ConcatenatedList (an ObservableList of 
ObservableList offering a living read-only view of concatenated lists). 
I have copied code (without listeners, for readability and shortness). 
As an use-case, I am interested in using it to populate a TableView with 
items coming from multiples sources (changing sources, changing items 
from each source).|
||
public class ConcatenatedList<E> extends 
ObservableListWrapper<ObservableList<? extends E>> {

     private final class ReadOnlyView extends 
ReadOnlyUnbackedObservableList<E> {

         @Override
         public final E get(final int index) {
             final int search = ConcatenatedList.this.readPartIndex(index);
             final ObservableList<? extends E> part = 
ConcatenatedList.this.get(search);
             return part.get(index - ConcatenatedList.this.ends[search]);
         }

         @Override
         public final int size() {
             return 
ConcatenatedList.this.ends[ConcatenatedList.this.ends.length - 1];
         }

     }

     private final ReadOnlyView concatenated = new ReadOnlyView();

     /**
      * Boundaries between parts with (this.size()+1) items. first item 
is 0, last item is size of concatenated list.
      */
     private final int[] ends;

     /* this listener transform change by changing list (from part list 
to global view)
     /* and shifting indexes and resend it on concatenated view 
listeners. */
     private final ListChangeListener<E> listener;

     @SafeVarargs
     ConcatenatedList(final Class<E> type, final ObservableList<? 
extends E>... parts) {
         super(Arrays.asList(parts));
         final int size = this.size();
         this.ends = new int[size + 1];
         this.ends[0] = 0;
         for (int i = 0; i < size; i++) {
             final ObservableList<? extends E> part = this.get(i);
             this.ends[i + 1] = this.ends[i] + part.size();
             part.addListener(this.listener);
         }
         /* the following listener add and remove listeners on parts 
added or removed.
         * It need to creating change by adding, removing and permuting 
blocks of items corresponding to each part list. */
         this.addListener(/*  */);
     }

     @Override
     public final ObservableList<E> getConcatenated() {
         return this.concatenated;
     }

     /**
      *
      * @param index
      * @return part corresponding to this index
      */
     private final int readPartIndex(final int index) {
         int search = Arrays.binarySearch(this.ends, index);
         if (search < 0) {
             search = -search - 1;
         }
         return search;
     }

}|

Thank you,
Daniel.


More information about the openjfx-dev mailing list