We had already used them before, but only in those parts of the framework that directly depend on the JavaFX toolkit. In all other places we had used our own extensions to Java Beans properties, because we did not want to introduce JavaFX dependencies there. However, as JavaFX is part of JavaSE 1.7 and JavaSE-1.8 and its collections and properties are completely independent of the UI toolkit, we (no longer) considered the additional dependencies as being a real show stopper. Instead, we highly valued the chance to provide only a single notification mechanism to our adopters. The very rich possibilities offered by JavaFX's binding mechanism in addition tipped the scales.
As JavaFX only provides observable variants for Set, Map, and List, I had to create observable variants and related (collection) properties for those Google Guava collections we use (Multiset and SetMultimap) as a prerequisite. I had to dig deep into the implementation details of JavaFX collections and properties to achieve this, learned quite a lot, and found the one or other oddity that I think is worth to be shared. I also implemented a couple of replacement classes to fix problems in JavaFX collections and properties, which are published as part of GEF4 Common.
But before going into details, let me shortly explain what JavaFX observable collection and properties are about.
Properties, Observable Collections, and (Observable) Collection Properties
In general, a JavaFX property may be regarded as a specialization of a Java Beans property. What it adds is support for lazy computation of its value as well as for notification of invalidation (a lazily computed value may need to be re-computed) and change listeners, which may - in contrast to Java Beans - be registered directly at the property, not at the enclosing bean.
Using JavaFX properties implies a certain API style (similar to Java Beans), as it is expected to provide a getter and setter to access the property value, as well as an accessor for the property itself. We may consider javafx.scene.Node as an example, which amongst various others provides a boolean pickOnBounds property that (is lazily created and) controls whether picking is computed by intersecting with the rectangular bounds of the node or not:
public abstract class Node implements EventTarget, Stylable {
...
private BooleanProperty pickOnBounds;
private BooleanProperty pickOnBounds;
public final void setPickOnBounds(boolean value) {
pickOnBoundsProperty().set(value);
}
public final boolean isPickOnBounds() {
public final boolean isPickOnBounds() {
return pickOnBounds == null ? false : pickOnBounds.get();
}
public final BooleanProperty pickOnBoundsProperty() {
public final BooleanProperty pickOnBoundsProperty() {
if (pickOnBounds == null) {
pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds");
}
return pickOnBounds;
}
}
In addition to the notification support already mentioned, values of properties may be bound to values of others, or even to values that are computed by more complex expressions, via so called bindings. This is a quite powerful mechanism that reduces the need for custom listener implementations significantly. If the pickOnBounds value of one node should for instance be kept equal to the pickOnBounds value of another, the following binding is all that is required:
node1.pickOnBoundsProperty().bind(node2.pickOnBoundsProperty());
node1.pickOnBoundsProperty().bind(node2.pickOnBoundsProperty().or(node3.visibleProperty()));
node1.pickOnBoundsProperty().bind(new BooleanBinding() {
{
// specify dependencies to other properties, whose changes
// will trigger the re-computation of our value
super.bind(node2.pickOnBoundsProperty());
super.bind(node3.layoutBoundsProperty());
}
@Override
protected boolean computeValue() {
// some arbitrary expression based on the values of our dependencies
return node2.pickOnBoundsProperty().get() &&
node3.layoutBoundsProperty().get().isEmpty();
}
});
JavaFX provides property implementations for all Java primitives (BooleanProperty, DoubleProperty, FloatProperty, IntegerProperty, LongProperty, StringProperty, as well as a generic ObjectProperty, which can be used to wrap arbitrary object values. It is important to point out that an ObjectProperty will of course only notify invalidation and change listeners in case the property value is changed, i.e. it is altered to refer to a different object identity, not when changes are applied to the contained property value. Accordingly, an ObjectProperty that wraps a collection only notifies about changes in case a different collection is set as property value, not when the currently observed collection is changed by adding elements to or removing elements from it:
ObjectProperty<List<Integer>> observableListObjectProperty = new SimpleObjectProperty<>();
observableListObjectProperty.addListener(new ChangeListener<List<Integer>>() {
@Override
public void changed(ObservableValue<? extends List<Integer>> observable,
List<Integer> oldValue, List<Integer> newValue)
System.out.println("Change from " + oldValue + " to " + newValue);
}
});
// change listener will be notified about identity change from 'null' to '[]'
// change listener will be notified about identity change from 'null' to '[]'
observableListObjectProperty.set(new ArrayList<Integer>());
// change listener will not be notified
// change listener will not be notified
observableListObjectProperty.get().addAll(Arrays.asList(1, 2, 3));
This is where JavaFX observable collections come into play. As Java does not provide notification support in its standard collections, JavaFX delivers dedicated observable variants: ObservableList, ObservableMap and ObservableSet. They all support invalidation listener notification (as properties do) and in addition define their own respective change listeners (ListChangeListener, MapChangeListener, and SetChangeListener).
ObservableList also extends List by adding setAll(E... elements) and setAll(Collection<? extends E> c), which combines a clear() with an addAll(Collection< ? extends E> c) into a single atomic replace operation, as well as a remove(int from, int to) that supports removal within an index interval. This allows to 'reduce noise', which is quite important to a graphical framework like GEF, where complex computations might be triggered by changes.
List changes are iterable, i.e. they comprise several sub-changes, so that even a complex operation like setAll(Collection<? extends E> c) results in a single change notification:
Similar to properties, observable collections may even be used to establish bindings using so called content bindings:
As such, observable collections are quite usable, even if not being wrapped into a property. As long as the identity of an observable collection is not to be changed, it may directly be exposed without being wrapped into a property. And that's exactly how JavaFX uses them in its own API. As an example consider javafx.scene.Parent, which exposes its children via an ObservableList:
Wrapping it into a property however is required, if a collection's identity is to be changed (in a way transparent to listeners) or properties are to be bound to it. In principle an observable collection could be wrapped directly into an ObjectProperty but this has the disadvantage that two listeners are required if collection changes are to be properly tracked.
Consider an ObservableList being wrapped into a SimpleObjectProperty as an example. While changes to the list can be observed by registering a ListChangeListener a ChangeListener is required in addition to keep track of changes to the property's value itself (and to transfer the list change listener from an old property value to a new one):
final ListChangeListener<Integer> listChangeListener = new ListChangeListener<Integer>(){
As this is quite cumbersome, JavaFX offers respective collection properties that can be used as an alternative: ListProperty, SetProperty, and MapProperty. They support invalidation and change listeners as well as the respective collection specific listeners and will even synthesize a collection change when the observed property value is changed:
final ListChangeListener<Integer> listChangeListener = new ListChangeListener<Integer>() {
In addition, collection properties define their own (read-only) properties for emptiness, equality, size, etc., so that advanced bindings can also be created:
I have no idea why the observable collections API was designed in such an inhomogeneous way (it's discussed at JDK-8092534 without providing much more insight), but I think that an observable collection should rather behave like ObservableList, i.e. fire only a single change notification for each method call. If all required operations can be performed atomically via dedicated methods, a client can fully control which notifications are produced. As already laid out, ObservableList follows this to some extend with the additionally provided setAll() methods that combine clear() and addAll() into a single atomic operation, which would otherwise yield two notifications. However, an atomic move() operation is still lacking for ObservableList, so that movement of elements currently cannot be performed atomically.
When creating ObservableSetMultimap and ObservableMultiset, I tried to follow the contract of ObservableList for the above mentioned reasons. Both notify their listeners through a single atomic change for each method call, which provides details about elementary sub-changes (related to a single element or key), very similar to ListChangeListener#Change. In accordance to the addAll() of ObservableList, I added a replaceAll() operation to both to offer an atomic operation via which the contents of the collections can be replaced. Change notifications are iterable, as for ObservableList:
ObservableSetMultimap<Integer, String> observableSetMultimap = CollectionUtils.<Integer, String> observableHashMultimap();
I also though about providing replacement classes for ObservableMap and ObservableSet that rule out the inhomogeneity of the JavaFX collections API, but this would have required to extend their respective listener interfaces, and I thus abstained.
List changes are iterable, i.e. they comprise several sub-changes, so that even a complex operation like setAll(Collection<? extends E> c) results in a single change notification:
ObservableList<Integer> observableList = FXCollections.observableArrayList();
observableList.addListener(new ListChangeListener<Integer>() {
@Override
@Override
public void onChanged(Change<? extends Integer> change) {
while (change.next()) {
int from = change.getFrom();
int to = change.getTo();
// iterate through the sub-changes
if (change.wasReplaced()) {
// replacement (simultaneous removal and addition in a continuous range)
System.out.println("Replaced " + change.getRemoved()
+ " with " + change.getAddedSubList() + ".");
} else if (change.wasAdded()) {
// addition (added sublist within from-to range)
System.out.println("Added " + change.getAddedSubList()
+ " within [" + from + ", " + to + ").");
} else if (change.wasRemoved()) {
// removal (change provides removed sublist and from index)
System.out.println("Removed " + change.getRemoved() + " at " + from + ".");
} else if (change.wasPermutated()) {
// permutation (change provides mapping of old indexes to new indexes)
System.out.print("Permutated within [" + change.getFrom() + ", " + to + "):");
for (int i = from; i < to; i++) {
System.out.print((i == from ? " " : ", ")
+ i + " -> " + change.getPermutation(i)
+ (i == to - 1 ? ".\n" : ""));
}
}
}
}
});
// one comprised change: 'Added [3, 1, 2] within [0, 3).'
// one comprised change: 'Added [3, 1, 2] within [0, 3).'
observableList.setAll(Arrays.asList(3, 1, 2));
// one comprised change: 'Permutated within [0, 3): 0 -> 2, 1 -> 0, 2 -> 1.'
// one comprised change: 'Permutated within [0, 3): 0 -> 2, 1 -> 0, 2 -> 1.'
Collections.sort(observableList);
// one comprised change: 'Replaced [1, 2, 3] with [4, 5, 6].'
// one comprised change: 'Replaced [1, 2, 3] with [4, 5, 6].'
observableList.setAll(Arrays.asList(4, 5, 6));
// two comprised changes: 'Removed [4] at index 0.', 'Removed [6] at index 1.'
// two comprised changes: 'Removed [4] at index 0.', 'Removed [6] at index 1.'
observableList.removeAll(Arrays.asList(4, 6));
Similar to properties, observable collections may even be used to establish bindings using so called content bindings:
// ensure that elements of list are synchronized with that of observableList
List<Integer> list = new ArrayList<>();
Bindings.bindContent(list, observableList);
As such, observable collections are quite usable, even if not being wrapped into a property. As long as the identity of an observable collection is not to be changed, it may directly be exposed without being wrapped into a property. And that's exactly how JavaFX uses them in its own API. As an example consider javafx.scene.Parent, which exposes its children via an ObservableList:
public abstract class Parent extends Node {
...
protected ObservableList<Node> getChildren() {
protected ObservableList<Node> getChildren() {
return children;
}
@ReturnsUnmodifiableCollection
@ReturnsUnmodifiableCollection
public ObservableList<Node> getChildrenUnmodifiable() {
return unmodifiableChildren;
}
}
Wrapping it into a property however is required, if a collection's identity is to be changed (in a way transparent to listeners) or properties are to be bound to it. In principle an observable collection could be wrapped directly into an ObjectProperty but this has the disadvantage that two listeners are required if collection changes are to be properly tracked.
Consider an ObservableList being wrapped into a SimpleObjectProperty as an example. While changes to the list can be observed by registering a ListChangeListener a ChangeListener is required in addition to keep track of changes to the property's value itself (and to transfer the list change listener from an old property value to a new one):
ObjectProperty<ObservableList<Integer>> observableListObjectProperty =
new SimpleObjectProperty<>();
new SimpleObjectProperty<>();
final ListChangeListener<Integer> listChangeListener = new ListChangeListener<Integer>(){
@Override
public void onChanged(ListChangeListener.Change<? extends Integer> c) {
// react to list changes
}
};
// register list change listener at (current) property value
// register list change listener at (current) property value
observableListObjectProperty.get().addListener(listChangeListener);
// register change listener to transfer list change listener
// register change listener to transfer list change listener
observableListObjectProperty.addListener(new ChangeListener<ObservableList<Integer>>() {
@Override
public void changed(ObservableValue<? extends ObservableList<Integer>> observable,
ObservableList<Integer> oldValue,
ObservableList<Integer> newValue) {
// transfer list change listener from old value to new one
if(oldValue != null && oldValue != newValue){
oldValue.removeListener(listChangeListener);
}
if(newValue != null && oldValue != newValue){
newValue.addListener(listChangeListener);
}
}
});
}
As this is quite cumbersome, JavaFX offers respective collection properties that can be used as an alternative: ListProperty, SetProperty, and MapProperty. They support invalidation and change listeners as well as the respective collection specific listeners and will even synthesize a collection change when the observed property value is changed:
ListProperty<Integer> listProperty = new SimpleListProperty<>(
FXCollections.<Integer> observableArrayList());
final ListChangeListener<Integer> listChangeListener = new ListChangeListener<Integer>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Integer> change) {
// handle list changes
}
};
listProperty.addListener(listChangeListener);
// forwarded list change: 'Added [1, 2, 3] within [0, 3).'
// forwarded list change: 'Added [1, 2, 3] within [0, 3).'
listProperty.addAll(Arrays.asList(1, 2, 3));
// synthesized list change: 'Replaced [1, 2, 3] with [4, 5, 6].'
// synthesized list change: 'Replaced [1, 2, 3] with [4, 5, 6].'
listProperty.set(FXCollections.observableArrayList(4, 5, 6));
In addition, collection properties define their own (read-only) properties for emptiness, equality, size, etc., so that advanced bindings can also be created:
// bind boolean property to 'isEmpty'
BooleanProperty someBooleanProperty = new SimpleBooleanProperty();
someBooleanProperty.bind(listProperty.emptyProperty());
// bind integer property to 'size'
// bind integer property to 'size'
IntegerProperty someIntegerProperty = new SimpleIntegerProperty();
someIntegerProperty.bind(listProperty.sizeProperty());
While observable properties are thus not required to notify about collection changes (which is already possible using observable collections alone), they add quite some comfort when having to deal with situations where collections may be replaced or where bindings have to rely on certain properties (emptiness, size, etc.) of a collection.
As already mentioned, GEF4 MVC uses some of Google Guava's collection classes, while JavaFX only offers observable variants of Set, Map, and List. In order to facilitate a unique style for property change notifications in our complete code base, observable variants had to be created up front. That actually involved more design decisions than I had expected. To my own surprise, I also ended up with a replacement class for ObservableList and a utility class to augment the original API, because that seemed quite necessary in a couple of places.
GEF4 Common Collections
As already mentioned, GEF4 MVC uses some of Google Guava's collection classes, while JavaFX only offers observable variants of Set, Map, and List. In order to facilitate a unique style for property change notifications in our complete code base, observable variants had to be created up front. That actually involved more design decisions than I had expected. To my own surprise, I also ended up with a replacement class for ObservableList and a utility class to augment the original API, because that seemed quite necessary in a couple of places.
Obtaining atomic changes
As this might not be directly obvious from what was said before, let me point out that only ObservableList is indeed capable of notifying about changes atomically in the way laid out before. ObservableSet and ObservableMap notify their listeners for each elementary change individually. That is, an ObservableMap notifies change listeners independently for each affected key change, while ObservableSet will do likewise for each affected element. Calling clear() on an ObservableMap or can thus lead to various change notifications.I have no idea why the observable collections API was designed in such an inhomogeneous way (it's discussed at JDK-8092534 without providing much more insight), but I think that an observable collection should rather behave like ObservableList, i.e. fire only a single change notification for each method call. If all required operations can be performed atomically via dedicated methods, a client can fully control which notifications are produced. As already laid out, ObservableList follows this to some extend with the additionally provided setAll() methods that combine clear() and addAll() into a single atomic operation, which would otherwise yield two notifications. However, an atomic move() operation is still lacking for ObservableList, so that movement of elements currently cannot be performed atomically.
When creating ObservableSetMultimap and ObservableMultiset, I tried to follow the contract of ObservableList for the above mentioned reasons. Both notify their listeners through a single atomic change for each method call, which provides details about elementary sub-changes (related to a single element or key), very similar to ListChangeListener#Change. In accordance to the addAll() of ObservableList, I added a replaceAll() operation to both to offer an atomic operation via which the contents of the collections can be replaced. Change notifications are iterable, as for ObservableList:
ObservableSetMultimap<Integer, String> observableSetMultimap = CollectionUtils.<Integer, String> observableHashMultimap();
observableSetMultimap.addListener(new SetMultimapChangeListener<Integer, String>() {
@Override
public void onChanged(SetMultimapChangeListener.Change<? extends Integer,
public void onChanged(SetMultimapChangeListener.Change<? extends Integer,
? extends String> change) {
while (change.next()) {
if (change.wasAdded()) {
// values added for key
System.out.println("Added " + change.getValuesAdded()
+ " for key " + change.getKey() + ".");
} else if (change.wasRemoved()) {
// values removed for key
System.out.println("Removed " + change.getValuesRemoved() + " for key "
+ change.getKey() + ".");
}
}
}
});
// one comprised change: 'Added [1] for key 1.'
// one comprised change: 'Added [1] for key 1.'
observableSetMultimap.put(1, "1");
// one comprised change: 'Added [2] for key 2.'
// one comprised change: 'Added [2] for key 2.'
observableSetMultimap.put(2, "2");
// two comprised changes: 'Removed [1] for key 1.', 'Removed [2] for key 2.'
// two comprised changes: 'Removed [1] for key 1.', 'Removed [2] for key 2.'
observableSetMultimap.clear();
I also though about providing replacement classes for ObservableMap and ObservableSet that rule out the inhomogeneity of the JavaFX collections API, but this would have required to extend their respective listener interfaces, and I thus abstained.
Retrieving the "previous" contents of an observable collection
While in principle I like the API of ListChangeListener#Change, what really bothers me is that there is no convenience method to retrieve the old state of an ObservableList before it was changed. It has to be recomputed from the respective addition, removal, and permutation sub-changes (which will be propagated when sorting the list) that are comprised.
When creating ObservableMultiset and ObservableSetMultimap, I added a getPreviousContent() method to both, so clients can easily access the contents the collection contained before the change was applied. I also added a utility method (within CollectionUtils) that can be used to retrieve the previous contents of an ObservableList:
ObservableList<Integer> observableList = FXCollections.observableArrayList();
observableList.addAll(4, 3, 1, 5, 2);
// Previous contents: [4, 3, 1, 5, 2]
It should no longer be surprising that I did not only create these, but also ended up with replacement classes for JavaFX's own collection properties (SimpleSetPropertyEx, ReadOnlySetWrapperEx, SimpleMapPropertyEx, ReadOnlyMapWrapperEx, SimpleListPropertyEx, ReadOnlyListWrapperEx), because a comparable, consistent behavior could otherwise not be guaranteed.
However, this is not the full story. The observable collection properties offered by JavaFX fire change notifications even if the observed value did not change (JDK-8089169). That is, every collection change will not only lead to the notification of collection-specific change listeners, but also to the notification of all property change listeners:
// will (correctly) notify list change listeners and (property) change
As this leads to a lot of unwanted noise, I have ensured that MultisetProperty and SetMultimapProperty, which are provided as part of GEF4 Common, do not babble likewise. I ensured that the replacement classes we provide for the JavaFX collection properties behave accordingly, too.
Interestingly, JavaFX properties don't behave like that. Thus, all observable collection properties are inconsistent in a sense that collection change listener invocations will be guarded, while (property) change listener notifications won't. Again, I have ensured that the GEF4 Common provided collection properties show consistent behavior by guarding all their listener notifications, and I have done likewise for the replacement classes we provide.
As GEF4 still aims at providing support for JavaSE-1.7, I tried to ensure that all collection properties we provide, including the replacement classes, can be used in a JavaSE-1.7 environment, too. Except that the static methods JavaFX offers to create and remove bindings cannot be applied (because the binding mechanism has been completely revised from JavaSE-1.7 to JavaSE-1.8), this could be achieved.
While we make intensive use of GEF4 Common Collections and Properties in our own GEF4 framework, GEF4 Common can be used independently. If you need observable variants of Google Guava's Multiset or SetMultimap, or if you want to get rid of the aforementioned inconsistencies, it may be worth taking a look.
When creating ObservableMultiset and ObservableSetMultimap, I added a getPreviousContent() method to both, so clients can easily access the contents the collection contained before the change was applied. I also added a utility method (within CollectionUtils) that can be used to retrieve the previous contents of an ObservableList:
ObservableList<Integer> observableList = FXCollections.observableArrayList();
observableList.addAll(4, 3, 1, 5, 2);
observableList.addListener(new ListChangeListener<Integer>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Integer> change) {
System.out.println("Previous contents: " + CollectionUtils.getPreviousContents(change));
}
});
// Previous contents: [4, 3, 1, 5, 2]
observableList.set(3, 7);
// Previous contents: [4, 3, 1, 7, 2]
observableList.clear();
Obtaining immutable changes from an observable collection
While list fires atomic changes, its change objects are not immutable (see JDK-8092504). Thus, when a listener manipulates the observed list as a result to a change notification, the change object it currently processed will actually be changed. This is a likely pitfall, as client code may not even be aware that it is actually called from within a change notification context. Consider the following snippet:
ObservableList<Integer> observableList = FXCollections
.observableArrayList();
observableList.addListener(new ListChangeListener<Integer>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Integer> change) {
public void onChanged(ListChangeListener.Change<? extends Integer> change) {
while (change.next()) {
System.out.println(change);
}
// manipulate the list from within the change notification
if (!change.getList().contains(2)) {
((List<Integer>) change.getList()).add(2);
}
}
});
// change list by adding element '1'
observableList.add(1);
It will yield the following in valid change notifications:
ObservableList observableList = CollectionUtils.observableArrayList();
{ [1] added at 0 }
{ [1, 2] added at 0 }
Again, I cannot understand why this was designed that way, but the documentation clearly states that the list may not be manipulated from within a change notification context and the implementation follows it consequently by finalizing the change object only after all listeners have been notified. In a framework as GEF having non immutable change objects is a real show stopper. Accordingly, I designed ObservableMultiset and ObservableSetMultimap to use immutable change objects only. I also created a replacement class for com.sun.javafx.collections.ObservableListWrapper (the class that is instantiated when calling FXCollections.observableArrayList()) that produces immutable change objects. It can be created using a respective utility method:ObservableList
When using the replacement class in the above quoted scenario, it will yield the following output:
As ObservableSet and ObservableMap are not capable of comprising several changes into a single atomic one, the immutability problem does not arise there, so the alternative ObservableList implementation is all that is needed.
In addition to the ObservableMultiset and ObservableSetMultimap collections, I also added respective properties (and related bindings) to wrap these values to GEF4 Common: SimpleMultisetProperty, ReadOnlyMultisetProperty, SimpleSetMultimapProperty, and ReadOnlySetMultimapProperty.
Added[1] at 0.
Added[2] at 1.
Added[2] at 1.
As ObservableSet and ObservableMap are not capable of comprising several changes into a single atomic one, the immutability problem does not arise there, so the alternative ObservableList implementation is all that is needed.
GEF4 Common (Collection) Properties
In addition to the ObservableMultiset and ObservableSetMultimap collections, I also added respective properties (and related bindings) to wrap these values to GEF4 Common: SimpleMultisetProperty, ReadOnlyMultisetProperty, SimpleSetMultimapProperty, and ReadOnlySetMultimapProperty.
It should no longer be surprising that I did not only create these, but also ended up with replacement classes for JavaFX's own collection properties (SimpleSetPropertyEx, ReadOnlySetWrapperEx, SimpleMapPropertyEx, ReadOnlyMapWrapperEx, SimpleListPropertyEx, ReadOnlyListWrapperEx), because a comparable, consistent behavior could otherwise not be guaranteed.
Stopping the noise
As I have elaborated quite intensively already, observable collection notify about element changes, whereas properties notify about (identity) changes of their contained (observable) value. Collection properties in addition forward all collection notifications, so that a replacement of the property's value is transparent to collection listeners.However, this is not the full story. The observable collection properties offered by JavaFX fire change notifications even if the observed value did not change (JDK-8089169). That is, every collection change will not only lead to the notification of collection-specific change listeners, but also to the notification of all property change listeners:
SimpleListProperty<Integer> listProperty
= new
SimpleListProperty<>(
FXCollections.<Integer>
observableArrayList());
listProperty.addListener(new
ChangeListener<ObservableList<Integer>>() {
@Override
public void changed(ObservableValue<?
extends
ObservableList<Integer>> observable,
ObservableList<Integer> oldValue,
ObservableList<Integer> newValue) {
System.out.println("Observable (collection)
value changed.");
}
});
listProperty.addListener(new
ListChangeListener<Integer>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Integer> c) {
public void onChanged(ListChangeListener.Change<? extends Integer> c) {
System.out.println("Collection changed.");
}
});
// will (incorrectly)
notify (property) change listeners in addition to
// list change
listeners
listProperty.add(5);
// will (correctly) notify list change listeners and (property) change
//
listeners
listProperty.set(FXCollections.<Integer>
observableArrayList());
As this leads to a lot of unwanted noise, I have ensured that MultisetProperty and SetMultimapProperty, which are provided as part of GEF4 Common, do not babble likewise. I ensured that the replacement classes we provide for the JavaFX collection properties behave accordingly, too.
Guarding notifications consistently
JavaFX observable collection capture all exceptions that occur during a listener notification. That is, the listener notification is guarded as follows:
try {
listener.onChanged(change);
} catch
(Exception e) {
Thread.currentThread().getUncaughtExceptionHandler().
uncaughtException(Thread.currentThread(), e);
uncaughtException(Thread.currentThread(), e);
}
Interestingly, JavaFX properties don't behave like that. Thus, all observable collection properties are inconsistent in a sense that collection change listener invocations will be guarded, while (property) change listener notifications won't. Again, I have ensured that the GEF4 Common provided collection properties show consistent behavior by guarding all their listener notifications, and I have done likewise for the replacement classes we provide.
Fixing further issues and ensuring JavaSE-1.7 compatibility
Having the replacement classes at hand, I also fixed some further inconsistencies that bothered us quite severely. This includes the recently introduced regression that JavaFX MapProperty removes all attached change listeners when just one is to be removed (JDK-8136465), as well as the fact that read-only properties can currently not be properly bound (JDK-8089557).As GEF4 still aims at providing support for JavaSE-1.7, I tried to ensure that all collection properties we provide, including the replacement classes, can be used in a JavaSE-1.7 environment, too. Except that the static methods JavaFX offers to create and remove bindings cannot be applied (because the binding mechanism has been completely revised from JavaSE-1.7 to JavaSE-1.8), this could be achieved.
While we make intensive use of GEF4 Common Collections and Properties in our own GEF4 framework, GEF4 Common can be used independently. If you need observable variants of Google Guava's Multiset or SetMultimap, or if you want to get rid of the aforementioned inconsistencies, it may be worth taking a look.