Integrated: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue
John Hendrikx
jhendrikx at openjdk.org
Fri Jul 8 20:52:58 UTC 2022
On Thu, 18 Nov 2021 20:58:06 GMT, John Hendrikx <jhendrikx at openjdk.org> wrote:
> 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
> ));
>
> // Standard JavaFX + Optional:
> label.textProperty().bind(Bindings.createStringBinding(
> () -> Optinal.ofNullable(employee.get())
> .map(Employee::getCompany)
> .map(Company::getName)
> .orElse(""),
> 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.
This pull request has now been integrated.
Changeset: 60c75b82
Author: John Hendrikx <jhendrikx at openjdk.org>
Committer: Kevin Rushforth <kcr at openjdk.org>
URL: https://git.openjdk.org/jfx/commit/60c75b82a4e1523b108fa79f44084190d78f5be1
Stats: 1923 lines in 12 files changed: 1919 ins; 0 del; 4 mod
8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue
Reviewed-by: nlisker, mstrauss, kcr
-------------
PR: https://git.openjdk.org/jfx/pull/675
More information about the openjfx-dev
mailing list