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