Skip to content

Commit

Permalink
8290040: Provide simplified deterministic way to manage listeners
Browse files Browse the repository at this point in the history
Reviewed-by: nlisker, angorya, kcr, mstrauss
  • Loading branch information
hjohn authored and nlisker committed Dec 14, 2022
1 parent f217d5e commit adfc022
Show file tree
Hide file tree
Showing 3 changed files with 400 additions and 2 deletions.
@@ -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;
}
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -251,4 +252,49 @@ 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>
*
* @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);
}
}

0 comments on commit adfc022

Please sign in to comment.