DocCommentTester.java revision 4278:a6cee0419f93
1/* 2 * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import java.io.File; 25import java.io.FileWriter; 26import java.io.IOException; 27import java.io.PrintWriter; 28import java.io.StringWriter; 29import java.io.Writer; 30import java.text.BreakIterator; 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.List; 34import java.util.Locale; 35import java.util.regex.Matcher; 36import java.util.regex.Pattern; 37import java.util.stream.Collectors; 38 39import javax.lang.model.element.Name; 40import javax.tools.JavaFileObject; 41import javax.tools.StandardJavaFileManager; 42 43import com.sun.source.doctree.*; 44import com.sun.source.tree.ClassTree; 45import com.sun.source.tree.CompilationUnitTree; 46import com.sun.source.tree.MethodTree; 47import com.sun.source.tree.Tree; 48import com.sun.source.tree.VariableTree; 49import com.sun.source.util.DocTreeScanner; 50import com.sun.source.util.DocTrees; 51import com.sun.source.util.JavacTask; 52import com.sun.source.util.TreePath; 53import com.sun.source.util.TreePathScanner; 54import com.sun.tools.javac.api.JavacTool; 55import com.sun.tools.javac.tree.DCTree; 56import com.sun.tools.javac.tree.DCTree.DCDocComment; 57import com.sun.tools.javac.tree.DCTree.DCErroneous; 58import com.sun.tools.javac.tree.DocPretty; 59 60public class DocCommentTester { 61 62 public static final String BI_MARKER = "BREAK_ITERATOR"; 63 public final boolean useBreakIterator; 64 65 public DocCommentTester(boolean useBreakIterator) { 66 this.useBreakIterator = useBreakIterator; 67 } 68 public static void main(String... args) throws Exception { 69 ArrayList<String> list = new ArrayList(Arrays.asList(args)); 70 if (!list.isEmpty() && "-useBreakIterator".equals(list.get(0))) { 71 list.remove(0); 72 new DocCommentTester(true).run(list); 73 } else { 74 new DocCommentTester(false).run(list); 75 } 76 } 77 78 public void run(List<String> args) throws Exception { 79 String testSrc = System.getProperty("test.src"); 80 81 List<File> files = args.stream() 82 .map(arg -> new File(testSrc, arg)) 83 .collect(Collectors.toList()); 84 85 JavacTool javac = JavacTool.create(); 86 StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null); 87 88 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files); 89 90 JavacTask t = javac.getTask(null, fm, null, null, null, fos); 91 final DocTrees trees = DocTrees.instance(t); 92 93 if (useBreakIterator) { 94 // BreakIterators are locale dependent wrt. behavior 95 trees.setBreakIterator(BreakIterator.getSentenceInstance(Locale.ENGLISH)); 96 } 97 98 final Checker[] checkers = { 99 new ASTChecker(this, trees), 100 new PosChecker(this, trees), 101 new PrettyChecker(this, trees) 102 }; 103 104 DeclScanner d = new DeclScanner() { 105 @Override 106 public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) { 107 for (Checker c: checkers) 108 c.visitCompilationUnit(tree); 109 return super.visitCompilationUnit(tree, ignore); 110 } 111 112 @Override 113 void visitDecl(Tree tree, Name name) { 114 TreePath path = getCurrentPath(); 115 String dc = trees.getDocComment(path); 116 if (dc != null) { 117 for (Checker c : checkers) { 118 try { 119 System.err.println(path.getLeaf().getKind() 120 + " " + name 121 + " " + c.getClass().getSimpleName()); 122 123 c.check(path, name); 124 125 System.err.println(); 126 } catch (Exception e) { 127 error("Exception " + e); 128 e.printStackTrace(System.err); 129 } 130 } 131 } 132 } 133 }; 134 135 Iterable<? extends CompilationUnitTree> units = t.parse(); 136 for (CompilationUnitTree unit: units) { 137 d.scan(unit, null); 138 } 139 140 if (errors > 0) 141 throw new Exception(errors + " errors occurred"); 142 } 143 144 static abstract class DeclScanner extends TreePathScanner<Void, Void> { 145 abstract void visitDecl(Tree tree, Name name); 146 147 @Override 148 public Void visitClass(ClassTree tree, Void ignore) { 149 super.visitClass(tree, ignore); 150 visitDecl(tree, tree.getSimpleName()); 151 return null; 152 } 153 154 @Override 155 public Void visitMethod(MethodTree tree, Void ignore) { 156 super.visitMethod(tree, ignore); 157 visitDecl(tree, tree.getName()); 158 return null; 159 } 160 161 @Override 162 public Void visitVariable(VariableTree tree, Void ignore) { 163 super.visitVariable(tree, ignore); 164 visitDecl(tree, tree.getName()); 165 return null; 166 } 167 } 168 169 /** 170 * Base class for checkers to check the doc comment on a declaration 171 * (when present.) 172 */ 173 abstract class Checker { 174 final DocTrees trees; 175 176 Checker(DocTrees trees) { 177 this.trees = trees; 178 } 179 180 void visitCompilationUnit(CompilationUnitTree tree) { } 181 182 abstract void check(TreePath tree, Name name) throws Exception; 183 184 void error(String msg) { 185 DocCommentTester.this.error(msg); 186 } 187 } 188 189 void error(String msg) { 190 System.err.println("Error: " + msg); 191 errors++; 192 } 193 194 int errors; 195 196 /** 197 * Verify the structure of the DocTree AST by comparing it against golden text. 198 */ 199 static class ASTChecker extends Checker { 200 static final String NEWLINE = System.getProperty("line.separator"); 201 Printer printer = new Printer(); 202 String source; 203 DocCommentTester test; 204 205 ASTChecker(DocCommentTester test, DocTrees t) { 206 test.super(t); 207 this.test = test; 208 } 209 210 @Override 211 void visitCompilationUnit(CompilationUnitTree tree) { 212 try { 213 source = tree.getSourceFile().getCharContent(true).toString(); 214 } catch (IOException e) { 215 source = ""; 216 } 217 } 218 219 void check(TreePath path, Name name) { 220 StringWriter out = new StringWriter(); 221 DocCommentTree dc = trees.getDocCommentTree(path); 222 printer.print(dc, out); 223 out.flush(); 224 String found = out.toString().replace(NEWLINE, "\n"); 225 226 /* 227 * Look for the first block comment after the first occurrence 228 * of name, noting that, block comments with BI_MARKER may 229 * very well be present. 230 */ 231 int start = test.useBreakIterator 232 ? source.indexOf("\n/*\n" + BI_MARKER + "\n", findName(source, name)) 233 : source.indexOf("\n/*\n", findName(source, name)); 234 int end = source.indexOf("\n*/\n", start); 235 int startlen = start + (test.useBreakIterator ? BI_MARKER.length() + 1 : 0) + 4; 236 String expect = source.substring(startlen, end + 1); 237 if (!found.equals(expect)) { 238 if (test.useBreakIterator) { 239 System.err.println("Using BreakIterator"); 240 } 241 System.err.println("Expect:\n" + expect); 242 System.err.println("Found:\n" + found); 243 error("AST mismatch for " + name); 244 } 245 } 246 247 /** 248 * This main program is to set up the golden comments used by this 249 * checker. 250 * Usage: 251 * java DocCommentTester$ASTChecker -o dir file... 252 * The given files are written to the output directory with their 253 * golden comments updated. The intent is that the files should 254 * then be compared with the originals, e.g. with meld, and if the 255 * changes are approved, the new files can be used to replace the old. 256 */ 257 public static void main(String... args) throws Exception { 258 List<File> files = new ArrayList<File>(); 259 File o = null; 260 for (int i = 0; i < args.length; i++) { 261 String arg = args[i]; 262 if (arg.equals("-o")) 263 o = new File(args[++i]); 264 else if (arg.startsWith("-")) 265 throw new IllegalArgumentException(arg); 266 else { 267 files.add(new File(arg)); 268 } 269 } 270 271 if (o == null) 272 throw new IllegalArgumentException("no output dir specified"); 273 final File outDir = o; 274 275 JavacTool javac = JavacTool.create(); 276 StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null); 277 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files); 278 279 JavacTask t = javac.getTask(null, fm, null, null, null, fos); 280 final DocTrees trees = DocTrees.instance(t); 281 282 DeclScanner d = new DeclScanner() { 283 Printer p = new Printer(); 284 String source; 285 286 @Override 287 public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) { 288 System.err.println("processing " + tree.getSourceFile().getName()); 289 try { 290 source = tree.getSourceFile().getCharContent(true).toString(); 291 } catch (IOException e) { 292 source = ""; 293 } 294 // remove existing gold by removing all block comments after the first '{'. 295 int start = source.indexOf("{"); 296 while ((start = source.indexOf("\n/*\n", start)) != -1) { 297 int end = source.indexOf("\n*/\n"); 298 source = source.substring(0, start + 1) + source.substring(end + 4); 299 } 300 301 // process decls in compilation unit 302 super.visitCompilationUnit(tree, ignore); 303 304 // write the modified source 305 File f = new File(tree.getSourceFile().getName()); 306 File outFile = new File(outDir, f.getName()); 307 try { 308 FileWriter out = new FileWriter(outFile); 309 try { 310 out.write(source); 311 } finally { 312 out.close(); 313 } 314 } catch (IOException e) { 315 System.err.println("Can't write " + tree.getSourceFile().getName() 316 + " to " + outFile + ": " + e); 317 } 318 return null; 319 } 320 321 @Override 322 void visitDecl(Tree tree, Name name) { 323 DocTree dc = trees.getDocCommentTree(getCurrentPath()); 324 if (dc != null) { 325 StringWriter out = new StringWriter(); 326 p.print(dc, out); 327 String found = out.toString(); 328 329 // Look for the empty line after the first occurrence of name 330 int pos = source.indexOf("\n\n", findName(source, name)); 331 332 // Insert the golden comment 333 source = source.substring(0, pos) 334 + "\n/*\n" 335 + found 336 + "*/" 337 + source.substring(pos); 338 } 339 } 340 341 }; 342 343 Iterable<? extends CompilationUnitTree> units = t.parse(); 344 for (CompilationUnitTree unit: units) { 345 d.scan(unit, null); 346 } 347 } 348 349 static int findName(String source, Name name) { 350 Pattern p = Pattern.compile("\\s" + name + "[(;]"); 351 Matcher m = p.matcher(source); 352 if (!m.find()) 353 throw new Error("cannot find " + name); 354 return m.start(); 355 } 356 357 static class Printer implements DocTreeVisitor<Void, Void> { 358 PrintWriter out; 359 360 void print(DocTree tree, Writer out) { 361 this.out = (out instanceof PrintWriter) 362 ? (PrintWriter) out : new PrintWriter(out); 363 tree.accept(this, null); 364 this.out.flush(); 365 } 366 367 public Void visitAttribute(AttributeTree node, Void p) { 368 header(node); 369 indent(+1); 370 print("name", node.getName().toString()); 371 print("vkind", node.getValueKind().toString()); 372 print("value", node.getValue()); 373 indent(-1); 374 indent(); 375 out.println("]"); 376 return null; 377 } 378 379 public Void visitAuthor(AuthorTree node, Void p) { 380 header(node); 381 indent(+1); 382 print("name", node.getName()); 383 indent(-1); 384 indent(); 385 out.println("]"); 386 return null; 387 } 388 389 public Void visitComment(CommentTree node, Void p) { 390 header(node, compress(node.getBody())); 391 return null; 392 } 393 394 public Void visitDeprecated(DeprecatedTree node, Void p) { 395 header(node); 396 indent(+1); 397 print("body", node.getBody()); 398 indent(-1); 399 indent(); 400 out.println("]"); 401 return null; 402 } 403 404 public Void visitDocComment(DocCommentTree node, Void p) { 405 header(node); 406 indent(+1); 407 print("firstSentence", node.getFirstSentence()); 408 print("body", node.getBody()); 409 print("block tags", node.getBlockTags()); 410 indent(-1); 411 indent(); 412 out.println("]"); 413 return null; 414 } 415 416 public Void visitDocRoot(DocRootTree node, Void p) { 417 header(node, ""); 418 return null; 419 } 420 421 public Void visitEndElement(EndElementTree node, Void p) { 422 header(node, node.getName().toString()); 423 return null; 424 } 425 426 public Void visitEntity(EntityTree node, Void p) { 427 header(node, node.getName().toString()); 428 return null; 429 } 430 431 public Void visitErroneous(ErroneousTree node, Void p) { 432 header(node); 433 indent(+1); 434 print("code", ((DCErroneous) node).diag.getCode()); 435 print("body", compress(node.getBody())); 436 indent(-1); 437 indent(); 438 out.println("]"); 439 return null; 440 } 441 442 public Void visitHidden(HiddenTree node, Void p) { 443 header(node); 444 indent(+1); 445 print("body", node.getBody()); 446 indent(-1); 447 indent(); 448 out.println("]"); 449 return null; 450 } 451 452 public Void visitIdentifier(IdentifierTree node, Void p) { 453 header(node, compress(node.getName().toString())); 454 return null; 455 } 456 457 @Override 458 public Void visitIndex(IndexTree node, Void p) { 459 header(node); 460 indent(+1); 461 print("term", node.getSearchTerm()); 462 print("description", node.getDescription()); 463 indent(-1); 464 indent(); 465 out.println("]"); 466 return null; 467 } 468 469 public Void visitInheritDoc(InheritDocTree node, Void p) { 470 header(node, ""); 471 return null; 472 } 473 474 public Void visitLink(LinkTree node, Void p) { 475 header(node); 476 indent(+1); 477 print("reference", node.getReference()); 478 print("body", node.getLabel()); 479 indent(-1); 480 indent(); 481 out.println("]"); 482 return null; 483 } 484 485 public Void visitLiteral(LiteralTree node, Void p) { 486 header(node, compress(node.getBody().getBody())); 487 return null; 488 } 489 490 public Void visitParam(ParamTree node, Void p) { 491 header(node); 492 indent(+1); 493 print("name", node.getName()); 494 print("description", node.getDescription()); 495 indent(-1); 496 indent(); 497 out.println("]"); 498 return null; 499 } 500 501 public Void visitProvides(ProvidesTree node, Void p) { 502 header(node); 503 indent(+1); 504 print("serviceName", node.getServiceType()); 505 print("description", node.getDescription()); 506 indent(-1); 507 indent(); 508 out.println("]"); 509 return null; 510 } 511 512 public Void visitReference(ReferenceTree node, Void p) { 513 header(node, compress(node.getSignature())); 514 return null; 515 } 516 517 public Void visitReturn(ReturnTree node, Void p) { 518 header(node); 519 indent(+1); 520 print("description", node.getDescription()); 521 indent(-1); 522 indent(); 523 out.println("]"); 524 return null; 525 } 526 527 public Void visitSee(SeeTree node, Void p) { 528 header(node); 529 indent(+1); 530 print("reference", node.getReference()); 531 indent(-1); 532 indent(); 533 out.println("]"); 534 return null; 535 } 536 537 public Void visitSerial(SerialTree node, Void p) { 538 header(node); 539 indent(+1); 540 print("description", node.getDescription()); 541 indent(-1); 542 indent(); 543 out.println("]"); 544 return null; 545 } 546 547 public Void visitSerialData(SerialDataTree node, Void p) { 548 header(node); 549 indent(+1); 550 print("description", node.getDescription()); 551 indent(-1); 552 indent(); 553 out.println("]"); 554 return null; 555 } 556 557 public Void visitSerialField(SerialFieldTree node, Void p) { 558 header(node); 559 indent(+1); 560 print("name", node.getName()); 561 print("type", node.getType()); 562 print("description", node.getDescription()); 563 indent(-1); 564 indent(); 565 out.println("]"); 566 return null; 567 } 568 569 public Void visitSince(SinceTree node, Void p) { 570 header(node); 571 indent(+1); 572 print("body", node.getBody()); 573 indent(-1); 574 indent(); 575 out.println("]"); 576 return null; 577 } 578 579 public Void visitStartElement(StartElementTree node, Void p) { 580 header(node); 581 indent(+1); 582 indent(); 583 out.println("name:" + node.getName()); 584 print("attributes", node.getAttributes()); 585 indent(-1); 586 indent(); 587 out.println("]"); 588 return null; 589 } 590 591 @Override 592 public Void visitSummary(SummaryTree node, Void p) { 593 header(node); 594 indent(+1); 595 print("summary", node.getSummary()); 596 indent(-1); 597 indent(); 598 out.println("]"); 599 return null; 600 } 601 602 public Void visitText(TextTree node, Void p) { 603 header(node, compress(node.getBody())); 604 return null; 605 } 606 607 public Void visitThrows(ThrowsTree node, Void p) { 608 header(node); 609 indent(+1); 610 print("exceptionName", node.getExceptionName()); 611 print("description", node.getDescription()); 612 indent(-1); 613 indent(); 614 out.println("]"); 615 return null; 616 } 617 618 public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) { 619 header(node); 620 indent(+1); 621 indent(); 622 out.println("tag:" + node.getTagName()); 623 print("content", node.getContent()); 624 indent(-1); 625 indent(); 626 out.println("]"); 627 return null; 628 } 629 630 public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) { 631 header(node); 632 indent(+1); 633 indent(); 634 out.println("tag:" + node.getTagName()); 635 print("content", node.getContent()); 636 indent(-1); 637 indent(); 638 out.println("]"); 639 return null; 640 } 641 642 public Void visitUses(UsesTree node, Void p) { 643 header(node); 644 indent(+1); 645 print("serviceName", node.getServiceType()); 646 print("description", node.getDescription()); 647 indent(-1); 648 indent(); 649 out.println("]"); 650 return null; 651 } 652 653 public Void visitValue(ValueTree node, Void p) { 654 header(node); 655 indent(+1); 656 print("reference", node.getReference()); 657 indent(-1); 658 indent(); 659 out.println("]"); 660 return null; 661 } 662 663 public Void visitVersion(VersionTree node, Void p) { 664 header(node); 665 indent(+1); 666 print("body", node.getBody()); 667 indent(-1); 668 indent(); 669 out.println("]"); 670 return null; 671 } 672 673 public Void visitOther(DocTree node, Void p) { 674 throw new UnsupportedOperationException("Not supported yet."); 675 } 676 677 /* 678 * Use this method to start printing a multi-line representation of a 679 * DocTree node. The representation should be termintated by calling 680 * out.println("]"). 681 */ 682 void header(DocTree node) { 683 indent(); 684 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos); 685 } 686 687 /* 688 * Use this method to print a single-line representation of a DocTree node. 689 */ 690 void header(DocTree node, String rest) { 691 indent(); 692 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos 693 + (rest.isEmpty() ? "" : ", " + rest) 694 + "]"); 695 } 696 697 String simpleClassName(DocTree node) { 698 return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1"); 699 } 700 701 void print(String name, DocTree item) { 702 indent(); 703 if (item == null) 704 out.println(name + ": null"); 705 else { 706 out.println(name + ":"); 707 indent(+1); 708 item.accept(this, null); 709 indent(-1); 710 } 711 } 712 713 void print(String name, String s) { 714 indent(); 715 out.println(name + ": " + s); 716 } 717 718 void print(String name, List<? extends DocTree> list) { 719 indent(); 720 if (list == null) 721 out.println(name + ": null"); 722 else if (list.isEmpty()) 723 out.println(name + ": empty"); 724 else { 725 out.println(name + ": " + list.size()); 726 indent(+1); 727 for (DocTree tree: list) { 728 tree.accept(this, null); 729 } 730 indent(-1); 731 } 732 } 733 734 int indent = 0; 735 736 void indent() { 737 for (int i = 0; i < indent; i++) { 738 out.print(" "); 739 } 740 } 741 742 void indent(int n) { 743 indent += n; 744 } 745 746 String compress(String s) { 747 s = s.replace("\n", "|").replace(" ", "_"); 748 return (s.length() < 32) 749 ? s 750 : s.substring(0, 16) + "..." + s.substring(16); 751 } 752 753 String quote(String s) { 754 if (s.contains("\"")) 755 return "'" + s + "'"; 756 else if (s.contains("'") || s.contains(" ")) 757 return '"' + s + '"'; 758 else 759 return s; 760 } 761 } 762 } 763 764 /** 765 * Verify the reported tree positions by comparing the characters found 766 * at and after the reported position with the beginning of the pretty- 767 * printed text. 768 */ 769 static class PosChecker extends Checker { 770 PosChecker(DocCommentTester test, DocTrees t) { 771 test.super(t); 772 } 773 774 @Override 775 void check(TreePath path, Name name) throws Exception { 776 JavaFileObject fo = path.getCompilationUnit().getSourceFile(); 777 final CharSequence cs = fo.getCharContent(true); 778 779 final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path); 780 DCTree t = (DCTree) trees.getDocCommentTree(path); 781 782 DocTreeScanner scanner = new DocTreeScanner<Void,Void>() { 783 @Override 784 public Void scan(DocTree node, Void ignore) { 785 if (node != null) { 786 try { 787 String expect = getExpectText(node); 788 long pos = ((DCTree) node).getSourcePosition(dc); 789 String found = getFoundText(cs, (int) pos, expect.length()); 790 if (!found.equals(expect)) { 791 System.err.println("expect: " + expect); 792 System.err.println("found: " + found); 793 error("mismatch"); 794 } 795 796 } catch (StringIndexOutOfBoundsException e) { 797 error(node.getClass() + ": " + e.toString()); 798 e.printStackTrace(); 799 } 800 } 801 return super.scan(node, ignore); 802 } 803 }; 804 805 scanner.scan(t, null); 806 } 807 808 String getExpectText(DocTree t) { 809 StringWriter sw = new StringWriter(); 810 DocPretty p = new DocPretty(sw); 811 try { p.print(t); } catch (IOException never) { } 812 String s = sw.toString(); 813 if (s.length() <= 1) 814 return s; 815 int ws = s.replaceAll("\\s+", " ").indexOf(" "); 816 if (ws != -1) s = s.substring(0, ws); 817 return (s.length() < 5) ? s : s.substring(0, 5); 818 } 819 820 String getFoundText(CharSequence cs, int pos, int len) { 821 return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString(); 822 } 823 } 824 825 /** 826 * Verify the pretty printed text against a normalized form of the 827 * original doc comment. 828 */ 829 static class PrettyChecker extends Checker { 830 831 PrettyChecker(DocCommentTester test, DocTrees t) { 832 test.super(t); 833 } 834 835 @Override 836 void check(TreePath path, Name name) throws Exception { 837 String raw = trees.getDocComment(path); 838 String normRaw = normalize(raw); 839 840 StringWriter out = new StringWriter(); 841 DocPretty dp = new DocPretty(out); 842 dp.print(trees.getDocCommentTree(path)); 843 String pretty = out.toString(); 844 845 if (!pretty.equals(normRaw)) { 846 error("mismatch"); 847 System.err.println("*** expected:"); 848 System.err.println(normRaw.replace(" ", "_")); 849 System.err.println("*** found:"); 850 System.err.println(pretty.replace(" ", "_")); 851 // throw new Error(); 852 } 853 } 854 855 /** 856 * Normalize white space in places where the tree does not preserve it. 857 */ 858 String normalize(String s) { 859 s = s.trim() 860 .replaceFirst("\\.\\s*\\n *@", ".\n@") 861 .replaceAll("\\{@docRoot\\s+\\}", "{@docRoot}") 862 .replaceAll("\\{@inheritDoc\\s+\\}", "{@inheritDoc}") 863 .replaceAll("(\\{@value\\s+[^}]+)\\s+(\\})", "$1$2") 864 .replaceAll("\n[ \t]+@", "\n@") 865 .replaceAll("(\\{@code)(\\x20)(\\s+.*)", "$1$3"); 866 return s; 867 } 868 } 869} 870 871