001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.grammars.GeneratedJavaLexer; 040import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer; 041import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 042 043/** 044 * Helper methods to parse java source files. 045 * 046 * @author Oliver Burn 047 * @author Pavel Bludov 048 */ 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 * @param contents contains the contents of the file 075 * @return the root of the AST 076 * @throws CheckstyleException if the contents is not a valid Java source 077 */ 078 public static DetailAST parse(FileContents contents) 079 throws CheckstyleException { 080 final String fullText = contents.getText().getFullText().toString(); 081 final Reader reader = new StringReader(fullText); 082 final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader); 083 lexer.setCommentListener(contents); 084 lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken"); 085 086 final TokenStreamHiddenTokenFilter filter = new TokenStreamHiddenTokenFilter(lexer); 087 filter.hide(TokenTypes.SINGLE_LINE_COMMENT); 088 filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN); 089 090 final GeneratedJavaRecognizer parser = new GeneratedJavaRecognizer(filter); 091 parser.setFilename(contents.getFileName()); 092 parser.setASTNodeClass(DetailAST.class.getName()); 093 try { 094 parser.compilationUnit(); 095 } 096 catch (RecognitionException | TokenStreamException ex) { 097 final String exceptionMsg = String.format(Locale.ROOT, 098 "%s occurred while parsing file %s.", 099 ex.getClass().getSimpleName(), contents.getFileName()); 100 throw new CheckstyleException(exceptionMsg, ex); 101 } 102 103 return (DetailAST) parser.getAST(); 104 } 105 106 /** 107 * Parse a text and return the parse tree. 108 * @param text the text to parse 109 * @param options {@link Options} to control inclusion of comment nodes 110 * @return the root node of the parse tree 111 * @throws CheckstyleException if the text is not a valid Java source 112 */ 113 public static DetailAST parseFileText(FileText text, Options options) 114 throws CheckstyleException { 115 final FileContents contents = new FileContents(text); 116 DetailAST ast = parse(contents); 117 if (options == Options.WITH_COMMENTS) { 118 ast = appendHiddenCommentNodes(ast); 119 } 120 return ast; 121 } 122 123 /** 124 * Parses Java source file. 125 * @param file the file to parse 126 * @param options {@link Options} to control inclusion of comment nodes 127 * @return DetailAST tree 128 * @throws IOException if the file could not be read 129 * @throws CheckstyleException if the file is not a valid Java source file 130 */ 131 public static DetailAST parseFile(File file, Options options) 132 throws IOException, CheckstyleException { 133 final FileText text = new FileText(file.getAbsoluteFile(), 134 System.getProperty("file.encoding", StandardCharsets.UTF_8.name())); 135 return parseFileText(text, options); 136 } 137 138 /** 139 * Appends comment nodes to existing AST. 140 * It traverses each node in AST, looks for hidden comment tokens 141 * and appends found comment tokens as nodes in AST. 142 * @param root of AST 143 * @return root of AST with comment nodes 144 */ 145 public static DetailAST appendHiddenCommentNodes(DetailAST root) { 146 DetailAST result = root; 147 DetailAST curNode = root; 148 DetailAST lastNode = root; 149 150 while (curNode != null) { 151 if (isPositionGreater(curNode, lastNode)) { 152 lastNode = curNode; 153 } 154 155 CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore(); 156 DetailAST currentSibling = curNode; 157 while (tokenBefore != null) { 158 final DetailAST newCommentNode = 159 createCommentAstFromToken(tokenBefore); 160 161 currentSibling.addPreviousSibling(newCommentNode); 162 163 if (currentSibling == result) { 164 result = newCommentNode; 165 } 166 167 currentSibling = newCommentNode; 168 tokenBefore = tokenBefore.getHiddenBefore(); 169 } 170 171 DetailAST toVisit = curNode.getFirstChild(); 172 while (curNode != null && toVisit == null) { 173 toVisit = curNode.getNextSibling(); 174 if (toVisit == null) { 175 curNode = curNode.getParent(); 176 } 177 } 178 curNode = toVisit; 179 } 180 if (lastNode != null) { 181 CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter(); 182 DetailAST currentSibling = lastNode; 183 while (tokenAfter != null) { 184 final DetailAST newCommentNode = 185 createCommentAstFromToken(tokenAfter); 186 187 currentSibling.addNextSibling(newCommentNode); 188 189 currentSibling = newCommentNode; 190 tokenAfter = tokenAfter.getHiddenAfter(); 191 } 192 } 193 return result; 194 } 195 196 /** 197 * Checks if position of first DetailAST is greater than position of 198 * second DetailAST. Position is line number and column number in source file. 199 * @param ast1 first DetailAST node 200 * @param ast2 second DetailAST node 201 * @return true if position of ast1 is greater than position of ast2 202 */ 203 private static boolean isPositionGreater(DetailAST ast1, DetailAST ast2) { 204 boolean isGreater = ast1.getLineNo() > ast2.getLineNo(); 205 if (!isGreater && ast1.getLineNo() == ast2.getLineNo()) { 206 isGreater = ast1.getColumnNo() > ast2.getColumnNo(); 207 } 208 return isGreater; 209 } 210 211 /** 212 * Create comment AST from token. Depending on token type 213 * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created. 214 * @param token to create the AST 215 * @return DetailAST of comment node 216 */ 217 private static DetailAST createCommentAstFromToken(Token token) { 218 final DetailAST commentAst; 219 if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 220 commentAst = createSlCommentNode(token); 221 } 222 else { 223 commentAst = CommonUtils.createBlockCommentNode(token); 224 } 225 return commentAst; 226 } 227 228 /** 229 * Create single-line comment from token. 230 * @param token to create the AST 231 * @return DetailAST with SINGLE_LINE_COMMENT type 232 */ 233 private static DetailAST createSlCommentNode(Token token) { 234 final DetailAST slComment = new DetailAST(); 235 slComment.setType(TokenTypes.SINGLE_LINE_COMMENT); 236 slComment.setText("//"); 237 238 // column counting begins from 0 239 slComment.setColumnNo(token.getColumn() - 1); 240 slComment.setLineNo(token.getLine()); 241 242 final DetailAST slCommentContent = new DetailAST(); 243 slCommentContent.setType(TokenTypes.COMMENT_CONTENT); 244 245 // column counting begins from 0 246 // plus length of '//' 247 slCommentContent.setColumnNo(token.getColumn() - 1 + 2); 248 slCommentContent.setLineNo(token.getLine()); 249 slCommentContent.setText(token.getText()); 250 251 slComment.addChild(slCommentContent); 252 return slComment; 253 } 254 255}