001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.io.Reader; 025import java.io.StringReader; 026import java.nio.charset.StandardCharsets; 027import java.util.Locale; 028 029import antlr.CommonASTWithHiddenTokens; 030import antlr.CommonHiddenStreamToken; 031import antlr.RecognitionException; 032import antlr.Token; 033import antlr.TokenStreamException; 034import antlr.TokenStreamHiddenTokenFilter; 035import antlr.TokenStreamSelector; 036import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 037import com.puppycrawl.tools.checkstyle.api.DetailAST; 038import com.puppycrawl.tools.checkstyle.api.FileContents; 039import com.puppycrawl.tools.checkstyle.api.FileText; 040import com.puppycrawl.tools.checkstyle.api.TokenTypes; 041import com.puppycrawl.tools.checkstyle.grammar.GeneratedJavaLexer; 042import com.puppycrawl.tools.checkstyle.grammar.GeneratedJavaRecognizer; 043import com.puppycrawl.tools.checkstyle.grammar.GeneratedTextBlockLexer; 044import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 045 046/** 047 * Helper methods to parse java source files. 048 * 049 */ 050// -@cs[ClassDataAbstractionCoupling] No way to split up class usage. 051public final class JavaParser { 052 053 /** 054 * Enum to be used for test if comments should be used. 055 */ 056 public enum Options { 057 058 /** 059 * Comments nodes should be processed. 060 */ 061 WITH_COMMENTS, 062 063 /** 064 * Comments nodes should be ignored. 065 */ 066 WITHOUT_COMMENTS, 067 068 } 069 070 /** Stop instances being created. **/ 071 private JavaParser() { 072 } 073 074 /** 075 * Static helper method to parses a Java source file. 076 * 077 * @param contents contains the contents of the file 078 * @return the root of the AST 079 * @throws CheckstyleException if the contents is not a valid Java source 080 */ 081 public static DetailAST parse(FileContents contents) 082 throws CheckstyleException { 083 final String fullText = contents.getText().getFullText().toString(); 084 final Reader reader = new StringReader(fullText); 085 final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader); 086 lexer.setCommentListener(contents); 087 088 final GeneratedTextBlockLexer textBlockLexer = 089 new GeneratedTextBlockLexer(lexer.getInputState()); 090 091 final String tokenObjectClass = "antlr.CommonHiddenStreamToken"; 092 lexer.setTokenObjectClass(tokenObjectClass); 093 textBlockLexer.setTokenObjectClass(tokenObjectClass); 094 095 final TokenStreamHiddenTokenFilter filter = new TokenStreamHiddenTokenFilter(lexer); 096 filter.hide(TokenTypes.SINGLE_LINE_COMMENT); 097 filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN); 098 099 final TokenStreamSelector selector = new TokenStreamSelector(); 100 lexer.selector = selector; 101 textBlockLexer.selector = selector; 102 selector.addInputStream(textBlockLexer, "textBlockLexer"); 103 selector.select(filter); 104 105 final GeneratedJavaRecognizer parser = new GeneratedJavaRecognizer(selector) { 106 @Override 107 public void reportError(RecognitionException ex) { 108 throw new IllegalStateException(ex); 109 } 110 }; 111 parser.setFilename(contents.getFileName()); 112 parser.setASTNodeClass(DetailAstImpl.class.getName()); 113 try { 114 parser.compilationUnit(); 115 } 116 catch (RecognitionException | TokenStreamException | IllegalStateException ex) { 117 final String exceptionMsg = String.format(Locale.ROOT, 118 "%s occurred while parsing file %s.", 119 ex.getClass().getSimpleName(), contents.getFileName()); 120 throw new CheckstyleException(exceptionMsg, ex); 121 } 122 123 return (DetailAST) parser.getAST(); 124 } 125 126 /** 127 * Parse a text and return the parse tree. 128 * 129 * @param text the text to parse 130 * @param options {@link Options} to control inclusion of comment nodes 131 * @return the root node of the parse tree 132 * @throws CheckstyleException if the text is not a valid Java source 133 */ 134 public static DetailAST parseFileText(FileText text, Options options) 135 throws CheckstyleException { 136 final FileContents contents = new FileContents(text); 137 DetailAST ast = parse(contents); 138 if (options == Options.WITH_COMMENTS) { 139 ast = appendHiddenCommentNodes(ast); 140 } 141 return ast; 142 } 143 144 /** 145 * Parses Java source file. 146 * 147 * @param file the file to parse 148 * @param options {@link Options} to control inclusion of comment nodes 149 * @return DetailAST tree 150 * @throws IOException if the file could not be read 151 * @throws CheckstyleException if the file is not a valid Java source file 152 */ 153 public static DetailAST parseFile(File file, Options options) 154 throws IOException, CheckstyleException { 155 final FileText text = new FileText(file.getAbsoluteFile(), 156 StandardCharsets.UTF_8.name()); 157 return parseFileText(text, options); 158 } 159 160 /** 161 * Appends comment nodes to existing AST. 162 * It traverses each node in AST, looks for hidden comment tokens 163 * and appends found comment tokens as nodes in AST. 164 * 165 * @param root of AST 166 * @return root of AST with comment nodes 167 */ 168 public static DetailAST appendHiddenCommentNodes(DetailAST root) { 169 DetailAST result = root; 170 DetailAST curNode = root; 171 DetailAST lastNode = root; 172 173 while (curNode != null) { 174 lastNode = curNode; 175 176 CommonHiddenStreamToken tokenBefore = ((CommonASTWithHiddenTokens) curNode) 177 .getHiddenBefore(); 178 DetailAST currentSibling = curNode; 179 while (tokenBefore != null) { 180 final DetailAST newCommentNode = 181 createCommentAstFromToken(tokenBefore); 182 183 ((DetailAstImpl) currentSibling).addPreviousSibling(newCommentNode); 184 185 if (currentSibling == result) { 186 result = newCommentNode; 187 } 188 189 currentSibling = newCommentNode; 190 tokenBefore = tokenBefore.getHiddenBefore(); 191 } 192 193 DetailAST toVisit = curNode.getFirstChild(); 194 while (curNode != null && toVisit == null) { 195 toVisit = curNode.getNextSibling(); 196 curNode = curNode.getParent(); 197 } 198 curNode = toVisit; 199 } 200 if (lastNode != null) { 201 CommonHiddenStreamToken tokenAfter = ((CommonASTWithHiddenTokens) lastNode) 202 .getHiddenAfter(); 203 DetailAST currentSibling = lastNode; 204 while (tokenAfter != null) { 205 final DetailAST newCommentNode = 206 createCommentAstFromToken(tokenAfter); 207 208 ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode); 209 210 currentSibling = newCommentNode; 211 tokenAfter = tokenAfter.getHiddenAfter(); 212 } 213 } 214 return result; 215 } 216 217 /** 218 * Create comment AST from token. Depending on token type 219 * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created. 220 * 221 * @param token to create the AST 222 * @return DetailAST of comment node 223 */ 224 private static DetailAST createCommentAstFromToken(Token token) { 225 final DetailAST commentAst; 226 if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 227 commentAst = createSlCommentNode(token); 228 } 229 else { 230 commentAst = CommonUtil.createBlockCommentNode(token); 231 } 232 return commentAst; 233 } 234 235 /** 236 * Create single-line comment from token. 237 * 238 * @param token to create the AST 239 * @return DetailAST with SINGLE_LINE_COMMENT type 240 */ 241 private static DetailAST createSlCommentNode(Token token) { 242 final DetailAstImpl slComment = new DetailAstImpl(); 243 slComment.setType(TokenTypes.SINGLE_LINE_COMMENT); 244 slComment.setText("//"); 245 246 // column counting begins from 0 247 slComment.setColumnNo(token.getColumn() - 1); 248 slComment.setLineNo(token.getLine()); 249 250 final DetailAstImpl slCommentContent = new DetailAstImpl(); 251 slCommentContent.setType(TokenTypes.COMMENT_CONTENT); 252 253 // column counting begins from 0 254 // plus length of '//' 255 slCommentContent.setColumnNo(token.getColumn() - 1 + 2); 256 slCommentContent.setLineNo(token.getLine()); 257 slCommentContent.setText(token.getText()); 258 259 slComment.addChild(slCommentContent); 260 return slComment; 261 } 262 263}