Skip to content

Commit adfc022

Browse files
hjohnnlisker
authored andcommitted
8290040: Provide simplified deterministic way to manage listeners
Reviewed-by: nlisker, angorya, kcr, mstrauss
1 parent f217d5e commit adfc022

File tree

3 files changed

+400
-2
lines changed

3 files changed

+400
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.sun.javafx.binding;
2+
3+
import java.util.Objects;
4+
5+
import javafx.beans.value.ObservableValue;
6+
7+
public class ConditionalBinding<T> extends LazyObjectBinding<T> {
8+
9+
private final ObservableValue<T> source;
10+
private final ObservableValue<Boolean> nonNullCondition;
11+
12+
private Subscription subscription;
13+
14+
public ConditionalBinding(ObservableValue<T> source, ObservableValue<Boolean> condition) {
15+
this.source = Objects.requireNonNull(source, "source cannot be null");
16+
this.nonNullCondition = Objects.requireNonNull(condition, "condition cannot be null").orElse(false);
17+
18+
// condition is always observed and never unsubscribed
19+
Subscription.subscribe(nonNullCondition, current -> {
20+
invalidate();
21+
22+
if (!current) {
23+
getValue();
24+
}
25+
});
26+
}
27+
28+
/**
29+
* This binding is valid whenever it is observed, or it is currently inactive.
30+
* When inactive, the binding has the value of its source at the time it became
31+
* inactive.
32+
*/
33+
@Override
34+
protected boolean allowValidation() {
35+
return super.allowValidation() || !isActive();
36+
}
37+
38+
@Override
39+
protected T computeValue() {
40+
if (isObserved() && isActive()) {
41+
if (subscription == null) {
42+
subscription = Subscription.subscribeInvalidations(source, this::invalidate);
43+
}
44+
}
45+
else {
46+
unsubscribe();
47+
}
48+
49+
return source.getValue();
50+
}
51+
52+
@Override
53+
protected Subscription observeSources() {
54+
return this::unsubscribe;
55+
}
56+
57+
private boolean isActive() {
58+
return nonNullCondition.getValue();
59+
}
60+
61+
private void unsubscribe() {
62+
if (subscription != null) {
63+
subscription.unsubscribe();
64+
subscription = null;
65+
}
66+
}
67+
}

modules/javafx.base/src/main/java/javafx/beans/value/ObservableValue.java

+46
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import java.util.function.Function;
2929

30+
import com.sun.javafx.binding.ConditionalBinding;
3031
import com.sun.javafx.binding.FlatMappedBinding;
3132
import com.sun.javafx.binding.MappedBinding;
3233
import com.sun.javafx.binding.OrElseBinding;
@@ -251,4 +252,49 @@ default ObservableValue<T> orElse(T constant) {
251252
default <U> ObservableValue<U> flatMap(Function<? super T, ? extends ObservableValue<? extends U>> mapper) {
252253
return new FlatMappedBinding<>(this, mapper);
253254
}
255+
256+
/**
257+
* Returns an {@code ObservableValue} that holds this value and is updated only
258+
* when {@code condition} holds {@code true}.
259+
* <p>
260+
* The returned {@code ObservableValue} only observes this value when
261+
* {@code condition} holds {@code true}. This allows this {@code ObservableValue}
262+
* and the conditional {@code ObservableValue} to be garbage collected if neither is
263+
* otherwise strongly referenced when {@code condition} holds {@code false}.
264+
* This is in contrast to the general behavior of bindings, where the binding is
265+
* only eligible for garbage collection when not observed itself.
266+
* <p>
267+
* A {@code condition} holding {@code null} is treated as holding {@code false}.
268+
* <p>
269+
* For example:
270+
* <pre>{@code
271+
* ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
272+
* ObservableValue<String> longLivedProperty = new SimpleStringProperty("A");
273+
* ObservableValue<String> whenProperty = longLivedProperty.when(condition);
274+
*
275+
* // observe whenProperty, which will in turn observe longLivedProperty
276+
* whenProperty.addListener((ov, old, current) -> System.out.println(current));
277+
*
278+
* longLivedProperty.setValue("B"); // "B" is printed
279+
*
280+
* condition.setValue(false);
281+
*
282+
* // After condition becomes false, whenProperty stops observing longLivedProperty; condition
283+
* // and whenProperty may now be eligible for GC despite being observed by the ChangeListener
284+
*
285+
* longLivedProperty.setValue("C"); // nothing is printed
286+
* longLivedProperty.setValue("D"); // nothing is printed
287+
*
288+
* condition.setValue(true); // longLivedProperty is observed again, and "D" is printed
289+
* }</pre>
290+
*
291+
* @param condition a boolean {@code ObservableValue}, cannot be {@code null}
292+
* @return an {@code ObservableValue} that holds this value whenever the given
293+
* condition evaluates to {@code true}, otherwise holds the last seen value;
294+
* never returns {@code null}
295+
* @since 20
296+
*/
297+
default ObservableValue<T> when(ObservableValue<Boolean> condition) {
298+
return new ConditionalBinding<>(this, condition);
299+
}
254300
}

0 commit comments

Comments
 (0)