Skip to content

Commit c5d704b

Browse files
committed
Initial proposal
1 parent 4b9cb21 commit c5d704b

File tree

11 files changed

+1501
-3
lines changed

11 files changed

+1501
-3
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.sun.javafx.binding;
27+
28+
import java.util.Objects;
29+
import java.util.function.Consumer;
30+
31+
import javafx.beans.InvalidationListener;
32+
import javafx.beans.value.ChangeListener;
33+
import javafx.beans.value.ObservableValue;
34+
35+
/**
36+
* A subscription encapsulates how to cancel it without having
37+
* to keep track of how it was created.<p>
38+
*
39+
* For example:<p>
40+
* <pre>Subscription s = property.subscribe(System.out::println)</pre>
41+
* The function passed in to {@code subscribe} does not need to be stored
42+
* in order to clean up the subscription later.
43+
*/
44+
@FunctionalInterface
45+
public interface Subscription {
46+
47+
/**
48+
* An empty subscription. Does nothing when cancelled.
49+
*/
50+
static final Subscription EMPTY = () -> {};
51+
52+
/**
53+
* Cancels this subscription.
54+
*/
55+
void unsubscribe();
56+
57+
/**
58+
* Combines this {@link Subscription} with the given {@code Subscription}
59+
* and returns a new {@code Subscription} which will cancel both when
60+
* cancelled.
61+
*
62+
* @param other another {@link Subscription}, cannot be null
63+
* @return a combined {@link Subscription} which will cancel both when
64+
* cancelled, never null
65+
*/
66+
default Subscription and(Subscription other) {
67+
Objects.requireNonNull(other);
68+
69+
return () -> {
70+
unsubscribe();
71+
other.unsubscribe();
72+
};
73+
}
74+
75+
/**
76+
* Creates a {@link Subscription} on this {@link ObservableValue} which
77+
* immediately provides its current value to the given {@code subscriber},
78+
* followed by any subsequent changes in value.
79+
*
80+
* @param subscriber a {@link Consumer} to supply with the values of this
81+
* {@link ObservableValue}, cannot be null
82+
* @return a {@link Subscription} which can be used to cancel this
83+
* subscription, never null
84+
*/
85+
static <T> Subscription subscribe(ObservableValue<T> observableValue, Consumer<? super T> subscriber) {
86+
ChangeListener<T> listener = (obs, old, current) -> subscriber.accept(current);
87+
88+
subscriber.accept(observableValue.getValue()); // eagerly send current value
89+
observableValue.addListener(listener);
90+
91+
return () -> observableValue.removeListener(listener);
92+
}
93+
94+
/**
95+
* Creates a {@link Subscription} on this {@link ObservableValue} which
96+
* calls the given {@code runnable} whenever this {@code ObservableValue}
97+
* becomes invalid.
98+
*
99+
* @param runnable a {@link Runnable} to call whenever this
100+
* {@link ObservableValue} becomes invalid, cannot be null
101+
* @return a {@link Subscription} which can be used to cancel this
102+
* subscription, never null
103+
*/
104+
static Subscription subscribeInvalidations(ObservableValue<?> observableValue, Runnable runnable) {
105+
InvalidationListener listener = obs -> runnable.run();
106+
107+
observableValue.addListener(listener);
108+
109+
return () -> observableValue.removeListener(listener);
110+
}
111+
}

