package org.codefx.libfx.nesting.listener;
import java.util.Objects;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import org.codefx.libfx.nesting.Nesting;
import org.codefx.libfx.nesting.NestingObserver;
import org.codefx.libfx.nesting.property.NestedProperty;
/**
* Contains a {@link ChangeListener} which is connected to a {@link Nesting}. Simply put, the listener is always added
* to the nesting's inner observable (more precisely, it is added to the {@link ObservableValue} instance contained in
* the optional value held by the nesting's {@link Nesting#innerObservableProperty() innerObservable} property). <h2>
* Inner Observable's Value Changes</h2> The listener is added to the nesting's inner observable. So when that
* observable's value changes, the listener is called as usual. <h2>Inner Observable Is Replaced</h2> When the nesting's
* inner observable is replaced by another, the listener is removed from the old and added to the new observable. If one
* of them is missing, the affected removal or add is not performed, which means the listener might not be added to any
* observable.
* <p>
* Note that if the observable is replaced, <b>the listener is not called</b>! If this is the desired behavior, a
* listener has to be added to a {@link NestedProperty}.
*
* @param <T>
* the type of the value wrapped by the {@link ObservableValue}
*/
public class NestedChangeListenerHandle<T> implements NestedListenerHandle {
// #begin PROPERTIES
/**
* The {@link Nesting} to whose inner observable the {@link #listener} is attached.
*/
private final Nesting<? extends ObservableValue<T>> nesting;
/**
* The property indicating whether the nesting's inner observable is currently present, i.e. not null.
*/
private final BooleanProperty innerObservablePresent;
/**
* The {@link ChangeListener} which is added to the {@link #nesting}'s inner observable.
*/
private final ChangeListener<? super T> listener;
/**
* Indicates whether the {@link #listener} is currently attached to the {@link #nesting}'s inner observable.
*/
private boolean attached;
//#end PROPERTIES
// #begin CONSTUCTION
/**
* Creates a new {@link NestedChangeListenerHandle} which can {@link #attach() attach} the specified listener to the
* specified nesting's inner observable.
* <p>
* The listener is initially detached.
*
* @param nesting
* the {@link Nesting} to which the listener is added
* @param listener
* the {@link ChangeListener} which is added to the nesting's {@link Nesting#innerObservableProperty()
* innerObservable}
*/
NestedChangeListenerHandle(Nesting<? extends ObservableValue<T>> nesting, ChangeListener<? super T> listener) {
Objects.requireNonNull(nesting, "The argument 'nesting' must not be null.");
Objects.requireNonNull(listener, "The argument 'listener' must not be null.");
this.nesting = nesting;
this.innerObservablePresent = new SimpleBooleanProperty(this, "innerObservablePresent");
this.listener = listener;
NestingObserver
.forNesting(nesting)
.withOldInnerObservable(this::remove)
.withNewInnerObservable(this::addIfAttached)
.whenInnerObservableChanges(
(any, newInnerObservablePresent) -> innerObservablePresent.set(newInnerObservablePresent))
.observe();
}
//#end CONSTUCTION
// #begin ADD & REMOVE
/**
* Adds the {@link #listener} to the specified observable, when indicated by {@link #attached}.
*
* @param observable
* the {@link ObservableValue} to which the listener will be added
*/
private void addIfAttached(ObservableValue<T> observable) {
if (attached)
observable.addListener(listener);
}
/**
* Removes the {@link #listener} from the specified observable.
*
* @param observable
* the {@link ObservableValue} from which the listener will be removed.
*/
private void remove(ObservableValue<T> observable) {
observable.removeListener(listener);
}
// #end ADD & REMOVE
// #begin IMPLEMENTATION OF 'NestedListenerHandle'
@Override
public void attach() {
if (!attached) {
attached = true;
nesting.innerObservableProperty().getValue().ifPresent(this::addIfAttached);
}
}
@Override
public void detach() {
if (attached) {
attached = false;
nesting.innerObservableProperty().getValue().ifPresent(this::remove);
}
}
@Override
public ReadOnlyBooleanProperty innerObservablePresentProperty() {
return innerObservablePresent;
}
@Override
public boolean isInnerObservablePresent() {
return innerObservablePresent.get();
}
//#end IMPLEMENTATION OF 'NestedListenerHandle'
}