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