package org.codefx.libfx.nesting.property;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import javafx.beans.property.Property;
import org.codefx.libfx.nesting.Nesting;
import org.codefx.libfx.nesting.property.InnerObservableMissingBehavior.WhenInnerObservableGoesMissing;
import org.codefx.libfx.nesting.property.InnerObservableMissingBehavior.WhenInnerObservableMissingOnUpdate;
/**
* Abstract superclass to nested property builders. Collects common builder settings; e.g. for the new property's
* {@link Property#getBean() bean} and {@link Property#getName() name}.
*
* @param <T>
* the most concrete type of the value wrapped by the property which will be built
* @param <O>
* the type of the nesting hierarchy's inner observable (which is a {@link Property})
* @param <P>
* the type of {@link Property} which will be built
* @param <B>
* the most concrete type of this builder (used for fluent API)
*/
abstract class AbstractNestedPropertyBuilder<T, O extends Property<?>, P extends NestedProperty<?>, B extends AbstractNestedPropertyBuilder<T, O, P, B>> {
// #begin PROPERTIES
/**
* The nesting which will be used for all nested properties.
*/
private final Nesting<O> nesting;
/**
* The behavior for the case that the inner observable is missing.
*/
private final MutableInnerObservableMissingBehavior<T> innerObservableMissingBehavior;
/**
* The property's future {@link Property#getBean() bean}.
*/
private Object bean;
/**
* The property's future {@link Property#getName() name}.
*/
private String name;
//#end PROPERTIES
// #begin CONSTRUCTOR
/**
* Creates a new abstract builder which uses the specified nesting.
*
* @param nesting
* the nesting which will be used for all nested properties
*/
protected AbstractNestedPropertyBuilder(Nesting<O> nesting) {
Objects.requireNonNull(nesting, "The argument 'nesting' must not be null.");
this.nesting = nesting;
this.innerObservableMissingBehavior = new MutableInnerObservableMissingBehavior<>();
}
//#end CONSTRUCTOR
// #begin ABSTRACT METHODS
/**
* Creates a new property instance. This method can be called arbitrarily often and each call returns a new
* instance.
*
* @return the new instance of {@code P}, i.e. an implementation of {@link NestedProperty}
*/
public abstract P build();
//#end ABSTRACT METHODS
// #begin MUTATORS
/**
* Sets the property's {@link Property#getBean() bean}.
*
* @param bean
* the property's future bean
* @return this builder
*/
public final B setBean(Object bean) {
Objects.requireNonNull(bean, "The argument 'bean' must not be null.");
this.bean = bean;
return thisAsB();
}
/**
* Sets the property's {@link Property#getName() name}.
*
* @param name
* the property's future name
* @return this builder
*/
public B setName(String name) {
Objects.requireNonNull(name, "The argument 'name' must not be null.");
this.name = name;
return thisAsB();
}
/**
* The property will keep its value when the inner observable goes missing (see {@link NestedProperty} for details
* on this).
* <p>
* This is the default behavior.
*
* @return this builder
*/
public B onInnerObservableMissingKeepValue() {
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.KEEP_VALUE);
return thisAsB();
}
/**
* The property will change to the default value for the wrapped type when the inner observable goes missing (see
* {@link NestedProperty} for details on this).
* <p>
* For primitive wrapping properties (e.g. {@link NestedIntegerProperty}), this will set the primitive default (e.g.
* 0); for reference wrapping properties this will be null.
*
* @return this builder
*/
public B onInnerObservableMissingSetDefaultValue() {
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.SET_DEFAULT_VALUE);
return thisAsB();
}
/**
* The property will change to the specified value when the inner observable goes missing (see
* {@link NestedProperty} for details on this).
* <p>
* This method does not accept null as a value. Call {@link #onInnerObservableMissingSetDefaultValue()} if the
* property should change to the default value for the wrapped type (e.g. 0 for {@link NestedIntegerProperty}).
*
* @param value
* the value to set
* @return this builder
*/
public B onInnerObservableMissingSetValue(T value) {
Objects.requireNonNull(value, "The argument 'value' must not be null.");
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.SET_VALUE_FROM_SUPPLIER);
innerObservableMissingBehavior.valueForMissing(() -> value);
return thisAsB();
}
/**
* The property will change to the value computed by the specified supplier when the inner observable goes missing
* (see {@link NestedProperty} for details on this).
* <p>
* The supplier may produce null in which case primitive wrapping properties will fall back to the type's default
* value (e.g. 0 for {@link NestedIntegerProperty}).
*
* @param valueSupplier
* the supplier which computes the value to set; may produce null
* @return this builder
*/
public B onInnerObservableMissingComputeValue(Supplier<T> valueSupplier) {
Objects.requireNonNull(valueSupplier, "The argument 'valueSupplier' must not be null.");
innerObservableMissingBehavior.whenGoesMissing(WhenInnerObservableGoesMissing.SET_VALUE_FROM_SUPPLIER);
innerObservableMissingBehavior.valueForMissing(valueSupplier);
return thisAsB();
}
/**
* The property will throw an {@link IllegalStateException} when it is updated (e.g. by calling
* {@link Property#setValue(Object) setValue} or via a binding) while the inner observable is missing (see
* {@link NestedProperty} for details on this).
* <p>
* This is the default behavior.
*
* @return this builder
*/
public B onUpdateWhenInnerObservableMissingThrowException() {
innerObservableMissingBehavior.onUpdate(WhenInnerObservableMissingOnUpdate.THROW_EXCEPTION);
return thisAsB();
}
/**
* The property will accept new values when it is updated (e.g. by calling {@link Property#setValue(Object)
* setValue} or via a binding) while the inner observable is missing (see {@link NestedProperty} for details on
* this).
* <p>
* Once the nesting changes to a new (non-missing) inner observable, the property will change to that observable's
* value.
*
* @return this builder
*/
public B onUpdateWhenInnerObservableMissingAcceptValues() {
innerObservableMissingBehavior
.onUpdate(WhenInnerObservableMissingOnUpdate.ACCEPT_VALUE_UNTIL_NEXT_INNER_OBSERVABLE);
return thisAsB();
}
/**
* Performs an unchecked cast to {@code B} which
*
* @return this builder as an instance of {@code B}
*/
@SuppressWarnings("unchecked")
private B thisAsB() {
B thisAsB = (B) this;
return thisAsB;
}
// #end MUTATORS
// #begin ACCESSORS FOR SUBCLASSES
/**
* @return the nesting which will be used for all nested properties
*/
protected final Nesting<O> getNesting() {
return nesting;
}
/**
* @return the property's {@link Property#getBean() bean}.
*/
protected final Object getBean() {
return bean;
}
/**
* @return the property's {@link Property#getBean() bean}.
*/
protected final String getName() {
return name;
}
/**
* @return the property's behavior for the case that the inner observable is missing
*/
protected final InnerObservableMissingBehavior<T> getInnerObservableMissingBehavior() {
return new ImmutableInnerObservableMissingBehavior<>(innerObservableMissingBehavior);
}
//#end ACCESSORS FOR SUBCLASSES
// #begin NESTED CLASSES
private static class MutableInnerObservableMissingBehavior<T> {
private static final WhenInnerObservableGoesMissing DEFAULT_WHEN_GOES_MISSING = WhenInnerObservableGoesMissing.KEEP_VALUE;
private static final WhenInnerObservableMissingOnUpdate DEFAULT_ON_UPDATE = WhenInnerObservableMissingOnUpdate.THROW_EXCEPTION;
private WhenInnerObservableGoesMissing whenGoesMissing;
private Optional<? extends Supplier<T>> valueForMissing;
private WhenInnerObservableMissingOnUpdate onUpdate;
public MutableInnerObservableMissingBehavior() {
this.whenGoesMissing = DEFAULT_WHEN_GOES_MISSING;
this.valueForMissing = Optional.empty();
this.onUpdate = DEFAULT_ON_UPDATE;
}
public void whenGoesMissing(WhenInnerObservableGoesMissing whenGoesMissing) {
assert whenGoesMissing != null : "The argument 'whenGoesMissing' must not be null.";
this.whenGoesMissing = whenGoesMissing;
}
public void valueForMissing(Supplier<T> valueForMissing) {
this.valueForMissing = Optional.of(valueForMissing);
}
public void onUpdate(WhenInnerObservableMissingOnUpdate onUpdate) {
assert onUpdate != null : "The argument 'onUpdate' must not be null.";
this.onUpdate = onUpdate;
}
}
private static class ImmutableInnerObservableMissingBehavior<T> implements InnerObservableMissingBehavior<T> {
private final WhenInnerObservableGoesMissing whenGoesMissing;
private final Optional<? extends Supplier<T>> valueForMissing;
private final WhenInnerObservableMissingOnUpdate onUpdate;
public ImmutableInnerObservableMissingBehavior(MutableInnerObservableMissingBehavior<T> behavior) {
this.whenGoesMissing = behavior.whenGoesMissing;
this.valueForMissing = behavior.valueForMissing;
this.onUpdate = behavior.onUpdate;
}
@Override
public WhenInnerObservableGoesMissing whenGoesMissing() {
return whenGoesMissing;
}
@Override
public Optional<? extends Supplier<T>> valueForMissing() {
return valueForMissing;
}
@Override
public WhenInnerObservableMissingOnUpdate onUpdate() {
return onUpdate;
}
}
// #end NESTED CLASSES
}