XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
package com.xfestudio.xfeservermanager.core.trigger;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/** Validation and depth-first traversal for conditional trigger action trees. */
public final class TriggerActionTree {
    public static final String CONDITION_TYPE = "condition";
    public static final int MAX_NODES = TriggerProgramValidator.MAX_NODES;
    public static final int MAX_NESTING_DEPTH = TriggerProgramValidator.MAX_NESTING_DEPTH;

    private TriggerActionTree() { }

    /**
     * Validates the recursive action shape shared by visual documents and XFE Script.
     */
    public static void validate(List<TriggerDefinition.Action> actions) {
        Objects.requireNonNull(actions, "actions");
        validateNodes(actions, 1, new Budget());
    }

    /** Returns every non-structural action in stable depth-first document order. */
    public static List<TriggerDefinition.Action> leafActions(List<TriggerDefinition.Action> actions) {
        validate(actions);
        List<TriggerDefinition.Action> result = new ArrayList<>();
        collectLeaves(actions, result);
        return List.copyOf(result);
    }

    /**
     * Returns executable actions whose enclosing condition nodes all match the event context.
     * Structural condition nodes are never returned to a platform action executor.
     */
    public static List<TriggerDefinition.Action> matchingActions(
            List<TriggerDefinition.Action> actions, Map<String, ?> context) {
        validate(actions);
        List<TriggerDefinition.Action> result = new ArrayList<>();
        collectMatching(actions, context == null ? Map.of() : context, result);
        return List.copyOf(result);
    }

    /** Returns the normalized condition represented by a structural action node. */
    public static TriggerDefinition.Condition condition(TriggerDefinition.Action action) {
        Objects.requireNonNull(action, "action");
        if (!CONDITION_TYPE.equals(action.type())) {
            throw new IllegalArgumentException("action is not a condition node: " + action.type());
        }
        return new TriggerDefinition.Condition(
                action.parameters().get("field"),
                action.parameters().get("operator"),
                action.parameters().getOrDefault("value", ""));
    }

    /**
     * Validates a complete program while the public catalog still describes leaf actions only.
     * This keeps old catalog callers compatible and gives catalog integrations a single migration
     * point: validate every leaf and every structural condition recursively.
     */
    public static void validateProgram(TriggerDefinition.Program program) {
        Objects.requireNonNull(program, "program");
        List<TriggerDefinition.Action> leaves = leafActions(program.actions());
        if (leaves.isEmpty()) {
            throw new IllegalArgumentException("a trigger needs at least one executable action");
        }
        TriggerCatalog.validate(new TriggerDefinition.Program(
                program.event(), program.conditionMode(), program.conditions(), leaves));

        List<TriggerDefinition.Condition> nestedConditions = new ArrayList<>();
        collectConditions(program.actions(), nestedConditions);
        if (!nestedConditions.isEmpty()) {
            // A valid condition node always has an executable descendant, so the representative
            // leaf also satisfies the catalog's non-empty action invariant.
            TriggerCatalog.validate(new TriggerDefinition.Program(
                    program.event(), TriggerDefinition.MatchMode.ALL,
                    nestedConditions, List.of(leaves.get(0))));
        }
    }

    private static void validateNodes(List<TriggerDefinition.Action> actions, int depth, Budget budget) {
        if (depth > MAX_NESTING_DEPTH) {
            throw new IllegalArgumentException("trigger action nesting exceeds " + MAX_NESTING_DEPTH);
        }
        for (TriggerDefinition.Action action : actions) {
            if (action == null) throw new IllegalArgumentException("action must not be null");
            if (++budget.nodes > MAX_NODES) {
                throw new IllegalArgumentException("trigger action tree exceeds " + MAX_NODES + " nodes");
            }
            if (!CONDITION_TYPE.equals(action.type())) {
                if (!action.children().isEmpty()) {
                    throw new IllegalArgumentException(
                            "only condition actions may contain child actions");
                }
                continue;
            }

            condition(action);
            if (action.children().isEmpty()) {
                throw new IllegalArgumentException("a condition action needs at least one child action");
            }
            validateNodes(action.children(), depth + 1, budget);
        }
    }

    private static final class Budget { private int nodes; }

    private static void collectLeaves(
            List<TriggerDefinition.Action> actions, List<TriggerDefinition.Action> result) {
        for (TriggerDefinition.Action action : actions) {
            if (CONDITION_TYPE.equals(action.type())) {
                collectLeaves(action.children(), result);
            } else {
                result.add(action);
            }
        }
    }

    private static void collectConditions(
            List<TriggerDefinition.Action> actions, List<TriggerDefinition.Condition> result) {
        for (TriggerDefinition.Action action : actions) {
            if (!CONDITION_TYPE.equals(action.type())) continue;
            result.add(condition(action));
            collectConditions(action.children(), result);
        }
    }

    private static void collectMatching(
            List<TriggerDefinition.Action> actions,
            Map<String, ?> context,
            List<TriggerDefinition.Action> result) {
        for (TriggerDefinition.Action action : actions) {
            if (!CONDITION_TYPE.equals(action.type())) {
                result.add(action);
                continue;
            }
            if (TriggerEvaluator.matches(condition(action), context)) {
                collectMatching(action.children(), context, result);
            }
        }
    }
}