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 
  24 import java.io.File;
  25 import java.io.FileWriter;
  26 import java.io.IOException;
  27 import java.io.PrintWriter;
  28 import java.io.StringWriter;
  29 import java.io.Writer;
  30 import java.text.BreakIterator;
  31 import java.util.ArrayList;
  32 import java.util.Arrays;
  33 import java.util.List;
  34 import java.util.Locale;
  35 import java.util.regex.Matcher;
  36 import java.util.regex.Pattern;
  37 import java.util.stream.Collectors;
  38 
  39 import javax.lang.model.element.Name;
  40 import javax.tools.JavaFileObject;
  41 import javax.tools.StandardJavaFileManager;
  42 
  43 import com.sun.source.doctree.*;
  44 import com.sun.source.tree.ClassTree;
  45 import com.sun.source.tree.CompilationUnitTree;
  46 import com.sun.source.tree.MethodTree;
  47 import com.sun.source.tree.Tree;
  48 import com.sun.source.tree.VariableTree;
  49 import com.sun.source.util.DocTreeScanner;
  50 import com.sun.source.util.DocTrees;
  51 import com.sun.source.util.JavacTask;
  52 import com.sun.source.util.TreePath;
  53 import com.sun.source.util.TreePathScanner;
  54 import com.sun.tools.javac.api.JavacTool;
  55 import com.sun.tools.javac.tree.DCTree;
  56 import com.sun.tools.javac.tree.DCTree.DCDocComment;
  57 import com.sun.tools.javac.tree.DCTree.DCErroneous;
  58 import com.sun.tools.javac.tree.DocPretty;
  59 
  60 public 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                 // Applicable only to html files, print iff non-empty
 408                 if (!node.getPreamble().isEmpty())
 409                     print("preamble", node.getPreamble());
 410 
 411                 print("firstSentence", node.getFirstSentence());
 412                 print("body", node.getBody());
 413                 print("block tags", node.getBlockTags());
 414 
 415                 // Applicable only to html files, print iff non-empty
 416                 if (!node.getPostamble().isEmpty())
 417                     print("postamble", node.getPostamble());
 418 
 419                 indent(-1);
 420                 indent();
 421                 out.println("]");
 422                 return null;
 423             }
 424 
 425             public Void visitDocRoot(DocRootTree node, Void p) {
 426                 header(node, "");
 427                 return null;
 428             }
 429 
 430             public Void visitDocType(DocTypeTree node, Void p) {
 431                 header(node, compress(node.getText()));
 432                 return null;
 433             }
 434 
 435             public Void visitEndElement(EndElementTree node, Void p) {
 436                 header(node, node.getName().toString());
 437                 return null;
 438             }
 439 
 440             public Void visitEntity(EntityTree node, Void p) {
 441                 header(node, node.getName().toString());
 442                 return null;
 443             }
 444 
 445             public Void visitErroneous(ErroneousTree node, Void p) {
 446                 header(node);
 447                 indent(+1);
 448                 print("code", ((DCErroneous) node).diag.getCode());
 449                 print("body", compress(node.getBody()));
 450                 indent(-1);
 451                 indent();
 452                 out.println("]");
 453                 return null;
 454             }
 455 
 456             public Void visitHidden(HiddenTree node, Void p) {
 457                 header(node);
 458                 indent(+1);
 459                 print("body", node.getBody());
 460                 indent(-1);
 461                 indent();
 462                 out.println("]");
 463                 return null;
 464             }
 465 
 466             public Void visitIdentifier(IdentifierTree node, Void p) {
 467                 header(node, compress(node.getName().toString()));
 468                 return null;
 469             }
 470 
 471             @Override
 472             public Void visitIndex(IndexTree node, Void p) {
 473                 header(node);
 474                 indent(+1);
 475                 print("term", node.getSearchTerm());
 476                 print("description", node.getDescription());
 477                 indent(-1);
 478                 indent();
 479                 out.println("]");
 480                 return null;
 481             }
 482 
 483             public Void visitInheritDoc(InheritDocTree node, Void p) {
 484                 header(node, "");
 485                 return null;
 486             }
 487 
 488             public Void visitLink(LinkTree node, Void p) {
 489                 header(node);
 490                 indent(+1);
 491                 print("reference", node.getReference());
 492                 print("body", node.getLabel());
 493                 indent(-1);
 494                 indent();
 495                 out.println("]");
 496                 return null;
 497             }
 498 
 499             public Void visitLiteral(LiteralTree node, Void p) {
 500                 header(node, compress(node.getBody().getBody()));
 501                 return null;
 502             }
 503 
 504             public Void visitParam(ParamTree node, Void p) {
 505                 header(node);
 506                 indent(+1);
 507                 print("name", node.getName());
 508                 print("description", node.getDescription());
 509                 indent(-1);
 510                 indent();
 511                 out.println("]");
 512                 return null;
 513             }
 514 
 515             public Void visitProvides(ProvidesTree node, Void p) {
 516                 header(node);
 517                 indent(+1);
 518                 print("serviceName", node.getServiceType());
 519                 print("description", node.getDescription());
 520                 indent(-1);
 521                 indent();
 522                 out.println("]");
 523                 return null;
 524             }
 525 
 526             public Void visitReference(ReferenceTree node, Void p) {
 527                 header(node, compress(node.getSignature()));
 528                 return null;
 529             }
 530 
 531             public Void visitReturn(ReturnTree node, Void p) {
 532                 header(node);
 533                 indent(+1);
 534                 print("description", node.getDescription());
 535                 indent(-1);
 536                 indent();
 537                 out.println("]");
 538                 return null;
 539             }
 540 
 541             public Void visitSee(SeeTree node, Void p) {
 542                 header(node);
 543                 indent(+1);
 544                 print("reference", node.getReference());
 545                 indent(-1);
 546                 indent();
 547                 out.println("]");
 548                 return null;
 549             }
 550 
 551             public Void visitSerial(SerialTree node, Void p) {
 552                 header(node);
 553                 indent(+1);
 554                 print("description", node.getDescription());
 555                 indent(-1);
 556                 indent();
 557                 out.println("]");
 558                 return null;
 559             }
 560 
 561             public Void visitSerialData(SerialDataTree node, Void p) {
 562                 header(node);
 563                 indent(+1);
 564                 print("description", node.getDescription());
 565                 indent(-1);
 566                 indent();
 567                 out.println("]");
 568                 return null;
 569             }
 570 
 571             public Void visitSerialField(SerialFieldTree node, Void p) {
 572                 header(node);
 573                 indent(+1);
 574                 print("name", node.getName());
 575                 print("type", node.getType());
 576                 print("description", node.getDescription());
 577                 indent(-1);
 578                 indent();
 579                 out.println("]");
 580                 return null;
 581             }
 582 
 583             public Void visitSince(SinceTree node, Void p) {
 584                 header(node);
 585                 indent(+1);
 586                 print("body", node.getBody());
 587                 indent(-1);
 588                 indent();
 589                 out.println("]");
 590                 return null;
 591             }
 592 
 593             public Void visitStartElement(StartElementTree node, Void p) {
 594                 header(node);
 595                 indent(+1);
 596                 indent();
 597                 out.println("name:" + node.getName());
 598                 print("attributes", node.getAttributes());
 599                 indent(-1);
 600                 indent();
 601                 out.println("]");
 602                 return null;
 603             }
 604 
 605             @Override
 606             public Void visitSummary(SummaryTree node, Void p) {
 607                 header(node);
 608                 indent(+1);
 609                 print("summary", node.getSummary());
 610                 indent(-1);
 611                 indent();
 612                 out.println("]");
 613                 return null;
 614             }
 615 
 616             @Override
 617             public Void visitSystemProperty(SystemPropertyTree node, Void p) {
 618                 header(node);
 619                 indent(+1);
 620                 print("property name", node.getPropertyName().toString());
 621                 indent(-1);
 622                 indent();
 623                 out.println("]");
 624                 return null;
 625             }
 626 
 627             public Void visitText(TextTree node, Void p) {
 628                 header(node, compress(node.getBody()));
 629                 return null;
 630             }
 631 
 632             public Void visitThrows(ThrowsTree node, Void p) {
 633                 header(node);
 634                 indent(+1);
 635                 print("exceptionName", node.getExceptionName());
 636                 print("description", node.getDescription());
 637                 indent(-1);
 638                 indent();
 639                 out.println("]");
 640                 return null;
 641             }
 642 
 643             public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
 644                 header(node);
 645                 indent(+1);
 646                 indent();
 647                 out.println("tag:" + node.getTagName());
 648                 print("content", node.getContent());
 649                 indent(-1);
 650                 indent();
 651                 out.println("]");
 652                 return null;
 653             }
 654 
 655             public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) {
 656                 header(node);
 657                 indent(+1);
 658                 indent();
 659                 out.println("tag:" + node.getTagName());
 660                 print("content", node.getContent());
 661                 indent(-1);
 662                 indent();
 663                 out.println("]");
 664                 return null;
 665             }
 666 
 667             public Void visitUses(UsesTree node, Void p) {
 668                 header(node);
 669                 indent(+1);
 670                 print("serviceName", node.getServiceType());
 671                 print("description", node.getDescription());
 672                 indent(-1);
 673                 indent();
 674                 out.println("]");
 675                 return null;
 676             }
 677 
 678             public Void visitValue(ValueTree node, Void p) {
 679                 header(node);
 680                 indent(+1);
 681                 print("reference", node.getReference());
 682                 indent(-1);
 683                 indent();
 684                 out.println("]");
 685                 return null;
 686             }
 687 
 688             public Void visitVersion(VersionTree node, Void p) {
 689                 header(node);
 690                 indent(+1);
 691                 print("body", node.getBody());
 692                 indent(-1);
 693                 indent();
 694                 out.println("]");
 695                 return null;
 696             }
 697 
 698             public Void visitOther(DocTree node, Void p) {
 699                 throw new UnsupportedOperationException("Not supported yet.");
 700             }
 701 
 702             /*
 703              * Use this method to start printing a multi-line representation of a
 704              * DocTree node. The representation should be termintated by calling
 705              * out.println("]").
 706              */
 707             void header(DocTree node) {
 708                 indent();
 709                 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos);
 710             }
 711 
 712             /*
 713              * Use this method to print a single-line representation of a DocTree node.
 714              */
 715             void header(DocTree node, String rest) {
 716                 indent();
 717                 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos
 718                         + (rest.isEmpty() ? "" : ", " + rest)
 719                         + "]");
 720             }
 721 
 722             String simpleClassName(DocTree node) {
 723                 return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1");
 724             }
 725 
 726             void print(String name, DocTree item) {
 727                 indent();
 728                 if (item == null)
 729                     out.println(name + ": null");
 730                 else {
 731                     out.println(name + ":");
 732                     indent(+1);
 733                     item.accept(this, null);
 734                     indent(-1);
 735                 }
 736             }
 737 
 738             void print(String name, String s) {
 739                 indent();
 740                 out.println(name + ": " + s);
 741             }
 742 
 743             void print(String name, List<? extends DocTree> list) {
 744                 indent();
 745                 if (list == null)
 746                     out.println(name + ": null");
 747                 else if (list.isEmpty())
 748                     out.println(name + ": empty");
 749                 else {
 750                     out.println(name + ": " + list.size());
 751                     indent(+1);
 752                     for (DocTree tree: list) {
 753                         tree.accept(this, null);
 754                     }
 755                     indent(-1);
 756                 }
 757             }
 758 
 759             int indent = 0;
 760 
 761             void indent() {
 762                 for (int i = 0; i < indent; i++) {
 763                     out.print("  ");
 764                 }
 765             }
 766 
 767             void indent(int n) {
 768                 indent += n;
 769             }
 770 
 771             String compress(String s) {
 772                 s = s.replace("\n", "|").replace(" ", "_");
 773                 return (s.length() < 32)
 774                         ? s
 775                         : s.substring(0, 16) + "..." + s.substring(16);
 776             }
 777 
 778             String quote(String s) {
 779                 if (s.contains("\""))
 780                     return "'" + s + "'";
 781                 else if (s.contains("'") || s.contains(" "))
 782                     return '"' + s + '"';
 783                 else
 784                     return s;
 785             }
 786 
 787             @Override
 788             public Void visitAccessor(AccessorTree node, Void p) {
 789                 header(node);
 790                 indent(+1);
 791                 print("description", node.getDescription());
 792                 indent(-1);
 793                 indent();
 794                 out.println("]");
 795                 return null;
 796             }
 797         }
 798     }
 799 
 800     /**
 801      * Verify the reported tree positions by comparing the characters found
 802      * at and after the reported position with the beginning of the pretty-
 803      * printed text.
 804      */
 805     static class PosChecker extends Checker {
 806         PosChecker(DocCommentTester test, DocTrees t) {
 807             test.super(t);
 808         }
 809 
 810         @Override
 811         void check(TreePath path, Name name) throws Exception {
 812             JavaFileObject fo = path.getCompilationUnit().getSourceFile();
 813             final CharSequence cs = fo.getCharContent(true);
 814 
 815             final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
 816             DCTree t = (DCTree) trees.getDocCommentTree(path);
 817 
 818             DocTreeScanner scanner = new DocTreeScanner<Void,Void>() {
 819                 @Override
 820                 public Void scan(DocTree node, Void ignore) {
 821                     if (node != null) {
 822                         try {
 823                             String expect = getExpectText(node);
 824                             long pos = ((DCTree) node).getSourcePosition(dc);
 825                             String found = getFoundText(cs, (int) pos, expect.length());
 826                             if (!found.equals(expect)) {
 827                                 System.err.println("expect: " + expect);
 828                                 System.err.println("found:  " + found);
 829                                 error("mismatch");
 830                             }
 831 
 832                         } catch (StringIndexOutOfBoundsException e) {
 833                             error(node.getClass() + ": " + e.toString());
 834                                 e.printStackTrace();
 835                         }
 836                     }
 837                     return super.scan(node, ignore);
 838                 }
 839             };
 840 
 841             scanner.scan(t, null);
 842         }
 843 
 844         String getExpectText(DocTree t) {
 845             StringWriter sw = new StringWriter();
 846             DocPretty p = new DocPretty(sw);
 847             try { p.print(t); } catch (IOException never) { }
 848             String s = sw.toString();
 849             if (s.length() <= 1)
 850                 return s;
 851             int ws = s.replaceAll("\\s+", " ").indexOf(" ");
 852             if (ws != -1) s = s.substring(0, ws);
 853             return (s.length() < 5) ? s : s.substring(0, 5);
 854         }
 855 
 856         String getFoundText(CharSequence cs, int pos, int len) {
 857             return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString();
 858         }
 859     }
 860 
 861     /**
 862      * Verify the pretty printed text against a normalized form of the
 863      * original doc comment.
 864      */
 865     static class PrettyChecker extends Checker {
 866 
 867         PrettyChecker(DocCommentTester test, DocTrees t) {
 868             test.super(t);
 869         }
 870 
 871         @Override
 872         void check(TreePath path, Name name) throws Exception {
 873             String raw = trees.getDocComment(path);
 874             String normRaw = normalize(raw);
 875 
 876             StringWriter out = new StringWriter();
 877             DocPretty dp = new DocPretty(out);
 878             dp.print(trees.getDocCommentTree(path));
 879             String pretty = out.toString();
 880 
 881             if (!pretty.equals(normRaw)) {
 882                 error("mismatch");
 883                 System.err.println("*** expected:");
 884                 System.err.println(normRaw.replace(" ", "_"));
 885                 System.err.println("*** found:");
 886                 System.err.println(pretty.replace(" ", "_"));
 887     //            throw new Error();
 888             }
 889         }
 890 
 891         /**
 892          * Normalize white space in places where the tree does not preserve it.
 893          */
 894         String normalize(String s) {
 895             s = s.trim()
 896                     .replaceFirst("\\.\\s*\\n *@", ".\n@")
 897                     .replaceAll("\\{@docRoot\\s+\\}", "{@docRoot}")
 898                     .replaceAll("\\{@inheritDoc\\s+\\}", "{@inheritDoc}")
 899                     .replaceAll("(\\{@value\\s+[^}]+)\\s+(\\})", "$1$2")
 900                     .replaceAll("\n[ \t]+@", "\n@")
 901                     .replaceAll("(\\{@code)(\\x20)(\\s+.*)", "$1$3");
 902             return s;
 903         }
 904     }
 905 }
 906