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