Skip to content

Commit e46cc83

Browse files
author
nicolaiparlog
committed
Enable creation of nestings on a broken observable hierarchy
As pointed out by #9, creating a 'DeepNesting' on an outer observable which contains null does not work as intended. Its 'innerObservableProperty' will contain null instead of an empty 'Oprional'. The same faulty behavior was found when one of the nested observables contains null. The problem was that the nesting's initialization could not distinguish between uninitialized values (which were represented as null) and null values contained in those observable. This lead to the initialization following an incorrect (and ultimately incomplete) computational path. The fix changes the way the value array is created. Instead of containing null for uninitialized values, it contains arbitrary instances which are not null an equal no instances which appears "in the wild".
1 parent 998aefa commit e46cc83

File tree

4 files changed

+93
-3
lines changed

4 files changed

+93
-3
lines changed

src/main/java/org/codefx/libfx/nesting/DeepNesting.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ final class DeepNesting<O extends Observable> implements Nesting<O> {
7474

7575
/**
7676
* The values currently held by the observables.
77+
* <p>
78+
* Before the initialization these will be non-null "uninitialized" objects. This is done to distinguish them from
79+
* null values which could be held by the observables.
7780
*/
7881
private final Object[] values;
7982

@@ -120,14 +123,25 @@ public DeepNesting(ObservableValue outerObservable, List<NestingStep> nestingSte
120123
maxLevel = nestingSteps.size();
121124

122125
this.observables = createObservables(outerObservable, maxLevel);
123-
this.values = new Object[maxLevel];
126+
this.values = createUnitializedValues();
124127
this.nestingSteps = nestingSteps.toArray(new NestingStep[maxLevel]);
125128
this.changeListeners = createChangeListeners(maxLevel);
126129
this.inner = new SimpleObjectProperty<>(this, "inner");
127130

128131
initializeNesting();
129132
}
130133

134+
/**
135+
* @return an array of uninitialized values (i.e. non-null values which do not equal any other instances occurring
136+
* "in the wild")
137+
*/
138+
private Object[] createUnitializedValues() {
139+
Object[] values = new Object[maxLevel];
140+
for (int i = 0; i < maxLevel; i++)
141+
values[i] = new Unitialized();
142+
return values;
143+
}
144+
131145
/**
132146
* Creates an initialized array of observables. Its first item is the specified outer observable (its other items
133147
* are null).
@@ -234,8 +248,8 @@ public void initialize() {
234248
* identical and nothing more needs to be updated.
235249
* <p>
236250
* Note that the loop will not stop on null observables and null values. Instead it continues and replaces all
237-
* stored observables and values with null. This is the desired behavior as the hierarchy is in now an incomplete
238-
* state and the old observables and values are obsolete and have to be replaced.
251+
* stored observables and values with null. This is the desired behavior as the hierarchy is now in an incomplete
252+
* state where the old observables and values are obsolete and have to be replaced.
239253
*/
240254
private class NestingUpdater {
241255

@@ -394,6 +408,13 @@ private void updateInnerObservable() {
394408

395409
}
396410

411+
/**
412+
* Represents an uninitialized entry in the {@link #values} array.
413+
*/
414+
private static class Unitialized {
415+
// no body needed
416+
}
417+
397418
//#end PRIVATE CLASSES
398419

399420
}

src/test/java/org/codefx/libfx/nesting/AbstractDeepNestingTest.java

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

33
import static org.codefx.libfx.nesting.testhelper.NestingAccess.getNestingObservable;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertNotNull;
46
import static org.junit.Assert.assertNull;
57
import static org.junit.Assert.assertSame;
68
import javafx.beans.Observable;
@@ -22,6 +24,37 @@ public abstract class AbstractDeepNestingTest<OO extends Observable, IO extends
2224

2325
// #region TESTS
2426

27+
// construction
28+
29+
/**
30+
* Tests whether creating a {@link DeepNesting} on an outer observable which contains null works correctly.
31+
* <p>
32+
* This ensures that a "broken" hierarchy is correctly initialized.
33+
*/
34+
@Test
35+
public void testCreatingWhenOuterObservableHasValueNull() {
36+
outerObservable = createNewNestingHierarchyWhereOuterObservableHasNullValue();
37+
nesting = createNewNestingFromOuterObservable(outerObservable);
38+
39+
assertNotNull(nesting.innerObservableProperty().getValue());
40+
assertFalse(nesting.innerObservableProperty().getValue().isPresent());
41+
}
42+
43+
/**
44+
* Tests whether creating a {@link DeepNesting} on a hierarchy where on of the nested observables contains null
45+
* works correctly.
46+
* <p>
47+
* This ensures that a "broken" hierarchy is correctly initialized.
48+
*/
49+
@Test
50+
public void testCreatingWhenNestedObservableHasValueNull() {
51+
outerObservable = createNewNestingHierarchyWhereNestedObservableHasNullValue();
52+
nesting = createNewNestingFromOuterObservable(outerObservable);
53+
54+
assertNotNull(nesting.innerObservableProperty().getValue());
55+
assertFalse(nesting.innerObservableProperty().getValue().isPresent());
56+
}
57+
2558
// nested value
2659

2760
/**
@@ -90,6 +123,21 @@ public void testWhenSettingOuterValueToNull() {
90123

91124
// #region ABSTRACT METHODS
92125

126+
/**
127+
* Creates a new outer observable with a null value. The returned instances must be new for each call.
128+
*
129+
* @return an {@link ObservableValue} containing null
130+
*/
131+
protected abstract OO createNewNestingHierarchyWhereOuterObservableHasNullValue();
132+
133+
/**
134+
* Creates a new nesting hierarchy where one of the nested observables contains null and returns the outer
135+
* observable. All returned instances must be new for each call.
136+
*
137+
* @return an {@link ObservableValue} containing the outer value of a nesting hierarchy
138+
*/
139+
protected abstract OO createNewNestingHierarchyWhereNestedObservableHasNullValue();
140+
93141
/**
94142
* Sets a new value of the specified kind on the specified level of the nesting hierarchy contained in the specified
95143
* outer observable.

src/test/java/org/codefx/libfx/nesting/AbstractDeepNestingTestForDefaultNesting.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ protected Property<OuterValue> createNewNestingHierarchy() {
2828
return new SimpleObjectProperty<>(outer);
2929
}
3030

31+
@Override
32+
protected Property<OuterValue> createNewNestingHierarchyWhereOuterObservableHasNullValue() {
33+
return new SimpleObjectProperty<>(null);
34+
}
35+
36+
@Override
37+
protected Property<OuterValue> createNewNestingHierarchyWhereNestedObservableHasNullValue() {
38+
OuterValue outer = OuterValue.createWithNull();
39+
return new SimpleObjectProperty<>(outer);
40+
}
41+
3142
@Override
3243
protected void setNewValue(Property<OuterValue> outerObservable, Level level, Value kindOfNewValue) {
3344

src/test/java/org/codefx/libfx/nesting/AbstractNestingTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ public void setUp() {
4444

4545
// #region TESTS
4646

47+
// construction
48+
49+
/**
50+
* Tests whether creating a nesting with on a null outer observable throws an exception.
51+
*/
52+
@Test(expected = NullPointerException.class)
53+
public void testExceptionWhenNullObservable() {
54+
nesting = createNewNestingFromOuterObservable(null);
55+
}
56+
4757
/**
4858
* Tests whether the {@link #nesting}'s {@link Nesting#innerObservableProperty() innerObservable} property contains
4959
* the correct observable, which is the {@link #outerObservable}'s inner observable.

0 commit comments

Comments
 (0)