I created a simple application to test filtered lists and their behavior when the corresponding source list changes. I'd like to test update changes also, so I created ObservableList
of ObservableList
s. It is faster and simpler than creating additional class like Person that have observable fields.
The code looks so:
ListChangeListener<ObservableList<String>> changeNotifier = new ListChangeListener<ObservableList<String>>() {
@Override
public void onChanged(Change<? extends ObservableList<String>> c) {
while (c.next()) {
if (c.wasPermutated()) {
System.out.println("permutation");
} else if (c.wasUpdated()) {
System.out.println("update");
} else {
if (c.wasRemoved()) {
System.out.println("remove");
}
if (c.wasAdded()) {
System.out.println("add");
}
if (c.wasReplaced()) {
System.out.println("replace");
}
}
}
}
};
Callback<ObservableList<String>, Observable[]> identityExtractor = new Callback<ObservableList<String>, Observable[]>() {
@Override
public Observable[] call(ObservableList<String> param) {
return new Observable[]{param};
}
};
Predicate<ObservableList<String>> nonEmptyFilter = new Predicate<ObservableList<String>>() {
@Override
public boolean test(ObservableList<String> obsl) {
boolean nonEmpty = ! obsl.isEmpty();
for (String item : obsl) {
nonEmpty = nonEmpty && (null != item) && ("" != item);
};
return nonEmpty;
}
};
ObservableList<ObservableList<String>> basicSimple = FXCollections.observableArrayList();
ObservableList<ObservableList<String>> basicComposed = FXCollections.observableArrayList( identityExtractor );
ObservableList<ObservableList<String>> filteredSimple = basicSimple.filtered( nonEmptyFilter );
ObservableList<ObservableList<String>> filteredComposed = basicComposed.filtered( nonEmptyFilter );
System.out.println("Basic testing");
System.out.println("Add invalid");
basicSimple.addAll( FXCollections.observableArrayList("") );
System.out.println( basicSimple );
System.out.println( filteredSimple );
System.out.println("Make it valid");
basicSimple.get(0).addAll("first");
System.out.println( filteredSimple );
System.out.println("Add valid");
basicSimple.addAll( FXCollections.observableArrayList("Second") );
System.out.println( filteredSimple );
System.out.println("Composed testing");
System.out.println("Add invalid");
basicComposed.addAll( FXCollections.observableArrayList("") );
System.out.println( basicComposed );
System.out.println( filteredComposed );
System.out.println("Make it valid");
basicComposed.get(0).addAll("first");
System.out.println( filteredComposed );
System.out.println("Add valid");
basicComposed.addAll( FXCollections.observableArrayList("Second") );
System.out.println( filteredComposed );
I've discovered a strange error during testing:
[info] Running helloworld.HelloWorld
Basic testing
Add invalid
[[]]
[]
Make it valid
[]
Add valid
[[Second]]
Composed testing
Add invalid
[[]]
[]
Make it valid
[error] (JavaFX Application Thread) java.lang.ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at javafx.collections.transformation.FilteredList.updateFilter(FilteredList.java:298)
at javafx.collections.transformation.FilteredList.update(FilteredList.java:239)
at javafx.collections.transformation.FilteredList.sourceChanged(FilteredList.java:137)
at javafx.collections.transformation.TransformationList.lambda$getListener$16(TransformationList.java:106)
at javafx.collections.transformation.TransformationList$$Lambda$63/1596532574.onChanged(Unknown Source)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:485)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at com.sun.javafx.collections.ObservableListWrapper.access$200(ObservableListWrapper.java:45)
at com.sun.javafx.collections.ObservableListWrapper$1$1.invalidated(ObservableListWrapper.java:75)
at com.sun.javafx.collections.ListListenerHelper$SingleInvalidation.fireValueChangedEvent(ListListenerHelper.java:126)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.addAll(ModifiableObservableListBase.java:102)
at javafx.collections.ObservableListBase.addAll(ObservableListBase.java:245)
at helloworld.HelloWorld.start(HelloWorld.java:87)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$153(LauncherImpl.java:821)
at com.sun.javafx.application.LauncherImpl$$Lambda$55/7143454.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$166(PlatformImpl.java:323)
at com.sun.javafx.application.PlatformImpl$$Lambda$51/397137382.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$164(PlatformImpl.java:292)
at com.sun.javafx.application.PlatformImpl$$Lambda$53/1802784360.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$165(PlatformImpl.java:291)
at com.sun.javafx.application.PlatformImpl$$Lambda$52/1184782272.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.lambda$null$45(GtkApplication.java:126)
at com.sun.glass.ui.gtk.GtkApplication$$Lambda$43/450111611.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
[trace] Stack trace suppressed: run last compile:run for the full output.
[]
Add valid
[[Second]]
The difference between basicSimple
and basicComposed
is that latter have an extractor defined, so it receives update events. In the middle of processing update event the exception was thrown by a native code. What should I consider to make the sample code work without errors?
Bit of debugging
I've inserted println to the end of the nonEmptyFilter
predicate test. It works correctly and the ObservableList passed to it is as expected new value that was just updated. The error occurs later when some iternal code of FilteredList is executing.
This looks like a bug; I think it is probably the same as this one, which the bug report says is fixed for JavaFX 8u60 (presumably too late for 8u40).
I tested on 1.8.0_40-ea-b23?? and 1.9.0-ea-b49 and the error appeared in both versions. If there is currently an ea release of 1.8.0u60 I'm not sure where to find it.
So the exception gets thrown by the line
basicComposed.get(0).addAll("first");
but counter intuitively its notget(0)
that's throwing the exception butaddAll
!?Another thing of interest is that if you remove the
identityExtractor
then the exception goes away ? In addition the exception is only ever thrown ifnonEmptyFilter
returns false !So there's some kind of dynamic between the
identityExtractor
andnonEmptyFilter
that causesaddAll
to throw the exception.It's possible to change the current situation so that the exception is only thrown if both the following two conditions hold true:
basicComposed.get(0).isEmpty()
andbasicComposed.get(0).addAll("")
// adding an empty string