001package gudusoft.gsqlparser.demos.prettyscore; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.ETokenType; 005import gudusoft.gsqlparser.TGSqlParser; 006import gudusoft.gsqlparser.TSourceToken; 007import gudusoft.gsqlparser.TSourceTokenList; 008 009import java.util.ArrayList; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Random; 013import java.util.Set; 014 015/** 016 * Produces layout-degraded variants of a SQL text that keep every token 017 * (so the fidelity gate still passes) but destroy the layout in a known way. 018 * Used by the monotonicity tests: a sane scorer must rank 019 * {@code formatted > randomIndent > minified} and {@code formatted > oneTokenPerLine}. 020 * 021 * <p>Whitespace only: no token text is ever changed. Lines that lie inside a 022 * multi-line token (block comment, string) are left untouched by 023 * {@link #randomIndent} so comments stay byte-exact. 024 */ 025public final class LayoutDegrader { 026 027 private LayoutDegrader() { 028 } 029 030 /** Every token separated by one space on one line (a line comment forces a line break after it). */ 031 public static String minify(String sql, EDbVendor vendor) { 032 StringBuilder sb = new StringBuilder(sql.length()); 033 boolean needBreak = false; 034 for (TSourceToken t : solidAndCommentTokens(sql, vendor)) { 035 if (sb.length() > 0) sb.append(needBreak ? '\n' : ' '); 036 sb.append(t.toString()); 037 needBreak = t.tokentype == ETokenType.ttsimplecomment || t.tokentype == ETokenType.ttCPPComment; 038 } 039 return sb.toString(); 040 } 041 042 /** Every token on its own line, no indentation. */ 043 public static String oneTokenPerLine(String sql, EDbVendor vendor) { 044 StringBuilder sb = new StringBuilder(sql.length() * 2); 045 for (TSourceToken t : solidAndCommentTokens(sql, vendor)) { 046 if (sb.length() > 0) sb.append('\n'); 047 sb.append(t.toString()); 048 } 049 return sb.toString(); 050 } 051 052 /** Keep the line structure, replace every line's indent by a random 0..{@code maxIndent} spaces. */ 053 public static String randomIndent(String sql, EDbVendor vendor, long seed, int maxIndent) { 054 Set<Integer> protectedLines = linesInsideMultiLineTokens(sql, vendor); 055 String[] lines = sql.split("\n", -1); 056 Random rnd = new Random(seed); 057 StringBuilder sb = new StringBuilder(sql.length() + lines.length * 4); 058 for (int i = 0; i < lines.length; i++) { 059 String l = lines[i]; 060 if (i > 0) sb.append('\n'); 061 if (protectedLines.contains(i) || l.trim().isEmpty()) { 062 sb.append(l); 063 continue; 064 } 065 int k = 0; 066 while (k < l.length() && (l.charAt(k) == ' ' || l.charAt(k) == '\t')) k++; 067 int indent = rnd.nextInt(maxIndent + 1); 068 for (int s = 0; s < indent; s++) sb.append(' '); 069 sb.append(l.substring(k)); 070 } 071 return sb.toString(); 072 } 073 074 static List<TSourceToken> solidAndCommentTokens(String sql, EDbVendor vendor) { 075 TGSqlParser p = new TGSqlParser(vendor); 076 p.sqltext = sql; 077 p.tokenizeSqltext(); 078 TSourceTokenList list = p.getSourcetokenlist(); 079 List<TSourceToken> out = new ArrayList<TSourceToken>(); 080 long lastEnd = -1; 081 for (int i = 0; i < list.size(); i++) { 082 TSourceToken t = list.get(i); 083 if (t == null) continue; 084 String text = t.toString(); 085 if (text == null || text.isEmpty()) continue; 086 if (t.tokentype == ETokenType.ttwhitespace || t.tokentype == ETokenType.ttreturn) continue; 087 if (t.offset < lastEnd) continue; // phantom inner token (see pp2 TokenCoverage) 088 out.add(t); 089 lastEnd = t.offset + text.length(); 090 } 091 return out; 092 } 093 094 private static Set<Integer> linesInsideMultiLineTokens(String sql, EDbVendor vendor) { 095 Set<Integer> lines = new HashSet<Integer>(); 096 for (TSourceToken t : solidAndCommentTokens(sql, vendor)) { 097 String text = t.toString(); 098 int nl = 0; 099 for (int i = 0; i < text.length(); i++) if (text.charAt(i) == '\n') nl++; 100 if (nl == 0) continue; 101 int line = 0; 102 for (int i = 0; i < t.offset && i < sql.length(); i++) if (sql.charAt(i) == '\n') line++; 103 for (int k = 1; k <= nl; k++) lines.add(line + k); 104 } 105 return lines; 106 } 107}