Skip to content
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

8290040: Provide simplified deterministic way to manage listeners #830

Closed
wants to merge 18 commits into from
Closed
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.sun.javafx.binding;

import java.util.Objects;

import javafx.beans.value.ObservableValue;

public class ConditionalBinding<T> extends LazyObjectBinding<T> {

private final ObservableValue<T> source;
private final ObservableValue<Boolean> nonNullCondition;

private Subscription subscription;

public ConditionalBinding(ObservableValue<T> source, ObservableValue<Boolean> condition) {
this.source = Objects.requireNonNull(source, "source cannot be null");
this.nonNullCondition = Objects.requireNonNull(condition, "condition cannot be null").orElse(false);

// condition is always observed and never unsubscribed
Subscription.subscribe(nonNullCondition, current -> {
invalidate();

if (!current) {
getValue();
}
});
}

/**
* This binding is valid whenever it is observed, or it is currently inactive.
* When inactive, the binding has the value of its source at the time it became
* inactive.
*/
@Override
protected boolean allowValidation() {
return super.allowValidation() || !isActive();
}

@Override
protected T computeValue() {
if (isObserved() && isActive()) {
if (subscription == null) {
subscription = Subscription.subscribeInvalidations(source, this::invalidate);
}
}
else {
unsubscribe();
}

return source.getValue();
}

@Override
protected Subscription observeSources() {
return this::unsubscribe;
}

private boolean isActive() {
return nonNullCondition.getValue();
}

private void unsubscribe() {
if (subscription != null) {
subscription.unsubscribe();
subscription = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@

import java.util.function.Function;

import com.sun.javafx.binding.ConditionalBinding;
import com.sun.javafx.binding.FlatMappedBinding;
import com.sun.javafx.binding.MappedBinding;
import com.sun.javafx.binding.OrElseBinding;
@@ -251,4 +252,55 @@ default ObservableValue<T> orElse(T constant) {
default <U> ObservableValue<U> flatMap(Function<? super T, ? extends ObservableValue<? extends U>> mapper) {
return new FlatMappedBinding<>(this, mapper);
}

/**
* Returns an {@code ObservableValue} that holds this value and is updated only
* when {@code condition} holds {@code true}.
* <p>
* The returned {@code ObservableValue} only observes this value when
* {@code condition} holds {@code true}. This allows this {@code ObservableValue}
* and the conditional {@code ObservableValue} to be garbage collected if neither is
* otherwise strongly referenced when {@code condition} holds {@code false}.
* This is in contrast to the general behavior of bindings, where the binding is
* only eligible for garbage collection when not observed itself.
* <p>
* A {@code condition} holding {@code null} is treated as holding {@code false}.
* <p>
* For example:
* <pre>{@code
* ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
* ObservableValue<String> longLivedProperty = new SimpleStringProperty("A");
* ObservableValue<String> whenProperty = longLivedProperty.when(condition);
*
* // observe whenProperty, which will in turn observe longLivedProperty
* whenProperty.addListener((ov, old, current) -> System.out.println(current));
*
* longLivedProperty.setValue("B"); // "B" is printed
*
* condition.setValue(false);
*
* // After condition becomes false, whenProperty stops observing longLivedProperty; condition
* // and whenProperty may now be eligible for GC despite being observed by the ChangeListener
*
* longLivedProperty.setValue("C"); // nothing is printed
* longLivedProperty.setValue("D"); // nothing is printed
*
* condition.setValue(true); // longLivedProperty is observed again, and "D" is printed
* }</pre>
* An example for binding a label's text to a long-lived property only when it is shown:
* <pre>{@code
* Label label = ... ;
* ObservableValue<String> longLivedProperty = new SimpleStringProperty("A");
* label.textProperty().bind(longLivedProperty.when(label::isShownProperty));
* }</pre>
*
* @param condition a boolean {@code ObservableValue}, cannot be {@code null}
* @return an {@code ObservableValue} that holds this value whenever the given
* condition evaluates to {@code true}, otherwise holds the last seen value;
* never returns {@code null}
* @since 20
*/
default ObservableValue<T> when(ObservableValue<Boolean> condition) {
return new ConditionalBinding<>(this, condition);
}
}
Loading