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 * @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     * Message property key for the Unclosed HTML message.
083     */
084    public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml";
085
086    /** Symbols with which javadoc starts. */
087    private static final String JAVADOC_START = "/**";
088
089    /**
090     * Line number of the Block comment AST that is being parsed.
091     */
092    private int blockCommentLineNumber;
093
094    /**
095     * Custom error listener.
096     */
097    private DescriptiveErrorListener errorListener;
098
099    /**
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        for (int i = 0; i < tokenList.size(); i++) {
488            final Token token = tokenList.get(i);
489            if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME
490                    && tokenList.get(i - 1).getType() == JavadocTokenTypes.START) {
491                stack.push(token);
492            }
493            else if (token.getType() == 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        }
502        if (htmlTagNameStart == null) {
503            htmlTagNameStart = stack.pop();
504        }
505        return htmlTagNameStart;
506    }
507
508    /**
509     * This method is used to get the first non-tight HTML tag encountered while parsing javadoc.
510     * This shall eventually be reflected by the {@link ParseStatus} object returned by
511     * {@link #parseJavadocAsDetailNode(DetailAST)} method via the instance member
512     * {@link ParseStatus#firstNonTightHtmlTag}, and checks not supposed to process non-tight HTML
513     * or the ones which are supposed to log violation for non-tight javadocs can utilize that.
514     *
515     * @param javadocParser The ANTLR recognizer instance which has been used to parse the javadoc
516     * @return First non-tight HTML tag if one exists; null otherwise
517     */
518    private Token getFirstNonTightHtmlTag(JavadocParser javadocParser) {
519        final CommonToken offendingToken;
520        final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
521        if (nonTightTagStartContext == null) {
522            offendingToken = null;
523        }
524        else {
525            final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
526                    .getSymbol();
527            offendingToken = new CommonToken(token);
528            offendingToken.setLine(offendingToken.getLine() + errorListener.offset);
529        }
530        return offendingToken;
531    }
532
533    /**
534     * Custom error listener for JavadocParser that prints user readable errors.
535     */
536    private static class DescriptiveErrorListener extends BaseErrorListener {
537
538        /**
539         * Offset is line number of beginning of the Javadoc comment. Log
540         * messages should have line number in scope of file, not in scope of
541         * Javadoc comment.
542         */
543        private int offset;
544
545        /**
546         * Error message that appeared while parsing.
547         */
548        private ParseErrorMessage errorMessage;
549
550        /**
551         * Getter for error message during parsing.
552         * @return Error message during parsing.
553         */
554        private ParseErrorMessage getErrorMessage() {
555            return errorMessage;
556        }
557
558        /**
559         * Sets offset. Offset is line number of beginning of the Javadoc
560         * comment. Log messages should have line number in scope of file, not
561         * in scope of Javadoc comment.
562         * @param offset
563         *        offset line number
564         */
565        public void setOffset(int offset) {
566            this.offset = offset;
567        }
568
569        /**
570         * Logs parser errors in Checkstyle manner. Parser can generate error
571         * messages. There is special error that parser can generate. It is
572         * missed close HTML tag. This case is special because parser prints
573         * error like {@code "no viable alternative at input 'b \n *\n'"} and it
574         * is not clear that error is about missed close HTML tag. Other error
575         * messages are not special and logged simply as "Parse Error...".
576         *
577         * <p>{@inheritDoc}
578         */
579        @Override
580        public void syntaxError(
581                Recognizer<?, ?> recognizer, Object offendingSymbol,
582                int line, int charPositionInLine,
583                String msg, RecognitionException ex) {
584            final int lineNumber = offset + line;
585
586            if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
587                errorMessage = new ParseErrorMessage(lineNumber,
588                        MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
589                        ((Token) offendingSymbol).getText());
590
591                throw new IllegalArgumentException(msg);
592            }
593            else {
594                final int ruleIndex = ex.getCtx().getRuleIndex();
595                final String ruleName = recognizer.getRuleNames()[ruleIndex];
596                final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to(
597                        CaseFormat.UPPER_UNDERSCORE, ruleName);
598
599                errorMessage = new ParseErrorMessage(lineNumber,
600                        MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
601            }
602        }
603
604    }
605
606    /**
607     * Contains result of parsing javadoc comment: DetailNode tree and parse
608     * error message.
609     */
610    public static class ParseStatus {
611
612        /**
613         * DetailNode tree (is null if parsing fails).
614         */
615        private DetailNode tree;
616
617        /**
618         * Parse error message (is null if parsing is successful).
619         */
620        private ParseErrorMessage parseErrorMessage;
621
622        /**
623         * Stores the first non-tight HTML tag encountered while parsing javadoc.
624         *
625         * @see <a
626         *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
627         *     Tight HTML rules</a>
628         */
629        private Token firstNonTightHtmlTag;
630
631        /**
632         * Getter for DetailNode tree.
633         * @return DetailNode tree if parsing was successful, null otherwise.
634         */
635        public DetailNode getTree() {
636            return tree;
637        }
638
639        /**
640         * Sets DetailNode tree.
641         * @param tree DetailNode tree.
642         */
643        public void setTree(DetailNode tree) {
644            this.tree = tree;
645        }
646
647        /**
648         * Getter for error message during parsing.
649         * @return Error message if parsing was unsuccessful, null otherwise.
650         */
651        public ParseErrorMessage getParseErrorMessage() {
652            return parseErrorMessage;
653        }
654
655        /**
656         * Sets parse error message.
657         * @param parseErrorMessage Parse error message.
658         */
659        public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
660            this.parseErrorMessage = parseErrorMessage;
661        }
662
663        /**
664         * This method is used to check if the javadoc parsed has non-tight HTML tags.
665         *
666         * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise
667         * @see <a
668         *     href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
669         *     Tight HTML rules</a>
670         */
671        public boolean isNonTight() {
672            return firstNonTightHtmlTag != null;
673        }
674
675        /**
676         * Getter for {@link #firstNonTightHtmlTag}.
677         *
678         * @return the first non-tight HTML tag that is encountered while parsing Javadoc,
679         *     if one exists
680         */
681        public Token getFirstNonTightHtmlTag() {
682            return firstNonTightHtmlTag;
683        }
684
685    }
686
687    /**
688     * Contains information about parse error message.
689     */
690    public static class ParseErrorMessage {
691
692        /**
693         * Line number where parse error occurred.
694         */
695        private final int lineNumber;
696
697        /**
698         * Key for error message.
699         */
700        private final String messageKey;
701
702        /**
703         * Error message arguments.
704         */
705        private final Object[] messageArguments;
706
707        /**
708         * Initializes parse error message.
709         *
710         * @param lineNumber line number
711         * @param messageKey message key
712         * @param messageArguments message arguments
713         */
714        ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) {
715            this.lineNumber = lineNumber;
716            this.messageKey = messageKey;
717            this.messageArguments = messageArguments.clone();
718        }
719
720        /**
721         * Getter for line number where parse error occurred.
722         * @return Line number where parse error occurred.
723         */
724        public int getLineNumber() {
725            return lineNumber;
726        }
727
728        /**
729         * Getter for key for error message.
730         * @return Key for error message.
731         */
732        public String getMessageKey() {
733            return messageKey;
734        }
735
736        /**
737         * Getter for error message arguments.
738         * @return Array of error message arguments.
739         */
740        public Object[] getMessageArguments() {
741            return messageArguments.clone();
742        }
743
744    }
745
746    /**
747     * The DefaultErrorStrategy used by ANTLR attempts to recover from parse errors
748     * which might result in a performance overhead. Also, a parse error indicate
749     * that javadoc doesn't follow checkstyle Javadoc grammar and the user should be made aware
750     * of it.
751     * <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/BailErrorStrategy.html">
752     * BailErrorStrategy</a> is used to make ANTLR generated parser bail out on the first error
753     * in parser and not attempt any recovery methods but it doesn't report error to the
754     * listeners. This class is to ensure proper error reporting.
755     *
756     * @see DescriptiveErrorListener
757     * @see <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/ANTLRErrorStrategy.html">
758     *     ANTLRErrorStrategy</a>
759     */
760    private static class JavadocParserErrorStrategy extends BailErrorStrategy {
761
762        @Override
763        public Token recoverInline(Parser recognizer) {
764            reportError(recognizer, new InputMismatchException(recognizer));
765            return super.recoverInline(recognizer);
766        }
767
768    }
769
770}