Skip to content

Commit 8a7217e

Browse files
author
nicolaiparlog
committed
Abstracted most of the code of 'PropertyToNestingBinding' into a more general 'NestingObserver' and implemented tests for that class.
1 parent 3c88e45 commit 8a7217e

File tree

6 files changed

+548
-89
lines changed

6 files changed

+548
-89
lines changed

src/org/codefx/libfx/nesting/Nesting.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* Nestings will usually be implemented such that they eagerly evaluate the nested observables.
1616
*
1717
* @param <O>
18-
* the hierarchy's innermost type of {@link Observable}
18+
* the type of the hierarchy's innermost {@link Observable}
1919
*/
2020
public interface Nesting<O extends Observable> {
2121

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package org.codefx.libfx.nesting;
2+
3+
import java.util.Optional;
4+
import java.util.function.BiConsumer;
5+
import java.util.function.Consumer;
6+
7+
import javafx.beans.Observable;
8+
9+
/**
10+
* Usability class which observes a {@link Nesting} and executes some methods when the nesting's
11+
* {@link Nesting#innerObservableProperty() innerObservable} changes (in that order):
12+
* <ol>
13+
* <li>if the old inner observable was present, a method is called with that observable as its argument; the method is
14+
* specified during building (see {@link NestingObserverBuilder#withOldInnerObservable(Consumer) withOldInnerObservable})
15+
* <li>if the new inner observable is present, a method is called with that observable as its argument; the method is
16+
* specified during building (see {@link NestingObserverBuilder#withNewInnerObservable(Consumer) withNewInnerObservable})
17+
* <li>in every case, another method is called with two Booleans as its arguments which indicate whether the old and the
18+
* new observable were/are present; the method is specified during building (see
19+
* {@link NestingObserverBuilder#whenInnerObservableChanges(BiConsumer) whenInnerObservableChanges})
20+
* </ol>
21+
* These methods are also called once after construction. At this point, there is of course no old inner observable
22+
* present.
23+
* <p>
24+
* The observer is created with a {@link NestingObserverBuilder} which can be obtained from
25+
* {@link NestingObserver#observe(Nesting) observe}. After setting some of the methods mentioned above, the observer is
26+
* built by calling {@link NestingObserverBuilder#build()}.
27+
*
28+
* @param <O>
29+
* the type of the nesting hierarchy's innermost {@link Observable}
30+
*/
31+
public final class NestingObserver<O extends Observable> {
32+
33+
// #region PROPERTIES
34+
35+
/**
36+
* The observed {@link Nesting}.
37+
*/
38+
private final Nesting<? extends O> nesting;
39+
40+
/**
41+
* Called when the inner observable changes and an old inner observable was present. That observable is also the
42+
* argument.
43+
*/
44+
private final Consumer<? super O> oldInnerObservableConsumer;
45+
46+
/**
47+
* Called when the inner observable changes and the new inner observable is present. That observable is also the
48+
* argument.
49+
*/
50+
private final Consumer<? super O> newInnerObservableConsumer;
51+
52+
/**
53+
* Called when the inner observable changes. The arguments are two Booleans indicating whether the old and new inner
54+
* observables are present.
55+
*/
56+
private final BiConsumer<Boolean, Boolean> innerObservableChanges;
57+
58+
//#end PROPERTIES
59+
60+
// #region CONSTRUCTION
61+
62+
/**
63+
* Creates a new {@link NestingObserver} from the specified {@link NestingObserverBuilder builder}.
64+
*
65+
* @param builder
66+
* the {@link NestingObserverBuilder} from which the observer is created
67+
*/
68+
private NestingObserver(NestingObserverBuilder<O> builder) {
69+
nesting = builder.nesting;
70+
oldInnerObservableConsumer = builder.oldInnerObservableConsumer;
71+
newInnerObservableConsumer = builder.newInnerObservableConsumer;
72+
innerObservableChanges = builder.innerObservableChanges;
73+
74+
initializeObserver();
75+
}
76+
77+
/**
78+
* Starts building a new {@link NestingObserver} which observes the specified nesting.
79+
*
80+
* @param <O>
81+
* the type of the nesting hierarchy's innermost {@link Observable}
82+
* @param nesting
83+
* the observed {@link Nesting}
84+
* @return a new {@link NestingObserverBuilder}
85+
*/
86+
public static <O extends Observable> NestingObserverBuilder<O> observe(Nesting<O> nesting) {
87+
return new NestingObserverBuilder<O>(nesting);
88+
}
89+
90+
//#end CONSTRUCTION
91+
92+
// #region OBSERVE
93+
94+
/**
95+
* Initializes the observer by observing the initial status and any changes made to it.
96+
*/
97+
private void initializeObserver() {
98+
// observe the initial status
99+
observeInnerObservableChange(Optional.empty(), nesting.innerObservableProperty().getValue());
100+
101+
// add a listener to the nesting which observes changes
102+
nesting.innerObservableProperty().addListener(
103+
(o, oldInnerObservable, newInnerObservable)
104+
-> observeInnerObservableChange(oldInnerObservable, newInnerObservable));
105+
}
106+
107+
/**
108+
* Calls {@link #oldInnerObservableConsumer}, {@link #newInnerObservableConsumer} and
109+
* {@link #innerObservableChanges} when the inner observable changes.
110+
*
111+
* @param oldInnerObservable
112+
* the old {@link Nesting#innerObservableProperty() innerObservable}
113+
* @param newInnerObservable
114+
* the new {@link Nesting#innerObservableProperty() innerObservable}
115+
*/
116+
private void observeInnerObservableChange(
117+
Optional<? extends O> oldInnerObservable, Optional<? extends O> newInnerObservable) {
118+
119+
oldInnerObservable.ifPresent(oldObservable -> oldInnerObservableConsumer.accept(oldObservable));
120+
newInnerObservable.ifPresent(newObservable -> newInnerObservableConsumer.accept(newObservable));
121+
122+
boolean oldInnerObservablePresent = oldInnerObservable.isPresent();
123+
boolean newInnerObservablePresent = newInnerObservable.isPresent();
124+
innerObservableChanges.accept(oldInnerObservablePresent, newInnerObservablePresent);
125+
}
126+
127+
//#end BIND
128+
129+
// #region INNER CLASSES
130+
131+
/**
132+
* Builds a {@link NestingObserver}.
133+
*
134+
* @param <O>
135+
* the type of the nesting hierarchy's innermost {@link Observable}
136+
*/
137+
public static class NestingObserverBuilder<O extends Observable> {
138+
139+
/**
140+
* The future value for {@link NestingObserver#nesting}.
141+
*/
142+
private final Nesting<? extends O> nesting;
143+
144+
/**
145+
* The future value for {@link NestingObserver#oldInnerObservableConsumer}.
146+
*/
147+
private Consumer<? super O> oldInnerObservableConsumer;
148+
149+
/**
150+
* The future value for {@link NestingObserver#newInnerObservableConsumer}.
151+
*/
152+
private Consumer<? super O> newInnerObservableConsumer;
153+
154+
/**
155+
* The future value for {@link NestingObserver#innerObservableChanges}.
156+
*/
157+
private BiConsumer<Boolean, Boolean> innerObservableChanges;
158+
159+
/**
160+
* Creates a new builder for a nesting observer which observes the specified nesting.
161+
*
162+
* @param nesting
163+
* the nesting which will be observed by the created {@link NestingObserver}
164+
*/
165+
private NestingObserverBuilder(Nesting<? extends O> nesting) {
166+
this.nesting = nesting;
167+
oldInnerObservableConsumer = observable -> {/* by default do nothing */};
168+
newInnerObservableConsumer = observable -> {/* by default do nothing */};
169+
innerObservableChanges = (oldObservablePresent, newObservablePresent) -> {/* by default do nothing */};
170+
}
171+
172+
/**
173+
* The specified method will be executed when the {@link Nesting#innerObservableProperty() innerObservable}
174+
* changes <b>and</b> the old observable was present.
175+
*
176+
* @param oldInnerObservableConsumer
177+
* the executed method; its argument is the old inner observable
178+
* @return this builder for fluent calls
179+
*/
180+
public NestingObserverBuilder<O> withOldInnerObservable(Consumer<? super O> oldInnerObservableConsumer) {
181+
this.oldInnerObservableConsumer = oldInnerObservableConsumer;
182+
return this;
183+
}
184+
185+
/**
186+
* The specified method will be executed when the {@link Nesting#innerObservableProperty() innerObservable}
187+
* changes <b>and</b> the new observable is present.
188+
*
189+
* @param newInnerObservableConsumer
190+
* the executed method; its argument is the new inner observable
191+
* @return this builder for fluent calls
192+
*/
193+
public NestingObserverBuilder<O> withNewInnerObservable(Consumer<? super O> newInnerObservableConsumer) {
194+
this.newInnerObservableConsumer = newInnerObservableConsumer;
195+
return this;
196+
}
197+
198+
/**
199+
* The specified method will be executed when the {@link Nesting#innerObservableProperty() innerObservable}
200+
* changes.
201+
*
202+
* @param innerObservableChanges
203+
* the executed method; its argument are two Booleans indicating whether the old and new inner
204+
* observables are present.
205+
* @return this builder for fluent calls
206+
*/
207+
public NestingObserverBuilder<O> whenInnerObservableChanges(BiConsumer<Boolean, Boolean> innerObservableChanges) {
208+
this.innerObservableChanges = innerObservableChanges;
209+
return this;
210+
}
211+
212+
/**
213+
* Builds a observer from this builder's settings.
214+
*
215+
* @return a new {@link NestingObserver}
216+
*/
217+
public NestingObserver<O> build() {
218+
return new NestingObserver<O>(this);
219+
}
220+
221+
}
222+
223+
//#end region
224+
225+
}
Lines changed: 17 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package org.codefx.libfx.nesting.property;
22

33
import java.util.Objects;
4-
import java.util.Optional;
4+
import java.util.function.BiConsumer;
55
import java.util.function.Consumer;
66

77
import javafx.beans.property.Property;
88

99
import org.codefx.libfx.nesting.Nesting;
10+
import org.codefx.libfx.nesting.NestingObserver;
1011

1112
/**
1213
* Implements the bidirectional binding between a nested property and its nesting's
@@ -17,33 +18,13 @@
1718
*/
1819
class PropertyToNestingBinding<T> {
1920

20-
// #region PROPERTIES
21-
22-
/**
23-
* The property which will be bound to the {@link #nesting}
24-
*/
25-
private final NestedProperty<T> nestedProperty;
26-
27-
/**
28-
* Sets the {@link #nestedProperty}'s {@link NestedProperty#innerObservablePresentProperty() innerObservablePresent}
29-
* property.
30-
*/
31-
private final Consumer<Boolean> innerObservablePresentSetter;
32-
33-
/**
34-
* The nesting to which the {@link #nestedProperty} will be bound.
35-
*/
36-
private final Nesting<? extends Property<T>> nesting;
37-
38-
//#end PROPERTIES
39-
40-
// #region CONSTRUCTION
41-
4221
/**
4322
* Bidirectionally binds the specified nested property to the specified nesting's property. The specified setter is
4423
* used to update the nested property's {@link NestedProperty#innerObservablePresentProperty()
4524
* innerObservablePresent} property.
4625
*
26+
* @param <T>
27+
* the type wrapped by the property
4728
* @param nestedProperty
4829
* the {@link Property} which will be bound to the specified nesting
4930
* @param innerObservablePresentSetter
@@ -53,7 +34,7 @@ class PropertyToNestingBinding<T> {
5334
* @throws NullPointerException
5435
* if any of the arguments is null
5536
*/
56-
private PropertyToNestingBinding(
37+
public static <T> void bind(
5738
NestedProperty<T> nestedProperty, Consumer<Boolean> innerObservablePresentSetter,
5839
Nesting<? extends Property<T>> nesting) {
5940

@@ -62,70 +43,18 @@ private PropertyToNestingBinding(
6243
"The argument 'innerObservablePresentSetter' must not be null.");
6344
Objects.requireNonNull(nesting, "The argument 'nesting' must not be null.");
6445

65-
this.nestedProperty = nestedProperty;
66-
this.innerObservablePresentSetter = innerObservablePresentSetter;
67-
this.nesting = nesting;
46+
// the 'innerObservablePresentSetter' only accepts one Boolean; create a 'BiConsumer' from it,
47+
// which accepts two and ignores the first
48+
BiConsumer<Boolean, Boolean> innerObservablePresentBiSetter =
49+
(oldPropertyPresent, newPropertyPresent) -> innerObservablePresentSetter.accept(newPropertyPresent);
50+
51+
// use a nesting observer to accomplish the binding/unbinding
52+
NestingObserver
53+
.observe(nesting)
54+
.withOldInnerObservable(oldProperty -> nestedProperty.unbindBidirectional(oldProperty))
55+
.withNewInnerObservable(newProperty -> nestedProperty.bindBidirectional(newProperty))
56+
.whenInnerObservableChanges(innerObservablePresentBiSetter)
57+
.build();
6858
}
6959

70-
/**
71-
* Bidirectionally binds the specified nested property to the specified nesting's property. The specified setter is
72-
* used to update the nested property's {@link NestedProperty#innerObservablePresentProperty()
73-
* innerObservablePresent} property.
74-
*
75-
* @param <T>
76-
* the type wrapped by the property
77-
* @param nestedProperty
78-
* the {@link Property} which will be bound to the specified nesting
79-
* @param innerObservablePresentSetter
80-
* the {@link Consumer} which sets the {@link NestedProperty#innerObservablePresentProperty()} property
81-
* @param nesting
82-
* the {@link Nesting} to which the property will be bound
83-
* @throws NullPointerException
84-
* if any of the arguments is null
85-
*/
86-
public static <T> void bind(
87-
NestedProperty<T> nestedProperty, Consumer<Boolean> innerObservablePresentSetter,
88-
Nesting<? extends Property<T>> nesting) {
89-
90-
PropertyToNestingBinding<T> binding =
91-
new PropertyToNestingBinding<>(nestedProperty, innerObservablePresentSetter, nesting);
92-
binding.bindToNestingProperty();
93-
}
94-
95-
//#end CONSTRUCTION
96-
97-
// #region BIND
98-
99-
/**
100-
* Binds this property's value to the nesting's property's value and adds a listener which updates that binding.
101-
*/
102-
private void bindToNestingProperty() {
103-
// bind to the nesting's current property
104-
moveBinding(Optional.empty(), nesting.innerObservableProperty().getValue());
105-
// add a listener to the nesting which moves the binding from one property to the next
106-
nesting.innerObservableProperty().addListener(
107-
(observable, oldProperty, newProperty) -> moveBinding(oldProperty, newProperty));
108-
}
109-
110-
/**
111-
* Moves the bidirectional binding from the specified old to the specified new observable (one or both can be null).
112-
*
113-
* @param oldPropertyOpt
114-
* the {@link Property} from which to unbind
115-
* @param newPropertyOpt
116-
* the {@link Property} to which to bind
117-
*/
118-
private void moveBinding(
119-
Optional<? extends Property<T>> oldPropertyOpt,
120-
Optional<? extends Property<T>> newPropertyOpt) {
121-
122-
oldPropertyOpt.ifPresent(oldProperty -> nestedProperty.unbindBidirectional(oldProperty));
123-
newPropertyOpt.ifPresent(newProperty -> nestedProperty.bindBidirectional(newProperty));
124-
125-
boolean innerObservablePresent = newPropertyOpt.isPresent();
126-
innerObservablePresentSetter.accept(innerObservablePresent);
127-
}
128-
129-
//#end BIND
130-
13160
}

0 commit comments

Comments
 (0)