
org.codefx.libfx.nesting.property.NestedPropertyInternals Maven / Gradle / Ivy
Show all versions of LibFX Show documentation
package org.codefx.libfx.nesting.property;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.codefx.libfx.nesting.Nesting;
import org.codefx.libfx.nesting.property.InnerObservableMissingBehavior.WhenInnerObservableGoesMissing;
import org.codefx.libfx.nesting.property.InnerObservableMissingBehavior.WhenInnerObservableMissingOnUpdate;
/**
* Contains the internal code of nested properties.
*
* This class exists to prevent having the same code in all property implementations for the different types. Instead
* they all delegate their calls to this class. That made it necessary to jump through some hoops and the design is not
* very elegant.
*
* @param
* the type of the value wrapped by the property
*/
final class NestedPropertyInternals {
private final Nesting extends Property> nesting;
private final NestedProperty outerProperty;
private final Consumer super T> setValueDirectly;
private final InnerObservableMissingBehavior extends T> missingBehavior;
private final BooleanProperty innerObservablePresent;
/**
* Creates a new internals.
*
* @param outerProperty
* the property whose internals are managed here
* @param nesting
* the nesting on which the outer nested property is based
* @param missingBehavior
* the behavior for the case that the inner observable is missing
* @param setValueDirectly
* called to immediately set the value on the outer property; must set the default value if called with
* null on a property which wraps a primitive type
*/
public NestedPropertyInternals(
NestedProperty outerProperty,
Nesting extends Property> nesting,
InnerObservableMissingBehavior extends T> missingBehavior,
Consumer super T> setValueDirectly) {
assert nesting != null : "The argument 'nesting' must not be null.";
assert outerProperty != null : "The argument 'outerProperty' must not be null.";
assert setValueDirectly != null : "The argument 'setValueDirectly' must not be null.";
assert missingBehavior != null : "The argument 'missingBehavior' must not be null.";
assert missingBehavior.whenGoesMissing() != WhenInnerObservableGoesMissing.SET_VALUE_FROM_SUPPLIER
|| missingBehavior.valueForMissing().isPresent() //
: "When 'missingBehavior' requests 'SET_VALUE_FROM_SUPPLIER', a supplier must be present.";
this.nesting = nesting;
this.outerProperty = outerProperty;
this.setValueDirectly = setValueDirectly;
this.innerObservablePresent = new SimpleBooleanProperty(outerProperty, "innerObservablePresent");
this.missingBehavior = missingBehavior;
}
/**
* Initializes the binding of the nested property to the nesting's inner observable.
*/
public void initializeBinding() {
bindToInnerObservable(nesting.innerObservableProperty().getValue());
nesting.innerObservableProperty().addListener(
(obs, oldInnerObservable, newInnerObservable)
-> moveBindingToNewInnerObservable(oldInnerObservable, newInnerObservable));
}
// #begin BIND TO INNER OBSERVABLE
private void moveBindingToNewInnerObservable(
Optional extends Property> oldInnerObservable, Optional extends Property> newInnerObservable) {
unbindFromInnerObservable(oldInnerObservable);
bindToInnerObservable(newInnerObservable);
}
private void unbindFromInnerObservable(Optional extends Property> innerObservable) {
innerObservable.ifPresent(outerProperty::unbindBidirectional);
}
private void bindToInnerObservable(Optional extends Property> innerObservable) {
innerObservablePresent.set(innerObservable.isPresent());
if (!innerObservable.isPresent())
handleMissingInnerObservable();
innerObservable.ifPresent(outerProperty::bindBidirectional);
}
private void handleMissingInnerObservable() {
WhenInnerObservableGoesMissing whenGoesMissing = missingBehavior.whenGoesMissing();
switch (whenGoesMissing) {
case KEEP_VALUE:
return;
case SET_DEFAULT_VALUE:
setDefaultValueIgnoringMissingInnerObservable();
return;
case SET_VALUE_FROM_SUPPLIER:
Supplier extends T> supplierForMissingValue = missingBehavior.valueForMissing().get();
setIgnoringMissingInnerObservable(supplierForMissingValue.get());
return;
default:
throw new IllegalArgumentException("Unknown procedere for missing inner observable: " + whenGoesMissing);
}
}
// #end BIND TO INNER OBSERVABLE
// #begin SET VALUE
private void set(T newValue, boolean checkMissingInnerObservable) {
if (checkMissingInnerObservable)
maybeThrowExceptionForMissingObservable();
setValueDirectly.accept(newValue);
}
private void maybeThrowExceptionForMissingObservable() {
boolean innerObservableMissing = !innerObservablePresent.get();
boolean throwExceptionConfigured =
missingBehavior.onUpdate() == WhenInnerObservableMissingOnUpdate.THROW_EXCEPTION;
if (innerObservableMissing && throwExceptionConfigured)
throw new IllegalStateException("The inner observable is missing so no value can be set.");
}
/**
* Sets the specified value on the nested property specified during construction.
*
* If the inner observable is missing, the behavior specified during construction is executed.
*
* @param newValue
* the new value to set
*/
public void setCheckingMissingInnerObservable(T newValue) {
set(newValue, true);
}
private void setIgnoringMissingInnerObservable(T newValue) {
set(newValue, false);
}
private void setDefaultValueIgnoringMissingInnerObservable() {
set(null, false);
}
// #end SET VALUE
/**
* @return whether the nesting's inner observable is present as a property
*/
public final ReadOnlyBooleanProperty innerObservablePresentProperty() {
return innerObservablePresent;
}
}