| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.calcite.util; |
| |
| import java.io.FilterWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| |
| /** |
| * A helper class for generating formatted text. StackWriter keeps track of |
| * nested formatting state like indentation level and quote escaping. Typically, |
| * it is inserted between a PrintWriter and the real Writer; directives are |
| * passed straight through the PrintWriter via the write method, as in the |
| * following example: |
| * |
| * <blockquote><pre><code> |
| * StringWriter sw = new StringWriter(); |
| * StackWriter stackw = new StackWriter(sw, StackWriter.INDENT_SPACE4); |
| * PrintWriter pw = new PrintWriter(stackw); |
| * pw.write(StackWriter.INDENT); |
| * pw.print("execute remote(link_name,"); |
| * pw.write(StackWriter.OPEN_SQL_STRING_LITERAL); |
| * pw.println(); |
| * pw.write(StackWriter.INDENT); |
| * pw.println("select * from t where c > 'alabama'"); |
| * pw.write(StackWriter.OUTDENT); |
| * pw.write(StackWriter.CLOSE_SQL_STRING_LITERAL); |
| * pw.println(");"); |
| * pw.write(StackWriter.OUTDENT); |
| * pw.close(); |
| * System.out.println(sw.toString()); |
| * </code></pre></blockquote> |
| * |
| * <p>which produces the following output: |
| * |
| * <blockquote><pre><code> |
| * execute remote(link_name,' |
| * select * from t where c > ''alabama'' |
| * '); |
| * </code></pre></blockquote> |
| */ |
| public class StackWriter extends FilterWriter { |
| //~ Static fields/initializers --------------------------------------------- |
| |
| /** |
| * Directive for increasing the indentation level. |
| */ |
| public static final int INDENT = 0xF0000001; |
| |
| /** |
| * Directive for decreasing the indentation level. |
| */ |
| public static final int OUTDENT = 0xF0000002; |
| |
| /** |
| * Directive for beginning an SQL string literal. |
| */ |
| public static final int OPEN_SQL_STRING_LITERAL = 0xF0000003; |
| |
| /** |
| * Directive for ending an SQL string literal. |
| */ |
| public static final int CLOSE_SQL_STRING_LITERAL = 0xF0000004; |
| |
| /** |
| * Directive for beginning an SQL identifier. |
| */ |
| public static final int OPEN_SQL_IDENTIFIER = 0xF0000005; |
| |
| /** |
| * Directive for ending an SQL identifier. |
| */ |
| public static final int CLOSE_SQL_IDENTIFIER = 0xF0000006; |
| |
| /** |
| * Tab indentation. |
| */ |
| public static final String INDENT_TAB = "\t"; |
| |
| /** |
| * Four-space indentation. |
| */ |
| public static final String INDENT_SPACE4 = " "; |
| private static final Character SINGLE_QUOTE = '\''; |
| private static final Character DOUBLE_QUOTE = '"'; |
| |
| //~ Instance fields -------------------------------------------------------- |
| |
| private int indentationDepth; |
| private String indentation; |
| private boolean needIndent; |
| private final Deque<Character> quoteStack = new ArrayDeque<>(); |
| |
| //~ Constructors ----------------------------------------------------------- |
| |
| /** |
| * Creates a new StackWriter on top of an existing Writer, with the |
| * specified string to be used for each level of indentation. |
| * |
| * @param writer underlying writer |
| * @param indentation indentation unit such as {@link #INDENT_TAB} or |
| * {@link #INDENT_SPACE4} |
| */ |
| public StackWriter(Writer writer, String indentation) { |
| super(writer); |
| this.indentation = indentation; |
| } |
| |
| //~ Methods ---------------------------------------------------------------- |
| |
| private void indentIfNeeded() throws IOException { |
| if (needIndent) { |
| for (int i = 0; i < indentationDepth; i++) { |
| out.write(indentation); |
| } |
| needIndent = false; |
| } |
| } |
| |
| private void writeQuote(Character quoteChar) throws IOException { |
| indentIfNeeded(); |
| int n = 1; |
| for (Character quote : quoteStack) { |
| if (quote.equals(quoteChar)) { |
| n *= 2; |
| } |
| } |
| for (int i = 0; i < n; i++) { |
| out.write(quoteChar); |
| } |
| } |
| |
| private void pushQuote(Character quoteChar) throws IOException { |
| writeQuote(quoteChar); |
| quoteStack.push(quoteChar); |
| } |
| |
| private void popQuote(Character quoteChar) throws IOException { |
| final Character pop = quoteStack.pop(); |
| assert pop.equals(quoteChar); |
| writeQuote(quoteChar); |
| } |
| |
| // implement Writer |
| public void write(int c) throws IOException { |
| switch (c) { |
| case INDENT: |
| indentationDepth++; |
| break; |
| case OUTDENT: |
| indentationDepth--; |
| break; |
| case OPEN_SQL_STRING_LITERAL: |
| pushQuote(SINGLE_QUOTE); |
| break; |
| case CLOSE_SQL_STRING_LITERAL: |
| popQuote(SINGLE_QUOTE); |
| break; |
| case OPEN_SQL_IDENTIFIER: |
| pushQuote(DOUBLE_QUOTE); |
| break; |
| case CLOSE_SQL_IDENTIFIER: |
| popQuote(DOUBLE_QUOTE); |
| break; |
| case '\n': |
| out.write(c); |
| needIndent = true; |
| break; |
| case '\r': |
| |
| // NOTE jvs 3-Jan-2006: suppress indentIfNeeded() in this case |
| // so that we don't get spurious diffs on Windows vs. Linux |
| out.write(c); |
| break; |
| case '\'': |
| writeQuote(SINGLE_QUOTE); |
| break; |
| case '"': |
| writeQuote(DOUBLE_QUOTE); |
| break; |
| default: |
| indentIfNeeded(); |
| out.write(c); |
| break; |
| } |
| } |
| |
| // implement Writer |
| public void write(char[] cbuf, int off, int len) throws IOException { |
| // TODO: something more efficient using searches for |
| // special characters |
| for (int i = off; i < (off + len); i++) { |
| write(cbuf[i]); |
| } |
| } |
| |
| // implement Writer |
| public void write(String str, int off, int len) throws IOException { |
| // TODO: something more efficient using searches for |
| // special characters |
| for (int i = off; i < (off + len); i++) { |
| write(str.charAt(i)); |
| } |
| } |
| |
| /** |
| * Writes an SQL string literal. |
| * |
| * @param pw PrintWriter on which to write |
| * @param s text of literal |
| */ |
| public static void printSqlStringLiteral(PrintWriter pw, String s) { |
| pw.write(OPEN_SQL_STRING_LITERAL); |
| pw.print(s); |
| pw.write(CLOSE_SQL_STRING_LITERAL); |
| } |
| |
| /** |
| * Writes an SQL identifier. |
| * |
| * @param pw PrintWriter on which to write |
| * @param s identifier |
| */ |
| public static void printSqlIdentifier(PrintWriter pw, String s) { |
| pw.write(OPEN_SQL_IDENTIFIER); |
| pw.print(s); |
| pw.write(CLOSE_SQL_IDENTIFIER); |
| } |
| } |