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