Coverage Report - com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser
 
Classes in this File Line Coverage Branch Coverage Complexity
JavadocDetailNodeParser
100%
166/166
100%
60/60
2.133
JavadocDetailNodeParser$1
N/A
N/A
2.133
JavadocDetailNodeParser$DescriptiveErrorListener
100%
16/16
100%
2/2
2.133
JavadocDetailNodeParser$JavadocParserErrorStrategy
100%
3/3
N/A
2.133
JavadocDetailNodeParser$ParseErrorMessage
100%
8/8
N/A
2.133
JavadocDetailNodeParser$ParseStatus
100%
9/9
100%
2/2
2.133
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2018 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle;
 21  
 
 22  
 import java.util.ArrayDeque;
 23  
 import java.util.Deque;
 24  
 import java.util.List;
 25  
 
 26  
 import org.antlr.v4.runtime.ANTLRInputStream;
 27  
 import org.antlr.v4.runtime.BailErrorStrategy;
 28  
 import org.antlr.v4.runtime.BaseErrorListener;
 29  
 import org.antlr.v4.runtime.BufferedTokenStream;
 30  
 import org.antlr.v4.runtime.CommonToken;
 31  
 import org.antlr.v4.runtime.CommonTokenStream;
 32  
 import org.antlr.v4.runtime.FailedPredicateException;
 33  
 import org.antlr.v4.runtime.InputMismatchException;
 34  
 import org.antlr.v4.runtime.NoViableAltException;
 35  
 import org.antlr.v4.runtime.Parser;
 36  
 import org.antlr.v4.runtime.ParserRuleContext;
 37  
 import org.antlr.v4.runtime.RecognitionException;
 38  
 import org.antlr.v4.runtime.Recognizer;
 39  
 import org.antlr.v4.runtime.Token;
 40  
 import org.antlr.v4.runtime.misc.Interval;
 41  
 import org.antlr.v4.runtime.misc.ParseCancellationException;
 42  
 import org.antlr.v4.runtime.tree.ParseTree;
 43  
 import org.antlr.v4.runtime.tree.TerminalNode;
 44  
 
 45  
 import com.google.common.base.CaseFormat;
 46  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 47  
 import com.puppycrawl.tools.checkstyle.api.DetailNode;
 48  
 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
 49  
 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl;
 50  
 import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocLexer;
 51  
 import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocParser;
 52  
 import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
 53  
 
 54  
 /**
 55  
  * Used for parsing Javadoc comment as DetailNode tree.
 56  
  * @author bizmailov
 57  
  *
 58  
  */
 59  106
 public class JavadocDetailNodeParser {
 60  
 
 61  
     /**
 62  
      * Message key of error message. Missed close HTML tag breaks structure
 63  
      * of parse tree, so parser stops parsing and generates such error
 64  
      * message. This case is special because parser prints error like
 65  
      * {@code "no viable alternative at input 'b \n *\n'"} and it is not
 66  
      * clear that error is about missed close HTML tag.
 67  
      */
 68  
     public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close";
 69  
 
 70  
     /**
 71  
      * Message key of error message.
 72  
      */
 73  
     public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
 74  
         "javadoc.wrong.singleton.html.tag";
 75  
 
 76  
     /**
 77  
      * Parse error while rule recognition.
 78  
      */
 79  
     public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error";
 80  
 
 81  
     /**
 82  
      * Message property key for the Unclosed HTML message.
 83  
      */
 84  
     public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml";
 85  
 
 86  
     /** Symbols with which javadoc starts. */
 87  
     private static final String JAVADOC_START = "/**";
 88  
 
 89  
     /**
 90  
      * Line number of the Block comment AST that is being parsed.
 91  
      */
 92  
     private int blockCommentLineNumber;
 93  
 
 94  
     /**
 95  
      * Custom error listener.
 96  
      */
 97  
     private DescriptiveErrorListener errorListener;
 98  
 
 99  
     /**
 100  
      * Parses Javadoc comment as DetailNode tree.
 101  
      * @param javadocCommentAst
 102  
      *        DetailAST of Javadoc comment
 103  
      * @return DetailNode tree of Javadoc comment
 104  
      */
 105  
     public ParseStatus parseJavadocAsDetailNode(DetailAST javadocCommentAst) {
 106  673
         blockCommentLineNumber = javadocCommentAst.getLineNo();
 107  
 
 108  673
         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  673
         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  673
         errorListener.setOffset(javadocCommentAst.getLineNo() - 1);
 119  
 
 120  673
         final ParseStatus result = new ParseStatus();
 121  
 
 122  
         try {
 123  673
             final JavadocParser javadocParser = createJavadocParser(javadocComment);
 124  
 
 125  673
             final ParseTree javadocParseTree = javadocParser.javadoc();
 126  
 
 127  632
             final DetailNode tree = convertParseTreeToDetailNode(javadocParseTree);
 128  
             // adjust first line to indent of /**
 129  1264
             adjustFirstLineToJavadocIndent(tree,
 130  632
                         javadocCommentAst.getColumnNo()
 131  632
                                 + JAVADOC_START.length());
 132  632
             result.setTree(tree);
 133  632
             result.firstNonTightHtmlTag = getFirstNonTightHtmlTag(javadocParser);
 134  
         }
 135  41
         catch (ParseCancellationException | IllegalArgumentException ex) {
 136  41
             ParseErrorMessage parseErrorMessage = null;
 137  
 
 138  41
             if (ex.getCause() instanceof FailedPredicateException
 139  39
                     || ex.getCause() instanceof NoViableAltException) {
 140  17
                 final RecognitionException recognitionEx = (RecognitionException) ex.getCause();
 141  17
                 if (recognitionEx.getCtx() instanceof JavadocParser.HtmlTagContext) {
 142  7
                     final Token htmlTagNameStart = getMissedHtmlTag(recognitionEx);
 143  7
                     parseErrorMessage = new ParseErrorMessage(
 144  7
                             errorListener.offset + htmlTagNameStart.getLine(),
 145  
                             MSG_JAVADOC_MISSED_HTML_CLOSE,
 146  7
                             htmlTagNameStart.getCharPositionInLine(),
 147  7
                             htmlTagNameStart.getText());
 148  
                 }
 149  
             }
 150  
 
 151  41
             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  34
                 parseErrorMessage = errorListener.getErrorMessage();
 156  
             }
 157  
 
 158  41
             result.setParseErrorMessage(parseErrorMessage);
 159  632
         }
 160  
 
 161  673
         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  673
         final ANTLRInputStream input = new ANTLRInputStream(blockComment);
 173  
 
 174  673
         final JavadocLexer lexer = new JavadocLexer(input);
 175  
 
 176  673
         final CommonTokenStream tokens = new CommonTokenStream(lexer);
 177  
 
 178  673
         final JavadocParser parser = new JavadocParser(tokens);
 179  
 
 180  
         // remove default error listeners
 181  673
         parser.removeErrorListeners();
 182  
 
 183  
         // add custom error listener that logs syntax errors
 184  673
         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  673
         parser.setErrorHandler(new JavadocParserErrorStrategy());
 189  
 
 190  673
         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  632
         final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode);
 202  
 
 203  632
         JavadocNodeImpl currentJavadocParent = rootJavadocNode;
 204  632
         ParseTree parseTreeParent = parseTreeNode;
 205  
 
 206  22224
         while (currentJavadocParent != null) {
 207  
             // remove unnecessary children tokens
 208  21592
             if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) {
 209  2966
                 currentJavadocParent
 210  2966
                         .setChildren((DetailNode[]) JavadocNodeImpl.EMPTY_DETAIL_NODE_ARRAY);
 211  
             }
 212  
 
 213  21592
             final JavadocNodeImpl[] children =
 214  21592
                     (JavadocNodeImpl[]) currentJavadocParent.getChildren();
 215  
 
 216  21592
             insertChildrenNodes(children, parseTreeParent);
 217  
 
 218  21592
             if (children.length > 0) {
 219  5275
                 currentJavadocParent = children[0];
 220  5275
                 parseTreeParent = parseTreeParent.getChild(0);
 221  
             }
 222  
             else {
 223  16317
                 JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
 224  16317
                         .getNextSibling(currentJavadocParent);
 225  
 
 226  16317
                 ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent);
 227  
 
 228  16317
                 if (nextJavadocSibling == null) {
 229  3189
                     JavadocNodeImpl tempJavadocParent =
 230  3189
                             (JavadocNodeImpl) currentJavadocParent.getParent();
 231  
 
 232  3189
                     ParseTree tempParseTreeParent = parseTreeParent.getParent();
 233  
 
 234  8464
                     while (nextJavadocSibling == null && tempJavadocParent != null) {
 235  5275
                         nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
 236  5275
                                 .getNextSibling(tempJavadocParent);
 237  
 
 238  5275
                         nextParseTreeSibling = getNextSibling(tempParseTreeParent);
 239  
 
 240  5275
                         tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent();
 241  5275
                         tempParseTreeParent = tempParseTreeParent.getParent();
 242  
                     }
 243  
                 }
 244  16317
                 currentJavadocParent = nextJavadocSibling;
 245  16317
                 parseTreeParent = nextParseTreeSibling;
 246  
             }
 247  21592
         }
 248  
 
 249  632
         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  42552
         for (int i = 0; i < nodes.length; i++) {
 259  20960
             final JavadocNodeImpl currentJavadocNode = nodes[i];
 260  20960
             final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i);
 261  20960
             final JavadocNodeImpl[] subChildren =
 262  20960
                     createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild);
 263  20960
             currentJavadocNode.setChildren((DetailNode[]) subChildren);
 264  
         }
 265  21592
     }
 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  20960
         final JavadocNodeImpl[] children =
 276  20960
                 new JavadocNodeImpl[parseTreeNode.getChildCount()];
 277  
 
 278  57678
         for (int j = 0; j < children.length; j++) {
 279  36718
             final JavadocNodeImpl child =
 280  36718
                     createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j);
 281  
 
 282  36718
             children[j] = child;
 283  
         }
 284  20960
         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  632
         final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1);
 294  
 
 295  632
         final int childCount = parseTreeNode.getChildCount();
 296  632
         final DetailNode[] children = rootJavadocNode.getChildren();
 297  
 
 298  7199
         for (int i = 0; i < childCount; i++) {
 299  6567
             final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i),
 300  
                     rootJavadocNode, i);
 301  6567
             children[i] = child;
 302  
         }
 303  632
         rootJavadocNode.setChildren(children);
 304  632
         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  43917
         final JavadocNodeImpl node = new JavadocNodeImpl();
 317  43917
         if (parseTree.getChildCount() == 0
 318  8241
                 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) {
 319  38642
             node.setText(parseTree.getText());
 320  
         }
 321  
         else {
 322  5275
             node.setText(getFormattedNodeClassNameWithoutContext(parseTree));
 323  
         }
 324  43917
         node.setColumnNumber(getColumn(parseTree));
 325  43917
         node.setLineNumber(getLine(parseTree) + blockCommentLineNumber);
 326  43917
         node.setIndex(index);
 327  43917
         node.setType(getTokenType(parseTree));
 328  43917
         node.setParent(parent);
 329  43917
         node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]);
 330  43917
         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  8055
         if (tree.getLineNumber() == blockCommentLineNumber) {
 340  2510
             ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber);
 341  2510
             final DetailNode[] children = tree.getChildren();
 342  9933
             for (DetailNode child : children) {
 343  7423
                 adjustFirstLineToJavadocIndent(child, javadocColumnNumber);
 344  
             }
 345  
         }
 346  8055
     }
 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  43917
         if (tree instanceof TerminalNode) {
 357  35676
             line = ((TerminalNode) tree).getSymbol().getLine() - 1;
 358  
         }
 359  
         else {
 360  8241
             final ParserRuleContext rule = (ParserRuleContext) tree;
 361  8241
             line = rule.start.getLine() - 1;
 362  
         }
 363  43917
         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  43917
         if (tree instanceof TerminalNode) {
 375  35676
             column = ((TerminalNode) tree).getSymbol().getCharPositionInLine();
 376  
         }
 377  
         else {
 378  8241
             final ParserRuleContext rule = (ParserRuleContext) tree;
 379  8241
             column = rule.start.getCharPositionInLine();
 380  
         }
 381  43917
         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  21592
         ParseTree nextSibling = null;
 391  
 
 392  21592
         if (node.getParent() != null) {
 393  20960
             final ParseTree parent = node.getParent();
 394  20960
             int index = 0;
 395  
             while (true) {
 396  174854
                 final ParseTree currentNode = parent.getChild(index);
 397  174854
                 if (currentNode.equals(node)) {
 398  20960
                     nextSibling = parent.getChild(index + 1);
 399  20960
                     break;
 400  
                 }
 401  153894
                 index++;
 402  153894
             }
 403  
         }
 404  21592
         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  43917
         if (node.getChildCount() == 0) {
 416  35676
             tokenType = ((TerminalNode) node).getSymbol().getType();
 417  
         }
 418  
         else {
 419  8241
             final String className = getNodeClassNameWithoutContext(node);
 420  8241
             final String typeName =
 421  8241
                     CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className);
 422  8241
             tokenType = JavadocUtils.getTokenId(typeName);
 423  
         }
 424  
 
 425  43917
         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  5275
         final String classNameWithoutContext = getNodeClassNameWithoutContext(node);
 437  5275
         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  21757
         final String className = node.getClass().getSimpleName();
 449  
         // remove 'Context' at the end
 450  21757
         final int contextLength = 7;
 451  21757
         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  7
         Token htmlTagNameStart = null;
 483  7
         final Interval sourceInterval = exception.getCtx().getSourceInterval();
 484  7
         final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
 485  7
                 .getTokens(sourceInterval.a, sourceInterval.b);
 486  7
         final Deque<Token> stack = new ArrayDeque<>();
 487  204
         for (int i = 0; i < tokenList.size(); i++) {
 488  197
             final Token token = tokenList.get(i);
 489  197
             if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME
 490  38
                     && tokenList.get(i - 1).getType() == JavadocTokenTypes.START) {
 491  21
                 stack.push(token);
 492  
             }
 493  176
             else if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) {
 494  16
                 if (stack.peek().getText().equals(token.getText())) {
 495  14
                     stack.pop();
 496  
                 }
 497  
                 else {
 498  2
                     htmlTagNameStart = stack.pop();
 499  
                 }
 500  
             }
 501  
         }
 502  7
         if (htmlTagNameStart == null) {
 503  5
             htmlTagNameStart = stack.pop();
 504  
         }
 505  7
         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  632
         final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
 521  632
         if (nonTightTagStartContext == null) {
 522  555
             offendingToken = null;
 523  
         }
 524  
         else {
 525  77
             final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
 526  77
                     .getSymbol();
 527  77
             offendingToken = new CommonToken(token);
 528  77
             offendingToken.setLine(offendingToken.getLine() + errorListener.offset);
 529  
         }
 530  632
         return offendingToken;
 531  
     }
 532  
 
 533  
     /**
 534  
      * Custom error listener for JavadocParser that prints user readable errors.
 535  
      */
 536  1464
     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  34
             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  673
             this.offset = offset;
 567  673
         }
 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  41
             final int lineNumber = offset + line;
 585  
 
 586  41
             if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
 587  7
                 errorMessage = new ParseErrorMessage(lineNumber,
 588  7
                         MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
 589  7
                         ((Token) offendingSymbol).getText());
 590  
 
 591  7
                 throw new IllegalArgumentException(msg);
 592  
             }
 593  
             else {
 594  34
                 final int ruleIndex = ex.getCtx().getRuleIndex();
 595  34
                 final String ruleName = recognizer.getRuleNames()[ruleIndex];
 596  34
                 final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to(
 597  
                         CaseFormat.UPPER_UNDERSCORE, ruleName);
 598  
 
 599  34
                 errorMessage = new ParseErrorMessage(lineNumber,
 600  34
                         MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
 601  
             }
 602  34
         }
 603  
 
 604  
     }
 605  
 
 606  
     /**
 607  
      * Contains result of parsing javadoc comment: DetailNode tree and parse
 608  
      * error message.
 609  
      */
 610  1305
     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  611
             return tree;
 637  
         }
 638  
 
 639  
         /**
 640  
          * Sets DetailNode tree.
 641  
          * @param tree DetailNode tree.
 642  
          */
 643  
         public void setTree(DetailNode tree) {
 644  632
             this.tree = tree;
 645  632
         }
 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  716
             return parseErrorMessage;
 653  
         }
 654  
 
 655  
         /**
 656  
          * Sets parse error message.
 657  
          * @param parseErrorMessage Parse error message.
 658  
          */
 659  
         public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
 660  41
             this.parseErrorMessage = parseErrorMessage;
 661  41
         }
 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  99
             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  60
             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  57
         ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) {
 715  57
             this.lineNumber = lineNumber;
 716  57
             this.messageKey = messageKey;
 717  57
             this.messageArguments = messageArguments.clone();
 718  57
         }
 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  65
             return lineNumber;
 726  
         }
 727  
 
 728  
         /**
 729  
          * Getter for key for error message.
 730  
          * @return Key for error message.
 731  
          */
 732  
         public String getMessageKey() {
 733  50
             return messageKey;
 734  
         }
 735  
 
 736  
         /**
 737  
          * Getter for error message arguments.
 738  
          * @return Array of error message arguments.
 739  
          */
 740  
         public Object[] getMessageArguments() {
 741  50
             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  1346
     private static class JavadocParserErrorStrategy extends BailErrorStrategy {
 761  
 
 762  
         @Override
 763  
         public Token recoverInline(Parser recognizer) {
 764  17
             reportError(recognizer, new InputMismatchException(recognizer));
 765  17
             return super.recoverInline(recognizer);
 766  
         }
 767  
 
 768  
     }
 769  
 
 770  
 }