Skip to content

Commit c365589

Browse files
author
nicolaiparlog
committed
Implemented nested listeners (including tests and demo).
1 parent 8d38e29 commit c365589

17 files changed

+1306
-25
lines changed

demo/org/codefx/libfx/nesting/NestedPropertyDemo.java renamed to demo/org/codefx/libfx/nesting/NestedDemo.java

Lines changed: 127 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.codefx.libfx.nesting;
22

3+
import javafx.beans.Observable;
34
import javafx.beans.property.DoubleProperty;
45
import javafx.beans.property.Property;
56
import javafx.beans.property.SimpleObjectProperty;
67
import javafx.beans.property.StringProperty;
8+
import javafx.beans.value.ObservableValue;
79

810
import org.codefx.libfx.nesting.property.NestedDoubleProperty;
911
import org.codefx.libfx.nesting.property.NestedProperty;
@@ -12,7 +14,7 @@
1214
/**
1315
* Demonstrates some features of the nesting API.
1416
*/
15-
public class NestedPropertyDemo {
17+
public class NestedDemo {
1618

1719
// #region ATTRIBUTES
1820

@@ -28,7 +30,7 @@ public class NestedPropertyDemo {
2830
/**
2931
* Creates a new demo.
3032
*/
31-
private NestedPropertyDemo() {
33+
private NestedDemo() {
3234
this.currentEmployee = new SimpleObjectProperty<>(new Employee(54000, "Some Street"));
3335
}
3436

@@ -39,8 +41,10 @@ private NestedPropertyDemo() {
3941
* command line arguments (will not be used)
4042
*/
4143
public static void main(String[] args) {
42-
NestedPropertyDemo demo = new NestedPropertyDemo();
44+
NestedDemo demo = new NestedDemo();
4345

46+
demo.nestingCreation();
47+
demo.nestedListenerCreation();
4448
demo.nestedPropertyCreation();
4549
demo.nestedPropertyCreationWithBuilder();
4650
demo.nestedPropertyBinding();
@@ -52,43 +56,133 @@ public static void main(String[] args) {
5256

5357
// #region DEMOS
5458

59+
/**
60+
* Demonstrates how to create a {@link Nesting}.
61+
*/
62+
private void nestingCreation() {
63+
print("NESTING CREATION");
64+
65+
/*
66+
* A 'Nesting' is the basic building block of this API. Its Javadoc explains the terminology which is used in
67+
* these demos as well as in the rest of the documentation.
68+
*/
69+
70+
/*
71+
* A 'Nesting'-instance is created in several steps, which are shown here. It can then be used to create other
72+
* nested objects like nested properties or nested listeners. Very often the nesting itself is not needed and
73+
* the goal is the creation of those other objects based in it. In those cases the builder methods for those
74+
* objects (e.g. 'buildProperty') can and should be called directly. What is important in this demo method is
75+
* that all possibilities before calling a builder method apply to all kinds of nested functionality like nested
76+
* properties and nested listeners.
77+
*/
78+
79+
// all created nestings wrap an observable which contains the current employee's street name (which is a String)
80+
81+
// create a 'Nesting<Property<String>>' by starting on the 'currentEmployee' property,
82+
// nest to the employee's address and then to the address' street name;
83+
Nesting<Property<String>> withObjectProperty = Nestings.on(currentEmployee)
84+
.nestProperty(employee -> employee.addressProperty())
85+
.nestProperty(address -> address.streetNameProperty())
86+
.buildNesting();
87+
print("The 'Nesting<Property<String>>' has the value: \"" + getValueFromNesting(withObjectProperty) + "\"");
88+
89+
// now, create a 'Nesting<StringProperty>' instead; note the second nesting step which is different from above
90+
Nesting<StringProperty> withStringProperty = Nestings.on(currentEmployee)
91+
.nestProperty(employee -> employee.addressProperty())
92+
.nestStringProperty(address -> address.streetNameProperty())
93+
.buildNesting();
94+
print("The 'Nesting<StringProperty>' has the value: \"" + getValueFromNesting(withStringProperty) + "\"");
95+
96+
// calls to 'nestProperty' can be cut short; note the first nesting step which is different from above
97+
Nesting<StringProperty> withShortcut = Nestings.on(currentEmployee)
98+
.nest(employee -> employee.addressProperty())
99+
.nestStringProperty(address -> address.streetNameProperty())
100+
.buildNesting();
101+
print("The 'Nesting<StringProperty>' (with shortcut) has the value: \""
102+
+ getValueFromNesting(withShortcut) + "\"");
103+
104+
// if 'employee.addressProperty' were no property but an 'ObservableValue', a 'Nesting<ObservableValue<String>'
105+
// could also be created; note the second nesting call which differs from those above
106+
Nesting<ObservableValue<String>> withObservableValue = Nestings.on(currentEmployee)
107+
.nestProperty(employee -> employee.addressProperty())
108+
.nestObservableValue(address -> address.streetNameProperty())
109+
.buildNesting();
110+
print("The 'Nesting<ObservableValue<String>' has the value: \""
111+
+ getValueFromNesting(withObservableValue) + "\"");
112+
113+
// the same is true, if it were only an 'Observable'
114+
Nesting<Observable> withObservable = Nestings.on(currentEmployee)
115+
.nestProperty(employee -> employee.addressProperty())
116+
.nestObservable(address -> address.streetNameProperty())
117+
.buildNesting();
118+
print("The 'Nesting<Observable's value can not be accessed, so let's call 'toString': \""
119+
+ withObservable.innerObservableProperty().getValue().get().toString() + "\"");
120+
121+
print();
122+
}
123+
124+
/**
125+
* Demonstrates how to create nested listener.
126+
*/
127+
private void nestedListenerCreation() {
128+
print("LISTENER CREATION");
129+
130+
/*
131+
* The listener creation is similar to the nesting creation (see above) and only differs in the final call to
132+
* 'build...'. Note that a listener can only be added if the type of the Nesting's inner observable allows it.
133+
* This means that a 'InvalidationListener' can always be added, but a 'ChangeListener' only to an
134+
* 'ObservableValue'.
135+
*/
136+
137+
// nest as above and then add a change listener
138+
Nestings.on(currentEmployee)
139+
.nestProperty(employee -> employee.addressProperty())
140+
.nestProperty(address -> address.streetNameProperty())
141+
.addListener((observable, oldValue, newValue) -> {/* do something here */});
142+
143+
// an invalidation listener could even be added if 'employee.addressProperty' were only an observable
144+
Nestings.on(currentEmployee)
145+
.nestProperty(employee -> employee.addressProperty())
146+
.nestObservable(address -> address.streetNameProperty())
147+
.addListener(observable -> {/* do something here */});
148+
149+
print();
150+
}
151+
55152
/**
56153
* Demonstrates how to create some nested properties.
57154
*/
58155
private void nestedPropertyCreation() {
59-
print("CREATION");
156+
print("PROPERTY CREATION");
60157

61-
// all created properties wrap the current employee's street name (which is a String)
158+
/*
159+
* The property creation is similar to the nesting creation (see above) and only differs in the final call to
160+
* 'build...'. Note that a property can only be created if the type of the Nesting's inner observable is also a
161+
* 'Property'. The reason for this is that only properties allow reading and writing their value.
162+
*/
62163

63-
// create a Property<String> by starting on the 'currentEmployee' property,
64-
// nest to the employee's address and then to the address' street name;
164+
// nest as above but instead of creating a 'Nesting<Property<String>>', create a 'Property<String>'
65165
Property<String> asObjectProperty = Nestings.on(currentEmployee)
66166
.nestProperty(employee -> employee.addressProperty())
67167
.nestProperty(address -> address.streetNameProperty())
68168
.buildProperty();
69169
print("The nested 'Property<String>' has the value: \"" + asObjectProperty.getValue() + "\"");
70170

71-
// now, create a StringProperty instead; note the second nesting step which is different from above
171+
// now, create a 'StringProperty instead'
72172
StringProperty asStringProperty = Nestings.on(currentEmployee)
73173
.nestProperty(employee -> employee.addressProperty())
74174
.nestStringProperty(address -> address.streetNameProperty())
75175
.buildProperty();
76176
print("The nested 'StringProperty' has the value: \"" + asStringProperty.getValue() + "\"");
77177

78178
// 'buildProperty' actually returns a 'Nested...Property', which also implements the interface 'Nested'
179+
// (its additional functionality is demonstrated further below)
79180
NestedStringProperty asNestedStringProperty = Nestings.on(currentEmployee)
80-
.nest(employee -> employee.addressProperty())
181+
.nestProperty(employee -> employee.addressProperty())
81182
.nestStringProperty(address -> address.streetNameProperty())
82183
.buildProperty();
83184
print("The 'NestedStringProperty' has the value: \"" + asNestedStringProperty.getValue() + "\"");
84185

85-
// calls to 'nestProperty' can be cut short; note the first nesting step which is different from above
86-
NestedStringProperty withShortcut = Nestings.on(currentEmployee)
87-
.nest(employee -> employee.addressProperty())
88-
.nestStringProperty(address -> address.streetNameProperty())
89-
.buildProperty();
90-
print("The 'NestedStringProperty' (with shortcut) has the value: \"" + withShortcut.getValue() + "\"");
91-
92186
print();
93187
}
94188

@@ -163,7 +257,7 @@ private void nestedPropertyBinding() {
163257
}
164258

165259
/**
166-
* Demonstrates how a {@link NestedProperty} behaves when the inner
260+
* Demonstrates how a {@link NestedProperty} behaves when the inner observable is missing.
167261
*/
168262
private void nestedPropertyBindingWithMissingInnerObservable() {
169263
print("NESTED PROPERTY BINDING WHEN INNER OBSERVABLE IS MISSING");
@@ -206,10 +300,9 @@ private void additionalNestedFeatures() {
206300
// the interface 'Nested' has a property which indicates whether the inner observable is present;
207301
// one use would be to automatically disable a UI element which displays the property's value;
208302
// in this case, a change listener is added which simply prints the new state
209-
currentEmployeesStreetName.innerObservablePresentProperty()
210-
.addListener(
211-
(observable, oldValue, newValue) -> print("\tInner observable present changed to \"" + newValue
212-
+ "\"."));
303+
currentEmployeesStreetName.innerObservablePresentProperty().addListener(
304+
(observable, oldValue, newValue)
305+
-> print("\tInner observable present changed to \"" + newValue + "\"."));
213306

214307
print("Set the 'currentEmployee' to null, which means that no inner observable will be present.");
215308
Employee notNullEmployee = currentEmployee.getValue();
@@ -251,6 +344,19 @@ private static void print(String text) {
251344
System.out.println(text);
252345
}
253346

347+
/**
348+
* Returns the value held by the specified nesting's inner observable.
349+
*
350+
* @param <T>
351+
* the type of value contained in the observable
352+
* @param nesting
353+
* the {@link Nesting} whose value will be returned
354+
* @return 'nesting.innerObservableProperty().getValue().get().getValue()'
355+
*/
356+
private static <T> T getValueFromNesting(Nesting<? extends ObservableValue<T>> nesting) {
357+
return nesting.innerObservableProperty().getValue().get().getValue();
358+
}
359+
254360
/**
255361
* Outputs the salary of both specified properties.
256362
*

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
import java.util.Objects;
66
import java.util.function.Function;
77

8+
import javafx.beans.InvalidationListener;
89
import javafx.beans.Observable;
910
import javafx.beans.value.ObservableValue;
1011

12+
import org.codefx.libfx.nesting.listener.NestedInvalidationListener;
13+
import org.codefx.libfx.nesting.listener.NestedInvalidationListenerBuilder;
14+
1115
/**
1216
* A superclass for builders for all kinds of nested functionality. Holds the nesting hierarchy (outer observable and
1317
* nesting steps) and can build a {@link Nesting} from it.
@@ -159,6 +163,25 @@ private void fillNestingConstructionKit(NestingConstructionKit kit) {
159163

160164
//#end BUILD
161165

166+
// #region LISTENERS
167+
168+
/**
169+
* Adds the specified invalidation listener to the nesting hierarchy's inner {@link Observable}.
170+
*
171+
* @param listener
172+
* the added {@link InvalidationListener}
173+
* @return the {@link NestedInvalidationListener} which can be used to check the nesting's state
174+
*/
175+
public NestedInvalidationListener addListener(InvalidationListener listener) {
176+
Nesting<O> nesting = buildNesting();
177+
return NestedInvalidationListenerBuilder
178+
.forNesting(nesting)
179+
.withListener(listener)
180+
.build();
181+
}
182+
183+
//#end LISTENERS
184+
162185
// #region PRIVATE CLASSES
163186

164187
/**

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package org.codefx.libfx.nesting;
22

3+
import javafx.beans.value.ChangeListener;
34
import javafx.beans.value.ObservableValue;
45

6+
import org.codefx.libfx.nesting.listener.NestedChangeListener;
7+
import org.codefx.libfx.nesting.listener.NestedChangeListenerBuilder;
8+
59
/**
610
* A nesting builder which allows adding change listeners.
711
*
@@ -43,4 +47,23 @@ protected <P> AbstractNestingBuilderOnObservableValue(
4347

4448
//#end CONSTRUCTION
4549

50+
// #region LISTENERS
51+
52+
/**
53+
* Adds the specified change listener to the nesting hierarchy's inner {@link ObservableValue}.
54+
*
55+
* @param listener
56+
* the added {@link ChangeListener}
57+
* @return the {@link NestedChangeListener} which can be used to check the nesting's state
58+
*/
59+
public NestedChangeListener<T> addListener(ChangeListener<? super T> listener) {
60+
Nesting<O> nesting = buildNesting();
61+
return NestedChangeListenerBuilder
62+
.forNesting(nesting)
63+
.withListener(listener)
64+
.build();
65+
}
66+
67+
//#end LISTENERS
68+
4669
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.codefx.libfx.nesting.listener;
2+
3+
import java.util.Objects;
4+
5+
import javafx.beans.property.BooleanProperty;
6+
import javafx.beans.property.ReadOnlyBooleanProperty;
7+
import javafx.beans.property.SimpleBooleanProperty;
8+
import javafx.beans.value.ChangeListener;
9+
import javafx.beans.value.ObservableValue;
10+
11+
import org.codefx.libfx.nesting.Nested;
12+
import org.codefx.libfx.nesting.Nesting;
13+
import org.codefx.libfx.nesting.NestingObserver;
14+
import org.codefx.libfx.nesting.property.NestedProperty;
15+
16+
/**
17+
* Contains a {@link ChangeListener} which is connected to a {@link Nesting}. Simply put, the listener is always added
18+
* to the nesting's {@link Nesting#innerObservableProperty() innerObservable}.
19+
* <p>
20+
* <h2>Inner Observable's Value Changes</h2> The listener is added to the nesting's inner observable. So when that
21+
* observable's value changes, the listener is called as usual.
22+
* <p>
23+
* <h2>Inner Observable Is Replaced</h2> When the nesting's inner observable is replaced by another, the listener is
24+
* removed from the old and added to the new observable. If one of them is missing, that step is left out.
25+
* <p>
26+
* Note that in this case <b>the listener is not called</b>! If this is the desired behavior, a listener has to be added
27+
* to a {@link NestedProperty}.
28+
*
29+
* @param <T>
30+
* the type of the value wrapped by the observable value
31+
*/
32+
public class NestedChangeListener<T> implements Nested {
33+
34+
// #region PROPERTIES
35+
36+
/**
37+
* The property indicating whether the nesting's inner observable is currently present, i.e. not null.
38+
*/
39+
private final BooleanProperty innerObservablePresent;
40+
41+
//#end PROPERTIES
42+
43+
// #region CONSTUCTION
44+
45+
/**
46+
* Creates a new {@link NestedChangeListener} which adds the specified listener to the specified nesting's inner
47+
* observable.
48+
*
49+
* @param nesting
50+
* the {@link Nesting} to which the listener is added
51+
* @param listener
52+
* the {@link ChangeListener} which is added to the nesting's {@link Nesting#innerObservableProperty()
53+
* innerObservable}
54+
*/
55+
NestedChangeListener(Nesting<? extends ObservableValue<T>> nesting, ChangeListener<? super T> listener) {
56+
Objects.requireNonNull(nesting, "The argument 'nesting' must not be null.");
57+
Objects.requireNonNull(listener, "The argument 'listener' must not be null.");
58+
59+
this.innerObservablePresent = new SimpleBooleanProperty(this, "innerObservablePresent");
60+
61+
NestingObserver
62+
.forNesting(nesting)
63+
.withOldInnerObservable(oldInnerObservable -> oldInnerObservable.removeListener(listener))
64+
.withNewInnerObservable(newInnerObservable -> newInnerObservable.addListener(listener))
65+
.whenInnerObservableChanges(
66+
(Boolean any, Boolean newInnerObservablePresent)
67+
-> innerObservablePresent.set(newInnerObservablePresent))
68+
.observe();
69+
}
70+
71+
//#end CONSTUCTION
72+
73+
// #region IMPLEMENTATION OF 'Nested'
74+
75+
@Override
76+
public ReadOnlyBooleanProperty innerObservablePresentProperty() {
77+
return innerObservablePresent;
78+
}
79+
80+
@Override
81+
public boolean isInnerObservablePresent() {
82+
return innerObservablePresent.get();
83+
}
84+
85+
//#end IMPLEMENTATION OF 'NestedProperty'
86+
87+
}

0 commit comments

Comments
 (0)