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