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
Show file tree
Hide file tree
Changes from all 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
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);
}
}
Loading