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}