OptionalUI.java

package swingtree;

import org.jspecify.annotations.Nullable;

import java.awt.*;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * A container object for AWT {@link Component} types
 * which may or may not contain a non-{@code null} value.
 * If a value is present, {@code isPresent()} returns {@code true}. If no
 * value is present, the object is considered <i>empty</i> and
 * {@code isPresent()} returns {@code false}.
 *
 * <p>Additional methods that depend on the presence or absence of a contained
 * value are provided, such as {@link #orElse(Component) orElse()}
 * (returns a default value if no value is present) and
 * {@link #ifPresent(Consumer) ifPresent()} (performs an
 * action if a value is present).
 *
 * <p>This is a <b>value-based</b>
 * class; use of identity-sensitive operations (including reference equality
 * ({@code ==}), identity hash code, or synchronization) on instances of
 * {@code OptionalUI} may have unpredictable results and should be avoided.
 *
 * Note that
 * {@code OptionalUI} is primarily intended for use as a SwingTree query return type where
 * there is a clear need to represent "no result," and where returning {@code null} as well
 * as expose the UI components to the application thread directly
 * is likely to cause errors. A variable whose type is {@code OptionalUI} should
 * never itself be {@code null}; it should always point to an {@code OptionalUI}
 * instance.
 * 	<p>
 * 	<b>Please take a look at the <a href="https://globaltcad.github.io/swing-tree/">living swing-tree documentation</a>
 * 	where you can browse a large collection of examples demonstrating how to use the API of this class.</b>
 *
 * @param <C> the type of component held by this instance
 */
public final class OptionalUI<C extends Component> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final OptionalUI<?> EMPTY = new OptionalUI<>(null);

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final @Nullable C _component;

    /**
     * Returns an empty {@code OptionalUI} instance.  No value is present for this
     * {@code OptionalUI}.
     *
     * @apiNote
     * Though it may be tempting to do so, avoid testing if an object is empty
     * by comparing with {@code ==} against instances returned by
     * {@code OptionalUI.empty()}.  There is no guarantee that it is a singleton.
     * Instead, use {@link #isPresent()}.
     *
     * @param <T> The type of the non-existent value
     * @return an empty {@code OptionalUI}
     */
    static<T extends Component> OptionalUI<T> empty() {
        @SuppressWarnings("unchecked")
        OptionalUI<T> t = (OptionalUI<T>) EMPTY;
        return t;
    }

    /**
     * Constructs an instance with the described value.
     *
     * @param value the value to describe; it's the caller's responsibility to
     *        ensure the value is non-{@code null} unless creating the singleton
     *        instance returned by {@code empty()}.
     */
    private OptionalUI( @Nullable C value ) { this._component = value; }

    /**
     * Returns an {@code OptionalUI} describing the given value, if
     * non-{@code null}, otherwise returns an empty {@code OptionalUI}.
     *
     * @param component the possibly-{@code null} value to describe
     * @param <T> the type of the value
     * @return an {@code OptionalUI} with a present value if the specified value
     *         is non-{@code null}, otherwise an empty {@code OptionalUI}
     */
    @SuppressWarnings("unchecked")
    static <T extends Component> OptionalUI<T> ofNullable(T component) {
        return component == null ? (OptionalUI<T>) EMPTY
                : new OptionalUI<>(component);
    }

    /**
     * If a component is present, returns {@code true}, otherwise {@code false}.
     *
     * @return {@code true} if a component is present, otherwise {@code false}
     */
    public boolean isPresent() { return _component != null; }

    /**
     * If a component is  not present, returns {@code true}, otherwise
     * {@code false}.
     *
     * @return  {@code true} if a component is not present, otherwise {@code false}
     * @since   11
     */
    public boolean isEmpty() { return _component == null; }

    /**
     * If a component is present, performs the given action with the component,
     * otherwise does nothing.
     *
     * @param action the action to be performed, if a component is present
     * @throws NullPointerException if component is present and the given action is
     *         {@code null}
     */
    public void ifPresent( Consumer<? super C> action ) {
        if ( _component != null )
            UI.run(() -> action.accept(_component));
    }

    /**
     * If a component is present, performs the given action with the component,
     * otherwise performs the given empty-based action.
     *
     * @param action the action to be performed, if a component is present
     * @param emptyAction the empty-based action to be performed, if no component is
     *        present
     * @throws NullPointerException if a component is present and the given action
     *         is {@code null}, or no component is present and the given empty-based
     *         action is {@code null}.
     * @since 9
     */
    public void ifPresentOrElse( Consumer<? super C> action, Runnable emptyAction ) {
        UI.run(()->{
            if ( _component != null )
                action.accept(_component);
            else
                emptyAction.run();
        });
    }

    /**
     * If a component is present, and the component matches the given predicate,
     * returns an {@code OptionalUI} describing the component, otherwise returns an
     * empty {@code OptionalUI}.
     *
     * @param predicate the predicate to apply to a component, if present
     * @return an {@code OptionalUI} describing the component of this
     *         {@code OptionalUI}, if a component is present and the component matches the
     *         given predicate, otherwise an empty {@code OptionalUI}
     * @throws NullPointerException if the predicate is {@code null}
     */
    public OptionalUI<C> filter(Predicate<? super C> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent()) {
            return this;
        } else {
            if ( !UI.thisIsUIThread() ) {
                try {
                    return UI.runAndGet(() -> filter(predicate));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            else
                return predicate.test(_component) ? this : empty();
        }
    }

    /**
     * If a component is present, returns an {@code OptionalUI} describing (as if by
     * {@link #ofNullable}) the result of applying the given mapping function to
     * the component, otherwise returns an empty {@code OptionalUI}.
     *
     * <p>If the mapping function returns a {@code null} result then this method
     * returns an empty {@code OptionalUI}.
     *
     * @param mapper the mapping function to apply to a component, if present
     * @param <U> The type of the component returned from the mapping function
     * @return an {@code Optional} describing the result of applying a mapping
     *         function to the UI component of this {@code OptionalUI}, if a component is
     *         present, otherwise an empty {@code OptionalUI}
     * @throws NullPointerException if the mapping function is {@code null}
     */
    public <U> Optional<U> map( Function<? super C, ? extends U> mapper ) {
        Objects.requireNonNull(mapper);
        if ( !this.isPresent() ) return Optional.empty();
        else {
            if ( UI.thisIsUIThread() )
                return Optional.ofNullable(mapper.apply(_component));
            else {
                try {
                    Optional<U> opt = UI.runAndGet(() -> map(mapper));
                    if ( opt.isPresent() && (opt.get() instanceof Component || opt.get() instanceof UIForAnySwing) )
                        throw new RuntimeException("A Swing component may not leak to another thread!");
                    else return opt;
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    /**
     * If a component is present, returns an {@code OptionalUI} describing the component,
     * otherwise returns an {@code OptionalUI} produced by the supplying function.
     * Use this to provide for alternative UI tree querying operations.
     *
     * @param supplier the supplying function that produces an {@code OptionalUI}
     *        to be returned
     * @return returns an {@code OptionalUI} describing the component of this
     *         {@code OptionalUI}, if a component is present, otherwise an
     *         {@code OptionalUI} produced by the supplying function.
     * @throws NullPointerException if the supplying function is {@code null} or
     *         produces a {@code null} result
     * @since 9
     */
    public OptionalUI<C> or(Supplier<? extends OptionalUI<? extends C>> supplier) {
        Objects.requireNonNull(supplier);
        if ( this.isPresent() ) return this;
        else {
            @SuppressWarnings("unchecked")
            OptionalUI<C> r = (OptionalUI<C>) supplier.get();
            return Objects.requireNonNull(r);
        }
    }

    /**
     * If a component is present, returns the component, otherwise returns
     * {@code other} or throws a null pointer exception if {@code other} is
     * {@code null}.
     *
     * @param other the component to be returned, if no component is present.
     *        May not be {@code null}.
     * @return the component, if present, otherwise {@code other}
     */
    public C orElseNullable( C other ) {
        if ( !UI.thisIsUIThread() )
            throw new RuntimeException("The UI component may only be accessed by the UI thread!");
        return _component != null ? _component : other;
    }

    /**
     * If a component is present, returns the component, otherwise returns
     * {@code other}.
     *
     * @param other the component to be returned, if no component is present.
     *        May not be {@code null}, use {@link #orElseNullable(Component)} if it can be null.
     * @return the component, if present, otherwise {@code other}
     */
    public C orElse(C other) {
        if ( !UI.thisIsUIThread() )
            throw new RuntimeException("The UI component may only be accessed by the UI thread!");
        Objects.requireNonNull(other);
        return _component != null ? _component : other;
    }

    /**
     *  If a component is present, returns the component, otherwise returns {@code null}.
     *
     * @return The component wrapped in this OptionalUI, or null if no component is present.
     */
    public @Nullable C orNull() {
        if ( !UI.thisIsUIThread() )
            throw new RuntimeException("The UI component may only be accessed by the UI thread!");
        return _component;
    }

    /**
     * If a component is present, returns the component, otherwise returns the result
     * produced by the supplying function.
     *
     * @param supplier the supplying function that produces a component to be returned
     * @return the component, if present, otherwise the result produced by the
     *         supplying function
     * @throws NullPointerException if no component is present and the supplying
     *         function is {@code null}
     */
    public C orElseGet(Supplier<? extends C> supplier) {
        if ( !UI.thisIsUIThread() )
            throw new RuntimeException("The UI component may only be accessed by the UI thread!");
        return _component != null ? _component : supplier.get();
    }

    /**
     * If a component is present, returns the component, otherwise throws
     * {@code NoSuchElementException}.
     *
     * @return the non-{@code null} component described by this {@code OptionalUI}
     * @throws NoSuchElementException if no component is present
     * @since 10
     */
    public C orElseThrow() {
        if ( !UI.thisIsUIThread() )
            throw new RuntimeException("The UI component may only be accessed by the UI thread!");
        if ( _component == null )
            throw new NoSuchElementException("No component present");

        return _component;
    }

    /**
     * If a component is present, returns the component, otherwise throws an exception
     * produced by the exception supplying function.
     *
     * Note that
     * A method reference to the exception constructor with an empty argument
     * list can be used as the supplier. For example,
     * {@code IllegalStateException::new}
     *
     * @param <X> Type of the exception to be thrown
     * @param exceptionSupplier the supplying function that produces an
     *        exception to be thrown
     * @return the component, if present
     * @throws X if no component is present
     * @throws NullPointerException if no component is present and the exception
     *          supplying function is {@code null}
     */
    public <X extends Throwable> C orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if ( !UI.thisIsUIThread() )
            throw new RuntimeException("The UI component may only be accessed by the UI thread!");
        if (_component != null) {
            return _component;
        } else {
            throw exceptionSupplier.get();
        }
    }

    /**
     * Indicates whether some other object is "equal to" this {@code OptionalUI}.
     * The other object is considered equal if:
     * <ul>
     * <li>it is also an {@code OptionalUI} and;
     * <li>both instances have no component present or;
     * <li>the present components are "equal to" each other via {@code equals()}.
     * </ul>
     *
     * @param obj an object to be tested for equality
     * @return {@code true} if the other object is "equal to" this object
     *         otherwise {@code false}
     */
    @Override
    public boolean equals( Object obj ) {
        if ( this == obj ) return true;
        if ( !(obj instanceof OptionalUI) ) return false;
        OptionalUI<?> other = (OptionalUI<?>) obj;
        return Objects.equals(_component, other._component);
    }

    /**
     * Returns the hash code of the component, if present, otherwise {@code 0}
     * (zero) if no component is present.
     *
     * @return hash code component of the present component or {@code 0} if no component is
     *         present
     */
    @Override
    public int hashCode() { return Objects.hashCode(_component); }

    /**
     * Returns a non-empty string representation of this {@code OptionalUI}
     * suitable for debugging.  The exact presentation format is unspecified and
     * may vary between implementations and versions.
     *
     * If a component is present the result must include its string representation
     * in the result.  Empty and present {@code OptionalUI}s must be unambiguously
     * differentiable.
     *
     * @return the string representation of this instance
     */
    @Override
    public String toString() {
        return _component != null
                ? String.format("OptionalUI[%s]", _component)
                : "OptionalUI.empty";
    }
}