001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle;
021
022import java.io.File;
023import java.io.IOException;
024import java.nio.charset.StandardCharsets;
025import java.util.Locale;
026import java.util.regex.Pattern;
027
028import antlr.RecognitionException;
029import antlr.TokenStreamException;
030import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
031import com.puppycrawl.tools.checkstyle.api.DetailAST;
032import com.puppycrawl.tools.checkstyle.api.DetailNode;
033import com.puppycrawl.tools.checkstyle.api.FileContents;
034import com.puppycrawl.tools.checkstyle.api.FileText;
035import com.puppycrawl.tools.checkstyle.api.TokenTypes;
036import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
037import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
038
039/**
040 * Class for printing AST to String.
041 * @author Vladislav Lisetskii
042 */
043public final class AstTreeStringPrinter {
044
045    /**
046     * Enum to be used for test if comments should be printed.
047     */
048    public enum PrintOptions {
049        /**
050         * Comments has to be printed.
051         */
052        WITH_COMMENTS,
053        /**
054         * Comments has NOT to be printed.
055         */
056        WITHOUT_COMMENTS
057    }
058
059    /** Newline pattern. */
060    private static final Pattern NEWLINE = Pattern.compile("\n");
061    /** Return pattern. */
062    private static final Pattern RETURN = Pattern.compile("\r");
063    /** Tab pattern. */
064    private static final Pattern TAB = Pattern.compile("\t");
065
066    /** OS specific line separator. */
067    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
068
069    /** Prevent instances. */
070    private AstTreeStringPrinter() {
071        // no code
072    }
073
074    /**
075     * Parse a file and print the parse tree.
076     * @param file the file to print.
077     * @param withComments true to include comments to AST
078     * @return the AST of the file in String form.
079     * @throws IOException if the file could not be read.
080     * @throws CheckstyleException if the file is not a Java source.
081     */
082    public static String printFileAst(File file, PrintOptions withComments)
083            throws IOException, CheckstyleException {
084        return printTree(parseFile(file, withComments));
085    }
086
087    /**
088     * Prints full AST (java + comments + javadoc) of the java file.
089     * @param file java file
090     * @return Full tree
091     * @throws IOException Failed to open a file
092     * @throws CheckstyleException error while parsing the file
093     */
094    public static String printJavaAndJavadocTree(File file)
095            throws IOException, CheckstyleException {
096        final DetailAST tree = parseFile(file, PrintOptions.WITH_COMMENTS);
097        return printJavaAndJavadocTree(tree);
098    }
099
100    /**
101     * Prints full tree (java + comments + javadoc) of the DetailAST.
102     * @param ast root DetailAST
103     * @return Full tree
104     */
105    private static String printJavaAndJavadocTree(DetailAST ast) {
106        final StringBuilder messageBuilder = new StringBuilder(1024);
107        DetailAST node = ast;
108        while (node != null) {
109            messageBuilder.append(getIndentation(node))
110                .append(getNodeInfo(node))
111                .append(LINE_SEPARATOR);
112            if (node.getType() == TokenTypes.COMMENT_CONTENT
113                    && JavadocUtils.isJavadocComment(node.getParent())) {
114                final String javadocTree = parseAndPrintJavadocTree(node);
115                messageBuilder.append(javadocTree);
116            }
117            else {
118                messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild()));
119            }
120            node = node.getNextSibling();
121        }
122        return messageBuilder.toString();
123    }
124
125    /**
126     * Parses block comment as javadoc and prints its tree.
127     * @param node block comment begin
128     * @return string javadoc tree
129     */
130    private static String parseAndPrintJavadocTree(DetailAST node) {
131        final DetailAST javadocBlock = node.getParent();
132        final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock);
133
134        String baseIndentation = getIndentation(node);
135        baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2);
136        final String rootPrefix = baseIndentation + "   `--";
137        final String prefix = baseIndentation + "       ";
138        return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix);
139    }
140
141    /**
142     * Parse a file and print the parse tree.
143     * @param text the text to parse.
144     * @param withComments true to include comments to AST
145     * @return the AST of the file in String form.
146     * @throws CheckstyleException if the file is not a Java source.
147     */
148    public static String printAst(FileText text,
149                                  PrintOptions withComments) throws CheckstyleException {
150        return printTree(parseFileText(text, withComments));
151    }
152
153    /**
154     * Print AST.
155     * @param ast the root AST node.
156     * @return string AST.
157     */
158    private static String printTree(DetailAST ast) {
159        final StringBuilder messageBuilder = new StringBuilder(1024);
160        DetailAST node = ast;
161        while (node != null) {
162            messageBuilder.append(getIndentation(node))
163                    .append(getNodeInfo(node))
164                    .append(LINE_SEPARATOR)
165                    .append(printTree(node.getFirstChild()));
166            node = node.getNextSibling();
167        }
168        return messageBuilder.toString();
169    }
170
171    /**
172     * Get string representation of the node as token name,
173     * node text, line number and column number.
174     * @param node DetailAST
175     * @return node info
176     */
177    private static String getNodeInfo(DetailAST node) {
178        return TokenUtils.getTokenName(node.getType())
179                + " -> " + escapeAllControlChars(node.getText())
180                + " [" + node.getLineNo() + ':' + node.getColumnNo() + ']';
181    }
182
183    /**
184     * Get indentation for an AST node.
185     * @param ast the AST to get the indentation for.
186     * @return the indentation in String format.
187     */
188    private static String getIndentation(DetailAST ast) {
189        final boolean isLastChild = ast.getNextSibling() == null;
190        DetailAST node = ast;
191        final StringBuilder indentation = new StringBuilder(1024);
192        while (node.getParent() != null) {
193            node = node.getParent();
194            if (node.getParent() == null) {
195                if (isLastChild) {
196                    // only ASCII symbols must be used due to
197                    // problems with running tests on Windows
198                    indentation.append("`--");
199                }
200                else {
201                    indentation.append("|--");
202                }
203            }
204            else {
205                if (node.getNextSibling() == null) {
206                    indentation.insert(0, "    ");
207                }
208                else {
209                    indentation.insert(0, "|   ");
210                }
211            }
212        }
213        return indentation.toString();
214    }
215
216    /**
217     * Replace all control chars with escaped symbols.
218     * @param text the String to process.
219     * @return the processed String with all control chars escaped.
220     */
221    private static String escapeAllControlChars(String text) {
222        final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
223        final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
224        return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
225    }
226
227    /**
228     * Parse a file and return the parse tree.
229     * @param file the file to parse.
230     * @param withComments true to include comment nodes to the tree
231     * @return the root node of the parse tree.
232     * @throws IOException if the file could not be read.
233     * @throws CheckstyleException if the file is not a Java source.
234     */
235    private static DetailAST parseFile(File file, PrintOptions withComments)
236            throws IOException, CheckstyleException {
237        final FileText text = new FileText(file.getAbsoluteFile(),
238            System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
239        return parseFileText(text, withComments);
240    }
241
242    /**
243     * Parse a text and return the parse tree.
244     * @param text the text to parse.
245     * @param withComments true to include comment nodes to the tree
246     * @return the root node of the parse tree.
247     * @throws CheckstyleException if the file is not a Java source.
248     */
249    private static DetailAST parseFileText(FileText text, PrintOptions withComments)
250            throws CheckstyleException {
251        final FileContents contents = new FileContents(text);
252        final DetailAST result;
253        try {
254            if (withComments == PrintOptions.WITH_COMMENTS) {
255                result = TreeWalker.parseWithComments(contents);
256            }
257            else {
258                result = TreeWalker.parse(contents);
259            }
260        }
261        catch (RecognitionException | TokenStreamException ex) {
262            final String exceptionMsg = String.format(Locale.ROOT,
263                "%s occurred during the analysis of file %s.",
264                ex.getClass().getSimpleName(), text.getFile().getPath());
265            throw new CheckstyleException(exceptionMsg, ex);
266        }
267
268        return result;
269    }
270}