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

es.iti.wakamiti.api.model.ExecutableTreeNode 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.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import static java.util.Objects.isNull;


/**
 * This class represents an executable tree node and
 * provides functionality related to execution, such as
 * assigning an execution ID, preparing for execution,
 * and retrieving execution details.
 *
 * @param  The type of the executable tree node itself
 * @param  The type of the result that can be obtained
 *            after execution
 * @author Luis Iñesta Gelabert - [email protected]
 * @see TreeNode
 */
public abstract class ExecutableTreeNode, R extends Comparable>
        extends TreeNode {

    private String executionID;
    private ExecutionState executionState;


    protected ExecutableTreeNode(List children) {
        super(children);
    }


    /**
     * Assigns an execution ID to this node.
     *
     * @param executionID The execution ID to be assigned
     * @throws IllegalStateException If the execution ID
     *                               has already been assigned
     */
    public void assignExecutionID(String executionID) {
        if (this.executionID != null) {
            throw new IllegalStateException("ExecutionID already assigned");
        }
        this.executionID = executionID;
        children().forEach(child -> child.assignExecutionID(executionID));
    }

    /**
     * Gets the assigned execution ID for this node.
     *
     * @return The execution ID
     */
    public String executionID() {
        return executionID;
    }

    /**
     * Prepares the node to be executed.
     *
     * @return The node execution state
     */
    public ExecutionState prepareExecution() {
        if (isNull(executionState)) {
            executionState = createExecutionState();
        }
        return executionState;
    }

    /**
     * Creates the execution state for this node.
     * Subclasses can override this method to provide a
     * custom execution state.
     *
     * @return The execution state
     */
    protected ExecutionState createExecutionState() {
        return new ExecutionState<>();
    }


    /**
     * Gets the execution state of this node.
     *
     * @return The execution state, which will be empty
     * until {@link #prepareExecution()} is called
     */
    public Optional> executionState() {
        return Optional.ofNullable(executionState);
    }


    /**
     * Gets the start instant of this node, if executed.
     * In the case of child-populated nodes returns the
     * minimum start instant of its children.
     *
     * @return The nullable optional start instant
     */
    public Optional startInstant() {
        if (hasChildren()) {
            return children()
                    .map(S::startInstant)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .min(Comparator.naturalOrder());
        }
        return executionState().flatMap(ExecutionState::startInstant);
    }


    /**
     * Gets the finish instant of this node, if executed.
     * In the case of child-populated nodes returns the
     * maximum finish instant of its children.
     *
     * @return The nullable optional finish instant
     */
    public Optional finishInstant() {
        if (hasChildren()) {
            return children()
                    .map(S::finishInstant)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .max(Comparator.naturalOrder());
        }
        return executionState().flatMap(ExecutionState::finishInstant);
    }


    /**
     * Gets the duration of the execution of this node, if executed.
     * In the case of child-populated nodes returns the sum of
     * durations of its children.
     *
     * @return The nullable optional duration
     */
    public Optional duration() {
        if (hasChildren()) {
            return children()
                    .map(S::duration)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .reduce(Duration::plus);
        }
        return executionState().flatMap(ExecutionState::duration);
    }


    /**
     * Gets the result of this node, if executed. In the case
     * of child-populated nodes, returns the result of maximum
     * priority among its children.
     *
     * @return The nullable optional result
     */
    public Optional result() {
        Optional result = executionState().flatMap(ExecutionState::result);
        if (result.isEmpty() && hasChildren()) {
            return children()
                    .map(S::result)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .max(Comparator.naturalOrder());
        }
        return result;
    }


    /**
     * Gets a stream with the errors of this node, if executed
     * and failed. In the case of child-populated nodes returns
     * all the errors of its children.
     *
     * @return A stream of errors
     */
    public Stream errors() {
        if (hasChildren()) {
            return children().flatMap(S::errors);
        }
        return executionState().flatMap(ExecutionState::error).stream();
    }


    /**
     * Gets a stream with the error classifiers of this node, if
     * executed and failed. In the case of child-populated nodes
     * returns all the error classifiers of its children.
     *
     * @return A stream of error classifiers
     */
    public Stream errorClassifiers() {
        if (hasChildren()) {
            return children().flatMap(S::errorClassifiers);
        }
        return executionState().flatMap(ExecutionState::errorClassifier).stream();
    }


    /**
     * Checks whether the execution of this node has been marked as
     * started. In the case of child-populated nodes, returns true
     * if every child has been started.
     *
     * @return {@code true} if the execution has started, {@code false}
     * otherwise
     */
    public boolean hasStarted() {
        if (hasChildren()) {
            return children().allMatch(S::hasStarted);
        }
        return executionState().map(ExecutionState::hasStarted).orElse(false);
    }


    /**
     * Checks whether the execution of this node has been marked as
     * finished. In the case of child-populated nodes, returns true
     * if every child has been finished.
     *
     * @return {@code true} if the execution has finished, {@code false}
     * otherwise
     */
    public boolean hasFinished() {
        if (hasChildren()) {
            return children().allMatch(S::hasFinished);
        }
        return executionState().map(ExecutionState::hasFinished).orElse(false);
    }
}