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}