Skip to content

Commit 7d44a67

Browse files
author
nicolaiparlog
committed
Enable to specify how nested properties behave when the inner observable is missing (issue #3)
Nested property builders now allow the user to specify a behavior for what happens when (a) the inner observable goes missing (b) the property is updated while the inner observable is missing
1 parent 903bccf commit 7d44a67

File tree

45 files changed

+1221
-727
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1221
-727
lines changed

src/demo/java/org/codefx/libfx/nesting/NestedDemo.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ private void nestedPropertyBinding() {
284284
* Demonstrates how a {@link NestedProperty} behaves when the inner observable is missing.
285285
*/
286286
private void nestedPropertyBindingWithMissingInnerObservable() {
287+
288+
/*
289+
* TODO update this example and include new ones
290+
*/
291+
287292
print("NESTED PROPERTY BINDING WHEN INNER OBSERVABLE IS MISSING");
288293

289294
// create a nested property for the current employee's street name
Lines changed: 211 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package org.codefx.libfx.nesting.property;
22

33
import java.util.Objects;
4+
import java.util.Optional;
5+
import java.util.function.Supplier;
46

57
import javafx.beans.property.Property;
68

79
import org.codefx.libfx.nesting.Nesting;
10+
import org.codefx.libfx.nesting.property.InnerObservableMissingBehavior.WhenInnerObservableGoesMissing;
11+
import org.codefx.libfx.nesting.property.InnerObservableMissingBehavior.WhenInnerObservableMissingOnUpdate;
812

913
/**
1014
* Abstract superclass to nested property builders. Collects common builder settings; e.g. for the new property's
1115
* {@link Property#getBean() bean} and {@link Property#getName() name}.
1216
*
17+
* @param <T>
18+
* the most concrete type of the value wrapped by the property which will be built
1319
* @param <O>
1420
* the type of the nesting hierarchy's inner observable (which is a {@link Property})
1521
* @param <P>
1622
* the type of {@link Property} which will be built
23+
* @param <B>
24+
* the most concrete type of this builder (used for fluent API)
1725
*/
18-
abstract class AbstractNestedPropertyBuilder<O extends Property<?>, P extends NestedProperty<?>> {
26+
abstract class AbstractNestedPropertyBuilder<T, O extends Property<?>, P extends NestedProperty<?>, B extends AbstractNestedPropertyBuilder<T, O, P, B>> {
1927

2028
// #begin PROPERTIES
2129

@@ -24,6 +32,11 @@ abstract class AbstractNestedPropertyBuilder<O extends Property<?>, P extends Ne
2432
*/
2533
private final Nesting<O> nesting;
2634

35+
/**
36+
* The behavior for the case that the inner observable is missing.
37+
*/
38+
private final MutableInnerObservableMissingBehavior<T> innerObservableMissingBehavior;
39+
2740
/**
2841
* The property's future {@link Property#getBean() bean}.
2942
*/
@@ -47,6 +60,7 @@ abstract class AbstractNestedPropertyBuilder<O extends Property<?>, P extends Ne
4760
protected AbstractNestedPropertyBuilder(Nesting<O> nesting) {
4861
Objects.requireNonNull(nesting, "The argument 'nesting' must not be null.");
4962
this.nesting = nesting;
63+
this.innerObservableMissingBehavior = new MutableInnerObservableMissingBehavior<>();
5064
}
5165

5266
//#end CONSTRUCTOR
@@ -63,76 +77,236 @@ protected AbstractNestedPropertyBuilder(Nesting<O> nesting) {
6377

6478
//#end ABSTRACT METHODS
6579

66-
// #begin ACCESSORS
80+
// #begin MUTATORS
6781

6882
/**
69-
* @return the nesting which will be used for all nested properties
83+
* Sets the property's {@link Property#getBean() bean}.
84+
*
85+
* @param bean
86+
* the property's future bean
87+
* @return this builder
7088
*/
71-
protected final Nesting<O> getNesting() {
72-
return nesting;
89+
public final B setBean(Object bean) {
90+
Objects.requireNonNull(bean, "The argument 'bean' must not be null.");
91+
this.bean = bean;
92+
return thisAsB();
7393
}
7494

7595
/**
76-
* @return the property's future {@link Property#getBean() bean}.
96+
* Sets the property's {@link Property#getName() name}.
97+
*
98+
* @param name
99+
* the property's future name
100+
* @return this builder
77101
*/
78-
protected final Object getBean() {
79-
return bean;
102+
public B setName(String name) {
103+
Objects.requireNonNull(name, "The argument 'name' must not be null.");
104+
this.name = name;
105+
return thisAsB();
80106
}
81107

82108
/**
83-
* Sets the property's future {@link Property#getBean() bean}.
109+
* The property will keep its value when the inner observable goes missing (see {@link NestedProperty} for details
110+
* on this).
111+
* <p>
112+
* This is the default behavior.
84113
*
85-
* @param bean
86-
* the property's future bean
114+
* @return this builder
87115
*/
88-
protected final void setTheBean(Object bean) {
89-
Objects.requireNonNull(bean, "The argument 'bean' must not be null.");
90-
this.bean = bean;
116+
public B onInnerObservableMissingKeepValue() {
117+
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.KEEP_VALUE);
118+
return thisAsB();
91119
}
92120

93121
/**
94-
* Sets the property's future {@link Property#getBean() bean}.
122+
* The property will change to the default value for the wrapped type when the inner observable goes missing (see
123+
* {@link NestedProperty} for details on this).
124+
* <p>
125+
* For primitive wrapping properties (e.g. {@link NestedIntegerProperty}), this will set the primitive default (e.g.
126+
* 0); for reference wrapping properties this will be null.
95127
*
96-
* @param bean
97-
* the property's future bean
98128
* @return this builder
99129
*/
100-
public AbstractNestedPropertyBuilder<O, P> setBean(Object bean) {
101-
Objects.requireNonNull(bean, "The argument 'bean' must not be null.");
102-
this.bean = bean;
103-
return this;
130+
public B onInnerObservableMissingSetDefaultValue() {
131+
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.SET_DEFAULT_VALUE);
132+
return thisAsB();
104133
}
105134

106135
/**
107-
* @return the property's future {@link Property#getBean() bean}.
136+
* The property will change to the specified value when the inner observable goes missing (see
137+
* {@link NestedProperty} for details on this).
138+
* <p>
139+
* This method does not accept null as a value. Call {@link #onInnerObservableMissingSetDefaultValue()} if the
140+
* property should change to the default value for the wrapped type (e.g. 0 for {@link NestedIntegerProperty}).
141+
*
142+
* @param value
143+
* the value to set
144+
* @return this builder
108145
*/
109-
protected final String getName() {
110-
return name;
146+
public B onInnerObservableMissingSetValue(T value) {
147+
Objects.requireNonNull(value, "The argument 'value' must not be null.");
148+
149+
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.SET_VALUE_FROM_SUPPLIER);
150+
innerObservableMissingBehavior.valueForMissing(() -> value);
151+
return thisAsB();
111152
}
112153

113154
/**
114-
* Sets the property's future {@link Property#getName() name}.
155+
* The property will change to the value computed by the specified supplier when the inner observable goes missing
156+
* (see {@link NestedProperty} for details on this).
157+
* <p>
158+
* The supplier may produce null in which case primitive wrapping properties will fall back to the type's default
159+
* value (e.g. 0 for {@link NestedIntegerProperty}).
115160
*
116-
* @param name
117-
* the property's future name
161+
* @param valueSupplier
162+
* the supplier which computes the value to set; may produce null
163+
* @return this builder
118164
*/
119-
protected final void setTheName(String name) {
120-
Objects.requireNonNull(name, "The argument 'name' must not be null.");
121-
this.name = name;
165+
public B onInnerObservableMissingComputeValue(Supplier<T> valueSupplier) {
166+
Objects.requireNonNull(valueSupplier, "The argument 'valueSupplier' must not be null.");
167+
168+
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.SET_VALUE_FROM_SUPPLIER);
169+
innerObservableMissingBehavior.valueForMissing(valueSupplier);
170+
return thisAsB();
122171
}
123172

124173
/**
125-
* Sets the property's future {@link Property#getName() name}.
174+
* The property will throw an {@link IllegalStateException} when it is updated (e.g. by calling
175+
* {@link Property#setValue(Object) setValue} or via a binding) while the inner observable is missing (see
176+
* {@link NestedProperty} for details on this).
177+
* <p>
178+
* This is the default behavior.
179+
*
180+
* @return this builder
181+
*/
182+
public B onUpdateWhenInnerObservableMissingThrowException() {
183+
innerObservableMissingBehavior.onUpdate(WhenInnerObservableMissingOnUpdate.THROW_EXCEPTION);
184+
return thisAsB();
185+
}
186+
187+
/**
188+
* The property will accept new values when it is updated (e.g. by calling {@link Property#setValue(Object)
189+
* setValue} or via a binding) while the inner observable is missing (see {@link NestedProperty} for details on
190+
* this).
191+
* <p>
192+
* Once the nesting changes to a new (non-missing) inner observable, the property will change to that observable's
193+
* value.
126194
*
127-
* @param name
128-
* the property's future name
129195
* @return this builder
130196
*/
131-
public AbstractNestedPropertyBuilder<O, P> setName(String name) {
132-
setTheName(name);
133-
return this;
197+
public B onUpdateWhenInnerObservableMissingAcceptValues() {
198+
innerObservableMissingBehavior
199+
.onUpdate(WhenInnerObservableMissingOnUpdate.ACCEPT_VALUE_UNTIL_NEXT_INNER_OBSERVABLE);
200+
return thisAsB();
201+
}
202+
203+
/**
204+
* Performs an unchecked cast to {@code B} which
205+
*
206+
* @return this builder as an instance of {@code B}
207+
*/
208+
@SuppressWarnings("unchecked")
209+
private B thisAsB() {
210+
B thisAsB = (B) this;
211+
return thisAsB;
212+
}
213+
214+
// #end MUTATORS
215+
216+
// #begin ACCESSORS FOR SUBCLASSES
217+
218+
/**
219+
* @return the nesting which will be used for all nested properties
220+
*/
221+
protected final Nesting<O> getNesting() {
222+
return nesting;
223+
}
224+
225+
/**
226+
* @return the property's {@link Property#getBean() bean}.
227+
*/
228+
protected final Object getBean() {
229+
return bean;
230+
}
231+
232+
/**
233+
* @return the property's {@link Property#getBean() bean}.
234+
*/
235+
protected final String getName() {
236+
return name;
237+
}
238+
239+
/**
240+
* @return the property's behavior for the case that the inner observable is missing
241+
*/
242+
protected final InnerObservableMissingBehavior<T> getInnerObservableMissingBehavior() {
243+
return new ImmutableInnerObservableMissingBehavior<>(innerObservableMissingBehavior);
244+
}
245+
246+
//#end ACCESSORS FOR SUBCLASSES
247+
248+
// #begin NESTED CLASSES
249+
250+
private static class MutableInnerObservableMissingBehavior<T> {
251+
252+
private static final WhenInnerObservableGoesMissing DEFAULT_WHEN_GOES_MISSING = WhenInnerObservableGoesMissing.KEEP_VALUE;
253+
private static final WhenInnerObservableMissingOnUpdate DEFAULT_ON_UPDATE = WhenInnerObservableMissingOnUpdate.THROW_EXCEPTION;
254+
255+
private WhenInnerObservableGoesMissing whenGoesMissing;
256+
private Optional<? extends Supplier<T>> valueForMissing;
257+
private WhenInnerObservableMissingOnUpdate onUpdate;
258+
259+
public MutableInnerObservableMissingBehavior() {
260+
this.whenGoesMissing = DEFAULT_WHEN_GOES_MISSING;
261+
this.valueForMissing = Optional.empty();
262+
this.onUpdate = DEFAULT_ON_UPDATE;
263+
}
264+
265+
public void whenGoesMissing(WhenInnerObservableGoesMissing whenGoesMissing) {
266+
assert whenGoesMissing != null : "The argument 'whenGoesMissing' must not be null.";
267+
this.whenGoesMissing = whenGoesMissing;
268+
}
269+
270+
public void valueForMissing(Supplier<T> valueForMissing) {
271+
this.valueForMissing = Optional.of(valueForMissing);
272+
}
273+
274+
public void onUpdate(WhenInnerObservableMissingOnUpdate onUpdate) {
275+
assert onUpdate != null : "The argument 'onUpdate' must not be null.";
276+
this.onUpdate = onUpdate;
277+
}
278+
279+
}
280+
281+
private static class ImmutableInnerObservableMissingBehavior<T> implements InnerObservableMissingBehavior<T> {
282+
283+
private final WhenInnerObservableGoesMissing whenGoesMissing;
284+
private final Optional<? extends Supplier<T>> valueForMissing;
285+
private final WhenInnerObservableMissingOnUpdate onUpdate;
286+
287+
public ImmutableInnerObservableMissingBehavior(MutableInnerObservableMissingBehavior<T> behavior) {
288+
this.whenGoesMissing = behavior.whenGoesMissing;
289+
this.valueForMissing = behavior.valueForMissing;
290+
this.onUpdate = behavior.onUpdate;
291+
}
292+
293+
@Override
294+
public WhenInnerObservableGoesMissing whenGoesMissing() {
295+
return whenGoesMissing;
296+
}
297+
298+
@Override
299+
public Optional<? extends Supplier<T>> valueForMissing() {
300+
return valueForMissing;
301+
}
302+
303+
@Override
304+
public WhenInnerObservableMissingOnUpdate onUpdate() {
305+
return onUpdate;
306+
}
307+
134308
}
135309

136-
//#end ACCESSORS
310+
// #end NESTED CLASSES
137311

138312
}

0 commit comments

Comments
 (0)