> 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:
Update API docs for ObservableValue
- Used wording from java.util.stream.Stream and java.util.Optional
- Replaced "Creates a new" with "Returns an" to allow for flexibility
(potentially we could return the same instance for the same calls); this
also how Stream starts descriptions
- Added @throws NullPointerException in the style of java.util.Optional
- Used "the" instead of "a" or "an" when describing parameters (same as
Optional)
- Removed references to {@code Function}, neither Optional or Stream
mention the interface explicitely
-------------
Changes:
- all: https://git.openjdk.java.net/jfx/pull/675/files
- new: https://git.openjdk.java.net/jfx/pull/675/files/6a5358d7..cb01f114
Webrevs:
- full: https://webrevs.openjdk.java.net/?repo=jfx&pr=675&range=11
- incr: https://webrevs.openjdk.java.net/?repo=jfx&pr=675&range=10-11
Stats: 26 lines in 1 file changed: 2 ins; 1 del; 23 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