001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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; 024 025import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage; 026import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus; 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.JavadocTokenTypes; 031import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 034 035/** 036 * Parses file as javadoc DetailNode tree and prints to system output stream. 037 * @author bizmailov 038 */ 039public final class DetailNodeTreeStringPrinter { 040 041 /** OS specific line separator. */ 042 private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 043 044 /** Symbols with which javadoc starts. */ 045 private static final String JAVADOC_START = "/**"; 046 047 /** Prevent instances. */ 048 private DetailNodeTreeStringPrinter() { 049 // no code 050 } 051 052 /** 053 * Parse a file and print the parse tree. 054 * @param file the file to print. 055 * @return parse tree as a string 056 * @throws IOException if the file could not be read. 057 */ 058 public static String printFileAst(File file) throws IOException { 059 return printTree(parseFile(file), "", ""); 060 } 061 062 /** 063 * Parse block comment DetailAST as Javadoc DetailNode tree. 064 * @param blockComment DetailAST 065 * @return DetailNode tree 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 * @param javadocComment javadoc comment content 079 * @return tree 080 */ 081 private static DetailNode parseJavadocAsDetailNode(String javadocComment) { 082 final DetailAST blockComment = createFakeBlockComment(javadocComment); 083 return parseJavadocAsDetailNode(blockComment); 084 } 085 086 /** 087 * Builds error message base on ParseErrorMessage's message key, its arguments, etc. 088 * @param parseErrorMessage ParseErrorMessage 089 * @return error message 090 */ 091 private static String getParseErrorMessage(ParseErrorMessage parseErrorMessage) { 092 final LocalizedMessage lmessage = new LocalizedMessage( 093 parseErrorMessage.getLineNumber(), 094 "com.puppycrawl.tools.checkstyle.checks.javadoc.messages", 095 parseErrorMessage.getMessageKey(), 096 parseErrorMessage.getMessageArguments(), 097 "", 098 DetailNodeTreeStringPrinter.class, 099 null); 100 return "[ERROR:" + parseErrorMessage.getLineNumber() + "] " + lmessage.getMessage(); 101 } 102 103 /** 104 * Print AST. 105 * @param ast the root AST node. 106 * @param rootPrefix prefix for the root node 107 * @param prefix prefix for other nodes 108 * @return string AST. 109 */ 110 public static String printTree(DetailNode ast, String rootPrefix, String prefix) { 111 final StringBuilder messageBuilder = new StringBuilder(); 112 DetailNode node = ast; 113 while (node != null) { 114 if (node.getType() == JavadocTokenTypes.JAVADOC) { 115 messageBuilder.append(rootPrefix); 116 } 117 else { 118 messageBuilder.append(prefix); 119 } 120 messageBuilder.append(getIndentation(node)) 121 .append(JavadocUtils.getTokenName(node.getType())).append(" -> ") 122 .append(JavadocUtils.escapeAllControlChars(node.getText())).append(" [") 123 .append(node.getLineNumber()).append(':').append(node.getColumnNumber()) 124 .append(']').append(LINE_SEPARATOR) 125 .append(printTree(JavadocUtils.getFirstChild(node), rootPrefix, prefix)); 126 node = JavadocUtils.getNextSibling(node); 127 } 128 return messageBuilder.toString(); 129 } 130 131 /** 132 * Get indentation for a node. 133 * @param node the DetailNode to get the indentation for. 134 * @return the indentation in String format. 135 */ 136 private static String getIndentation(DetailNode node) { 137 final boolean isLastChild = JavadocUtils.getNextSibling(node) == null; 138 DetailNode currentNode = node; 139 final StringBuilder indentation = new StringBuilder(); 140 while (currentNode.getParent() != null) { 141 currentNode = currentNode.getParent(); 142 if (currentNode.getParent() == null) { 143 if (isLastChild) { 144 // only ASCII symbols must be used due to 145 // problems with running tests on Windows 146 indentation.append("`--"); 147 } 148 else { 149 indentation.append("|--"); 150 } 151 } 152 else { 153 if (JavadocUtils.getNextSibling(currentNode) == null) { 154 indentation.insert(0, " "); 155 } 156 else { 157 indentation.insert(0, "| "); 158 } 159 } 160 } 161 return indentation.toString(); 162 } 163 164 /** 165 * Parse a file and return the parse tree. 166 * @param file the file to parse. 167 * @return the root node of the parse tree. 168 * @throws IOException if the file could not be read. 169 */ 170 private static DetailNode parseFile(File file) throws IOException { 171 // Details: https://github.com/checkstyle/checkstyle/issues/3034 172 //noinspection MismatchedQueryAndUpdateOfCollection 173 final FileText text = new FileText(file.getAbsoluteFile(), 174 System.getProperty("file.encoding", "UTF-8")); 175 return parseJavadocAsDetailNode(text.getFullText().toString()); 176 } 177 178 /** 179 * Creates DetailAST block comment to pass it to the Javadoc parser. 180 * @param content comment content. 181 * @return DetailAST block comment 182 */ 183 private static DetailAST createFakeBlockComment(String content) { 184 final DetailAST blockCommentBegin = new DetailAST(); 185 blockCommentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN); 186 blockCommentBegin.setText("/*"); 187 blockCommentBegin.setLineNo(0); 188 blockCommentBegin.setColumnNo(-JAVADOC_START.length()); 189 190 final DetailAST commentContent = new DetailAST(); 191 commentContent.setType(TokenTypes.COMMENT_CONTENT); 192 commentContent.setText("*" + content); 193 commentContent.setLineNo(0); 194 // javadoc should starts at 0 column, so COMMENT_CONTENT node 195 // that contains javadoc identificator has -1 column 196 commentContent.setColumnNo(-1); 197 198 final DetailAST blockCommentEnd = new DetailAST(); 199 blockCommentEnd.setType(TokenTypes.BLOCK_COMMENT_END); 200 blockCommentEnd.setText("*/"); 201 202 blockCommentBegin.setFirstChild(commentContent); 203 commentContent.setNextSibling(blockCommentEnd); 204 return blockCommentBegin; 205 } 206 207}