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