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.

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

Commit messages:
 - Initial proposal

Changes: https://git.openjdk.java.net/jfx/pull/675/files
 Webrev: https://webrevs.openjdk.java.net/?repo=jfx&pr=675&range=00
  Issue: https://bugs.openjdk.java.net/browse/JDK-8274771
  Stats: 1501 lines in 11 files changed: 1498 ins; 0 del; 3 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

Reply via email to