All Downloads are FREE. Search and download functionalities are using the official Maven repository.

es.iti.wakamiti.api.model.TreeNodeBuilder Maven / Gradle / Ivy

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */
package es.iti.wakamiti.api.model;


import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;


/**
 * Abstract class representing a builder for a tree node with
 * a generic type.
 *
 * @param  The type of the tree node builder
 * @author Luis Iñesta Gelabert - [email protected]
 */
@SuppressWarnings("unchecked")
public abstract class TreeNodeBuilder> {

    private final List children = new ArrayList<>();
    private S parent;

    protected TreeNodeBuilder() {
        super();
    }

    protected TreeNodeBuilder(Collection children) {
        addChildren(children);
    }

    /**
     * Gets a stream of the children of this tree node builder.
     *
     * @return The stream of children
     */
    public Stream children() {
        return children.stream();
    }

    /**
     * Gets a stream of the children of this tree node builder
     * filtered by the given predicate.
     *
     * @param filter The predicate to filter children
     * @return The stream of filtered children
     */
    public Stream children(Predicate filter) {
        return children.stream().filter(filter);
    }

    /**
     * Gets the child at the specified index.
     *
     * @param index The index of the child
     * @return The child at the specified index
     */
    public S child(int index) {
        return children.get(index);
    }

    /**
     * Gets the number of children of this tree node builder.
     *
     * @return The number of children
     */
    public int numChildren() {
        return children.size();
    }

    /**
     * Checks if this tree node builder has children.
     *
     * @return {@code true} if this tree node builder has children,
     * {@code false} otherwise
     */
    public boolean hasChildren() {
        return !children.isEmpty();
    }

    /**
     * Gets the optional parent of this tree node builder.
     *
     * @return The optional parent
     */
    public Optional parent() {
        return Optional.ofNullable(parent);
    }

    /**
     * Gets the position of the specified child in the list of
     * children.
     *
     * @param child The child to find
     * @return The position of the child, or -1 if not found
     */
    public int positionOfChild(S child) {
        return children.indexOf(child);
    }

    /**
     * Gets the position of this tree node builder in its parent's
     * list of children.
     *
     * @return The position in the parent, or {@code -1} if there is
     * no parent
     */
    public int positionInParent() {
        return parent().map(p -> p.positionOfChild((S) this)).orElse(-1);
    }

    /**
     * Gets the root of the tree containing this tree node builder.
     *
     * @return The root of the tree
     */
    public S root() {
        return Optional.ofNullable(parent).map(TreeNodeBuilder::root).orElse((S) this);
    }

    /**
     * Gets a stream of ancestors of this tree node builder.
     *
     * @return The stream of ancestors
     */
    public Stream ancestors() {
        return Optional.ofNullable(parent).map(s -> Stream.concat(Stream.of(s), s.ancestors())).orElseGet(Stream::empty);
    }

    /**
     * Gets a stream of siblings of this tree node builder.
     *
     * @return The stream of siblings
     */
    public Stream siblings() {
        return Optional.ofNullable(parent).stream().flatMap(S::children).filter(x -> x != this);
    }

    /**
     * Gets a stream of descendants of this tree node builder.
     *
     * @return The stream of descendants
     */
    public Stream descendants() {
        return Stream.concat(children(), children().flatMap(S::descendants));
    }

    /**
     * Adds the specified child to the list of children.
     *
     * @param child The child to add
     * @return This tree node builder
     */
    public S addChild(S child) {
        children.add(child);
        child.parent().ifPresent(previousParent -> previousParent.removeChild(child));
        ((TreeNodeBuilder) child).parent = (S) this;
        return (S) this;
    }

    /**
     * Adds the specified child to the list of children at
     * the specified index.
     *
     * @param child The child to add
     * @param index The index at which to add the child
     * @return This tree node builder
     */
    public S addChild(S child, int index) {
        children.add(index, child);
        child.parent().ifPresent(previousParent -> previousParent.removeChild(child));
        ((TreeNodeBuilder) child).parent = (S) this;
        return (S) this;
    }

    /**
     * Adds the specified child to the list of children as
     * the first child.
     *
     * @param child The child to add
     * @return This tree node builder
     */
    public S addFirstChild(S child) {
        return addChild(child, 0);
    }

    /**
     * Adds the specified child to the list of children if
     * the provided predicate is true.
     *
     * @param child     The child to add
     * @param predicate The predicate to check before adding
     *                  the child
     * @return This tree node builder
     */
    public S addChildIf(S child, Predicate predicate) {
        if (predicate.test(child)) {
            addChild(child);
        }
        return (S) this;
    }

    /**
     * Adds a collection of children to the list of children.
     *
     * @param children The children to add
     * @return This tree node builder
     */
    public S addChildren(Collection children) {
        for (S child : children) {
            addChild(child);
        }
        return (S) this;
    }

    /**
     * Replaces the old child with the new child in the list
     * of children.
     *
     * @param oldChild The child to be replaced
     * @param newChild The new child to replace the old child
     * @return This tree node builder
     * @throws IllegalArgumentException If the old child is not
     *                                  a current child node
     */
    public S replaceChild(S oldChild, S newChild) {
        int index = children.indexOf(oldChild);
        if (index == -1) {
            throw new IllegalArgumentException("Node to replace is not a current child node");
        }
        children.set(index, newChild);
        ((TreeNodeBuilder) oldChild).parent = null;
        newChild.parent().ifPresent(previousParent -> previousParent.removeChild(newChild));
        ((TreeNodeBuilder) newChild).parent = (S) this;
        return (S) this;
    }

    /**
     * Checks if the specified child is in the list of children.
     *
     * @param child The child to check
     * @return {@code true} if the child is present,
     * {@code false} otherwise
     */
    public boolean containsChild(S child) {
        return children.contains(child);
    }

    /**
     * Removes the specified child from the list of children.
     *
     * @param child The child to remove
     * @return This tree node builder
     */
    public S removeChild(S child) {
        children.remove(child);
        ((TreeNodeBuilder) child).parent = null;
        return (S) this;
    }

    /**
     * Removes children from the list of children based on
     * the provided predicate.
     *
     * @param predicate The predicate to filter children for
     *                  removal
     * @return This tree node builder
     */
    public S removeChildrenIf(Predicate predicate) {
        for (S child : children) {
            if (predicate.test(child)) {
                removeChild(child);
            }
        }
        return (S) this;
    }

    /**
     * Clears the list of children.
     *
     * @return This tree node builder
     */
    public S clearChildren() {
        children.clear();
        return (S) this;
    }

    /**
     * Creates a copy of this tree node builder.
     *
     * @return The copied tree node builder
     */
    public abstract S copy();

    /**
     * Copies the specified tree node builder and adds its
     * children to this tree node builder.
     *
     * @param copy The tree node builder to copy
     * @return This tree node builder
     */
    protected S copy(S copy) {
        for (S child : this.children) {
            copy.addChild(child.copy());
        }
        return copy;
    }

}