001package gudusoft.gsqlparser.pp2.layout;
002
003import gudusoft.gsqlparser.pp.logger.PPLogger;
004
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.List;
008
009/**
010 * Ordered collection of {@link LayoutRule}s applied in sequence to a
011 * {@link LayoutContext}.
012 *
013 * <p>Each rule is applied in registration order; before invoking a rule the
014 * pipeline sets the context's active priority to that rule's
015 * {@link LayoutRule#priority()}, so the rule's requested writes are mediated by
016 * the {@link LayoutConflictResolver}. A rule that throws is logged and skipped
017 * (the pipeline never propagates a rule failure — fault tolerance), and later
018 * rules still run.
019 *
020 * <p>With zero registered rules, {@link #run(LayoutContext)} leaves every
021 * {@link LayoutDecision} {@link LayoutDecision#UNSET} — the identity pass.
022 *
023 * <p>Plan reference: §7.3/S23, §7.4/S23.
024 */
025public final class LayoutRulePipeline {
026
027    private final List<LayoutRule> rules = new ArrayList<LayoutRule>();
028
029    /** Register a rule. Returns {@code this} for chaining. */
030    public LayoutRulePipeline register(LayoutRule rule) {
031        if (rule == null) throw new NullPointerException("rule");
032        rules.add(rule);
033        return this;
034    }
035
036    /** Read-only view of the registered rules, in registration order. */
037    public List<LayoutRule> getRules() {
038        return Collections.unmodifiableList(rules);
039    }
040
041    /**
042     * Apply every registered rule to {@code context}, in order. Never throws for
043     * a non-null context; a rule that throws is logged and skipped.
044     *
045     * @param context the layout context; must not be null
046     * @return the same context, with decisions populated
047     * @throws NullPointerException if {@code context} is null
048     */
049    public LayoutContext run(LayoutContext context) {
050        if (context == null) throw new NullPointerException("context");
051        for (LayoutRule rule : rules) {
052            context.setActivePriority(rule.priority());
053            try {
054                rule.apply(context);
055            } catch (Throwable t) {
056                PPLogger.error(t);
057                PPLogger.info("LayoutRulePipeline: rule '" + safeName(rule)
058                    + "' threw and was skipped; continuing with remaining rules.");
059            }
060        }
061        return context;
062    }
063
064    private static String safeName(LayoutRule rule) {
065        try {
066            return rule.name();
067        } catch (Throwable t) {
068            return rule.getClass().getName();
069        }
070    }
071}