Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue
Reviewed-by: nlisker, mstrauss, kcr
- Loading branch information
1 parent
178d898
commit 60c75b8
Showing
12 changed files
with
1,923 additions
and
4 deletions.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
modules/javafx.base/src/main/java/com/sun/javafx/binding/FlatMappedBinding.java
This file contains 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,110 @@ | ||
/* | ||
* Copyright (c) 2022, 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.Function; | ||
|
||
import javafx.beans.value.ObservableValue; | ||
|
||
/** | ||
* A binding holding the value of an indirect source. The indirect source results from | ||
* applying a mapping to the given source. | ||
* | ||
* <p>Implementation: | ||
* | ||
* <p>In a flat mapped binding there are always two subscriptions involved: | ||
* <ul> | ||
* <li>The subscription on its source</li> | ||
* <li>The subscription on the value resulting from the mapping of the source: the indirect source</li> | ||
* </ul> | ||
* The subscription on its given source is present when this binding itself is observed and not present otherwise. | ||
* | ||
* <p>The subscription on the indirect source must change whenever the value of the given source changes or is invalidated. More | ||
* specifically, when the given source is invalidated the indirect subscription should be removed, and when it is revalidated it | ||
* should resubscribe to the newly calculated indirect source. The binding avoids resubscribing when only the value of | ||
* the indirect source changes. | ||
* | ||
* @param <S> the type of the source | ||
* @param <T> the type of the resulting binding | ||
*/ | ||
public class FlatMappedBinding<S, T> extends LazyObjectBinding<T> { | ||
|
||
private final ObservableValue<S> source; | ||
private final Function<? super S, ? extends ObservableValue<? extends T>> mapper; | ||
|
||
private Subscription indirectSourceSubscription = Subscription.EMPTY; | ||
private ObservableValue<? extends T> indirectSource; | ||
|
||
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> newIndirectSource = value == null ? null : mapper.apply(value); | ||
|
||
if (isObserved() && indirectSource != newIndirectSource) { // only resubscribe when observed and the indirect source changed | ||
indirectSourceSubscription.unsubscribe(); | ||
indirectSourceSubscription = newIndirectSource == null ? Subscription.EMPTY : Subscription.subscribeInvalidations(newIndirectSource, this::invalidate); | ||
indirectSource = newIndirectSource; | ||
} | ||
|
||
return newIndirectSource == null ? null : newIndirectSource.getValue(); | ||
} | ||
|
||
@Override | ||
protected Subscription observeSources() { | ||
Subscription subscription = Subscription.subscribeInvalidations(source, this::invalidateAll); | ||
|
||
return () -> { | ||
subscription.unsubscribe(); | ||
unsubscribeIndirectSource(); | ||
}; | ||
} | ||
|
||
/** | ||
* Called when the primary source changes. Invalidates this binding and unsubscribes the indirect source | ||
* to avoid holding a strong reference to it. If the binding becomes valid later, {@link #computeValue()} will | ||
* subscribe to a newly calculated indirect source. | ||
* | ||
* <p>Note that this only needs to be called for changes of the primary source; changes in the indirect | ||
* source only need to invalidate this binding without also unsubscribing, as it would be wasteful to resubscribe | ||
* to the same indirect source for each invalidation of that source. | ||
*/ | ||
private void invalidateAll() { | ||
unsubscribeIndirectSource(); | ||
invalidate(); | ||
} | ||
|
||
private void unsubscribeIndirectSource() { | ||
indirectSourceSubscription.unsubscribe(); | ||
indirectSourceSubscription = Subscription.EMPTY; | ||
indirectSource = null; | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
modules/javafx.base/src/main/java/com/sun/javafx/binding/LazyObjectBinding.java
This file contains 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,126 @@ | ||
/* | ||
* Copyright (c) 2022, 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 javafx.beans.InvalidationListener; | ||
import javafx.beans.binding.ObjectBinding; | ||
import javafx.beans.value.ChangeListener; | ||
|
||
/** | ||
* 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); | ||
|
||
updateSubscriptionAfterAdd(); | ||
} | ||
|
||
@Override | ||
public void removeListener(ChangeListener<? super T> listener) { | ||
super.removeListener(listener); | ||
|
||
updateSubscriptionAfterRemove(); | ||
} | ||
|
||
@Override | ||
public void addListener(InvalidationListener listener) { | ||
super.addListener(listener); | ||
|
||
updateSubscriptionAfterAdd(); | ||
} | ||
|
||
@Override | ||
public void removeListener(InvalidationListener listener) { | ||
super.removeListener(listener); | ||
|
||
updateSubscriptionAfterRemove(); | ||
} | ||
|
||
@Override | ||
protected boolean allowValidation() { | ||
return isObserved(); | ||
} | ||
|
||
/** | ||
* Called after a listener was added to start observing inputs if they're not observed already. | ||
*/ | ||
private void updateSubscriptionAfterAdd() { | ||
if (!wasObserved) { // was first observer registered? | ||
subscription = observeSources(); // 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. | ||
* | ||
* See com.sun.javafx.binding.ExpressionHelper (which is used | ||
* by ObjectBinding) where it will do a call to ObservableValue#getValue | ||
* BEFORE adding the actual listener. This results in ObjectBinding#get | ||
* to be called in which the #allowValidation call will block it from | ||
* becoming valid as the condition is "isObserved()"; this is technically | ||
* correct as the listener wasn't added yet, but means we must call | ||
* #get again to make this binding valid. | ||
*/ | ||
|
||
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 | ||
* observing this binding. | ||
*/ | ||
private void updateSubscriptionAfterRemove() { | ||
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 observeSources(); | ||
} |
54 changes: 54 additions & 0 deletions
54
modules/javafx.base/src/main/java/com/sun/javafx/binding/MappedBinding.java
This file contains 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,54 @@ | ||
/* | ||
* Copyright (c) 2022, 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.Function; | ||
|
||
import javafx.beans.value.ObservableValue; | ||
|
||
public class MappedBinding<S, T> extends LazyObjectBinding<T> { | ||
|
||
private final ObservableValue<S> source; | ||
private final Function<? super S, ? extends T> mapper; | ||
|
||
public MappedBinding(ObservableValue<S> source, Function<? super S, ? 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(); | ||
|
||
return value == null ? null : mapper.apply(value); | ||
} | ||
|
||
@Override | ||
protected Subscription observeSources() { | ||
return Subscription.subscribeInvalidations(source, this::invalidate); // start observing source | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
modules/javafx.base/src/main/java/com/sun/javafx/binding/OrElseBinding.java
This file contains 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,53 @@ | ||
/* | ||
* Copyright (c) 2022, 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 javafx.beans.value.ObservableValue; | ||
|
||
public class OrElseBinding<T> extends LazyObjectBinding<T> { | ||
|
||
private final ObservableValue<T> source; | ||
private final T constant; | ||
|
||
public OrElseBinding(ObservableValue<T> source, T constant) { | ||
this.source = Objects.requireNonNull(source, "source cannot be null"); | ||
this.constant = constant; | ||
} | ||
|
||
@Override | ||
protected T computeValue() { | ||
T value = source.getValue(); | ||
|
||
return value == null ? constant : value; | ||
} | ||
|
||
@Override | ||
protected Subscription observeSources() { | ||
return Subscription.subscribeInvalidations(source, this::invalidate); // start observing source | ||
} | ||
} |
Oops, something went wrong.
60c75b8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review
Issues