TreeRoots.java

package swingtree;

import org.jspecify.annotations.Nullable;
import sprouts.Tuple;

import java.util.Collections;
import java.util.Objects;

/**
 *  Reads the top level nodes out of the single value a tree is bound to, and writes them
 *  back into it. A tree bound through {@link UI#tree(sprouts.Var, swingtree.api.Configurator)}
 *  has exactly one top level node, its root. A forest bound through
 *  {@link UI#trees(sprouts.Var, swingtree.api.Configurator)} has as many as its tuple holds,
 *  and no root at all.
 *  <p>
 *  Everything below the top level is the same in both forms, so this is the only place the
 *  two differ, which is what lets one {@link PropertyTreeModel} and one {@link TreePathLens}
 *  serve them both.
 *  <p>
 *  {@link PropertyTreeModel} deliberately does <b>not</b> route its update walk through here.
 *  That walk stops at every value reference identical to the one it had before, and a single
 *  root wrapped in a tuple allocated on the spot would never be.
 *
 * @param <N> The common node type of the tree.
 * @param <R> The type of the value the tree is bound to: one node, or a tuple of them.
 */
interface TreeRoots<N, R>
{
    /** The bound value is the root node itself, so there is exactly one top level node. */
    static <N> TreeRoots<N, N> single( Class<N> nodeType ) {
        Objects.requireNonNull(nodeType);
        return new TreeRoots<N, N>() {
            @Override public Tuple<N> of( @Nullable N boundValue ) {
                return ( boundValue == null
                            ? Tuple.of(nodeType)
                            : Tuple.of(nodeType, Collections.singletonList(boundValue)) );
            }
            @Override public N with( @Nullable N boundValue, Tuple<N> roots ) {
                return roots.first();
            }
            @Override public boolean isForest() {
                return false;
            }
            @Override public String toString() {
                return "single(" + nodeType.getSimpleName() + ")";
            }
        };
    }

    /** The bound value is the tuple of top level nodes, which may hold any number of them. */
    static <N> TreeRoots<N, Tuple<N>> forest( Class<N> nodeType ) {
        Objects.requireNonNull(nodeType);
        return new TreeRoots<N, Tuple<N>>() {
            @Override public Tuple<N> of( @Nullable Tuple<N> boundValue ) {
                return ( boundValue == null ? Tuple.of(nodeType) : boundValue );
            }
            @Override public Tuple<N> with( @Nullable Tuple<N> boundValue, Tuple<N> roots ) {
                return roots;
            }
            @Override public boolean isForest() {
                return true;
            }
            @Override public String toString() {
                return "forest(" + nodeType.getSimpleName() + ")";
            }
        };
    }

    /**
     *  The top level nodes held by the given bound value, which is empty where the property
     *  holding it is.
     *
     * @param boundValue The value the tree is bound to, or {@code null} where it holds nothing.
     * @return Every node at the top level of the tree.
     */
    Tuple<N> of( @Nullable R boundValue );

    /**
     *  The bound value that holds the given top level nodes instead of the ones it had.
     *
     * @param boundValue The value the tree is bound to, or {@code null} where it holds nothing.
     * @param roots The top level nodes to put in its place, exactly one of them for a
     *              tree which is not a forest.
     * @return A new bound value, the old one being immutable.
     */
    R with( @Nullable R boundValue, Tuple<N> roots );

    /**
     *  Tells whether the top level of this tree is a tuple the user bound, rather than one
     *  root node. A forest has no root of its own, so nothing names it and no path leads
     *  to it.
     *
     * @return True for a tree bound to a tuple of top level nodes.
     */
    boolean isForest();
}