001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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}