001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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.checks.javadoc; 021 022import java.util.HashMap; 023import java.util.Map; 024 025import org.antlr.v4.runtime.ANTLRInputStream; 026import org.antlr.v4.runtime.BailErrorStrategy; 027import org.antlr.v4.runtime.BaseErrorListener; 028import org.antlr.v4.runtime.CommonTokenStream; 029import org.antlr.v4.runtime.ParserRuleContext; 030import org.antlr.v4.runtime.RecognitionException; 031import org.antlr.v4.runtime.Recognizer; 032import org.antlr.v4.runtime.Token; 033import org.antlr.v4.runtime.misc.ParseCancellationException; 034import org.antlr.v4.runtime.tree.ParseTree; 035import org.antlr.v4.runtime.tree.TerminalNode; 036 037import com.google.common.base.CaseFormat; 038import com.google.common.primitives.Ints; 039import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 040import com.puppycrawl.tools.checkstyle.api.DetailAST; 041import com.puppycrawl.tools.checkstyle.api.DetailNode; 042import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 043import com.puppycrawl.tools.checkstyle.api.TokenTypes; 044import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocLexer; 045import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocParser; 046import com.puppycrawl.tools.checkstyle.utils.BlockCommentPosition; 047import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 048 049/** 050 * Base class for Checks that process Javadoc comments. 051 * @author Baratali Izmailov 052 */ 053public abstract class AbstractJavadocCheck extends AbstractCheck { 054 /** 055 * Error message key for common javadoc errors. 056 */ 057 public static final String MSG_KEY_PARSE_ERROR = "javadoc.parse.error"; 058 059 /** 060 * Unrecognized error from antlr parser. 061 */ 062 public static final String MSG_KEY_UNRECOGNIZED_ANTLR_ERROR = 063 "javadoc.unrecognized.antlr.error"; 064 /** 065 * Message key of error message. Missed close HTML tag breaks structure 066 * of parse tree, so parser stops parsing and generates such error 067 * message. This case is special because parser prints error like 068 * {@code "no viable alternative at input 'b \n *\n'"} and it is not 069 * clear that error is about missed close HTML tag. 070 */ 071 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close"; 072 /** 073 * Message key of error message. 074 */ 075 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG = 076 "javadoc.wrong.singleton.html.tag"; 077 078 /** 079 * Parse error while rule recognition. 080 */ 081 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error"; 082 083 /** 084 * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal} 085 * to guarantee basic thread safety and avoid shared, mutable state when not necessary. 086 */ 087 private static final ThreadLocal<Map<String, ParseStatus>> TREE_CACHE = 088 new ThreadLocal<Map<String, ParseStatus>>() { 089 @Override 090 protected Map<String, ParseStatus> initialValue() { 091 return new HashMap<>(); 092 } 093 }; 094 095 /** 096 * Custom error listener. 097 */ 098 private DescriptiveErrorListener errorListener; 099 100 /** 101 * DetailAST node of considered Javadoc comment that is just a block comment 102 * in Java language syntax tree. 103 */ 104 private DetailAST blockCommentAst; 105 106 /** 107 * Returns the default token types a check is interested in. 108 * @return the default token types 109 * @see JavadocTokenTypes 110 */ 111 public abstract int[] getDefaultJavadocTokens(); 112 113 /** 114 * Called to process a Javadoc token. 115 * @param ast 116 * the token to process 117 */ 118 public abstract void visitJavadocToken(DetailNode ast); 119 120 /** 121 * Called before the starting to process a tree. 122 * @param rootAst 123 * the root of the tree 124 */ 125 public void beginJavadocTree(DetailNode rootAst) { 126 // No code by default, should be overridden only by demand at subclasses 127 } 128 129 /** 130 * Called after finished processing a tree. 131 * @param rootAst 132 * the root of the tree 133 */ 134 public void finishJavadocTree(DetailNode rootAst) { 135 // No code by default, should be overridden only by demand at subclasses 136 } 137 138 /** 139 * Called after all the child nodes have been process. 140 * @param ast 141 * the token leaving 142 */ 143 public void leaveJavadocToken(DetailNode ast) { 144 // No code by default, should be overridden only by demand at subclasses 145 } 146 147 /** 148 * Defined final to not allow JavadocChecks to change default tokens. 149 * @return default tokens 150 */ 151 @Override 152 public final int[] getDefaultTokens() { 153 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN }; 154 } 155 156 /** 157 * Defined final because all JavadocChecks require comment nodes. 158 * @return true 159 */ 160 @Override 161 public final boolean isCommentNodesRequired() { 162 return true; 163 } 164 165 @Override 166 public final void beginTree(DetailAST rootAST) { 167 TREE_CACHE.get().clear(); 168 } 169 170 @Override 171 public final void finishTree(DetailAST rootAST) { 172 TREE_CACHE.get().clear(); 173 } 174 175 @Override 176 public final void visitToken(DetailAST blockCommentNode) { 177 if (JavadocUtils.isJavadocComment(blockCommentNode) 178 && isCorrectJavadocPosition(blockCommentNode)) { 179 // store as field, to share with child Checks 180 blockCommentAst = blockCommentNode; 181 182 final String treeCacheKey = blockCommentNode.getLineNo() + ":" 183 + blockCommentNode.getColumnNo(); 184 185 final ParseStatus result; 186 187 if (TREE_CACHE.get().containsKey(treeCacheKey)) { 188 result = TREE_CACHE.get().get(treeCacheKey); 189 } 190 else { 191 result = parseJavadocAsDetailNode(blockCommentNode); 192 TREE_CACHE.get().put(treeCacheKey, result); 193 } 194 195 if (result.getParseErrorMessage() == null) { 196 processTree(result.getTree()); 197 } 198 else { 199 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage(); 200 log(parseErrorMessage.getLineNumber(), 201 parseErrorMessage.getMessageKey(), 202 parseErrorMessage.getMessageArguments()); 203 } 204 } 205 206 } 207 208 /** 209 * Getter for block comment in Java language syntax tree. 210 * @return A block comment in the syntax tree. 211 */ 212 protected DetailAST getBlockCommentAst() { 213 return blockCommentAst; 214 } 215 216 /** 217 * Checks Javadoc comment it's in right place. 218 * From Javadoc util documentation: 219 * "Placement of comments - Documentation comments are recognized only when placed 220 * immediately before class, interface, constructor, method, or field 221 * declarations -- see the class example, method example, and field example. 222 * Documentation comments placed in the body of a method are ignored. Only one 223 * documentation comment per declaration statement is recognized by the Javadoc tool." 224 * 225 * @param blockComment Block comment AST 226 * @return true if Javadoc is in right place 227 */ 228 private static boolean isCorrectJavadocPosition(DetailAST blockComment) { 229 return BlockCommentPosition.isOnClass(blockComment) 230 || BlockCommentPosition.isOnInterface(blockComment) 231 || BlockCommentPosition.isOnEnum(blockComment) 232 || BlockCommentPosition.isOnMethod(blockComment) 233 || BlockCommentPosition.isOnField(blockComment) 234 || BlockCommentPosition.isOnConstructor(blockComment) 235 || BlockCommentPosition.isOnEnumConstant(blockComment) 236 || BlockCommentPosition.isOnAnnotationDef(blockComment); 237 } 238 239 /** 240 * Parses Javadoc comment as DetailNode tree. 241 * @param javadocCommentAst 242 * DetailAST of Javadoc comment 243 * @return DetailNode tree of Javadoc comment 244 */ 245 private ParseStatus parseJavadocAsDetailNode(DetailAST javadocCommentAst) { 246 final String javadocComment = JavadocUtils.getJavadocCommentContent(javadocCommentAst); 247 248 // Use a new error listener each time to be able to use 249 // one check instance for multiple files to be checked 250 // without getting side effects. 251 errorListener = new DescriptiveErrorListener(); 252 253 // Log messages should have line number in scope of file, 254 // not in scope of Javadoc comment. 255 // Offset is line number of beginning of Javadoc comment. 256 errorListener.setOffset(javadocCommentAst.getLineNo() - 1); 257 258 final ParseStatus result = new ParseStatus(); 259 260 try { 261 final ParseTree parseTree = parseJavadocAsParseTree(javadocComment); 262 263 final DetailNode tree = convertParseTreeToDetailNode(parseTree); 264 result.setTree(tree); 265 } 266 catch (ParseCancellationException ex) { 267 // If syntax error occurs then message is printed by error listener 268 // and parser throws this runtime exception to stop parsing. 269 // Just stop processing current Javadoc comment. 270 ParseErrorMessage parseErrorMessage = errorListener.getErrorMessage(); 271 272 // There are cases when antlr error listener does not handle syntax error 273 if (parseErrorMessage == null) { 274 parseErrorMessage = new ParseErrorMessage(javadocCommentAst.getLineNo(), 275 MSG_KEY_UNRECOGNIZED_ANTLR_ERROR, 276 javadocCommentAst.getColumnNo(), ex.getMessage()); 277 } 278 279 result.setParseErrorMessage(parseErrorMessage); 280 } 281 282 return result; 283 } 284 285 /** 286 * Converts ParseTree (that is generated by ANTLRv4) to DetailNode tree. 287 * 288 * @param parseTreeNode root node of ParseTree 289 * @return root of DetailNode tree 290 */ 291 private DetailNode convertParseTreeToDetailNode(ParseTree parseTreeNode) { 292 final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode); 293 294 JavadocNodeImpl currentJavadocParent = rootJavadocNode; 295 ParseTree parseTreeParent = parseTreeNode; 296 297 while (currentJavadocParent != null) { 298 final JavadocNodeImpl[] children = 299 (JavadocNodeImpl[]) currentJavadocParent.getChildren(); 300 301 insertChildrenNodes(children, parseTreeParent); 302 303 if (children.length > 0) { 304 currentJavadocParent = children[0]; 305 parseTreeParent = parseTreeParent.getChild(0); 306 } 307 else { 308 JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtils 309 .getNextSibling(currentJavadocParent); 310 311 ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent); 312 313 if (nextJavadocSibling == null) { 314 JavadocNodeImpl tempJavadocParent = 315 (JavadocNodeImpl) currentJavadocParent.getParent(); 316 317 ParseTree tempParseTreeParent = parseTreeParent.getParent(); 318 319 while (nextJavadocSibling == null && tempJavadocParent != null) { 320 321 nextJavadocSibling = (JavadocNodeImpl) JavadocUtils 322 .getNextSibling(tempJavadocParent); 323 324 nextParseTreeSibling = getNextSibling(tempParseTreeParent); 325 326 tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent(); 327 tempParseTreeParent = tempParseTreeParent.getParent(); 328 } 329 } 330 currentJavadocParent = nextJavadocSibling; 331 parseTreeParent = nextParseTreeSibling; 332 } 333 } 334 335 return rootJavadocNode; 336 } 337 338 /** 339 * Creates child nodes for each node from 'nodes' array. 340 * @param parseTreeParent original ParseTree parent node 341 * @param nodes array of JavadocNodeImpl nodes 342 */ 343 private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) { 344 for (int i = 0; i < nodes.length; i++) { 345 final JavadocNodeImpl currentJavadocNode = nodes[i]; 346 final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i); 347 final JavadocNodeImpl[] subChildren = 348 createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild); 349 currentJavadocNode.setChildren(subChildren); 350 } 351 } 352 353 /** 354 * Creates children Javadoc nodes base on ParseTree node's children. 355 * @param parentJavadocNode node that will be parent for created children 356 * @param parseTreeNode original ParseTree node 357 * @return array of Javadoc nodes 358 */ 359 private JavadocNodeImpl[] 360 createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) { 361 final JavadocNodeImpl[] children = 362 new JavadocNodeImpl[parseTreeNode.getChildCount()]; 363 364 for (int j = 0; j < children.length; j++) { 365 final JavadocNodeImpl child = 366 createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j); 367 368 children[j] = child; 369 } 370 return children; 371 } 372 373 /** 374 * Creates root JavadocNodeImpl node base on ParseTree root node. 375 * @param parseTreeNode ParseTree root node 376 * @return root Javadoc node 377 */ 378 private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) { 379 final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1); 380 381 final int childCount = parseTreeNode.getChildCount(); 382 final JavadocNodeImpl[] children = new JavadocNodeImpl[childCount]; 383 384 for (int i = 0; i < childCount; i++) { 385 final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i), 386 rootJavadocNode, i); 387 children[i] = child; 388 } 389 rootJavadocNode.setChildren(children); 390 return rootJavadocNode; 391 } 392 393 /** 394 * Creates JavadocNodeImpl node on base of ParseTree node. 395 * 396 * @param parseTree ParseTree node 397 * @param parent DetailNode that will be parent of new node 398 * @param index child index that has new node 399 * @return JavadocNodeImpl node on base of ParseTree node. 400 */ 401 private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) { 402 final JavadocNodeImpl node = new JavadocNodeImpl(); 403 node.setText(parseTree.getText()); 404 node.setColumnNumber(getColumn(parseTree)); 405 node.setLineNumber(getLine(parseTree) + blockCommentAst.getLineNo()); 406 node.setIndex(index); 407 node.setType(getTokenType(parseTree)); 408 node.setParent(parent); 409 node.setChildren(new JavadocNodeImpl[parseTree.getChildCount()]); 410 return node; 411 } 412 413 /** 414 * Gets next sibling of ParseTree node. 415 * @param node ParseTree node 416 * @return next sibling of ParseTree node. 417 */ 418 private static ParseTree getNextSibling(ParseTree node) { 419 ParseTree nextSibling = null; 420 421 if (node.getParent() != null) { 422 final ParseTree parent = node.getParent(); 423 final int childCount = parent.getChildCount(); 424 425 int index = 0; 426 while (true) { 427 final ParseTree currentNode = parent.getChild(index); 428 if (currentNode.equals(node)) { 429 if (index != childCount - 1) { 430 nextSibling = parent.getChild(index + 1); 431 } 432 break; 433 } 434 index++; 435 } 436 } 437 return nextSibling; 438 } 439 440 /** 441 * Gets token type of ParseTree node from JavadocTokenTypes class. 442 * @param node ParseTree node. 443 * @return token type from JavadocTokenTypes 444 */ 445 private static int getTokenType(ParseTree node) { 446 final int tokenType; 447 448 if (node.getChildCount() == 0) { 449 tokenType = ((TerminalNode) node).getSymbol().getType(); 450 } 451 else { 452 final String className = getNodeClassNameWithoutContext(node); 453 final String typeName = 454 CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className); 455 tokenType = JavadocUtils.getTokenId(typeName); 456 } 457 458 return tokenType; 459 } 460 461 /** 462 * Gets class name of ParseTree node and removes 'Context' postfix at the 463 * end. 464 * @param node 465 * ParseTree node. 466 * @return class name without 'Context' 467 */ 468 private static String getNodeClassNameWithoutContext(ParseTree node) { 469 final String className = node.getClass().getSimpleName(); 470 // remove 'Context' at the end 471 final int contextLength = 7; 472 return className.substring(0, className.length() - contextLength); 473 } 474 475 /** 476 * Gets line number from ParseTree node. 477 * @param tree 478 * ParseTree node 479 * @return line number 480 */ 481 private static int getLine(ParseTree tree) { 482 if (tree instanceof TerminalNode) { 483 return ((TerminalNode) tree).getSymbol().getLine() - 1; 484 } 485 else { 486 final ParserRuleContext rule = (ParserRuleContext) tree; 487 return rule.start.getLine() - 1; 488 } 489 } 490 491 /** 492 * Gets column number from ParseTree node. 493 * @param tree 494 * ParseTree node 495 * @return column number 496 */ 497 private static int getColumn(ParseTree tree) { 498 if (tree instanceof TerminalNode) { 499 return ((TerminalNode) tree).getSymbol().getCharPositionInLine(); 500 } 501 else { 502 final ParserRuleContext rule = (ParserRuleContext) tree; 503 return rule.start.getCharPositionInLine(); 504 } 505 } 506 507 /** 508 * Parses block comment content as javadoc comment. 509 * @param blockComment 510 * block comment content. 511 * @return parse tree 512 */ 513 private ParseTree parseJavadocAsParseTree(String blockComment) { 514 final ANTLRInputStream input = new ANTLRInputStream(blockComment); 515 516 final JavadocLexer lexer = new JavadocLexer(input); 517 518 // remove default error listeners 519 lexer.removeErrorListeners(); 520 521 // add custom error listener that logs parsing errors 522 lexer.addErrorListener(errorListener); 523 524 final CommonTokenStream tokens = new CommonTokenStream(lexer); 525 526 final JavadocParser parser = new JavadocParser(tokens); 527 528 // remove default error listeners 529 parser.removeErrorListeners(); 530 531 // add custom error listener that logs syntax errors 532 parser.addErrorListener(errorListener); 533 534 // This strategy stops parsing when parser error occurs. 535 // By default it uses Error Recover Strategy which is slow and useless. 536 parser.setErrorHandler(new BailErrorStrategy()); 537 538 return parser.javadoc(); 539 } 540 541 /** 542 * Processes JavadocAST tree notifying Check. 543 * @param root 544 * root of JavadocAST tree. 545 */ 546 private void processTree(DetailNode root) { 547 beginJavadocTree(root); 548 walk(root); 549 finishJavadocTree(root); 550 } 551 552 /** 553 * Processes a node calling Check at interested nodes. 554 * @param root 555 * the root of tree for process 556 */ 557 private void walk(DetailNode root) { 558 final int[] defaultTokenTypes = getDefaultJavadocTokens(); 559 560 DetailNode curNode = root; 561 while (curNode != null) { 562 final boolean waitsFor = Ints.contains(defaultTokenTypes, curNode.getType()); 563 564 if (waitsFor) { 565 visitJavadocToken(curNode); 566 } 567 DetailNode toVisit = JavadocUtils.getFirstChild(curNode); 568 while (curNode != null && toVisit == null) { 569 570 if (waitsFor) { 571 leaveJavadocToken(curNode); 572 } 573 574 toVisit = JavadocUtils.getNextSibling(curNode); 575 if (toVisit == null) { 576 curNode = curNode.getParent(); 577 } 578 } 579 curNode = toVisit; 580 } 581 } 582 583 /** 584 * Custom error listener for JavadocParser that prints user readable errors. 585 */ 586 private static class DescriptiveErrorListener extends BaseErrorListener { 587 588 /** 589 * Offset is line number of beginning of the Javadoc comment. Log 590 * messages should have line number in scope of file, not in scope of 591 * Javadoc comment. 592 */ 593 private int offset; 594 595 /** 596 * Error message that appeared while parsing. 597 */ 598 private ParseErrorMessage errorMessage; 599 600 /** 601 * Getter for error message during parsing. 602 * @return Error message during parsing. 603 */ 604 private ParseErrorMessage getErrorMessage() { 605 return errorMessage; 606 } 607 608 /** 609 * Sets offset. Offset is line number of beginning of the Javadoc 610 * comment. Log messages should have line number in scope of file, not 611 * in scope of Javadoc comment. 612 * @param offset 613 * offset line number 614 */ 615 public void setOffset(int offset) { 616 this.offset = offset; 617 } 618 619 /** 620 * Logs parser errors in Checkstyle manner. Parser can generate error 621 * messages. There is special error that parser can generate. It is 622 * missed close HTML tag. This case is special because parser prints 623 * error like {@code "no viable alternative at input 'b \n *\n'"} and it 624 * is not clear that error is about missed close HTML tag. Other error 625 * messages are not special and logged simply as "Parse Error...". 626 * 627 * <p>{@inheritDoc} 628 */ 629 @Override 630 public void syntaxError( 631 Recognizer<?, ?> recognizer, Object offendingSymbol, 632 int line, int charPositionInLine, 633 String msg, RecognitionException ex) { 634 final int lineNumber = offset + line; 635 final Token token = (Token) offendingSymbol; 636 637 if (MSG_JAVADOC_MISSED_HTML_CLOSE.equals(msg)) { 638 errorMessage = new ParseErrorMessage(lineNumber, 639 MSG_JAVADOC_MISSED_HTML_CLOSE, charPositionInLine, token.getText()); 640 641 throw new ParseCancellationException(msg); 642 } 643 else if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) { 644 errorMessage = new ParseErrorMessage(lineNumber, 645 MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine, token.getText()); 646 647 throw new ParseCancellationException(msg); 648 } 649 else { 650 final int ruleIndex = ex.getCtx().getRuleIndex(); 651 final String ruleName = recognizer.getRuleNames()[ruleIndex]; 652 final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to( 653 CaseFormat.UPPER_UNDERSCORE, ruleName); 654 655 errorMessage = new ParseErrorMessage(lineNumber, 656 MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName); 657 } 658 } 659 } 660 661 /** 662 * Contains result of parsing javadoc comment: DetailNode tree and parse 663 * error message. 664 */ 665 private static class ParseStatus { 666 /** 667 * DetailNode tree (is null if parsing fails). 668 */ 669 private DetailNode tree; 670 671 /** 672 * Parse error message (is null if parsing is successful). 673 */ 674 private ParseErrorMessage parseErrorMessage; 675 676 /** 677 * Getter for DetailNode tree. 678 * @return DetailNode tree if parsing was successful, null otherwise. 679 */ 680 public DetailNode getTree() { 681 return tree; 682 } 683 684 /** 685 * Sets DetailNode tree. 686 * @param tree DetailNode tree. 687 */ 688 public void setTree(DetailNode tree) { 689 this.tree = tree; 690 } 691 692 /** 693 * Getter for error message during parsing. 694 * @return Error message if parsing was unsuccessful, null otherwise. 695 */ 696 public ParseErrorMessage getParseErrorMessage() { 697 return parseErrorMessage; 698 } 699 700 /** 701 * Sets parse error message. 702 * @param parseErrorMessage Parse error message. 703 */ 704 public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) { 705 this.parseErrorMessage = parseErrorMessage; 706 } 707 708 } 709 710 /** 711 * Contains information about parse error message. 712 */ 713 private static class ParseErrorMessage { 714 /** 715 * Line number where parse error occurred. 716 */ 717 private final int lineNumber; 718 719 /** 720 * Key for error message. 721 */ 722 private final String messageKey; 723 724 /** 725 * Error message arguments. 726 */ 727 private final Object[] messageArguments; 728 729 /** 730 * Initializes parse error message. 731 * 732 * @param lineNumber line number 733 * @param messageKey message key 734 * @param messageArguments message arguments 735 */ 736 ParseErrorMessage(int lineNumber, String messageKey, Object ... messageArguments) { 737 this.lineNumber = lineNumber; 738 this.messageKey = messageKey; 739 this.messageArguments = messageArguments.clone(); 740 } 741 742 /** 743 * Getter for line number where parse error occurred. 744 * @return Line number where parse error occurred. 745 */ 746 public int getLineNumber() { 747 return lineNumber; 748 } 749 750 /** 751 * Getter for key for error message. 752 * @return Key for error message. 753 */ 754 public String getMessageKey() { 755 return messageKey; 756 } 757 758 /** 759 * Getter for error message arguments. 760 * @return Array of error message arguments. 761 */ 762 public Object[] getMessageArguments() { 763 return messageArguments.clone(); 764 } 765 } 766}