001package gudusoft.gsqlparser.pp2.layout.rules;
002
003import gudusoft.gsqlparser.ETokenType;
004import gudusoft.gsqlparser.pp2.island.IslandKind;
005import gudusoft.gsqlparser.pp2.island.IslandScope;
006import gudusoft.gsqlparser.pp2.layout.LayoutContext;
007import gudusoft.gsqlparser.pp2.layout.LayoutPriorities;
008import gudusoft.gsqlparser.pp2.layout.LayoutRule;
009import gudusoft.gsqlparser.pp2.token.Pp2Token;
010import gudusoft.gsqlparser.pp2.token.Pp2TokenStream;
011
012import java.util.List;
013import java.util.Locale;
014
015/**
016 * Lays out {@code CASE} expressions vertically: a linebreak before each
017 * {@code WHEN}, {@code ELSE}, and the closing {@code END}, keeping {@code THEN}
018 * on its {@code WHEN} line.
019 *
020 * <pre>
021 * CASE
022 * WHEN x = 1 THEN 'a'
023 * WHEN x = 2 THEN 'b'
024 * ELSE 'c'
025 * END
026 * </pre>
027 *
028 * <p>Operates over the {@link IslandKind#CASE} islands recognised in S22 (so a
029 * {@code BEGIN ... END} block's {@code END} is never mistaken for a CASE end).
030 * Incomplete CASE islands (no {@code END},
031 * {@link gudusoft.gsqlparser.pp2.island.IncompleteReason#MISSING_END}) are still
032 * laid out — their {@code WHEN}/{@code ELSE} break, and no {@code END} is
033 * fabricated. Nested CASE expressions are handled because each is its own island.
034 *
035 * <p>Priority {@link LayoutPriorities#CASE_ANDOR}. Needs the S22 islands on the
036 * context. Iterative (no recursion); read-only.
037 *
038 * <p>Plan reference: §7.3/S27, §7.4/S27.
039 */
040public final class CaseRules implements LayoutRule {
041
042    @Override
043    public int priority() { return LayoutPriorities.CASE_ANDOR; }
044
045    @Override
046    public String name() { return "CaseRules"; }
047
048    @Override
049    public void apply(LayoutContext context) {
050        List<IslandScope> islands = context.getIslands();
051        if (islands == null || islands.isEmpty()) return;
052        Pp2TokenStream stream = context.getStream();
053        int n = stream.size();
054        for (IslandScope island : islands) {
055            if (island.getKind() != IslandKind.CASE) continue;
056            int start = island.getStartIndex();
057            int end = Math.min(island.getEndIndex(), n - 1);
058            for (int j = start + 1; j <= end; j++) {
059                String u = upper(stream.get(j));
060                boolean isEnd = "END".equals(u) && j == island.getEndIndex() && island.isComplete();
061                if ("WHEN".equals(u) || "ELSE".equals(u) || isEnd) {
062                    context.requestLinebreaksBefore(j, 1);
063                    context.requestBlanksBefore(j, 0);
064                }
065            }
066        }
067    }
068
069    private static String upper(Pp2Token t) {
070        if (t.getSourceToken().tokentype != ETokenType.ttkeyword) return "";
071        String s = t.getText();
072        return s == null ? "" : s.toUpperCase(Locale.ROOT);
073    }
074}