001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.DetailNode;
029import com.puppycrawl.tools.checkstyle.api.FileText;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
032import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
033
034/**
035 * Class for printing AST to String.
036 * @author Vladislav Lisetskii
037 */
038public final class AstTreeStringPrinter {
039
040    /** Newline pattern. */
041    private static final Pattern NEWLINE = Pattern.compile("\n");
042    /** Return pattern. */
043    private static final Pattern RETURN = Pattern.compile("\r");
044    /** Tab pattern. */
045    private static final Pattern TAB = Pattern.compile("\t");
046
047    /** OS specific line separator. */
048    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
049
050    /** Prevent instances. */
051    private AstTreeStringPrinter() {
052        // no code
053    }
054
055    /**
056     * Parse a file and print the parse tree.
057     * @param file the file to print.
058     * @param options {@link JavaParser.Options} to control the inclusion of comment nodes.
059     * @return the AST of the file in String form.
060     * @throws IOException if the file could not be read.
061     * @throws CheckstyleException if the file is not a Java source.
062     */
063    public static String printFileAst(File file, JavaParser.Options options)
064            throws IOException, CheckstyleException {
065        return printTree(JavaParser.parseFile(file, options));
066    }
067
068    /**
069     * Prints full AST (java + comments + javadoc) of the java file.
070     * @param file java file
071     * @return Full tree
072     * @throws IOException Failed to open a file
073     * @throws CheckstyleException error while parsing the file
074     */
075    public static String printJavaAndJavadocTree(File file)
076            throws IOException, CheckstyleException {
077        final DetailAST tree = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS);
078        return printJavaAndJavadocTree(tree);
079    }
080
081    /**
082     * Prints full tree (java + comments + javadoc) of the DetailAST.
083     * @param ast root DetailAST
084     * @return Full tree
085     */
086    private static String printJavaAndJavadocTree(DetailAST ast) {
087        final StringBuilder messageBuilder = new StringBuilder(1024);
088        DetailAST node = ast;
089        while (node != null) {
090            messageBuilder.append(getIndentation(node))
091                .append(getNodeInfo(node))
092                .append(LINE_SEPARATOR);
093            if (node.getType() == TokenTypes.COMMENT_CONTENT
094                    && JavadocUtils.isJavadocComment(node.getParent())) {
095                final String javadocTree = parseAndPrintJavadocTree(node);
096                messageBuilder.append(javadocTree);
097            }
098            else {
099                messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild()));
100            }
101            node = node.getNextSibling();
102        }
103        return messageBuilder.toString();
104    }
105
106    /**
107     * Parses block comment as javadoc and prints its tree.
108     * @param node block comment begin
109     * @return string javadoc tree
110     */
111    private static String parseAndPrintJavadocTree(DetailAST node) {
112        final DetailAST javadocBlock = node.getParent();
113        final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock);
114
115        String baseIndentation = getIndentation(node);
116        baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2);
117        final String rootPrefix = baseIndentation + "   `--";
118        final String prefix = baseIndentation + "       ";
119        return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix);
120    }
121
122    /**
123     * Parse a file and print the parse tree.
124     * @param text the text to parse.
125     * @param options {@link JavaParser.Options} to control the inclusion of comment nodes.
126     * @return the AST of the file in String form.
127     * @throws CheckstyleException if the file is not a Java source.
128     */
129    public static String printAst(FileText text, JavaParser.Options options)
130            throws CheckstyleException {
131        final DetailAST ast = JavaParser.parseFileText(text, options);
132        return printTree(ast);
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(1024);
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                + " -> " + escapeAllControlChars(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(1024);
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 escaped symbols.
200     * @param text the String to process.
201     * @return the processed String with all control chars escaped.
202     */
203    private static String escapeAllControlChars(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}