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