package org.codefx.libfx.nesting; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; import javafx.beans.Observable; /** * Usability class which observes a {@link Nesting} and executes some methods when the nesting's * {@link Nesting#innerObservableProperty() innerObservable} changes. These are the methods and the order in which they * are called: * <ol> * <li>if the old inner observable was present, a method is called with that observable as its argument; the method is * specified during building (see {@link NestingObserverBuilder#withOldInnerObservable(Consumer) withOldInnerObservable}) * <li>if the new inner observable is present, a method is called with that observable as its argument; the method is * specified during building (see {@link NestingObserverBuilder#withNewInnerObservable(Consumer) withNewInnerObservable}) * <li>in every case, another method is called with two Booleans as its arguments which indicate whether the old and the * new observable were/are present; the method is specified during building (see * {@link NestingObserverBuilder#whenInnerObservableChanges(BiConsumer) whenInnerObservableChanges}) * </ol> * These methods are also called once during construction. At this point, there is of course no old inner observable * present. * <p> * The observer is created with a {@link NestingObserverBuilder} which can be obtained from * {@link NestingObserver#forNesting(Nesting) forNesting}. After setting some of the methods mentioned above, the * observer is built by calling {@link NestingObserverBuilder#observe()}. * * @param <O> * the type of the nesting hierarchy's inner {@link Observable} */ public final class NestingObserver<O extends Observable> { // #begin PROPERTIES /** * The observed {@link Nesting}. */ private final Nesting<? extends O> nesting; /** * Called when the inner observable is replaced and an old inner observable was present. That observable is also the * argument. */ private final Consumer<? super O> oldInnerObservableConsumer; /** * Called when the inner observable is replaced and the new inner observable is present. That observable is also the * argument. */ private final Consumer<? super O> newInnerObservableConsumer; /** * Called when the inner observable is replaced. The arguments are two Booleans indicating whether the old and new * inner observables are present. */ private final BiConsumer<Boolean, Boolean> innerObservableChanges; //#end PROPERTIES // #begin CONSTRUCTION /** * Creates a new {@link NestingObserver} from the specified {@link NestingObserverBuilder builder}. * * @param builder * the {@link NestingObserverBuilder} from which the observer is created */ private NestingObserver(NestingObserverBuilder<O> builder) { nesting = builder.nesting; oldInnerObservableConsumer = builder.oldInnerObservableConsumer; newInnerObservableConsumer = builder.newInnerObservableConsumer; innerObservableChanges = builder.innerObservableChanges; initializeObserver(); } /** * Starts building a new {@link NestingObserver} which observes the specified nesting. * * @param <O> * the type of the nesting hierarchy's inner {@link Observable} * @param nesting * the observed {@link Nesting} * @return a new {@link NestingObserverBuilder} */ public static <O extends Observable> NestingObserverBuilder<O> forNesting(Nesting<O> nesting) { return new NestingObserverBuilder<O>(nesting); } //#end CONSTRUCTION // #begin OBSERVE /** * Initializes the observer by observing the initial status and any changes made to it. */ private void initializeObserver() { // observe the initial status observeInnerObservableChange(Optional.empty(), nesting.innerObservableProperty().getValue()); // add a listener to the nesting which observes changes nesting.innerObservableProperty().addListener( (o, oldInnerObservable, newInnerObservable) -> observeInnerObservableChange(oldInnerObservable, newInnerObservable)); } /** * Calls {@link #oldInnerObservableConsumer}, {@link #newInnerObservableConsumer} and * {@link #innerObservableChanges} when the inner observable is replaced. * * @param oldInnerObservable * the old {@link Nesting#innerObservableProperty() innerObservable} * @param newInnerObservable * the new {@link Nesting#innerObservableProperty() innerObservable} */ private void observeInnerObservableChange( Optional<? extends O> oldInnerObservable, Optional<? extends O> newInnerObservable) { oldInnerObservable.ifPresent(oldObservable -> oldInnerObservableConsumer.accept(oldObservable)); newInnerObservable.ifPresent(newObservable -> newInnerObservableConsumer.accept(newObservable)); boolean oldInnerObservablePresent = oldInnerObservable.isPresent(); boolean newInnerObservablePresent = newInnerObservable.isPresent(); innerObservableChanges.accept(oldInnerObservablePresent, newInnerObservablePresent); } //#end BIND // #begin INNER CLASSES /** * Builds a {@link NestingObserver}. * * @param <O> * the type of the nesting hierarchy's inner {@link Observable} */ public static class NestingObserverBuilder<O extends Observable> { /** * The future value for {@link NestingObserver#nesting}. */ private final Nesting<? extends O> nesting; /** * The future value for {@link NestingObserver#oldInnerObservableConsumer}. */ private Consumer<? super O> oldInnerObservableConsumer; /** * The future value for {@link NestingObserver#newInnerObservableConsumer}. */ private Consumer<? super O> newInnerObservableConsumer; /** * The future value for {@link NestingObserver#innerObservableChanges}. */ private BiConsumer<Boolean, Boolean> innerObservableChanges; /** * Creates a new builder for a nesting observer which observes the specified nesting. * * @param nesting * the nesting which will be observed by the created {@link NestingObserver} */ private NestingObserverBuilder(Nesting<? extends O> nesting) { this.nesting = nesting; oldInnerObservableConsumer = observable -> {/* by default do nothing */}; newInnerObservableConsumer = observable -> {/* by default do nothing */}; innerObservableChanges = (oldObservablePresent, newObservablePresent) -> {/* by default do nothing */}; } /** * The specified method will be executed when the {@link Nesting#innerObservableProperty() innerObservable} * changes <b>and</b> the old observable was present. * * @param oldInnerObservableConsumer * the executed method; its argument is the old inner observable * @return this builder for fluent calls */ public NestingObserverBuilder<O> withOldInnerObservable(Consumer<? super O> oldInnerObservableConsumer) { this.oldInnerObservableConsumer = oldInnerObservableConsumer; return this; } /** * The specified method will be executed when the {@link Nesting#innerObservableProperty() innerObservable} * changes <b>and</b> the new observable is present. * * @param newInnerObservableConsumer * the executed method; its argument is the new inner observable * @return this builder for fluent calls */ public NestingObserverBuilder<O> withNewInnerObservable(Consumer<? super O> newInnerObservableConsumer) { this.newInnerObservableConsumer = newInnerObservableConsumer; return this; } /** * The specified method will be executed when the {@link Nesting#innerObservableProperty() innerObservable} * changes. * * @param innerObservableChanges * the executed method; its argument are two Booleans indicating whether the old and new inner * observables are present. * @return this builder for fluent calls */ public NestingObserverBuilder<O> whenInnerObservableChanges(BiConsumer<Boolean, Boolean> innerObservableChanges) { this.innerObservableChanges = innerObservableChanges; return this; } /** * Builds a observer from this builder's settings. */ public void observe() { @SuppressWarnings("unused") NestingObserver<O> observer = new NestingObserver<O>(this); } } //#end region }