001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle;
021
022import java.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 *
057 */
058public class JavadocDetailNodeParser {
059
060    /**
061     * Message key of error message. Missed close HTML tag breaks structure
062     * of parse tree, so parser stops parsing and generates such error
063     * message. This case is special because parser prints error like
064     * {@code "no viable alternative at input 'b \n *\n'"} and it is not
065     * clear that error is about missed close HTML tag.
066     */
067    public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close";
068
069    /**
070     * Message key of error message.
071     */
072    public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
073        "javadoc.wrong.singleton.html.tag";
074
075    /**
076     * Parse error while rule recognition.
077     */
078    public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error";
079
080    /**
081     * Message property key for the Unclosed HTML message.
082     */
083    public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml";
084
085    /** Symbols with which javadoc starts. */
086    private static final String JAVADOC_START = "/**";
087
088    /**
089     * Line number of the Block comment AST that is being parsed.
090     */
091    private int blockCommentLineNumber;
092
093    /**
094     * Custom error listener.
095     */
096    private DescriptiveErrorListener errorListener;
097
098    /**
099     * Parses Javadoc comment as DetailNode tree.
100     * @param javadocCommentAst
101     *        DetailAST of Javadoc comment
102     * @return DetailNode tree of Javadoc comment
103     */
104    public ParseStatus parseJavadocAsDetailNode(DetailAST javadocCommentAst) {
105        blockCommentLineNumber = javadocCommentAst.getLineNo();
106
107        final String javadocComment = JavadocUtils.getJavadocCommentContent(javadocCommentAst);
108
109        // Use a new error listener each time to be able to use
110        // one check instance for multiple files to be checked
111        // without getting side effects.
112        errorListener = new DescriptiveErrorListener();
113
114        // Log messages should have line number in scope of file,
115        // not in scope of Javadoc comment.
116        // Offset is line number of beginning of Javadoc comment.
117        errorListener.setOffset(javadocCommentAst.getLineNo() - 1);
118
119        final ParseStatus result = new ParseStatus();
120
121        try {
122            final JavadocParser javadocParser = createJavadocParser(javadocComment);
123
124            final ParseTree javadocParseTree = javadocParser.javadoc();
125
126            final DetailNode tree = convertParseTreeToDetailNode(javadocParseTree);
127            // adjust first line to indent of /**
128            adjustFirstLineToJavadocIndent(tree,
129                        javadocCommentAst.getColumnNo()
130                                + JAVADOC_START.length());
131            result.setTree(tree);
132            result.firstNonTightHtmlTag = getFirstNonTightHtmlTag(javadocParser);
133        }
134        catch (ParseCancellationException | IllegalArgumentException ex) {
135            ParseErrorMessage parseErrorMessage = null;
136
137            if (ex.getCause() instanceof FailedPredicateException
138                    || ex.getCause() instanceof NoViableAltException) {
139                final RecognitionException recognitionEx = (RecognitionException) ex.getCause();
140                if (recognitionEx.getCtx() instanceof JavadocParser.HtmlTagContext) {
141                    final Token htmlTagNameStart = getMissedHtmlTag(recognitionEx);
142                    parseErrorMessage = new ParseErrorMessage(
143                            errorListener.offset + htmlTagNameStart.getLine(),
144                            MSG_JAVADOC_MISSED_HTML_CLOSE,
145                            htmlTagNameStart.getCharPositionInLine(),
146                            htmlTagNameStart.getText());
147                }
148            }
149
150            if (parseErrorMessage == null) {
151                // If syntax error occurs then message is printed by error listener
152                // and parser throws this runtime exception to stop parsing.
153                // Just stop processing current Javadoc comment.
154                parseErrorMessage = errorListener.getErrorMessage();
155            }
156
157            result.setParseErrorMessage(parseErrorMessage);
158        }
159
160        return result;
161    }
162
163    /**
164     * Parses block comment content as javadoc comment.
165     * @param blockComment
166     *        block comment content.
167     * @return parse tree
168     * @noinspection deprecation
169     */
170    private JavadocParser createJavadocParser(String blockComment) {
171        final ANTLRInputStream input = new ANTLRInputStream(blockComment);
172
173        final JavadocLexer lexer = new JavadocLexer(input);
174
175        final CommonTokenStream tokens = new CommonTokenStream(lexer);
176
177        final JavadocParser parser = new JavadocParser(tokens);
178
179        // remove default error listeners
180        parser.removeErrorListeners();
181
182        // add custom error listener that logs syntax errors
183        parser.addErrorListener(errorListener);
184
185        // JavadocParserErrorStrategy stops parsing on first parse error encountered unlike the
186        // DefaultErrorStrategy used by ANTLR which rather attempts error recovery.
187        parser.setErrorHandler(new JavadocParserErrorStrategy());
188
189        return parser;
190    }
191
192    /**
193     * Converts ParseTree (that is generated by ANTLRv4) to DetailNode tree.
194     *
195     * @param parseTreeNode root node of ParseTree
196     * @return root of DetailNode tree
197     * @noinspection SuspiciousArrayCast
198     */
199    private DetailNode convertParseTreeToDetailNode(ParseTree parseTreeNode) {
200        final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode);
201
202        JavadocNodeImpl currentJavadocParent = rootJavadocNode;
203        ParseTree parseTreeParent = parseTreeNode;
204
205        while (currentJavadocParent != null) {
206            // remove unnecessary children tokens
207            if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) {
208                currentJavadocParent
209                        .setChildren((DetailNode[]) JavadocNodeImpl.EMPTY_DETAIL_NODE_ARRAY);
210            }
211
212            final JavadocNodeImpl[] children =
213                    (JavadocNodeImpl[]) currentJavadocParent.getChildren();
214
215            insertChildrenNodes(children, parseTreeParent);
216
217            if (children.length > 0) {
218                currentJavadocParent = children[0];
219                parseTreeParent = parseTreeParent.getChild(0);
220            }
221            else {
222                JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
223                        .getNextSibling(currentJavadocParent);
224
225                ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent);
226
227                if (nextJavadocSibling == null) {
228                    JavadocNodeImpl tempJavadocParent =
229                            (JavadocNodeImpl) currentJavadocParent.getParent();
230
231                    ParseTree tempParseTreeParent = parseTreeParent.getParent();
232
233                    while (nextJavadocSibling == null && tempJavadocParent != null) {
234                        nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
235                                .getNextSibling(tempJavadocParent);
236
237                        nextParseTreeSibling = getNextSibling(tempParseTreeParent);
238
239                        tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent();
240                        tempParseTreeParent = tempParseTreeParent.getParent();
241                    }
242                }
243                currentJavadocParent = nextJavadocSibling;
244                parseTreeParent = nextParseTreeSibling;
245            }
246        }
247
248        return rootJavadocNode;
249    }
250
251    /**
252     * Creates child nodes for each node from 'nodes' array.
253     * @param parseTreeParent original ParseTree parent node
254     * @param nodes array of JavadocNodeImpl nodes
255     */
256    private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) {
257        for (int i = 0; i < nodes.length; i++) {
258            final JavadocNodeImpl currentJavadocNode = nodes[i];
259            final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i);
260            final JavadocNodeImpl[] subChildren =
261                    createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild);
262            currentJavadocNode.setChildren((DetailNode[]) subChildren);
263        }
264    }
265
266    /**
267     * Creates children Javadoc nodes base on ParseTree node's children.
268     * @param parentJavadocNode node that will be parent for created children
269     * @param parseTreeNode original ParseTree node
270     * @return array of Javadoc nodes
271     */
272    private JavadocNodeImpl[]
273            createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) {
274        final JavadocNodeImpl[] children =
275                new JavadocNodeImpl[parseTreeNode.getChildCount()];
276
277        for (int j = 0; j < children.length; j++) {
278            final JavadocNodeImpl child =
279                    createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j);
280
281            children[j] = child;
282        }
283        return children;
284    }
285
286    /**
287     * Creates root JavadocNodeImpl node base on ParseTree root node.
288     * @param parseTreeNode ParseTree root node
289     * @return root Javadoc node
290     */
291    private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) {
292        final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1);
293
294        final int childCount = parseTreeNode.getChildCount();
295        final DetailNode[] children = rootJavadocNode.getChildren();
296
297        for (int i = 0; i < childCount; i++) {
298            final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i),
299                    rootJavadocNode, i);
300            children[i] = child;
301        }
302        rootJavadocNode.setChildren(children);
303        return rootJavadocNode;
304    }
305
306    /**
307     * Creates JavadocNodeImpl node on base of ParseTree node.
308     *
309     * @param parseTree ParseTree node
310     * @param parent DetailNode that will be parent of new node
311     * @param index child index that has new node
312     * @return JavadocNodeImpl node on base of ParseTree node.
313     */
314    private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) {
315        final JavadocNodeImpl node = new JavadocNodeImpl();
316        if (parseTree.getChildCount() == 0
317                || "Text".equals(getNodeClassNameWithoutContext(parseTree))) {
318            node.setText(parseTree.getText());
319        }
320        else {
321            node.setText(getFormattedNodeClassNameWithoutContext(parseTree));
322        }
323        node.setColumnNumber(getColumn(parseTree));
324        node.setLineNumber(getLine(parseTree) + blockCommentLineNumber);
325        node.setIndex(index);
326        node.setType(getTokenType(parseTree));
327        node.setParent(parent);
328        node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]);
329        return node;
330    }
331
332    /**
333     * Adjust first line nodes to javadoc indent.
334     * @param tree DetailNode tree root
335     * @param javadocColumnNumber javadoc indent
336     */
337    private void adjustFirstLineToJavadocIndent(DetailNode tree, int javadocColumnNumber) {
338        if (tree.getLineNumber() == blockCommentLineNumber) {
339            ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber);
340            final DetailNode[] children = tree.getChildren();
341            for (DetailNode child : children) {
342                adjustFirstLineToJavadocIndent(child, javadocColumnNumber);
343            }
344        }
345    }
346
347    /**
348     * Gets line number from ParseTree node.
349     * @param tree
350     *        ParseTree node
351     * @return line number
352     */
353    private static int getLine(ParseTree tree) {
354        final int line;
355        if (tree instanceof TerminalNode) {
356            line = ((TerminalNode) tree).getSymbol().getLine() - 1;
357        }
358        else {
359            final ParserRuleContext rule = (ParserRuleContext) tree;
360            line = rule.start.getLine() - 1;
361        }
362        return line;
363    }
364
365    /**
366     * Gets column number from ParseTree node.
367     * @param tree
368     *        ParseTree node
369     * @return column number
370     */
371    private static int getColumn(ParseTree tree) {
372        final int column;
373        if (tree instanceof TerminalNode) {
374            column = ((TerminalNode) tree).getSymbol().getCharPositionInLine();
375        }
376        else {
377            final ParserRuleContext rule = (ParserRuleContext) tree;
378            column = rule.start.getCharPositionInLine();
379        }
380        return column;
381    }
382
383    /**
384     * Gets next sibling of ParseTree node.
385     * @param node ParseTree node
386     * @return next sibling of ParseTree node.
387     */
388    private static ParseTree getNextSibling(ParseTree node) {
389        ParseTree nextSibling = null;
390
391        if (node.getParent() != null) {
392            final ParseTree parent = node.getParent();
393            int index = 0;
394            while (true) {
395                final ParseTree currentNode = parent.getChild(index);
396                if (currentNode.equals(node)) {
397                    nextSibling = parent.getChild(index + 1);
398                    break;
399                }
400                index++;
401            }
402        }
403        return nextSibling;
404    }
405
406    /**
407     * Gets token type of ParseTree node from JavadocTokenTypes class.
408     * @param node ParseTree node.
409     * @return token type from JavadocTokenTypes
410     */
411    private static int getTokenType(ParseTree node) {
412        final int tokenType;
413
414        if (node.getChildCount() == 0) {
415            tokenType = ((TerminalNode) node).getSymbol().getType();
416        }
417        else {
418            final String className = getNodeClassNameWithoutContext(node);
419            final String typeName =
420                    CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className);
421            tokenType = JavadocUtils.getTokenId(typeName);
422        }
423
424        return tokenType;
425    }
426
427    /**
428     * Gets class name of ParseTree node and removes 'Context' postfix at the
429     * end and formats it.
430     * @param node {@code ParseTree} node whose class name is to be formatted and returned
431     * @return uppercased class name without the word 'Context' and with appropriately
432     *     inserted underscores
433     */
434    private static String getFormattedNodeClassNameWithoutContext(ParseTree node) {
435        final String classNameWithoutContext = getNodeClassNameWithoutContext(node);
436        return CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, classNameWithoutContext);
437    }
438
439    /**
440     * Gets class name of ParseTree node and removes 'Context' postfix at the
441     * end.
442     * @param node
443     *        ParseTree node.
444     * @return class name without 'Context'
445     */
446    private static String getNodeClassNameWithoutContext(ParseTree node) {
447        final String className = node.getClass().getSimpleName();
448        // remove 'Context' at the end
449        final int contextLength = 7;
450        return className.substring(0, className.length() - contextLength);
451    }
452
453    /**
454     * Method to get the missed HTML tag to generate more informative error message for the user.
455     * This method doesn't concern itself with
456     * <a href="https://www.w3.org/TR/html51/syntax.html#void-elements">void elements</a>
457     * since it is forbidden to close them.
458     * Missed HTML tags for the following tags will <i>not</i> generate an error message from ANTLR:
459     * {@code
460     * <p>
461     * <li>
462     * <tr>
463     * <td>
464     * <th>
465     * <body>
466     * <colgroup>
467     * <dd>
468     * <dt>
469     * <head>
470     * <html>
471     * <option>
472     * <tbody>
473     * <thead>
474     * <tfoot>
475     * }
476     * @param exception {@code NoViableAltException} object catched while parsing javadoc
477     * @return returns appropriate {@link Token} if a HTML close tag is missed;
478     *     null otherwise
479     */
480    private static Token getMissedHtmlTag(RecognitionException exception) {
481        Token htmlTagNameStart = null;
482        final Interval sourceInterval = exception.getCtx().getSourceInterval();
483        final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
484                .getTokens(sourceInterval.a, sourceInterval.b);
485        final Deque<Token> stack = new ArrayDeque<>();
486        int prevTokenType = JavadocTokenTypes.EOF;
487        for (final Token token : tokenList) {
488            final int tokenType = token.getType();
489            if (tokenType == JavadocTokenTypes.HTML_TAG_NAME
490                    && prevTokenType == JavadocTokenTypes.START) {
491                stack.push(token);
492            }
493            else if (tokenType == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) {
494                if (stack.peek().getText().equals(token.getText())) {
495                    stack.pop();
496                }
497                else {
498                    htmlTagNameStart = stack.pop();
499                }
500            }
501            prevTokenType = tokenType;
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    /**
608     * Contains result of parsing javadoc comment: DetailNode tree and parse
609     * error message.
610     */
611    public static class ParseStatus {
612
613        /**
614         * DetailNode tree (is null if parsing fails).
615         */
616        private DetailNode tree;
617
618        /**
619         * Parse error message (is null if parsing is successful).
620         */
621        private ParseErrorMessage parseErrorMessage;
622
623        /**
624         * Stores the first non-tight HTML tag encountered while parsing javadoc.
625         *
626         * @see <a
627         *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
628         *     Tight HTML rules</a>
629         */
630        private Token firstNonTightHtmlTag;
631
632        /**
633         * Getter for DetailNode tree.
634         * @return DetailNode tree if parsing was successful, null otherwise.
635         */
636        public DetailNode getTree() {
637            return tree;
638        }
639
640        /**
641         * Sets DetailNode tree.
642         * @param tree DetailNode tree.
643         */
644        public void setTree(DetailNode tree) {
645            this.tree = tree;
646        }
647
648        /**
649         * Getter for error message during parsing.
650         * @return Error message if parsing was unsuccessful, null otherwise.
651         */
652        public ParseErrorMessage getParseErrorMessage() {
653            return parseErrorMessage;
654        }
655
656        /**
657         * Sets parse error message.
658         * @param parseErrorMessage Parse error message.
659         */
660        public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
661            this.parseErrorMessage = parseErrorMessage;
662        }
663
664        /**
665         * This method is used to check if the javadoc parsed has non-tight HTML tags.
666         *
667         * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise
668         * @see <a
669         *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
670         *     Tight HTML rules</a>
671         */
672        public boolean isNonTight() {
673            return firstNonTightHtmlTag != null;
674        }
675
676        /**
677         * Getter for {@link #firstNonTightHtmlTag}.
678         *
679         * @return the first non-tight HTML tag that is encountered while parsing Javadoc,
680         *     if one exists
681         */
682        public Token getFirstNonTightHtmlTag() {
683            return firstNonTightHtmlTag;
684        }
685
686    }
687
688    /**
689     * Contains information about parse error message.
690     */
691    public static class ParseErrorMessage {
692
693        /**
694         * Line number where parse error occurred.
695         */
696        private final int lineNumber;
697
698        /**
699         * Key for error message.
700         */
701        private final String messageKey;
702
703        /**
704         * Error message arguments.
705         */
706        private final Object[] messageArguments;
707
708        /**
709         * Initializes parse error message.
710         *
711         * @param lineNumber line number
712         * @param messageKey message key
713         * @param messageArguments message arguments
714         */
715        ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) {
716            this.lineNumber = lineNumber;
717            this.messageKey = messageKey;
718            this.messageArguments = messageArguments.clone();
719        }
720
721        /**
722         * Getter for line number where parse error occurred.
723         * @return Line number where parse error occurred.
724         */
725        public int getLineNumber() {
726            return lineNumber;
727        }
728
729        /**
730         * Getter for key for error message.
731         * @return Key for error message.
732         */
733        public String getMessageKey() {
734            return messageKey;
735        }
736
737        /**
738         * Getter for error message arguments.
739         * @return Array of error message arguments.
740         */
741        public Object[] getMessageArguments() {
742            return messageArguments.clone();
743        }
744
745    }
746
747    /**
748     * The DefaultErrorStrategy used by ANTLR attempts to recover from parse errors
749     * which might result in a performance overhead. Also, a parse error indicate
750     * that javadoc doesn't follow checkstyle Javadoc grammar and the user should be made aware
751     * of it.
752     * <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/BailErrorStrategy.html">
753     * BailErrorStrategy</a> is used to make ANTLR generated parser bail out on the first error
754     * in parser and not attempt any recovery methods but it doesn't report error to the
755     * listeners. This class is to ensure proper error reporting.
756     *
757     * @see DescriptiveErrorListener
758     * @see <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/ANTLRErrorStrategy.html">
759     *     ANTLRErrorStrategy</a>
760     */
761    private static class JavadocParserErrorStrategy extends BailErrorStrategy {
762
763        @Override
764        public Token recoverInline(Parser recognizer) {
765            reportError(recognizer, new InputMismatchException(recognizer));
766            return super.recoverInline(recognizer);
767        }
768
769    }
770
771}