package org.codefx.libfx.nesting; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Function; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ObservableValue; import org.codefx.libfx.nesting.listener.NestedInvalidationListenerBuilder; import org.codefx.libfx.nesting.listener.NestedInvalidationListenerHandle; /** * A superclass for builders for all kinds of nested functionality. Holds the nesting hierarchy (outer observable and * nesting steps) and can build a {@link Nesting} from it. * <p> * Subclasses must not allow nesting if type parameter {@code O} does not also implement {@link ObservableValue}! (Which * wouldn't make sense anyhow because then no value would be available for the nesting step.) * * @param <T> * the type of the wrapped value * @param <O> * the type of {@link Observable} this builder uses as an inner observable */ abstract class AbstractNestingBuilderOnObservable<T, O extends Observable> { /* * A builder can either be the outer or a nested builder of a nesting. In the first case, 'outerObservable' is * non-null, in the second case 'previousBuilder' and 'nestedGetter' are non-null. The method 'isOuterBuilder()' * indicates which kind of builder this is. */ //#begin PROPERTIES /** * The outer observable upon which all nestings depend. This is only non-null for the outer builder (indicated by * {@link #isOuterBuilder()}). All others have a {@link #previousBuilder} and a {@link #nestingStep}. */ private final O outerObservable; /** * The previous builder upon which this builder depends. This is only non-null for nested builders (indicated by * {@link #isOuterBuilder()}). */ private final AbstractNestingBuilderOnObservable<?, ?> previousBuilder; /** * The function which performs the {@link NestingStep} from an instance of the previous builder's wrapped type to * the next observable. This is only non-null for nested builders (indicated by {@link #isOuterBuilder()}). */ private final NestingStep<?, ? extends O> nestingStep; //#end PROPERTIES //#begin CONSTRUCTION /** * Creates a new nesting builder which acts as the outer builder, i.e. has the specified {@link #outerObservable} . * * @param outerObservable * the outer observable upon which the constructed nesting depends */ protected AbstractNestingBuilderOnObservable(O outerObservable) { Objects.requireNonNull(outerObservable, "The argument 'outerObservable' must not be null."); this.outerObservable = outerObservable; this.previousBuilder = null; this.nestingStep = null; } /** * Creates a new nesting builder which acts as a nested builder, i.e. has the specified {@link #previousBuilder} and * {@link #nestingStep}. * * @param <P> * the type the previous builder wraps * @param previousBuilder * the previous builder * @param nestingStep * the function which performs the nesting step from one observable to the next */ protected <P> AbstractNestingBuilderOnObservable( AbstractNestingBuilderOnObservable<P, ?> previousBuilder, NestingStep<P, ? extends O> nestingStep) { Objects.requireNonNull(previousBuilder, "The argument 'previousBuilder' must not be null."); Objects.requireNonNull(nestingStep, "The argument 'nestingStep' must not be null."); this.outerObservable = null; this.previousBuilder = previousBuilder; this.nestingStep = nestingStep; } //#end CONSTRUCTION // #begin BUILD /** * Creates a new nesting from this builder's settings. This method can be called arbitrarily often and each call * returns a new instance. * * @return a new instance of {@link Nesting} */ public Nesting<O> buildNesting() { if (isOuterBuilder()) return new ShallowNesting<>(outerObservable); // create a construction kit and use it to create a deep nesting NestingConstructionKit kit = createNestingConstructionKit(); return new DeepNesting<>(kit.getOuterObservable(), kit.getNestingSteps()); } /** * Indicates whether this builder is the outer builder. * * @return true if this is the outer builder */ private boolean isOuterBuilder() { return outerObservable != null; } /** * Returns a nesting construction kit based in this builder's current settings. * * @return an instance of {@link NestingConstructionKit} */ private NestingConstructionKit createNestingConstructionKit() { NestingConstructionKit kit = new NestingConstructionKit(); fillNestingConstructionKit(kit); return kit; } /** * Fills the specified kit with an observable value and all nesting steps which were given to this and its previous * nesting builders. The steps' order is from outer to inner property and hence is correct for the constructor of * {@link DeepNesting}. * * @param kit * the {@link NestingConstructionKit} to fill with an {@link #outerObservable} and {@link #nestingStep * nestingSteps} */ @SuppressWarnings("rawtypes") private void fillNestingConstructionKit(NestingConstructionKit kit) { /* * Uses recursion to move up the chain of 'previousNestedBuilder's until the outer builder is reached. This * builder's 'outerObservable' is set to the specified 'kit'. The 'kit's list of nesting steps is then filled * when the recursion is closed, i.e. top down from the outermost builder to the inner one. */ if (isOuterBuilder()) /* * This class' contract states that nesting must not occur when the outerObservable's type O is not also an * 'ObservableValue'. */ kit.setOuterObservable((ObservableValue) outerObservable); else { previousBuilder.fillNestingConstructionKit(kit); kit.getNestingSteps().add(nestingStep); } } //#end BUILD // #begin LISTENERS /** * Adds the specified invalidation listener to the nesting hierarchy's inner {@link Observable}. * * @param listener * the added {@link InvalidationListener} * @return the {@link NestedInvalidationListenerHandle} which can be used to check the nesting's state */ public NestedInvalidationListenerHandle addListener(InvalidationListener listener) { Nesting<O> nesting = buildNesting(); return NestedInvalidationListenerBuilder .forNesting(nesting) .withListener(listener) .buildAttached(); } //#end LISTENERS // #begin PRIVATE CLASSES /** * An editable class which can be used to collect all instances needed to call * {@link DeepNesting#DeepNesting(ObservableValue, List) new Nesting(...)}. */ @SuppressWarnings("rawtypes") protected static class NestingConstructionKit { // #begin PROPERTIES /** * The outer {@link ObservableValue} */ private ObservableValue outerObservable; /** * The list of functions which perform the {@link NestingStep NestingSteps} from one {@link ObservableValue * observable} to the next. */ private final List<NestingStep> nestingSteps; //#end PROPERTIES // #begin CONSTRUCTOR /** * Creates a new empty construction kit. */ public NestingConstructionKit() { nestingSteps = new ArrayList<>(); } //#end CONSTRUCTOR // #begin ACCESSORS /** * @return the outer {@link ObservableValue} */ public ObservableValue getOuterObservable() { return outerObservable; } /** * Sets the new outer observable value. * * @param outerObservable * the outer {@link ObservableValue} to set */ public void setOuterObservable(ObservableValue outerObservable) { this.outerObservable = outerObservable; } /** * @return the list of {@link Function Functions} which get the nested {@link ObservableValue observables} */ public List<NestingStep> getNestingSteps() { return nestingSteps; } //#end ACCESSORS } //#end PRIVATE CLASSES }