-
Notifications
You must be signed in to change notification settings - Fork 552
8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue #675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
d9bfefe
Initial proposal
hjohn 312fb50
Upgrade tests to JUnit 5
hjohn 30e8cea
Apply changes suggested in review and updated copyright years to 2022
hjohn 040bfe4
Fix grammar mistakes and did some small rephrases
hjohn b013e2d
Change code according to review comments
hjohn 14048a9
Clean up some missed asserts and some nested class names
hjohn 30733cc
Fix wrong test values
hjohn 29dc2af
Process review comments
hjohn 8f9bf89
Process review comments (2)
hjohn 72cd24d
Add line feed at last line where it was missing
hjohn 711ea90
Fix code blocks
hjohn 8ba9e92
Clean up docs in Subscription
hjohn 49e1f81
Add missing javadoc tags
hjohn 6a5358d
Reword flat map docs a bit and fixed a link
hjohn cb01f11
Update API docs for ObservableValue
hjohn e09186c
Small wording change in API of ObservableValue after proof reading
hjohn e2703e6
Fix wording
hjohn a880666
Add since tags to all new API
hjohn c352390
Expand flatMap javadoc with additional wording from Optional#flatMap
hjohn 3ac734b
Rename observeInputs to observeSources
hjohn baa97bd
Fix typos in LazyObjectBinding
hjohn abe1333
Fix bug invalidation bug in FlatMappedBinding
hjohn a93b826
Add note to Bindings#select to consider ObservableValue#flatMap
hjohn 2c3a9d9
Move private binding classes to com.sun.javafx.binding package
hjohn 60b3311
Update copyrights
hjohn 6ae74d1
Add null checks in Subscription
hjohn d66f2ba
Merge branch 'openjdk:master' into feature/fluent-bindings
hjohn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
modules/javafx.base/src/main/java/com/sun/javafx/binding/Subscription.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| /* | ||
| * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. | ||
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| * | ||
| * This code is free software; you can redistribute it and/or modify it | ||
| * under the terms of the GNU General Public License version 2 only, as | ||
| * published by the Free Software Foundation. Oracle designates this | ||
| * particular file as subject to the "Classpath" exception as provided | ||
| * by Oracle in the LICENSE file that accompanied this code. | ||
| * | ||
| * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| * version 2 for more details (a copy is included in the LICENSE file that | ||
| * accompanied this code). | ||
| * | ||
| * You should have received a copy of the GNU General Public License version | ||
| * 2 along with this work; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| * | ||
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| * or visit www.oracle.com if you need additional information or have any | ||
| * questions. | ||
| */ | ||
|
|
||
| package com.sun.javafx.binding; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.function.Consumer; | ||
|
|
||
| import javafx.beans.InvalidationListener; | ||
| import javafx.beans.value.ChangeListener; | ||
| import javafx.beans.value.ObservableValue; | ||
|
|
||
| /** | ||
| * A subscription encapsulates how to cancel it without having | ||
| * to keep track of how it was created.<p> | ||
| * | ||
| * For example:<p> | ||
| * <pre>Subscription s = property.subscribe(System.out::println)</pre> | ||
| * The function passed in to {@code subscribe} does not need to be stored | ||
| * in order to clean up the subscription later. | ||
| */ | ||
| @FunctionalInterface | ||
| public interface Subscription { | ||
|
|
||
| /** | ||
| * An empty subscription. Does nothing when cancelled. | ||
| */ | ||
| static final Subscription EMPTY = () -> {}; | ||
|
|
||
| /** | ||
| * Cancels this subscription. | ||
| */ | ||
| void unsubscribe(); | ||
hjohn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Combines this {@link Subscription} with the given {@code Subscription} | ||
| * and returns a new {@code Subscription} which will cancel both when | ||
| * cancelled. | ||
| * | ||
| * @param other another {@link Subscription}, cannot be null | ||
| * @return a combined {@link Subscription} which will cancel both when | ||
| * cancelled, never null | ||
| */ | ||
| default Subscription and(Subscription other) { | ||
| Objects.requireNonNull(other); | ||
hjohn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return () -> { | ||
| unsubscribe(); | ||
| other.unsubscribe(); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a {@link Subscription} on this {@link ObservableValue} which | ||
| * immediately provides its current value to the given {@code subscriber}, | ||
| * followed by any subsequent changes in value. | ||
| * | ||
| * @param subscriber a {@link Consumer} to supply with the values of this | ||
| * {@link ObservableValue}, cannot be null | ||
| * @return a {@link Subscription} which can be used to cancel this | ||
| * subscription, never null | ||
| */ | ||
| static <T> Subscription subscribe(ObservableValue<T> observableValue, Consumer<? super T> subscriber) { | ||
| ChangeListener<T> listener = (obs, old, current) -> subscriber.accept(current); | ||
hjohn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| subscriber.accept(observableValue.getValue()); // eagerly send current value | ||
| observableValue.addListener(listener); | ||
|
|
||
| return () -> observableValue.removeListener(listener); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a {@link Subscription} on this {@link ObservableValue} which | ||
| * calls the given {@code runnable} whenever this {@code ObservableValue} | ||
| * becomes invalid. | ||
| * | ||
| * @param runnable a {@link Runnable} to call whenever this | ||
| * {@link ObservableValue} becomes invalid, cannot be null | ||
| * @return a {@link Subscription} which can be used to cancel this | ||
| * subscription, never null | ||
| */ | ||
| static Subscription subscribeInvalidations(ObservableValue<?> observableValue, Runnable runnable) { | ||
| InvalidationListener listener = obs -> runnable.run(); | ||
|
|
||
| observableValue.addListener(listener); | ||
|
|
||
| return () -> observableValue.removeListener(listener); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
modules/javafx.base/src/main/java/javafx/beans/value/FlatMappedBinding.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| /* | ||
| * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. | ||
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| * | ||
| * This code is free software; you can redistribute it and/or modify it | ||
| * under the terms of the GNU General Public License version 2 only, as | ||
| * published by the Free Software Foundation. Oracle designates this | ||
| * particular file as subject to the "Classpath" exception as provided | ||
| * by Oracle in the LICENSE file that accompanied this code. | ||
| * | ||
| * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| * version 2 for more details (a copy is included in the LICENSE file that | ||
| * accompanied this code). | ||
| * | ||
| * You should have received a copy of the GNU General Public License version | ||
| * 2 along with this work; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| * | ||
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| * or visit www.oracle.com if you need additional information or have any | ||
| * questions. | ||
| */ | ||
|
|
||
| package javafx.beans.value; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.function.Function; | ||
|
|
||
| import com.sun.javafx.binding.Subscription; | ||
|
|
||
| class FlatMappedBinding<S, T> extends LazyObjectBinding<T> { | ||
| private final ObservableValue<S> source; | ||
| private final Function<? super S, ? extends ObservableValue<? extends T>> mapper; | ||
|
|
||
| private Subscription mappedSubscription = Subscription.EMPTY; | ||
|
|
||
| public FlatMappedBinding(ObservableValue<S> source, Function<? super S, ? extends ObservableValue<? extends T>> mapper) { | ||
| this.source = Objects.requireNonNull(source, "source cannot be null"); | ||
| this.mapper = Objects.requireNonNull(mapper, "mapper cannot be null"); | ||
| } | ||
|
|
||
| @Override | ||
| protected T computeValue() { | ||
| S value = source.getValue(); | ||
| ObservableValue<? extends T> mapped = value == null ? null : mapper.apply(value); | ||
|
|
||
| if (isObserved()) { | ||
| mappedSubscription.unsubscribe(); | ||
| mappedSubscription = mapped == null ? Subscription.EMPTY : Subscription.subscribeInvalidations(mapped, this::invalidate); | ||
| } | ||
|
|
||
| return mapped == null ? null : mapped.getValue(); | ||
| } | ||
|
|
||
| @Override | ||
| protected Subscription observeInputs() { | ||
| Subscription subscription = Subscription.subscribeInvalidations(source, this::invalidate); | ||
|
|
||
| return () -> { | ||
| subscription.unsubscribe(); | ||
| mappedSubscription.unsubscribe(); | ||
| mappedSubscription = Subscription.EMPTY; | ||
| }; | ||
| } | ||
| } | ||
hjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
118 changes: 118 additions & 0 deletions
118
modules/javafx.base/src/main/java/javafx/beans/value/LazyObjectBinding.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| /* | ||
| * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. | ||
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| * | ||
| * This code is free software; you can redistribute it and/or modify it | ||
| * under the terms of the GNU General Public License version 2 only, as | ||
| * published by the Free Software Foundation. Oracle designates this | ||
| * particular file as subject to the "Classpath" exception as provided | ||
| * by Oracle in the LICENSE file that accompanied this code. | ||
| * | ||
| * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| * version 2 for more details (a copy is included in the LICENSE file that | ||
| * accompanied this code). | ||
| * | ||
| * You should have received a copy of the GNU General Public License version | ||
| * 2 along with this work; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| * | ||
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| * or visit www.oracle.com if you need additional information or have any | ||
| * questions. | ||
| */ | ||
|
|
||
| package javafx.beans.value; | ||
|
|
||
| import com.sun.javafx.binding.Subscription; | ||
|
|
||
| import javafx.beans.InvalidationListener; | ||
| import javafx.beans.binding.ObjectBinding; | ||
|
|
||
| /** | ||
| * Extends {@link ObjectBinding} with the ability to lazily register and eagerly unregister listeners on its | ||
| * dependencies. | ||
| * | ||
| * @param <T> the type of the wrapped {@code Object} | ||
| */ | ||
| abstract class LazyObjectBinding<T> extends ObjectBinding<T> { | ||
| private Subscription subscription; | ||
| private boolean wasObserved; | ||
|
|
||
| @Override | ||
| public void addListener(ChangeListener<? super T> listener) { | ||
| super.addListener(listener); | ||
|
|
||
| updateSubcriptionAfterAdd(); | ||
| } | ||
|
|
||
| @Override | ||
| public void removeListener(ChangeListener<? super T> listener) { | ||
| super.removeListener(listener); | ||
|
|
||
| updateSubcriptionAfterRemove(); | ||
| } | ||
|
|
||
| @Override | ||
| public void addListener(InvalidationListener listener) { | ||
| super.addListener(listener); | ||
|
|
||
| updateSubcriptionAfterAdd(); | ||
| } | ||
|
|
||
| @Override | ||
| public void removeListener(InvalidationListener listener) { | ||
| super.removeListener(listener); | ||
|
|
||
| updateSubcriptionAfterRemove(); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean allowValidation() { | ||
| return isObserved(); | ||
| } | ||
|
|
||
| /** | ||
| * Called after a listener was added to start observing inputs, if they're not observed already. | ||
hjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| private void updateSubcriptionAfterAdd() { | ||
hjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (!wasObserved) { // was first observer registered? | ||
| subscription = observeInputs(); // start observing source | ||
|
|
||
| /* | ||
| * Although the act of registering a listener already attempts to make | ||
| * this binding valid, allowValidation won't allow it as the binding is | ||
| * not observed yet. This is because isObserved will not yet return true | ||
| * when the process of registering the listener hasn't completed yet. | ||
| * | ||
| * As the binding must be valid after it becomes observed the first time | ||
| * 'get' is called again. | ||
hjohn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
|
|
||
| get(); // make binding valid as source wasn't tracked until now | ||
| wasObserved = true; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Called after a listener was removed to stop observing inputs, if this was the last listener | ||
hjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * observing this binding. | ||
| */ | ||
| private void updateSubcriptionAfterRemove() { | ||
hjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (wasObserved && !isObserved()) { // was last observer unregistered? | ||
| subscription.unsubscribe(); | ||
| subscription = null; | ||
| invalidate(); // make binding invalid as source is no longer tracked | ||
| wasObserved = false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Called when this binding was previously not observed and a new observer was added. Implementors must return a | ||
| * {@link Subscription} which will be cancelled when this binding no longer has any observers. | ||
| * | ||
| * @return a {@link Subscription} which will be cancelled when this binding no longer has any observers, never null | ||
| */ | ||
| protected abstract Subscription observeInputs(); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.