-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathNestedDemo.java
438 lines (359 loc) · 18 KB
/
NestedDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
package org.codefx.libfx.nesting;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import org.codefx.libfx.nesting.Employee.Address;
import org.codefx.libfx.nesting.property.NestedDoubleProperty;
import org.codefx.libfx.nesting.property.NestedProperty;
import org.codefx.libfx.nesting.property.NestedStringProperty;
/**
* Demonstrates some features of the nesting API.
*/
public class NestedDemo {
// #begin FIELDS
/**
* The currently selected employee.
*/
private final Property<Employee> currentEmployee;
//#end FIELDS
// #begin CONSTRUCTION & MAIN
/**
* Creates a new demo.
*/
private NestedDemo() {
this.currentEmployee = new SimpleObjectProperty<>(new Employee(54000, "Some Street"));
}
/**
* Runs this demo.
*
* @param args
* command line arguments (will not be used)
*/
public static void main(String[] args) {
NestedDemo demo = new NestedDemo();
demo.nestingCreation();
demo.nestingCreationWithMethodReferences();
demo.nestedListenerCreation();
demo.nestedPropertyCreation();
demo.nestedPropertyCreationWithBuilder();
demo.nestedPropertyBinding();
demo.nestedPropertyBindingWithMissingInnerObservableAndDefaultBehavior();
demo.nestedPropertyBindingWithMissingInnerObservableAndCustomizedBehavior();
demo.additionalNestedFeatures();
}
//#end CONSTRUCTION & MAIN
// #begin DEMOS
/**
* Demonstrates how to create a {@link Nesting}.
*/
private void nestingCreation() {
print("NESTING CREATION");
/*
* A 'Nesting' is the basic building block of this API. Its Javadoc explains the terminology which is used in
* these demos as well as in the rest of the documentation.
*/
/*
* A 'Nesting'-instance is created in several steps, which are shown here. It can then be used to create other
* nested objects like nested properties or nested listeners. Very often the nesting itself is not needed and
* the goal is the creation of those other objects based in it. In those cases the builder methods for those
* objects (e.g. 'buildProperty') can and should be called directly. What is important in this demo method is
* that all possibilities before calling a builder method apply to all kinds of nested functionality like nested
* properties and nested listeners.
*/
// all created nestings wrap an observable which contains the current employee's street name (which is a String)
// create a 'Nesting<Property<String>>' by starting on the 'currentEmployee' property,
// nest to the employee's address and then to the address' street name;
Nesting<Property<String>> withObjectProperty = Nestings.on(currentEmployee)
.nestProperty(employee -> employee.addressProperty())
.nestProperty(address -> address.streetNameProperty())
.buildNesting();
print("The 'Nesting<Property<String>>' has the value: \"" + getValueFromNesting(withObjectProperty) + "\"");
// now, create a 'Nesting<StringProperty>' instead; note the second nesting step which is different from above
Nesting<StringProperty> withStringProperty = Nestings.on(currentEmployee)
.nestProperty(employee -> employee.addressProperty())
.nestStringProperty(address -> address.streetNameProperty())
.buildNesting();
print("The 'Nesting<StringProperty>' has the value: \"" + getValueFromNesting(withStringProperty) + "\"");
// calls to 'nestProperty' can be cut short; note the first nesting step which is different from above
Nesting<StringProperty> withShortcut = Nestings.on(currentEmployee)
.nest(employee -> employee.addressProperty())
.nestStringProperty(address -> address.streetNameProperty())
.buildNesting();
print("The 'Nesting<StringProperty>' (with shortcut) has the value: \""
+ getValueFromNesting(withShortcut) + "\"");
// if 'employee.addressProperty' were no property but an 'ObservableValue', a 'Nesting<ObservableValue<String>'
// could also be created; note the second nesting call which differs from those above
Nesting<ObservableValue<String>> withObservableValue = Nestings.on(currentEmployee)
.nestProperty(employee -> employee.addressProperty())
.nestObservableValue(address -> address.streetNameProperty())
.buildNesting();
print("The 'Nesting<ObservableValue<String>>' has the value: \""
+ getValueFromNesting(withObservableValue) + "\"");
// the same is true, if it were only an 'Observable'
Nesting<Observable> withObservable = Nestings.on(currentEmployee)
.nestProperty(employee -> employee.addressProperty())
.nestObservable(address -> address.streetNameProperty())
.buildNesting();
print("The 'Nesting<Observable>'s value can not be accessed, so let's call 'toString': \""
+ withObservable.innerObservableProperty().getValue().get().toString() + "\"");
print();
}
/**
* Demonstrates how to create a {@link Nesting}.
*/
private void nestingCreationWithMethodReferences() {
print("NESTING CREATION WITH METHOD REFERENCES");
/*
* The nesting steps take as input a function from an object in the nesting hierarchy to the property containing
* the next object. Instead of using lambda expressions to create those functions, method references are also
* possible. They might even be considered more expressive and are hence the recommended way to create nestings.
*/
Nesting<StringProperty> withShortcut = Nestings.on(currentEmployee)
.nest(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildNesting();
print("The 'Nesting<StringProperty>' (with method references) has the value: \""
+ getValueFromNesting(withShortcut) + "\"");
print();
}
/**
* Demonstrates how to create nested listener.
*/
private void nestedListenerCreation() {
print("LISTENER CREATION");
/*
* The listener creation is similar to the nesting creation (see above) and only differs in the final call to
* 'build...'. Note that a listener can only be added if the type of the Nesting's inner observable allows it.
* This means that a 'InvalidationListener' can always be added, but a 'ChangeListener' only to an
* 'ObservableValue'.
*/
// nest as above and then add a change listener
Nestings.on(currentEmployee)
.nestProperty(Employee::addressProperty)
.nestProperty(Address::streetNameProperty)
.addListener((observable, oldValue, newValue) -> {/* do something here */});
// an invalidation listener could even be added if 'employee.addressProperty' were only an observable
Nestings.on(currentEmployee)
.nestProperty(Employee::addressProperty)
.nestObservable(Address::streetNameProperty)
.addListener(observable -> {/* do something here */});
print();
}
/**
* Demonstrates how to create some nested properties.
*/
private void nestedPropertyCreation() {
print("PROPERTY CREATION");
/*
* The property creation is similar to the nesting creation (see above) and only differs in the final call to
* 'build...'. Note that a property can only be created if the type of the Nesting's inner observable is also a
* 'Property'. The reason for this is that only properties allow reading and writing their value.
*/
// nest as above but instead of creating a 'Nesting<Property<String>>', create a 'Property<String>'
Property<String> asObjectProperty = Nestings.on(currentEmployee)
.nestProperty(Employee::addressProperty)
.nestProperty(Address::streetNameProperty)
.buildProperty();
print("The nested 'Property<String>' has the value: \"" + asObjectProperty.getValue() + "\"");
// now, create a 'StringProperty instead'
StringProperty asStringProperty = Nestings.on(currentEmployee)
.nestProperty(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildProperty();
print("The nested 'StringProperty' has the value: \"" + asStringProperty.getValue() + "\"");
// 'buildProperty' actually returns a 'Nested...Property', which also implements the interface 'Nested'
// (its additional functionality is demonstrated further below)
NestedStringProperty asNestedStringProperty = Nestings.on(currentEmployee)
.nestProperty(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildProperty();
print("The 'NestedStringProperty' has the value: \"" + asNestedStringProperty.getValue() + "\"");
print();
}
/**
* Demonstrates how to create nested properties with builders.
*/
private void nestedPropertyCreationWithBuilder() {
print("CREATION WITH BUILDER");
// after nesting is done, the call to 'buildProperty' can be replaced by 'buildPropertyWithBuilder',
// which allows to set additional values for the created nested property
NestedStringProperty addressWithBeanAndName = Nestings.on(currentEmployee)
.nest(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildPropertyWithBuilder()
.setBean(this)
.setName("addressWithBean")
.build();
print("The 'NestedStringProperty' has bean class \""
+ addressWithBeanAndName.getBean().getClass().getSimpleName()
+ "\" and bean name \"" + addressWithBeanAndName.getName() + "\"");
print();
}
/**
* Demonstrates how the binding between the object hierarchy's inner property and the nested property works.
*/
private void nestedPropertyBinding() {
print("NESTED PROPERTY BINDING");
// create a nested property for the current employee's salary
NestedDoubleProperty currentEmployeesSalary = Nestings.on(currentEmployee)
.nestDoubleProperty(Employee::salaryProperty)
.buildProperty();
print("The object hierarchy's value is initially the same as the nested property's value:");
printSalaryValues(currentEmployee, currentEmployeesSalary);
// change the values
currentEmployee.getValue().salaryProperty().set(58000);
print("When the object hierarchy's value is changed, the nested property's value changes as well:");
printSalaryValues(currentEmployee, currentEmployeesSalary);
currentEmployeesSalary.set(62000);
print("When the nested property's value is changed, the object hierarchy's value changes as well:");
printSalaryValues(currentEmployee, currentEmployeesSalary);
// change the object hierarchy
print("\nNow change the object hierarchy so that the inner property is a different one.\n");
Employee oldEmployee = currentEmployee.getValue();
Employee newEmployee = new Employee(42000, "Another Street");
currentEmployee.setValue(newEmployee);
print("When the object hierarchy is changed, the nested property's value changes as well:");
printSalaryValues(currentEmployee, currentEmployeesSalary);
currentEmployee.getValue().salaryProperty().set(45000);
print("When the new object hierarchy's value is changed, the nested property's value changes as well:");
printSalaryValues(currentEmployee, currentEmployeesSalary);
print("But the old hierarchy - in this case the old employee's salary - is unchanged: \""
+ oldEmployee.salaryProperty().get() + "\"");
currentEmployeesSalary.set(48000);
print("Similarly, when the nested property's value is changed, the new object hierarchy's value changes as well:");
printSalaryValues(currentEmployee, currentEmployeesSalary);
print("Again, the old hierarchy - in this case the old employee's salary - is unchanged: \""
+ oldEmployee.salaryProperty().get() + "\"");
print();
}
/**
* Demonstrates how a {@link NestedProperty} behaves by default when the inner observable is missing.
*/
private void nestedPropertyBindingWithMissingInnerObservableAndDefaultBehavior() {
print("NESTED PROPERTY BINDING WHEN INNER OBSERVABLE IS MISSING (DEFAULT)");
currentEmployee.getValue().addressProperty().getValue().streetNameProperty().set("Some Street");
// create a nested property for the current employee's street name
NestedStringProperty currentEmployeesStreetName = Nestings.on(currentEmployee)
.nest(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildProperty();
print("Nested property's initial street name: \"" + currentEmployeesStreetName.get() + "\"");
currentEmployee.getValue().addressProperty().setValue(null);
print("The inner observable is now missing (is present: \""
+ currentEmployeesStreetName.isInnerObservablePresent() + "\")");
try {
currentEmployeesStreetName.set("Null Street");
print("You should never see this on the console.");
} catch (Exception ex) {
print("By default, the nested property can not be changed: " + ex.getClass());
// reset the example to a proper state
currentEmployee.getValue().addressProperty().setValue(new Employee.Address("Some Street"));
}
print();
}
/**
* Demonstrates how a {@link NestedProperty} can be configured to behave differently when the inner observable is
* missing.
*/
private void nestedPropertyBindingWithMissingInnerObservableAndCustomizedBehavior() {
print("NESTED PROPERTY BINDING WHEN INNER OBSERVABLE IS MISSING (CUSTOM)");
// create a nested property for the current employee's street name
NestedStringProperty currentEmployeesStreetName = Nestings.on(currentEmployee)
.nest(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildPropertyWithBuilder()
.onInnerObservableMissingSetValue("Null street")
.onUpdateWhenInnerObservableMissingAcceptValues()
.build();
print("Nested property's initial street name: \"" + currentEmployeesStreetName.get() + "\"");
currentEmployee.getValue().addressProperty().setValue(null);
print("The inner observable is now missing (is present: \""
+ currentEmployeesStreetName.isInnerObservablePresent() + "\")");
print("The street name changed to the specified value: \"" + currentEmployeesStreetName.get() + "\"");
currentEmployeesStreetName.set("Another Street");
print("The nested property can be changed: \"" + currentEmployeesStreetName.get() + "\"");
currentEmployee.getValue().addressProperty().setValue(new Employee.Address("New Street"));
print("When a new inner observable is present (\"" + currentEmployeesStreetName.isInnerObservablePresent()
+ "\"), the nested property holds its value: \"" + currentEmployeesStreetName.get() + "\"");
print();
}
/**
* Demonstrates the additional features of the interface {@link Nested}, which is implemented by all nested
* properties.
*/
private void additionalNestedFeatures() {
print("FEATURES OF THE INTERFACE 'NESTED'");
// create a nested property for the current employee's street name
NestedStringProperty currentEmployeesStreetName = Nestings.on(currentEmployee)
.nest(Employee::addressProperty)
.nestStringProperty(Address::streetNameProperty)
.buildProperty();
// the interface 'Nested' has a property which indicates whether the inner observable is present;
// one use would be to automatically disable a UI element which displays the property's value;
// in this case, a change listener is added which simply prints the new state
currentEmployeesStreetName.innerObservablePresentProperty().addListener(
(observable, oldValue, newValue)
-> print("\tInner observable present changed to \"" + newValue + "\"."));
print("Set the 'currentEmployee' to null, which means that no inner observable will be present.");
Employee notNullEmployee = currentEmployee.getValue();
currentEmployee.setValue(null);
print("Reset the old employee, which makes an inner observable present.");
currentEmployee.setValue(notNullEmployee);
print("Set a new address for the current employee, which will make *another* inner observable present.");
currentEmployee.getValue().addressProperty().setValue(new Employee.Address("A new street."));
print("Set the current employee's address to null, which means that no inner observable will be present.");
currentEmployee.getValue().addressProperty().setValue(null);
print("Set a new address with a null street name for the current employee, "
+ "which will make an inner observable available. "
+ "That its value will be null does not matter as it is still present.");
currentEmployee.getValue().addressProperty().setValue(new Employee.Address(null));
print();
}
//#end DEMOS
/**
* Prints an empty line to the console.
*/
private static void print() {
System.out.println();
}
/**
* Prints the specified text to the console.
*
* @param text
* the text to print
*/
private static void print(String text) {
System.out.println(text);
}
/**
* Returns the value held by the specified nesting's inner observable.
*
* @param <T>
* the type of value contained in the observable
* @param nesting
* the {@link Nesting} whose value will be returned
* @return 'nesting.innerObservableProperty().getValue().get().getValue()'
*/
private static <T> T getValueFromNesting(Nesting<? extends ObservableValue<T>> nesting) {
return nesting.innerObservableProperty().getValue().get().getValue();
}
/**
* Outputs the salary of both specified properties.
*
* @param currentEmployee
* the property holding the current employee; the printed value is accessed by moving through the object
* hierarchy
* @param currentEmployeesSalary
* the nested property holding the current employee's salary; the printed value is accessed by simply
* getting it
*/
private static void printSalaryValues(Property<Employee> currentEmployee, DoubleProperty currentEmployeesSalary) {
String salaries = "\tSalaries: ";
salaries += "\"" + currentEmployee.getValue().salaryProperty().get() + "\" (object hierarchy) ";
salaries += "\"" + currentEmployeesSalary.get() + "\" (nested property) ";
print(salaries);
}
}