-
Bug
-
Resolution: Fixed
-
P3
-
None
This is only really a problem because it's a pattern used by all of the Bindings and WeakListeners shipped by JavaFX—they remove themselves when their WeakReferences get collected.
Here's a minimal repro:
{code}
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
public class SimpleMapPropertyRepro {
public void runMe() {
final SimpleMapProperty<String, String> m = new SimpleMapProperty<String, String>(FXCollections.<String, String>observableHashMap());
m.addListener(new InvalidationListener() {
public void invalidated(Observable observable) {
m.removeListener(this);
}
});
m.addListener(new InvalidationListener() {
public void invalidated(Observable observable) {
// do nothing; a dummy listener needs to be added afterwards to trigger the NPE
}
});
m.put("world", "hello");
m.put("world", "goodbye"); // NullPointerException
}
}
{code}
I've tracked the issue down to some code in MapExpressionHelper.Generic.removeListener which decrements the listener counter inside an if (!isLocked) check; the correct behavior I think is to decrement that counter outside the check. Same goes for SetExpressionHelper.Generic. (Interestingly, ListExpressionHelper.Generic.removeListener is already written with the decrement outside, so a SimpleListProperty doesn't produce the NPE when used in code similar to the above.)
Here's a minimal repro:
{code}
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
public class SimpleMapPropertyRepro {
public void runMe() {
final SimpleMapProperty<String, String> m = new SimpleMapProperty<String, String>(FXCollections.<String, String>observableHashMap());
m.addListener(new InvalidationListener() {
public void invalidated(Observable observable) {
m.removeListener(this);
}
});
m.addListener(new InvalidationListener() {
public void invalidated(Observable observable) {
// do nothing; a dummy listener needs to be added afterwards to trigger the NPE
}
});
m.put("world", "hello");
m.put("world", "goodbye"); // NullPointerException
}
}
{code}
I've tracked the issue down to some code in MapExpressionHelper.Generic.removeListener which decrements the listener counter inside an if (!isLocked) check; the correct behavior I think is to decrement that counter outside the check. Same goes for SetExpressionHelper.Generic. (Interestingly, ListExpressionHelper.Generic.removeListener is already written with the decrement outside, so a SimpleListProperty doesn't produce the NPE when used in code similar to the above.)