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