View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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                         nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
236                                 .getNextSibling(tempJavadocParent);
237 
238                         nextParseTreeSibling = getNextSibling(tempParseTreeParent);
239 
240                         tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent();
241                         tempParseTreeParent = tempParseTreeParent.getParent();
242                     }
243                 }
244                 currentJavadocParent = nextJavadocSibling;
245                 parseTreeParent = nextParseTreeSibling;
246             }
247         }
248 
249         return rootJavadocNode;
250     }
251 
252     /**
253      * Creates child nodes for each node from 'nodes' array.
254      * @param parseTreeParent original ParseTree parent node
255      * @param nodes array of JavadocNodeImpl nodes
256      */
257     private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) {
258         for (int i = 0; i < nodes.length; i++) {
259             final JavadocNodeImpl currentJavadocNode = nodes[i];
260             final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i);
261             final JavadocNodeImpl[] subChildren =
262                     createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild);
263             currentJavadocNode.setChildren((DetailNode[]) subChildren);
264         }
265     }
266 
267     /**
268      * Creates children Javadoc nodes base on ParseTree node's children.
269      * @param parentJavadocNode node that will be parent for created children
270      * @param parseTreeNode original ParseTree node
271      * @return array of Javadoc nodes
272      */
273     private JavadocNodeImpl[]
274             createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) {
275         final JavadocNodeImpl[] children =
276                 new JavadocNodeImpl[parseTreeNode.getChildCount()];
277 
278         for (int j = 0; j < children.length; j++) {
279             final JavadocNodeImpl child =
280                     createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j);
281 
282             children[j] = child;
283         }
284         return children;
285     }
286 
287     /**
288      * Creates root JavadocNodeImpl node base on ParseTree root node.
289      * @param parseTreeNode ParseTree root node
290      * @return root Javadoc node
291      */
292     private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) {
293         final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1);
294 
295         final int childCount = parseTreeNode.getChildCount();
296         final DetailNode[] children = rootJavadocNode.getChildren();
297 
298         for (int i = 0; i < childCount; i++) {
299             final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i),
300                     rootJavadocNode, i);
301             children[i] = child;
302         }
303         rootJavadocNode.setChildren(children);
304         return rootJavadocNode;
305     }
306 
307     /**
308      * Creates JavadocNodeImpl node on base of ParseTree node.
309      *
310      * @param parseTree ParseTree node
311      * @param parent DetailNode that will be parent of new node
312      * @param index child index that has new node
313      * @return JavadocNodeImpl node on base of ParseTree node.
314      */
315     private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) {
316         final JavadocNodeImpl node = new JavadocNodeImpl();
317         if (parseTree.getChildCount() == 0
318                 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) {
319             node.setText(parseTree.getText());
320         }
321         else {
322             node.setText(getFormattedNodeClassNameWithoutContext(parseTree));
323         }
324         node.setColumnNumber(getColumn(parseTree));
325         node.setLineNumber(getLine(parseTree) + blockCommentLineNumber);
326         node.setIndex(index);
327         node.setType(getTokenType(parseTree));
328         node.setParent(parent);
329         node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]);
330         return node;
331     }
332 
333     /**
334      * Adjust first line nodes to javadoc indent.
335      * @param tree DetailNode tree root
336      * @param javadocColumnNumber javadoc indent
337      */
338     private void adjustFirstLineToJavadocIndent(DetailNode tree, int javadocColumnNumber) {
339         if (tree.getLineNumber() == blockCommentLineNumber) {
340             ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber);
341             final DetailNode[] children = tree.getChildren();
342             for (DetailNode child : children) {
343                 adjustFirstLineToJavadocIndent(child, javadocColumnNumber);
344             }
345         }
346     }
347 
348     /**
349      * Gets line number from ParseTree node.
350      * @param tree
351      *        ParseTree node
352      * @return line number
353      */
354     private static int getLine(ParseTree tree) {
355         final int line;
356         if (tree instanceof TerminalNode) {
357             line = ((TerminalNode) tree).getSymbol().getLine() - 1;
358         }
359         else {
360             final ParserRuleContext rule = (ParserRuleContext) tree;
361             line = rule.start.getLine() - 1;
362         }
363         return line;
364     }
365 
366     /**
367      * Gets column number from ParseTree node.
368      * @param tree
369      *        ParseTree node
370      * @return column number
371      */
372     private static int getColumn(ParseTree tree) {
373         final int column;
374         if (tree instanceof TerminalNode) {
375             column = ((TerminalNode) tree).getSymbol().getCharPositionInLine();
376         }
377         else {
378             final ParserRuleContext rule = (ParserRuleContext) tree;
379             column = rule.start.getCharPositionInLine();
380         }
381         return column;
382     }
383 
384     /**
385      * Gets next sibling of ParseTree node.
386      * @param node ParseTree node
387      * @return next sibling of ParseTree node.
388      */
389     private static ParseTree getNextSibling(ParseTree node) {
390         ParseTree nextSibling = null;
391 
392         if (node.getParent() != null) {
393             final ParseTree parent = node.getParent();
394             int index = 0;
395             while (true) {
396                 final ParseTree currentNode = parent.getChild(index);
397                 if (currentNode.equals(node)) {
398                     nextSibling = parent.getChild(index + 1);
399                     break;
400                 }
401                 index++;
402             }
403         }
404         return nextSibling;
405     }
406 
407     /**
408      * Gets token type of ParseTree node from JavadocTokenTypes class.
409      * @param node ParseTree node.
410      * @return token type from JavadocTokenTypes
411      */
412     private static int getTokenType(ParseTree node) {
413         final int tokenType;
414 
415         if (node.getChildCount() == 0) {
416             tokenType = ((TerminalNode) node).getSymbol().getType();
417         }
418         else {
419             final String className = getNodeClassNameWithoutContext(node);
420             final String typeName =
421                     CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className);
422             tokenType = JavadocUtils.getTokenId(typeName);
423         }
424 
425         return tokenType;
426     }
427 
428     /**
429      * Gets class name of ParseTree node and removes 'Context' postfix at the
430      * end and formats it.
431      * @param node {@code ParseTree} node whose class name is to be formatted and returned
432      * @return uppercased class name without the word 'Context' and with appropriately
433      *     inserted underscores
434      */
435     private static String getFormattedNodeClassNameWithoutContext(ParseTree node) {
436         final String classNameWithoutContext = getNodeClassNameWithoutContext(node);
437         return CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, classNameWithoutContext);
438     }
439 
440     /**
441      * Gets class name of ParseTree node and removes 'Context' postfix at the
442      * end.
443      * @param node
444      *        ParseTree node.
445      * @return class name without 'Context'
446      */
447     private static String getNodeClassNameWithoutContext(ParseTree node) {
448         final String className = node.getClass().getSimpleName();
449         // remove 'Context' at the end
450         final int contextLength = 7;
451         return className.substring(0, className.length() - contextLength);
452     }
453 
454     /**
455      * Method to get the missed HTML tag to generate more informative error message for the user.
456      * This method doesn't concern itself with
457      * <a href="https://www.w3.org/TR/html51/syntax.html#void-elements">void elements</a>
458      * since it is forbidden to close them.
459      * Missed HTML tags for the following tags will <i>not</i> generate an error message from ANTLR:
460      * {@code
461      * <p>
462      * <li>
463      * <tr>
464      * <td>
465      * <th>
466      * <body>
467      * <colgroup>
468      * <dd>
469      * <dt>
470      * <head>
471      * <html>
472      * <option>
473      * <tbody>
474      * <thead>
475      * <tfoot>
476      * }
477      * @param exception {@code NoViableAltException} object catched while parsing javadoc
478      * @return returns appropriate {@link Token} if a HTML close tag is missed;
479      *     null otherwise
480      */
481     private static Token getMissedHtmlTag(RecognitionException exception) {
482         Token htmlTagNameStart = null;
483         final Interval sourceInterval = exception.getCtx().getSourceInterval();
484         final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
485                 .getTokens(sourceInterval.a, sourceInterval.b);
486         final Deque<Token> stack = new ArrayDeque<>();
487         int prevTokenType = JavadocTokenTypes.EOF;
488         for (final Token token : tokenList) {
489             final int tokenType = token.getType();
490             if (tokenType == JavadocTokenTypes.HTML_TAG_NAME
491                     && prevTokenType == JavadocTokenTypes.START) {
492                 stack.push(token);
493             }
494             else if (tokenType == 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             prevTokenType = tokenType;
503         }
504         if (htmlTagNameStart == null) {
505             htmlTagNameStart = stack.pop();
506         }
507         return htmlTagNameStart;
508     }
509 
510     /**
511      * This method is used to get the first non-tight HTML tag encountered while parsing javadoc.
512      * This shall eventually be reflected by the {@link ParseStatus} object returned by
513      * {@link #parseJavadocAsDetailNode(DetailAST)} method via the instance member
514      * {@link ParseStatus#firstNonTightHtmlTag}, and checks not supposed to process non-tight HTML
515      * or the ones which are supposed to log violation for non-tight javadocs can utilize that.
516      *
517      * @param javadocParser The ANTLR recognizer instance which has been used to parse the javadoc
518      * @return First non-tight HTML tag if one exists; null otherwise
519      */
520     private Token getFirstNonTightHtmlTag(JavadocParser javadocParser) {
521         final CommonToken offendingToken;
522         final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
523         if (nonTightTagStartContext == null) {
524             offendingToken = null;
525         }
526         else {
527             final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
528                     .getSymbol();
529             offendingToken = new CommonToken(token);
530             offendingToken.setLine(offendingToken.getLine() + errorListener.offset);
531         }
532         return offendingToken;
533     }
534 
535     /**
536      * Custom error listener for JavadocParser that prints user readable errors.
537      */
538     private static class DescriptiveErrorListener extends BaseErrorListener {
539 
540         /**
541          * Offset is line number of beginning of the Javadoc comment. Log
542          * messages should have line number in scope of file, not in scope of
543          * Javadoc comment.
544          */
545         private int offset;
546 
547         /**
548          * Error message that appeared while parsing.
549          */
550         private ParseErrorMessage errorMessage;
551 
552         /**
553          * Getter for error message during parsing.
554          * @return Error message during parsing.
555          */
556         private ParseErrorMessage getErrorMessage() {
557             return errorMessage;
558         }
559 
560         /**
561          * Sets offset. Offset is line number of beginning of the Javadoc
562          * comment. Log messages should have line number in scope of file, not
563          * in scope of Javadoc comment.
564          * @param offset
565          *        offset line number
566          */
567         public void setOffset(int offset) {
568             this.offset = offset;
569         }
570 
571         /**
572          * Logs parser errors in Checkstyle manner. Parser can generate error
573          * messages. There is special error that parser can generate. It is
574          * missed close HTML tag. This case is special because parser prints
575          * error like {@code "no viable alternative at input 'b \n *\n'"} and it
576          * is not clear that error is about missed close HTML tag. Other error
577          * messages are not special and logged simply as "Parse Error...".
578          *
579          * <p>{@inheritDoc}
580          */
581         @Override
582         public void syntaxError(
583                 Recognizer<?, ?> recognizer, Object offendingSymbol,
584                 int line, int charPositionInLine,
585                 String msg, RecognitionException ex) {
586             final int lineNumber = offset + line;
587 
588             if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
589                 errorMessage = new ParseErrorMessage(lineNumber,
590                         MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
591                         ((Token) offendingSymbol).getText());
592 
593                 throw new IllegalArgumentException(msg);
594             }
595             else {
596                 final int ruleIndex = ex.getCtx().getRuleIndex();
597                 final String ruleName = recognizer.getRuleNames()[ruleIndex];
598                 final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to(
599                         CaseFormat.UPPER_UNDERSCORE, ruleName);
600 
601                 errorMessage = new ParseErrorMessage(lineNumber,
602                         MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
603             }
604         }
605 
606     }
607 
608     /**
609      * Contains result of parsing javadoc comment: DetailNode tree and parse
610      * error message.
611      */
612     public static class ParseStatus {
613 
614         /**
615          * DetailNode tree (is null if parsing fails).
616          */
617         private DetailNode tree;
618 
619         /**
620          * Parse error message (is null if parsing is successful).
621          */
622         private ParseErrorMessage parseErrorMessage;
623 
624         /**
625          * Stores the first non-tight HTML tag encountered while parsing javadoc.
626          *
627          * @see <a
628          *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
629          *     Tight HTML rules</a>
630          */
631         private Token firstNonTightHtmlTag;
632 
633         /**
634          * Getter for DetailNode tree.
635          * @return DetailNode tree if parsing was successful, null otherwise.
636          */
637         public DetailNode getTree() {
638             return tree;
639         }
640 
641         /**
642          * Sets DetailNode tree.
643          * @param tree DetailNode tree.
644          */
645         public void setTree(DetailNode tree) {
646             this.tree = tree;
647         }
648 
649         /**
650          * Getter for error message during parsing.
651          * @return Error message if parsing was unsuccessful, null otherwise.
652          */
653         public ParseErrorMessage getParseErrorMessage() {
654             return parseErrorMessage;
655         }
656 
657         /**
658          * Sets parse error message.
659          * @param parseErrorMessage Parse error message.
660          */
661         public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
662             this.parseErrorMessage = parseErrorMessage;
663         }
664 
665         /**
666          * This method is used to check if the javadoc parsed has non-tight HTML tags.
667          *
668          * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise
669          * @see <a
670          *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
671          *     Tight HTML rules</a>
672          */
673         public boolean isNonTight() {
674             return firstNonTightHtmlTag != null;
675         }
676 
677         /**
678          * Getter for {@link #firstNonTightHtmlTag}.
679          *
680          * @return the first non-tight HTML tag that is encountered while parsing Javadoc,
681          *     if one exists
682          */
683         public Token getFirstNonTightHtmlTag() {
684             return firstNonTightHtmlTag;
685         }
686 
687     }
688 
689     /**
690      * Contains information about parse error message.
691      */
692     public static class ParseErrorMessage {
693 
694         /**
695          * Line number where parse error occurred.
696          */
697         private final int lineNumber;
698 
699         /**
700          * Key for error message.
701          */
702         private final String messageKey;
703 
704         /**
705          * Error message arguments.
706          */
707         private final Object[] messageArguments;
708 
709         /**
710          * Initializes parse error message.
711          *
712          * @param lineNumber line number
713          * @param messageKey message key
714          * @param messageArguments message arguments
715          */
716         ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) {
717             this.lineNumber = lineNumber;
718             this.messageKey = messageKey;
719             this.messageArguments = messageArguments.clone();
720         }
721 
722         /**
723          * Getter for line number where parse error occurred.
724          * @return Line number where parse error occurred.
725          */
726         public int getLineNumber() {
727             return lineNumber;
728         }
729 
730         /**
731          * Getter for key for error message.
732          * @return Key for error message.
733          */
734         public String getMessageKey() {
735             return messageKey;
736         }
737 
738         /**
739          * Getter for error message arguments.
740          * @return Array of error message arguments.
741          */
742         public Object[] getMessageArguments() {
743             return messageArguments.clone();
744         }
745 
746     }
747 
748     /**
749      * The DefaultErrorStrategy used by ANTLR attempts to recover from parse errors
750      * which might result in a performance overhead. Also, a parse error indicate
751      * that javadoc doesn't follow checkstyle Javadoc grammar and the user should be made aware
752      * of it.
753      * <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/BailErrorStrategy.html">
754      * BailErrorStrategy</a> is used to make ANTLR generated parser bail out on the first error
755      * in parser and not attempt any recovery methods but it doesn't report error to the
756      * listeners. This class is to ensure proper error reporting.
757      *
758      * @see DescriptiveErrorListener
759      * @see <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/ANTLRErrorStrategy.html">
760      *     ANTLRErrorStrategy</a>
761      */
762     private static class JavadocParserErrorStrategy extends BailErrorStrategy {
763 
764         @Override
765         public Token recoverInline(Parser recognizer) {
766             reportError(recognizer, new InputMismatchException(recognizer));
767             return super.recoverInline(recognizer);
768         }
769 
770     }
771 
772 }