modules/javafx.base/src/main/java/javafx/beans/binding/ObjectBinding.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -155,7 +155,10 @@ public ObservableList<?> getDependencies() {
155155
public final T get() {
156156
if (!valid) {
157157
value = computeValue();
158-
valid = true;
158+
159+
if (allowValidation()) {
160+
valid = true;
161+
}
159162
}
160163
return value;
161164
}
@@ -182,6 +185,28 @@ public final boolean isValid() {
182185
return valid;
183186
}
184187

188+
/**
189+
* Returns {@code true} when this binding currently has one or more
190+
* listeners, otherwise {@code false}.
191+
*
192+
* @return {@code true} when this binding currently has one or more
193+
* listeners, otherwise {@code false}
194+
*/
195+
protected final boolean isObserved() {
196+
return helper != null;
197+
}
198+
199+
/**
200+
* Can be overriden in extending classes to prevent a binding from becoming
201+
* valid. The default implementation always allows bindings to become valid.
202+
*
203+
* @return {@code true} if this binding is allowed to become valid, otherwise
204+
* {@code false}
205+
*/
206+
protected boolean allowValidation() {
207+
return true;
208+
}
209+
185210
/**
186211
* Calculates the current value of this binding.
187212
* <p>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package javafx.beans.value;
27+
28+
import java.util.Objects;
29+
import java.util.function.Function;
30+
31+
import com.sun.javafx.binding.Subscription;
32+
33+
class FlatMappedBinding<S, T> extends LazyObjectBinding<T> {
34+
private final ObservableValue<S> source;
35+
private final Function<? super S, ? extends ObservableValue<? extends T>> mapper;
36+
37+
private Subscription mappedSubscription = Subscription.EMPTY;
38+
39+
public FlatMappedBinding(ObservableValue<S> source, Function<? super S, ? extends ObservableValue<? extends T>> mapper) {
40+
this.source = Objects.requireNonNull(source, "source cannot be null");
41+
this.mapper = Objects.requireNonNull(mapper, "mapper cannot be null");
42+
}
43+
44+
@Override
45+
protected T computeValue() {
46+
S value = source.getValue();
47+
ObservableValue<? extends T> mapped = value == null ? null : mapper.apply(value);
48+
49+
if (isObserved()) {
50+
mappedSubscription.unsubscribe();
51+
mappedSubscription = mapped == null ? Subscription.EMPTY : Subscription.subscribeInvalidations(mapped, this::invalidate);
52+
}
53+
54+
return mapped == null ? null : mapped.getValue();
55+
}
56+
57+
@Override
58+
protected Subscription observeInputs() {
59+
Subscription subscription = Subscription.subscribeInvalidations(source, this::invalidate);
60+
61+
return () -> {
62+
subscription.unsubscribe();
63+
mappedSubscription.unsubscribe();
64+
mappedSubscription = Subscription.EMPTY;
65+
};
66+
}
67+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package javafx.beans.value;
27+
28+
import com.sun.javafx.binding.Subscription;
29+
30+
import javafx.beans.InvalidationListener;
31+
import javafx.beans.binding.ObjectBinding;
32+
33+
/**
34+
* Extends {@link ObjectBinding} with the ability to lazily register and eagerly unregister listeners on its
35+
* dependencies.
36+
*
37+
* @param <T> the type of the wrapped {@code Object}
38+
*/
39+
abstract class LazyObjectBinding<T> extends ObjectBinding<T> {
40+
private Subscription subscription;
41+
private boolean wasObserved;
42+
43+
@Override
44+
public void addListener(ChangeListener<? super T> listener) {
45+
super.addListener(listener);
46+
47+
updateSubcriptionAfterAdd();
48+
}
49+
50+
@Override
51+
public void removeListener(ChangeListener<? super T> listener) {
52+
super.removeListener(listener);
53+
54+
updateSubcriptionAfterRemove();
55+
}
56+
57+
@Override
58+
public void addListener(InvalidationListener listener) {
59+
super.addListener(listener);
60+
61+
updateSubcriptionAfterAdd();
62+
}
63+
64+
@Override
65+
public void removeListener(InvalidationListener listener) {
66+
super.removeListener(listener);
67+
68+
updateSubcriptionAfterRemove();
69+
}
70+
71+
@Override
72+
protected boolean allowValidation() {
73+
return isObserved();
74+
}
75+
76+
/**
77+
* Called after a listener was added to start observing inputs, if they're not observed already.
78+
*/
79+
private void updateSubcriptionAfterAdd() {
80+
if (!wasObserved) { // was first observer registered?
81+
subscription = observeInputs(); // start observing source
82+
83+
/*
84+
* Although the act of registering a listener already attempts to make
85+
* this binding valid, allowValidation won't allow it as the binding is
86+
* not observed yet. This is because isObserved will not yet return true
87+
* when the process of registering the listener hasn't completed yet.
88+
*
89+
* As the binding must be valid after it becomes observed the first time
90+
* 'get' is called again.
91+
*/
92+
93+
get(); // make binding valid as source wasn't tracked until now
94+
wasObserved = true;
95+
}
96+
}
97+
98+
/**
99+
* Called after a listener was removed to stop observing inputs, if this was the last listener
100+
* observing this binding.
101+
*/
102+
private void updateSubcriptionAfterRemove() {
103+
if (wasObserved && !isObserved()) { // was last observer unregistered?
104+
subscription.unsubscribe();
105+
subscription = null;
106+
invalidate(); // make binding invalid as source is no longer tracked
107+
wasObserved = false;
108+
}
109+
}
110+
111+
/**
112+
* Called when this binding was previously not observed and a new observer was added. Implementors must return a
113+
* {@link Subscription} which will be cancelled when this binding no longer has any observers.
114+
*
115+
* @return a {@link Subscription} which will be cancelled when this binding no longer has any observers, never null
116+
*/
117+
protected abstract Subscription observeInputs();
118+
}

0 commit comments

Comments
 (0)