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-2017 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  650
         blockCommentLineNumber = javadocCommentAst.getLineNo();
 107  
 
 108  650
         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  650
         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  650
         errorListener.setOffset(javadocCommentAst.getLineNo() - 1);
 119  
 
 120  650
         final ParseStatus result = new ParseStatus();
 121  
 
 122  
         try {
 123  650
             final JavadocParser javadocParser = createJavadocParser(javadocComment);
 124  
 
 125  650
             final ParseTree javadocParseTree = javadocParser.javadoc();
 126  
 
 127  609
             final DetailNode tree = convertParseTreeToDetailNode(javadocParseTree);
 128  
             // adjust first line to indent of /**
 129  1218
             adjustFirstLineToJavadocIndent(tree,
 130  609
                         javadocCommentAst.getColumnNo()
 131  609
                                 + JAVADOC_START.length());
 132  609
             result.setTree(tree);
 133  609
             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  609
         }
 160  
 
 161  650
         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  650
         final ANTLRInputStream input = new ANTLRInputStream(blockComment);
 173  
 
 174  650
         final JavadocLexer lexer = new JavadocLexer(input);
 175  
 
 176  650
         final CommonTokenStream tokens = new CommonTokenStream(lexer);
 177  
 
 178  650
         final JavadocParser parser = new JavadocParser(tokens);
 179  
 
 180  
         // remove default error listeners
 181  650
         parser.removeErrorListeners();
 182  
 
 183  
         // add custom error listener that logs syntax errors
 184  650
         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  650
         parser.setErrorHandler(new JavadocParserErrorStrategy());
 189  
 
 190  650
         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  609
         final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode);
 202  
 
 203  609
         JavadocNodeImpl currentJavadocParent = rootJavadocNode;
 204  609
         ParseTree parseTreeParent = parseTreeNode;
 205  
 
 206  22024
         while (currentJavadocParent != null) {
 207  
             // remove unnecessary children tokens
 208  21415
             if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) {
 209  2933
                 currentJavadocParent
 210  2933
                         .setChildren((DetailNode[]) JavadocNodeImpl.EMPTY_DETAIL_NODE_ARRAY);
 211  
             }
 212  
 
 213  21415
             final JavadocNodeImpl[] children =
 214  21415
                     (JavadocNodeImpl[]) currentJavadocParent.getChildren();
 215  
 
 216  21415
             insertChildrenNodes(children, parseTreeParent);
 217  
 
 218  21415
             if (children.length > 0) {
 219  5231
                 currentJavadocParent = children[0];
 220  5231
                 parseTreeParent = parseTreeParent.getChild(0);
 221  
             }
 222  
             else {
 223  16184
                 JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
 224  16184
                         .getNextSibling(currentJavadocParent);
 225  
 
 226  16184
                 ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent);
 227  
 
 228  16184
                 if (nextJavadocSibling == null) {
 229  3155
                     JavadocNodeImpl tempJavadocParent =
 230  3155
                             (JavadocNodeImpl) currentJavadocParent.getParent();
 231  
 
 232  3155
                     ParseTree tempParseTreeParent = parseTreeParent.getParent();
 233  
 
 234  8386
                     while (nextJavadocSibling == null && tempJavadocParent != null) {
 235  
 
 236  5231
                         nextJavadocSibling = (JavadocNodeImpl) JavadocUtils
 237  5231
                                 .getNextSibling(tempJavadocParent);
 238  
 
 239  5231
                         nextParseTreeSibling = getNextSibling(tempParseTreeParent);
 240  
 
 241  5231
                         tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent();
 242  5231
                         tempParseTreeParent = tempParseTreeParent.getParent();
 243  
                     }
 244  
                 }
 245  16184
                 currentJavadocParent = nextJavadocSibling;
 246  16184
                 parseTreeParent = nextParseTreeSibling;
 247  
             }
 248  21415
         }
 249  
 
 250  609
         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  42221
         for (int i = 0; i < nodes.length; i++) {
 260  20806
             final JavadocNodeImpl currentJavadocNode = nodes[i];
 261  20806
             final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i);
 262  20806
             final JavadocNodeImpl[] subChildren =
 263  20806
                     createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild);
 264  20806
             currentJavadocNode.setChildren((DetailNode[]) subChildren);
 265  
         }
 266  21415
     }
 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  20806
         final JavadocNodeImpl[] children =
 277  20806
                 new JavadocNodeImpl[parseTreeNode.getChildCount()];
 278  
 
 279  57173
         for (int j = 0; j < children.length; j++) {
 280  36367
             final JavadocNodeImpl child =
 281  36367
                     createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j);
 282  
 
 283  36367
             children[j] = child;
 284  
         }
 285  20806
         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  609
         final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1);
 295  
 
 296  609
         final int childCount = parseTreeNode.getChildCount();
 297  609
         final DetailNode[] children = rootJavadocNode.getChildren();
 298  
 
 299  7092
         for (int i = 0; i < childCount; i++) {
 300  6483
             final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i),
 301  
                     rootJavadocNode, i);
 302  6483
             children[i] = child;
 303  
         }
 304  609
         rootJavadocNode.setChildren(children);
 305  609
         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  43459
         final JavadocNodeImpl node = new JavadocNodeImpl();
 318  43459
         if (parseTree.getChildCount() == 0
 319  8164
                 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) {
 320  38228
             node.setText(parseTree.getText());
 321  
         }
 322  
         else {
 323  5231
             node.setText(getFormattedNodeClassNameWithoutContext(parseTree));
 324  
         }
 325  43459
         node.setColumnNumber(getColumn(parseTree));
 326  43459
         node.setLineNumber(getLine(parseTree) + blockCommentLineNumber);
 327  43459
         node.setIndex(index);
 328  43459
         node.setType(getTokenType(parseTree));
 329  43459
         node.setParent(parent);
 330  43459
         node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]);
 331  43459
         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  7948
         if (tree.getLineNumber() == blockCommentLineNumber) {
 341  2443
             ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber);
 342  2443
             final DetailNode[] children = tree.getChildren();
 343  9782
             for (DetailNode child : children) {
 344  7339
                 adjustFirstLineToJavadocIndent(child, javadocColumnNumber);
 345  
             }
 346  
         }
 347  7948
     }
 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  43459
         if (tree instanceof TerminalNode) {
 358  35295
             line = ((TerminalNode) tree).getSymbol().getLine() - 1;
 359  
         }
 360  
         else {
 361  8164
             final ParserRuleContext rule = (ParserRuleContext) tree;
 362  8164
             line = rule.start.getLine() - 1;
 363  
         }
 364  43459
         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  43459
         if (tree instanceof TerminalNode) {
 376  35295
             column = ((TerminalNode) tree).getSymbol().getCharPositionInLine();
 377  
         }
 378  
         else {
 379  8164
             final ParserRuleContext rule = (ParserRuleContext) tree;
 380  8164
             column = rule.start.getCharPositionInLine();
 381  
         }
 382  43459
         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  21415
         ParseTree nextSibling = null;
 392  
 
 393  21415
         if (node.getParent() != null) {
 394  20806
             final ParseTree parent = node.getParent();
 395  20806
             int index = 0;
 396  
             while (true) {
 397  174154
                 final ParseTree currentNode = parent.getChild(index);
 398  174154
                 if (currentNode.equals(node)) {
 399  20806
                     nextSibling = parent.getChild(index + 1);
 400  20806
                     break;
 401  
                 }
 402  153348
                 index++;
 403  153348
             }
 404  
         }
 405  21415
         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  43459
         if (node.getChildCount() == 0) {
 417  35295
             tokenType = ((TerminalNode) node).getSymbol().getType();
 418  
         }
 419  
         else {
 420  8164
             final String className = getNodeClassNameWithoutContext(node);
 421  8164
             final String typeName =
 422  8164
                     CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className);
 423  8164
             tokenType = JavadocUtils.getTokenId(typeName);
 424  
         }
 425  
 
 426  43459
         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  5231
         final String classNameWithoutContext = getNodeClassNameWithoutContext(node);
 438  5231
         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  21559
         final String className = node.getClass().getSimpleName();
 450  
         // remove 'Context' at the end
 451  21559
         final int contextLength = 7;
 452  21559
         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  7
         Token htmlTagNameStart = null;
 484  7
         final Interval sourceInterval = exception.getCtx().getSourceInterval();
 485  7
         final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
 486  7
                 .getTokens(sourceInterval.a, sourceInterval.b);
 487  7
         final Deque<Token> stack = new ArrayDeque<>();
 488  204
         for (int i = 0; i < tokenList.size(); i++) {
 489  197
             final Token token = tokenList.get(i);
 490  197
             if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME
 491  38
                     && tokenList.get(i - 1).getType() == JavadocTokenTypes.START) {
 492  21
                 stack.push(token);
 493  
             }
 494  176
             else if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) {
 495  16
                 if (stack.peek().getText().equals(token.getText())) {
 496  14
                     stack.pop();
 497  
                 }
 498  
                 else {
 499  2
                     htmlTagNameStart = stack.pop();
 500  
                 }
 501  
             }
 502  
         }
 503  7
         if (htmlTagNameStart == null) {
 504  5
             htmlTagNameStart = stack.pop();
 505  
         }
 506  7
         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  609
         final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
 522  609
         if (nonTightTagStartContext == null) {
 523  532
             offendingToken = null;
 524  
         }
 525  
         else {
 526  77
             final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
 527  77
                     .getSymbol();
 528  77
             offendingToken = new CommonToken(token);
 529  77
             offendingToken.setLine(offendingToken.getLine() + errorListener.offset);
 530  
         }
 531  609
         return offendingToken;
 532  
     }
 533  
 
 534  
     /**
 535  
      * Custom error listener for JavadocParser that prints user readable errors.
 536  
      */
 537  1418
     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  34
             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  650
             this.offset = offset;
 568  650
         }
 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  41
             final int lineNumber = offset + line;
 586  
 
 587  41
             if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
 588  7
                 errorMessage = new ParseErrorMessage(lineNumber,
 589  7
                         MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
 590  7
                         ((Token) offendingSymbol).getText());
 591  
 
 592  7
                 throw new IllegalArgumentException(msg);
 593  
             }
 594  
             else {
 595  34
                 final int ruleIndex = ex.getCtx().getRuleIndex();
 596  34
                 final String ruleName = recognizer.getRuleNames()[ruleIndex];
 597  34
                 final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to(
 598  
                         CaseFormat.UPPER_UNDERSCORE, ruleName);
 599  
 
 600  34
                 errorMessage = new ParseErrorMessage(lineNumber,
 601  34
                         MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
 602  
             }
 603  34
         }
 604  
     }
 605  
 
 606  
     /**
 607  
      * Contains result of parsing javadoc comment: DetailNode tree and parse
 608  
      * error message.
 609  
      */
 610  1259
     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  588
             return tree;
 636  
         }
 637  
 
 638  
         /**
 639  
          * Sets DetailNode tree.
 640  
          * @param tree DetailNode tree.
 641  
          */
 642  
         public void setTree(DetailNode tree) {
 643  609
             this.tree = tree;
 644  609
         }
 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  693
             return parseErrorMessage;
 652  
         }
 653  
 
 654  
         /**
 655  
          * Sets parse error message.
 656  
          * @param parseErrorMessage Parse error message.
 657  
          */
 658  
         public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
 659  41
             this.parseErrorMessage = parseErrorMessage;
 660  41
         }
 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  99
             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  60
             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  57
         ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) {
 713  57
             this.lineNumber = lineNumber;
 714  57
             this.messageKey = messageKey;
 715  57
             this.messageArguments = messageArguments.clone();
 716  57
         }
 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  65
             return lineNumber;
 724  
         }
 725  
 
 726  
         /**
 727  
          * Getter for key for error message.
 728  
          * @return Key for error message.
 729  
          */
 730  
         public String getMessageKey() {
 731  50
             return messageKey;
 732  
         }
 733  
 
 734  
         /**
 735  
          * Getter for error message arguments.
 736  
          * @return Array of error message arguments.
 737  
          */
 738  
         public Object[] getMessageArguments() {
 739  50
             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  1300
     private static class JavadocParserErrorStrategy extends BailErrorStrategy {
 758  
         @Override
 759  
         public Token recoverInline(Parser recognizer) {
 760  17
             reportError(recognizer, new InputMismatchException(recognizer));
 761  17
             return super.recoverInline(recognizer);
 762  
         }
 763  
     }
 764  
 
 765  
 }