RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v3]

John Hendrikx jhendrikx at openjdk.java.net
Wed Dec 15 11:43:36 UTC 2021


> This is an implementation of the proposal in https://bugs.openjdk.java.net/browse/JDK-8274771 that me and Nir Lisker (@nlisker) have been working on.  It's a complete implementation including good test coverage.  
> 
> This was based on https://github.com/openjdk/jfx/pull/434 but with a smaller API footprint.  Compared to the PoC this is lacking public API for subscriptions, and does not include `orElseGet` or the `conditionOn` conditional mapping.
> 
> **Flexible Mappings**
> Map the contents of a property any way you like with `map`, or map nested properties with `flatMap`.
> 
> **Lazy**
> The bindings created are lazy, which means they are always _invalid_ when not themselves observed. This allows for easier garbage collection (once the last observer is removed, a chain of bindings will stop observing their parents) and less listener management when dealing with nested properties.  Furthermore, this allows inclusion of such bindings in classes such as `Node` without listeners being created when the binding itself is not used (this would allow for the inclusion of a `treeShowingProperty` in `Node` without creating excessive listeners, see this fix I did in an earlier PR: https://github.com/openjdk/jfx/pull/185)
> 
> **Null Safe**
> The `map` and `flatMap` methods are skipped, similar to `java.util.Optional` when the value they would be mapping is `null`. This makes mapping nested properties with `flatMap` trivial as the `null` case does not need to be taken into account in a chain like this: `node.sceneProperty().flatMap(Scene::windowProperty).flatMap(Window::showingProperty)`.  Instead a default can be provided with `orElse`.
> 
> Some examples:
> 
>     void mapProperty() {
>       // Standard JavaFX:
>       label.textProperty().bind(Bindings.createStringBinding(() -> text.getValueSafe().toUpperCase(), text));
> 
>       // Fluent: much more compact, no need to handle null
>       label.textProperty().bind(text.map(String::toUpperCase));
>     }
> 
>     void calculateCharactersLeft() {
>       // Standard JavaFX:
>       label.textProperty().bind(text.length().negate().add(100).asString().concat(" characters left"));
> 
>       // Fluent: slightly more compact and more clear (no negate needed)
>       label.textProperty().bind(text.orElse("").map(v -> 100 - v.length() + " characters left"));
>     }
> 
>     void mapNestedValue() {
>       // Standard JavaFX:
>       label.textProperty().bind(Bindings.createStringBinding(
>         () -> employee.get() == null ? ""
>             : employee.get().getCompany() == null ? ""
>             : employee.get().getCompany().getName(),
>         employee
>       ));
> 
>       // Fluent: no need to handle nulls everywhere
>       label.textProperty().bind(
>         employee.map(Employee::getCompany)
>                 .map(Company::getName)
>                 .orElse("")
>       );
>     }
> 
>     void mapNestedProperty() {
>       // Standard JavaFX:
>       label.textProperty().bind(
>         Bindings.when(Bindings.selectBoolean(label.sceneProperty(), "window", "showing"))
>           .then("Visible")
>           .otherwise("Not Visible")
>       );
> 
>       // Fluent: type safe
>       label.textProperty().bind(label.sceneProperty()
>         .flatMap(Scene::windowProperty)
>         .flatMap(Window::showingProperty)
>         .orElse(false)
>         .map(showing -> showing ? "Visible" : "Not Visible")
>       );
>     }
> 
> Note that this is based on ideas in ReactFX and my own experiments in https://github.com/hjohn/hs.jfx.eventstream.  I've come to the conclusion that this is much better directly integrated into JavaFX, and I'm hoping this proof of concept will be able to move such an effort forward.

John Hendrikx has updated the pull request incrementally with one additional commit since the last revision:

  Upgrade tests to JUnit 5

-------------

Changes:
  - all: https://git.openjdk.java.net/jfx/pull/675/files
  - new: https://git.openjdk.java.net/jfx/pull/675/files/d9bfefe9..312fb506

Webrevs:
 - full: https://webrevs.openjdk.java.net/?repo=jfx&pr=675&range=02
 - incr: https://webrevs.openjdk.java.net/?repo=jfx&pr=675&range=01-02

  Stats: 383 lines in 2 files changed: 7 ins; 369 del; 7 mod
  Patch: https://git.openjdk.java.net/jfx/pull/675.diff
  Fetch: git fetch https://git.openjdk.java.net/jfx pull/675/head:pull/675

PR: https://git.openjdk.java.net/jfx/pull/675


More information about the openjfx-dev mailing list