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