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.nio.charset.StandardCharsets;
025
026import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage;
027import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.FileText;
031import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
032import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
033import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
034import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
035
036/**
037 * Parses file as javadoc DetailNode tree and prints to system output stream.
038 * @author bizmailov
039 */
040public final class DetailNodeTreeStringPrinter {
041
042    /** OS specific line separator. */
043    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
044
045    /** Prevent instances. */
046    private DetailNodeTreeStringPrinter() {
047        // no code
048    }
049
050    /**
051     * Parse a file and print the parse tree.
052     * @param file the file to print.
053     * @return parse tree as a string
054     * @throws IOException if the file could not be read.
055     */
056    public static String printFileAst(File file) throws IOException {
057        return printTree(parseFile(file), "", "");
058    }
059
060    /**
061     * Parse block comment DetailAST as Javadoc DetailNode tree.
062     * @param blockComment DetailAST
063     * @return DetailNode tree
064     */
065    public static DetailNode parseJavadocAsDetailNode(DetailAST blockComment) {
066        final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
067        final ParseStatus status = parser.parseJavadocAsDetailNode(blockComment);
068        if (status.getParseErrorMessage() != null) {
069            throw new IllegalArgumentException(getParseErrorMessage(status.getParseErrorMessage()));
070        }
071        return status.getTree();
072    }
073
074    /**
075     * Parse javadoc comment to DetailNode tree.
076     * @param javadocComment javadoc comment content
077     * @return tree
078     */
079    private static DetailNode parseJavadocAsDetailNode(String javadocComment) {
080        final DetailAST blockComment = CommonUtils.createBlockCommentNode(javadocComment);
081        return parseJavadocAsDetailNode(blockComment);
082    }
083
084    /**
085     * Builds error message base on ParseErrorMessage's message key, its arguments, etc.
086     * @param parseErrorMessage ParseErrorMessage
087     * @return error message
088     */
089    private static String getParseErrorMessage(ParseErrorMessage parseErrorMessage) {
090        final LocalizedMessage lmessage = new LocalizedMessage(
091                parseErrorMessage.getLineNumber(),
092                "com.puppycrawl.tools.checkstyle.checks.javadoc.messages",
093                parseErrorMessage.getMessageKey(),
094                parseErrorMessage.getMessageArguments(),
095                "",
096                DetailNodeTreeStringPrinter.class,
097                null);
098        return "[ERROR:" + parseErrorMessage.getLineNumber() + "] " + lmessage.getMessage();
099    }
100
101    /**
102     * Print AST.
103     * @param ast the root AST node.
104     * @param rootPrefix prefix for the root node
105     * @param prefix prefix for other nodes
106     * @return string AST.
107     */
108    public static String printTree(DetailNode ast, String rootPrefix, String prefix) {
109        final StringBuilder messageBuilder = new StringBuilder(1024);
110        DetailNode node = ast;
111        while (node != null) {
112            if (node.getType() == JavadocTokenTypes.JAVADOC) {
113                messageBuilder.append(rootPrefix);
114            }
115            else {
116                messageBuilder.append(prefix);
117            }
118            messageBuilder.append(getIndentation(node))
119                    .append(JavadocUtils.getTokenName(node.getType())).append(" -> ")
120                    .append(JavadocUtils.escapeAllControlChars(node.getText())).append(" [")
121                    .append(node.getLineNumber()).append(':').append(node.getColumnNumber())
122                    .append(']').append(LINE_SEPARATOR)
123                    .append(printTree(JavadocUtils.getFirstChild(node), rootPrefix, prefix));
124            node = JavadocUtils.getNextSibling(node);
125        }
126        return messageBuilder.toString();
127    }
128
129    /**
130     * Get indentation for a node.
131     * @param node the DetailNode to get the indentation for.
132     * @return the indentation in String format.
133     */
134    private static String getIndentation(DetailNode node) {
135        final boolean isLastChild = JavadocUtils.getNextSibling(node) == null;
136        DetailNode currentNode = node;
137        final StringBuilder indentation = new StringBuilder(1024);
138        while (currentNode.getParent() != null) {
139            currentNode = currentNode.getParent();
140            if (currentNode.getParent() == null) {
141                if (isLastChild) {
142                    // only ASCII symbols must be used due to
143                    // problems with running tests on Windows
144                    indentation.append("`--");
145                }
146                else {
147                    indentation.append("|--");
148                }
149            }
150            else {
151                if (JavadocUtils.getNextSibling(currentNode) == null) {
152                    indentation.insert(0, "    ");
153                }
154                else {
155                    indentation.insert(0, "|   ");
156                }
157            }
158        }
159        return indentation.toString();
160    }
161
162    /**
163     * Parse a file and return the parse tree.
164     * @param file the file to parse.
165     * @return the root node of the parse tree.
166     * @throws IOException if the file could not be read.
167     */
168    private static DetailNode parseFile(File file) throws IOException {
169        final FileText text = new FileText(file.getAbsoluteFile(),
170            System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
171        return parseJavadocAsDetailNode(text.getFullText().toString());
172    }
173
174}