View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.List;
25  
26  import org.antlr.v4.runtime.ANTLRInputStream;
27  import org.antlr.v4.runtime.BailErrorStrategy;
28  import org.antlr.v4.runtime.BaseErrorListener;
29  import org.antlr.v4.runtime.BufferedTokenStream;
30  import org.antlr.v4.runtime.CommonToken;
31  import org.antlr.v4.runtime.CommonTokenStream;
32  import org.antlr.v4.runtime.FailedPredicateException;
33  import org.antlr.v4.runtime.InputMismatchException;
34  import org.antlr.v4.runtime.NoViableAltException;
35  import org.antlr.v4.runtime.Parser;
36  import org.antlr.v4.runtime.ParserRuleContext;
37  import org.antlr.v4.runtime.RecognitionException;
38  import org.antlr.v4.runtime.Recognizer;
39  import org.antlr.v4.runtime.Token;
40  import org.antlr.v4.runtime.misc.Interval;
41  import org.antlr.v4.runtime.misc.ParseCancellationException;
42  import org.antlr.v4.runtime.tree.ParseTree;
43  import org.antlr.v4.runtime.tree.TerminalNode;
44  
45  import com.google.common.base.CaseFormat;
46  import com.puppycrawl.tools.checkstyle.api.DetailAST;
47  import com.puppycrawl.tools.checkstyle.api.DetailNode;
48  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
49  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl;
50  import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocLexer;
51  import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocParser;
52  import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
53  
54  /**
55   * Used for parsing Javadoc comment as DetailNode tree.
56   * @author bizmailov
57   *
58   */
59  public class JavadocDetailNodeParser {
60  
61      /**
62       * Message key of error message. Missed close HTML tag breaks structure
63       * of parse tree, so parser stops parsing and generates such error
64       * message. This case is special because parser prints error like
65       * {@code "no viable alternative at input 'b \n *\n'"} and it is not
66       * clear that error is about missed close HTML tag.
67       */
68      public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close";
69  
70      /**
71       * Message key of error message.
72       */
73      public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
74          "javadoc.wrong.singleton.html.tag";
75  
76      /**
77       * Parse error while rule recognition.
78       */
79      public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error";
80  
81      /**
82       * Message property key for the Unclosed HTML message.
83       */
84      public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml";
85  
86      /** Symbols with which javadoc starts. */
87      private static final String JAVADOC_START = "/**";
88  
89      /**
90       * Line number of the Block comment AST that is being parsed.
91       */
92      private int blockCommentLineNumber;
93  
94      /**
95       * Custom error listener.
96       */
97      private DescriptiveErrorListener errorListener;
98  
99      /**
100      * Parses Javadoc comment as DetailNode tree.
101      * @param javadocCommentAst
102      *        DetailAST of Javadoc comment
103      * @return DetailNode tree of Javadoc comment
104      */
105     public ParseStatus parseJavadocAsDetailNode(DetailAST javadocCommentAst) {
106         blockCommentLineNumber = javadocCommentAst.getLineNo();
107 
108         final String javadocComment = JavadocUtils.getJavadocCommentContent(javadocCommentAst);
109 
110         // Use a new error listener each time to be able to use
111         // one check instance for multiple files to be checked
112         // without getting side effects.
113         errorListener = new DescriptiveErrorListener();
114 
115         // Log messages should have line number in scope of file,
116         // not in scope of Javadoc comment.
117         // Offset is line number of beginning of Javadoc comment.
118         errorListener.setOffset(javadocCommentAst.getLineNo() - 1);
119 
120         final ParseStatus result = new ParseStatus();
121 
122         try {
123             final JavadocParser javadocParser = createJavadocParser(javadocComment);
124 
125             final ParseTree javadocParseTree = javadocParser.javadoc();
126 
127             final DetailNode tree = convertParseTreeToDetailNode(javadocParseTree);
128             // adjust first line to indent of /**
129             adjustFirstLineToJavadocIndent(tree,
130                         javadocCommentAst.getColumnNo()
131                                 + JAVADOC_START.length());
132             result.setTree(tree);
133             result.firstNonTightHtmlTag = getFirstNonTightHtmlTag(javadocParser);
134         }
135         catch (ParseCancellationException | IllegalArgumentException ex) {
136             ParseErrorMessage parseErrorMessage = null;
137 
138             if (ex.getCause() instanceof FailedPredicateException
139                     || ex.getCause() instanceof NoViableAltException) {
140                 final RecognitionException recognitionEx = (RecognitionException) ex.getCause();
141                 if (recognitionEx.getCtx() instanceof JavadocParser.HtmlTagContext) {
142                     final Token htmlTagNameStart = getMissedHtmlTag(recognitionEx);
143                     parseErrorMessage = new ParseErrorMessage(
144                             errorListener.offset + htmlTagNameStart.getLine(),
145                             MSG_JAVADOC_MISSED_HTML_CLOSE,
146                             htmlTagNameStart.getCharPositionInLine(),
147                             htmlTagNameStart.getText());
148                 }
149             }
150 
151             if (parseErrorMessage == null) {
152                 // If syntax error occurs then message is printed by error listener
153                 // and parser throws this runtime exception to stop parsing.
154                 // Just stop processing current Javadoc comment.
155                 parseErrorMessage = errorListener.getErrorMessage();
156             }
157 
158             result.setParseErrorMessage(parseErrorMessage);
159         }
160 
161         return result;
162     }
163 
164     /**
165      * Parses block comment content as javadoc comment.
166      * @param blockComment
167      *        block comment content.
168      * @return parse tree
169      * @noinspection deprecation
170      */
171     private JavadocParser createJavadocParser(String blockComment) {
172         final ANTLRInputStream input = new ANTLRInputStream(blockComment);
173 
174         final JavadocLexer lexer = new JavadocLexer(input);
175 
176         final CommonTokenStream tokens = new CommonTokenStream(lexer);
177 
178         final JavadocParser parser = new JavadocParser(tokens);
179 
180         // remove default error listeners
181         parser.removeErrorListeners();
182 
183         // add custom error listener that logs syntax errors
184         parser.addErrorListener(errorListener);
185 
186         // JavadocParserErrorStrategy stops parsing on first parse error encountered unlike the
187         // DefaultErrorStrategy used by ANTLR which rather attempts error recovery.
188         parser.setErrorHandler(new JavadocParserErrorStrategy());
189 
190         return parser;
191     }
192 
193     /**
194      * Converts ParseTree (that is generated by ANTLRv4) to DetailNode tree.
195      *
196      * @param parseTreeNode root node of ParseTree
197      * @return root of DetailNode tree
198      * @noinspection SuspiciousArrayCast
199      */
200     private DetailNode convertParseTreeToDetailNode(ParseTree parseTreeNode) {
201         final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode);
202 
203         JavadocNodeImpl currentJavadocParent = rootJavadocNode;
204         ParseTree parseTreeParent = parseTreeNode;
205 
206         while (currentJavadocParent != null) {
207             // remove unnecessary children tokens
208             if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) {
209                 currentJavadocParent
210                         .setChildren((DetailNode[]) JavadocNodeImpl.EMPTY_DETAIL_NODE_ARRAY);
211             }
212 
213             final JavadocNodeImpl[] children =
214                     (JavadocNodeImpl[]) currentJavadocParent.getChildren();
215 
216             insertChildrenNodes(children, parseTreeParent);
217 
218             if (children.length > 0) {
219                 currentJavadocParent = children[0];
220                 parseTreeParent = parseTreeParent.getChild(0);
221             }
222             else {
223                 JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
224                         .getNextSibling(currentJavadocParent);
225 
226                 ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent);
227 
228                 if (nextJavadocSibling == null) {
229                     JavadocNodeImpl tempJavadocParent =
230                             (JavadocNodeImpl) currentJavadocParent.getParent();
231 
232                     ParseTree tempParseTreeParent = parseTreeParent.getParent();
233 
234                     while (nextJavadocSibling == null && tempJavadocParent != null) {
235 
236                         nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
237                                 .getNextSibling(tempJavadocParent);
238 
239                         nextParseTreeSibling = getNextSibling(tempParseTreeParent);
240 
241                         tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent();
242                         tempParseTreeParent = tempParseTreeParent.getParent();
243                     }
244                 }
245                 currentJavadocParent = nextJavadocSibling;
246                 parseTreeParent = nextParseTreeSibling;
247             }
248         }
249 
250         return rootJavadocNode;
251     }
252 
253     /**
254      * Creates child nodes for each node from 'nodes' array.
255      * @param parseTreeParent original ParseTree parent node
256      * @param nodes array of JavadocNodeImpl nodes
257      */
258     private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) {
259         for (int i = 0; i < nodes.length; i++) {
260             final JavadocNodeImpl currentJavadocNode = nodes[i];
261             final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i);
262             final JavadocNodeImpl[] subChildren =
263                     createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild);
264             currentJavadocNode.setChildren((DetailNode[]) subChildren);
265         }
266     }
267 
268     /**
269      * Creates children Javadoc nodes base on ParseTree node's children.
270      * @param parentJavadocNode node that will be parent for created children
271      * @param parseTreeNode original ParseTree node
272      * @return array of Javadoc nodes
273      */
274     private JavadocNodeImpl[]
275             createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) {
276         final JavadocNodeImpl[] children =
277                 new JavadocNodeImpl[parseTreeNode.getChildCount()];
278 
279         for (int j = 0; j < children.length; j++) {
280             final JavadocNodeImpl child =
281                     createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j);
282 
283             children[j] = child;
284         }
285         return children;
286     }
287 
288     /**
289      * Creates root JavadocNodeImpl node base on ParseTree root node.
290      * @param parseTreeNode ParseTree root node
291      * @return root Javadoc node
292      */
293     private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) {
294         final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1);
295 
296         final int childCount = parseTreeNode.getChildCount();
297         final DetailNode[] children = rootJavadocNode.getChildren();
298 
299         for (int i = 0; i < childCount; i++) {
300             final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i),
301                     rootJavadocNode, i);
302             children[i] = child;
303         }
304         rootJavadocNode.setChildren(children);
305         return rootJavadocNode;
306     }
307 
308     /**
309      * Creates JavadocNodeImpl node on base of ParseTree node.
310      *
311      * @param parseTree ParseTree node
312      * @param parent DetailNode that will be parent of new node
313      * @param index child index that has new node
314      * @return JavadocNodeImpl node on base of ParseTree node.
315      */
316     private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) {
317         final JavadocNodeImpl node = new JavadocNodeImpl();
318         if (parseTree.getChildCount() == 0
319                 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) {
320             node.setText(parseTree.getText());
321         }
322         else {
323             node.setText(getFormattedNodeClassNameWithoutContext(parseTree));
324         }
325         node.setColumnNumber(getColumn(parseTree));
326         node.setLineNumber(getLine(parseTree) + blockCommentLineNumber);
327         node.setIndex(index);
328         node.setType(getTokenType(parseTree));
329         node.setParent(parent);
330         node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]);
331         return node;
332     }
333 
334     /**
335      * Adjust first line nodes to javadoc indent.
336      * @param tree DetailNode tree root
337      * @param javadocColumnNumber javadoc indent
338      */
339     private void adjustFirstLineToJavadocIndent(DetailNode tree, int javadocColumnNumber) {
340         if (tree.getLineNumber() == blockCommentLineNumber) {
341             ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber);
342             final DetailNode[] children = tree.getChildren();
343             for (DetailNode child : children) {
344                 adjustFirstLineToJavadocIndent(child, javadocColumnNumber);
345             }
346         }
347     }
348 
349     /**
350      * Gets line number from ParseTree node.
351      * @param tree
352      *        ParseTree node
353      * @return line number
354      */
355     private static int getLine(ParseTree tree) {
356         final int line;
357         if (tree instanceof TerminalNode) {
358             line = ((TerminalNode) tree).getSymbol().getLine() - 1;
359         }
360         else {
361             final ParserRuleContext rule = (ParserRuleContext) tree;
362             line = rule.start.getLine() - 1;
363         }
364         return line;
365     }
366 
367     /**
368      * Gets column number from ParseTree node.
369      * @param tree
370      *        ParseTree node
371      * @return column number
372      */
373     private static int getColumn(ParseTree tree) {
374         final int column;
375         if (tree instanceof TerminalNode) {
376             column = ((TerminalNode) tree).getSymbol().getCharPositionInLine();
377         }
378         else {
379             final ParserRuleContext rule = (ParserRuleContext) tree;
380             column = rule.start.getCharPositionInLine();
381         }
382         return column;
383     }
384 
385     /**
386      * Gets next sibling of ParseTree node.
387      * @param node ParseTree node
388      * @return next sibling of ParseTree node.
389      */
390     private static ParseTree getNextSibling(ParseTree node) {
391         ParseTree nextSibling = null;
392 
393         if (node.getParent() != null) {
394             final ParseTree parent = node.getParent();
395             int index = 0;
396             while (true) {
397                 final ParseTree currentNode = parent.getChild(index);
398                 if (currentNode.equals(node)) {
399                     nextSibling = parent.getChild(index + 1);
400                     break;
401                 }
402                 index++;
403             }
404         }
405         return nextSibling;
406     }
407 
408     /**
409      * Gets token type of ParseTree node from JavadocTokenTypes class.
410      * @param node ParseTree node.
411      * @return token type from JavadocTokenTypes
412      */
413     private static int getTokenType(ParseTree node) {
414         final int tokenType;
415 
416         if (node.getChildCount() == 0) {
417             tokenType = ((TerminalNode) node).getSymbol().getType();
418         }
419         else {
420             final String className = getNodeClassNameWithoutContext(node);
421             final String typeName =
422                     CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className);
423             tokenType = JavadocUtils.getTokenId(typeName);
424         }
425 
426         return tokenType;
427     }
428 
429     /**
430      * Gets class name of ParseTree node and removes 'Context' postfix at the
431      * end and formats it.
432      * @param node {@code ParseTree} node whose class name is to be formatted and returned
433      * @return uppercased class name without the word 'Context' and with appropriately
434      *     inserted underscores
435      */
436     private static String getFormattedNodeClassNameWithoutContext(ParseTree node) {
437         final String classNameWithoutContext = getNodeClassNameWithoutContext(node);
438         return CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, classNameWithoutContext);
439     }
440 
441     /**
442      * Gets class name of ParseTree node and removes 'Context' postfix at the
443      * end.
444      * @param node
445      *        ParseTree node.
446      * @return class name without 'Context'
447      */
448     private static String getNodeClassNameWithoutContext(ParseTree node) {
449         final String className = node.getClass().getSimpleName();
450         // remove 'Context' at the end
451         final int contextLength = 7;
452         return className.substring(0, className.length() - contextLength);
453     }
454 
455     /**
456      * Method to get the missed HTML tag to generate more informative error message for the user.
457      * This method doesn't concern itself with
458      * <a href="https://www.w3.org/TR/html51/syntax.html#void-elements">void elements</a>
459      * since it is forbidden to close them.
460      * Missed HTML tags for the following tags will <i>not</i> generate an error message from ANTLR:
461      * {@code
462      * <p>
463      * <li>
464      * <tr>
465      * <td>
466      * <th>
467      * <body>
468      * <colgroup>
469      * <dd>
470      * <dt>
471      * <head>
472      * <html>
473      * <option>
474      * <tbody>
475      * <thead>
476      * <tfoot>
477      * }
478      * @param exception {@code NoViableAltException} object catched while parsing javadoc
479      * @return returns appropriate {@link Token} if a HTML close tag is missed;
480      *     null otherwise
481      */
482     private static Token getMissedHtmlTag(RecognitionException exception) {
483         Token htmlTagNameStart = null;
484         final Interval sourceInterval = exception.getCtx().getSourceInterval();
485         final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
486                 .getTokens(sourceInterval.a, sourceInterval.b);
487         final Deque<Token> stack = new ArrayDeque<>();
488         for (int i = 0; i < tokenList.size(); i++) {
489             final Token token = tokenList.get(i);
490             if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME
491                     && tokenList.get(i - 1).getType() == JavadocTokenTypes.START) {
492                 stack.push(token);
493             }
494             else if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) {
495                 if (stack.peek().getText().equals(token.getText())) {
496                     stack.pop();
497                 }
498                 else {
499                     htmlTagNameStart = stack.pop();
500                 }
501             }
502         }
503         if (htmlTagNameStart == null) {
504             htmlTagNameStart = stack.pop();
505         }
506         return htmlTagNameStart;
507     }
508 
509     /**
510      * This method is used to get the first non-tight HTML tag encountered while parsing javadoc.
511      * This shall eventually be reflected by the {@link ParseStatus} object returned by
512      * {@link #parseJavadocAsDetailNode(DetailAST)} method via the instance member
513      * {@link ParseStatus#firstNonTightHtmlTag}, and checks not supposed to process non-tight HTML
514      * or the ones which are supposed to log violation for non-tight javadocs can utilize that.
515      *
516      * @param javadocParser The ANTLR recognizer instance which has been used to parse the javadoc
517      * @return First non-tight HTML tag if one exists; null otherwise
518      */
519     private Token getFirstNonTightHtmlTag(JavadocParser javadocParser) {
520         final CommonToken offendingToken;
521         final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
522         if (nonTightTagStartContext == null) {
523             offendingToken = null;
524         }
525         else {
526             final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
527                     .getSymbol();
528             offendingToken = new CommonToken(token);
529             offendingToken.setLine(offendingToken.getLine() + errorListener.offset);
530         }
531         return offendingToken;
532     }
533 
534     /**
535      * Custom error listener for JavadocParser that prints user readable errors.
536      */
537     private static class DescriptiveErrorListener extends BaseErrorListener {
538 
539         /**
540          * Offset is line number of beginning of the Javadoc comment. Log
541          * messages should have line number in scope of file, not in scope of
542          * Javadoc comment.
543          */
544         private int offset;
545 
546         /**
547          * Error message that appeared while parsing.
548          */
549         private ParseErrorMessage errorMessage;
550 
551         /**
552          * Getter for error message during parsing.
553          * @return Error message during parsing.
554          */
555         private ParseErrorMessage getErrorMessage() {
556             return errorMessage;
557         }
558 
559         /**
560          * Sets offset. Offset is line number of beginning of the Javadoc
561          * comment. Log messages should have line number in scope of file, not
562          * in scope of Javadoc comment.
563          * @param offset
564          *        offset line number
565          */
566         public void setOffset(int offset) {
567             this.offset = offset;
568         }
569 
570         /**
571          * Logs parser errors in Checkstyle manner. Parser can generate error
572          * messages. There is special error that parser can generate. It is
573          * missed close HTML tag. This case is special because parser prints
574          * error like {@code "no viable alternative at input 'b \n *\n'"} and it
575          * is not clear that error is about missed close HTML tag. Other error
576          * messages are not special and logged simply as "Parse Error...".
577          *
578          * <p>{@inheritDoc}
579          */
580         @Override
581         public void syntaxError(
582                 Recognizer<?, ?> recognizer, Object offendingSymbol,
583                 int line, int charPositionInLine,
584                 String msg, RecognitionException ex) {
585             final int lineNumber = offset + line;
586 
587             if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
588                 errorMessage = new ParseErrorMessage(lineNumber,
589                         MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
590                         ((Token) offendingSymbol).getText());
591 
592                 throw new IllegalArgumentException(msg);
593             }
594             else {
595                 final int ruleIndex = ex.getCtx().getRuleIndex();
596                 final String ruleName = recognizer.getRuleNames()[ruleIndex];
597                 final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to(
598                         CaseFormat.UPPER_UNDERSCORE, ruleName);
599 
600                 errorMessage = new ParseErrorMessage(lineNumber,
601                         MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
602             }
603         }
604     }
605 
606     /**
607      * Contains result of parsing javadoc comment: DetailNode tree and parse
608      * error message.
609      */
610     public static class ParseStatus {
611         /**
612          * DetailNode tree (is null if parsing fails).
613          */
614         private DetailNode tree;
615 
616         /**
617          * Parse error message (is null if parsing is successful).
618          */
619         private ParseErrorMessage parseErrorMessage;
620 
621         /**
622          * Stores the first non-tight HTML tag encountered while parsing javadoc.
623          *
624          * @see <a
625          *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
626          *     Tight HTML rules</a>
627          */
628         private Token firstNonTightHtmlTag;
629 
630         /**
631          * Getter for DetailNode tree.
632          * @return DetailNode tree if parsing was successful, null otherwise.
633          */
634         public DetailNode getTree() {
635             return tree;
636         }
637 
638         /**
639          * Sets DetailNode tree.
640          * @param tree DetailNode tree.
641          */
642         public void setTree(DetailNode tree) {
643             this.tree = tree;
644         }
645 
646         /**
647          * Getter for error message during parsing.
648          * @return Error message if parsing was unsuccessful, null otherwise.
649          */
650         public ParseErrorMessage getParseErrorMessage() {
651             return parseErrorMessage;
652         }
653 
654         /**
655          * Sets parse error message.
656          * @param parseErrorMessage Parse error message.
657          */
658         public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
659             this.parseErrorMessage = parseErrorMessage;
660         }
661 
662         /**
663          * This method is used to check if the javadoc parsed has non-tight HTML tags.
664          *
665          * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise
666          * @see <a
667          *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
668          *     Tight HTML rules</a>
669          */
670         public boolean isNonTight() {
671             return firstNonTightHtmlTag != null;
672         }
673 
674         /**
675          * Getter for {@link #firstNonTightHtmlTag}.
676          *
677          * @return the first non-tight HTML tag that is encountered while parsing Javadoc,
678          *     if one exists
679          */
680         public Token getFirstNonTightHtmlTag() {
681             return firstNonTightHtmlTag;
682         }
683 
684     }
685 
686     /**
687      * Contains information about parse error message.
688      */
689     public static class ParseErrorMessage {
690         /**
691          * Line number where parse error occurred.
692          */
693         private final int lineNumber;
694 
695         /**
696          * Key for error message.
697          */
698         private final String messageKey;
699 
700         /**
701          * Error message arguments.
702          */
703         private final Object[] messageArguments;
704 
705         /**
706          * Initializes parse error message.
707          *
708          * @param lineNumber line number
709          * @param messageKey message key
710          * @param messageArguments message arguments
711          */
712         ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) {
713             this.lineNumber = lineNumber;
714             this.messageKey = messageKey;
715             this.messageArguments = messageArguments.clone();
716         }
717 
718         /**
719          * Getter for line number where parse error occurred.
720          * @return Line number where parse error occurred.
721          */
722         public int getLineNumber() {
723             return lineNumber;
724         }
725 
726         /**
727          * Getter for key for error message.
728          * @return Key for error message.
729          */
730         public String getMessageKey() {
731             return messageKey;
732         }
733 
734         /**
735          * Getter for error message arguments.
736          * @return Array of error message arguments.
737          */
738         public Object[] getMessageArguments() {
739             return messageArguments.clone();
740         }
741     }
742 
743     /**
744      * The DefaultErrorStrategy used by ANTLR attempts to recover from parse errors
745      * which might result in a performance overhead. Also, a parse error indicate
746      * that javadoc doesn't follow checkstyle Javadoc grammar and the user should be made aware
747      * of it.
748      * <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/BailErrorStrategy.html">
749      * BailErrorStrategy</a> is used to make ANTLR generated parser bail out on the first error
750      * in parser and not attempt any recovery methods but it doesn't report error to the
751      * listeners. This class is to ensure proper error reporting.
752      *
753      * @see DescriptiveErrorListener
754      * @see <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/ANTLRErrorStrategy.html">
755      *     ANTLRErrorStrategy</a>
756      */
757     private static class JavadocParserErrorStrategy extends BailErrorStrategy {
758         @Override
759         public Token recoverInline(Parser recognizer) {
760             reportError(recognizer, new InputMismatchException(recognizer));
761             return super.recoverInline(recognizer);
762         }
763     }
764 
765 }