Fri, 24 Apr 2020 03:58:51 +0100
Merge
1 /*
2 * Copyright (c) 2012, 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 */
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.util.ArrayList;
31 import java.util.List;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
35 import javax.lang.model.element.Name;
36 import javax.tools.JavaFileObject;
37 import javax.tools.StandardJavaFileManager;
39 import com.sun.source.doctree.*;
40 import com.sun.source.tree.ClassTree;
41 import com.sun.source.tree.CompilationUnitTree;
42 import com.sun.source.tree.MethodTree;
43 import com.sun.source.tree.Tree;
44 import com.sun.source.tree.VariableTree;
45 import com.sun.source.util.DocTreeScanner;
46 import com.sun.source.util.DocTrees;
47 import com.sun.source.util.JavacTask;
48 import com.sun.source.util.TreePath;
49 import com.sun.source.util.TreePathScanner;
50 import com.sun.tools.javac.api.JavacTool;
51 import com.sun.tools.javac.tree.DCTree;
52 import com.sun.tools.javac.tree.DCTree.DCDocComment;
53 import com.sun.tools.javac.tree.DCTree.DCErroneous;
54 import com.sun.tools.javac.tree.DocPretty;
56 public class DocCommentTester {
58 public static void main(String... args) throws Exception {
59 new DocCommentTester().run(args);
60 }
62 public void run(String... args) throws Exception {
63 String testSrc = System.getProperty("test.src");
65 List<File> files = new ArrayList<File>();
66 for (String arg: args)
67 files.add(new File(testSrc, arg));
69 JavacTool javac = JavacTool.create();
70 StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null);
72 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
74 JavacTask t = javac.getTask(null, fm, null, null, null, fos);
75 final DocTrees trees = DocTrees.instance(t);
77 final Checker[] checkers = {
78 new ASTChecker(this, trees),
79 new PosChecker(this, trees),
80 new PrettyChecker(this, trees)
81 };
83 DeclScanner d = new DeclScanner() {
84 @Override
85 public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
86 for (Checker c: checkers)
87 c.visitCompilationUnit(tree);
88 return super.visitCompilationUnit(tree, ignore);
89 }
91 @Override
92 void visitDecl(Tree tree, Name name) {
93 TreePath path = getCurrentPath();
94 String dc = trees.getDocComment(path);
95 if (dc != null) {
96 for (Checker c : checkers) {
97 try {
98 System.err.println(path.getLeaf().getKind()
99 + " " + name
100 + " " + c.getClass().getSimpleName());
102 c.check(path, name);
104 System.err.println();
105 } catch (Exception e) {
106 error("Exception " + e);
107 e.printStackTrace(System.err);
108 }
109 }
110 }
111 }
112 };
114 Iterable<? extends CompilationUnitTree> units = t.parse();
115 for (CompilationUnitTree unit: units) {
116 d.scan(unit, null);
117 }
119 if (errors > 0)
120 throw new Exception(errors + " errors occurred");
121 }
123 static abstract class DeclScanner extends TreePathScanner<Void, Void> {
124 abstract void visitDecl(Tree tree, Name name);
126 @Override
127 public Void visitClass(ClassTree tree, Void ignore) {
128 super.visitClass(tree, ignore);
129 visitDecl(tree, tree.getSimpleName());
130 return null;
131 }
133 @Override
134 public Void visitMethod(MethodTree tree, Void ignore) {
135 super.visitMethod(tree, ignore);
136 visitDecl(tree, tree.getName());
137 return null;
138 }
140 @Override
141 public Void visitVariable(VariableTree tree, Void ignore) {
142 super.visitVariable(tree, ignore);
143 visitDecl(tree, tree.getName());
144 return null;
145 }
146 }
148 /**
149 * Base class for checkers to check the doc comment on a declaration
150 * (when present.)
151 */
152 abstract class Checker {
153 final DocTrees trees;
155 Checker(DocTrees trees) {
156 this.trees = trees;
157 }
159 void visitCompilationUnit(CompilationUnitTree tree) { }
161 abstract void check(TreePath tree, Name name) throws Exception;
163 void error(String msg) {
164 DocCommentTester.this.error(msg);
165 }
166 }
168 void error(String msg) {
169 System.err.println("Error: " + msg);
170 errors++;
171 }
173 int errors;
175 /**
176 * Verify the structure of the DocTree AST by comparing it against golden text.
177 */
178 static class ASTChecker extends Checker {
179 static final String NEWLINE = System.getProperty("line.separator");
180 Printer printer = new Printer();
181 String source;
183 ASTChecker(DocCommentTester test, DocTrees t) {
184 test.super(t);
185 }
187 @Override
188 void visitCompilationUnit(CompilationUnitTree tree) {
189 try {
190 source = tree.getSourceFile().getCharContent(true).toString();
191 } catch (IOException e) {
192 source = "";
193 }
194 }
196 void check(TreePath path, Name name) {
197 StringWriter out = new StringWriter();
198 DocCommentTree dc = trees.getDocCommentTree(path);
199 printer.print(dc, out);
200 out.flush();
201 String found = out.toString().replace(NEWLINE, "\n");
203 // Look for the first block comment after the first occurrence of name
204 int start = source.indexOf("\n/*\n", findName(source, name));
205 int end = source.indexOf("\n*/\n", start);
206 String expect = source.substring(start + 4, end + 1);
207 if (!found.equals(expect)) {
208 System.err.println("Expect:\n" + expect);
209 System.err.println("Found:\n" + found);
210 error("AST mismatch for " + name);
211 }
212 }
214 /**
215 * This main program is to set up the golden comments used by this
216 * checker.
217 * Usage:
218 * java DocCommentTester$ASTChecker -o dir file...
219 * The given files are written to the output directory with their
220 * golden comments updated. The intent is that the files should
221 * then be compared with the originals, e.g. with meld, and if the
222 * changes are approved, the new files can be used to replace the old.
223 */
224 public static void main(String... args) throws Exception {
225 List<File> files = new ArrayList<File>();
226 File o = null;
227 for (int i = 0; i < args.length; i++) {
228 String arg = args[i];
229 if (arg.equals("-o"))
230 o = new File(args[++i]);
231 else if (arg.startsWith("-"))
232 throw new IllegalArgumentException(arg);
233 else {
234 files.add(new File(arg));
235 }
236 }
238 if (o == null)
239 throw new IllegalArgumentException("no output dir specified");
240 final File outDir = o;
242 JavacTool javac = JavacTool.create();
243 StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null);
244 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
246 JavacTask t = javac.getTask(null, fm, null, null, null, fos);
247 final DocTrees trees = DocTrees.instance(t);
249 DeclScanner d = new DeclScanner() {
250 Printer p = new Printer();
251 String source;
253 @Override
254 public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
255 System.err.println("processing " + tree.getSourceFile().getName());
256 try {
257 source = tree.getSourceFile().getCharContent(true).toString();
258 } catch (IOException e) {
259 source = "";
260 }
262 // remove existing gold by removing all block comments after the first '{'.
263 int start = source.indexOf("{");
264 while ((start = source.indexOf("\n/*\n", start)) != -1) {
265 int end = source.indexOf("\n*/\n");
266 source = source.substring(0, start + 1) + source.substring(end + 4);
267 }
269 // process decls in compilation unit
270 super.visitCompilationUnit(tree, ignore);
272 // write the modified source
273 File f = new File(tree.getSourceFile().getName());
274 File outFile = new File(outDir, f.getName());
275 try {
276 FileWriter out = new FileWriter(outFile);
277 try {
278 out.write(source);
279 } finally {
280 out.close();
281 }
282 } catch (IOException e) {
283 System.err.println("Can't write " + tree.getSourceFile().getName()
284 + " to " + outFile + ": " + e);
285 }
286 return null;
287 }
289 @Override
290 void visitDecl(Tree tree, Name name) {
291 DocTree dc = trees.getDocCommentTree(getCurrentPath());
292 if (dc != null) {
293 StringWriter out = new StringWriter();
294 p.print(dc, out);
295 String found = out.toString();
297 // Look for the empty line after the first occurrence of name
298 int pos = source.indexOf("\n\n", findName(source, name));
300 // Insert the golden comment
301 source = source.substring(0, pos)
302 + "\n/*\n"
303 + found
304 + "*/"
305 + source.substring(pos);
306 }
307 }
309 };
311 Iterable<? extends CompilationUnitTree> units = t.parse();
312 for (CompilationUnitTree unit: units) {
313 d.scan(unit, null);
314 }
315 }
317 static int findName(String source, Name name) {
318 Pattern p = Pattern.compile("\\s" + name + "[(;]");
319 Matcher m = p.matcher(source);
320 if (!m.find())
321 throw new Error("cannot find " + name);
322 return m.start();
323 }
325 static class Printer implements DocTreeVisitor<Void, Void> {
326 PrintWriter out;
328 void print(DocTree tree, Writer out) {
329 this.out = (out instanceof PrintWriter)
330 ? (PrintWriter) out : new PrintWriter(out);
331 tree.accept(this, null);
332 this.out.flush();
333 }
335 public Void visitAttribute(AttributeTree node, Void p) {
336 header(node);
337 indent(+1);
338 print("name", node.getName().toString());
339 print("vkind", node.getValueKind().toString());
340 print("value", node.getValue());
341 indent(-1);
342 indent();
343 out.println("]");
344 return null;
345 }
347 public Void visitAuthor(AuthorTree node, Void p) {
348 header(node);
349 indent(+1);
350 print("name", node.getName());
351 indent(-1);
352 indent();
353 out.println("]");
354 return null;
355 }
357 public Void visitComment(CommentTree node, Void p) {
358 header(node, compress(node.getBody()));
359 return null;
360 }
362 public Void visitDeprecated(DeprecatedTree node, Void p) {
363 header(node);
364 indent(+1);
365 print("body", node.getBody());
366 indent(-1);
367 indent();
368 out.println("]");
369 return null;
370 }
372 public Void visitDocComment(DocCommentTree node, Void p) {
373 header(node);
374 indent(+1);
375 print("firstSentence", node.getFirstSentence());
376 print("body", node.getBody());
377 print("block tags", node.getBlockTags());
378 indent(-1);
379 indent();
380 out.println("]");
381 return null;
382 }
384 public Void visitDocRoot(DocRootTree node, Void p) {
385 header(node, "");
386 return null;
387 }
389 public Void visitEndElement(EndElementTree node, Void p) {
390 header(node, node.getName().toString());
391 return null;
392 }
394 public Void visitEntity(EntityTree node, Void p) {
395 header(node, node.getName().toString());
396 return null;
397 }
399 public Void visitErroneous(ErroneousTree node, Void p) {
400 header(node);
401 indent(+1);
402 print("code", ((DCErroneous) node).diag.getCode());
403 print("body", compress(node.getBody()));
404 indent(-1);
405 indent();
406 out.println("]");
407 return null;
408 }
410 public Void visitIdentifier(IdentifierTree node, Void p) {
411 header(node, compress(node.getName().toString()));
412 return null;
413 }
415 public Void visitInheritDoc(InheritDocTree node, Void p) {
416 header(node, "");
417 return null;
418 }
420 public Void visitLink(LinkTree node, Void p) {
421 header(node);
422 indent(+1);
423 print("reference", node.getReference());
424 print("body", node.getLabel());
425 indent(-1);
426 indent();
427 out.println("]");
428 return null;
429 }
431 public Void visitLiteral(LiteralTree node, Void p) {
432 header(node, compress(node.getBody().getBody()));
433 return null;
434 }
436 public Void visitParam(ParamTree node, Void p) {
437 header(node);
438 indent(+1);
439 print("name", node.getName());
440 print("description", node.getDescription());
441 indent(-1);
442 indent();
443 out.println("]");
444 return null;
445 }
447 public Void visitReference(ReferenceTree node, Void p) {
448 header(node, compress(node.getSignature()));
449 return null;
450 }
452 public Void visitReturn(ReturnTree node, Void p) {
453 header(node);
454 indent(+1);
455 print("description", node.getDescription());
456 indent(-1);
457 indent();
458 out.println("]");
459 return null;
460 }
462 public Void visitSee(SeeTree node, Void p) {
463 header(node);
464 indent(+1);
465 print("reference", node.getReference());
466 indent(-1);
467 indent();
468 out.println("]");
469 return null;
470 }
472 public Void visitSerial(SerialTree node, Void p) {
473 header(node);
474 indent(+1);
475 print("description", node.getDescription());
476 indent(-1);
477 indent();
478 out.println("]");
479 return null;
480 }
482 public Void visitSerialData(SerialDataTree node, Void p) {
483 header(node);
484 indent(+1);
485 print("description", node.getDescription());
486 indent(-1);
487 indent();
488 out.println("]");
489 return null;
490 }
492 public Void visitSerialField(SerialFieldTree node, Void p) {
493 header(node);
494 indent(+1);
495 print("name", node.getName());
496 print("type", node.getType());
497 print("description", node.getDescription());
498 indent(-1);
499 indent();
500 out.println("]");
501 return null;
502 }
504 public Void visitSince(SinceTree node, Void p) {
505 header(node);
506 indent(+1);
507 print("body", node.getBody());
508 indent(-1);
509 indent();
510 out.println("]");
511 return null;
512 }
514 public Void visitStartElement(StartElementTree node, Void p) {
515 header(node);
516 indent(+1);
517 indent();
518 out.println("name:" + node.getName());
519 print("attributes", node.getAttributes());
520 indent(-1);
521 indent();
522 out.println("]");
523 return null;
524 }
526 public Void visitText(TextTree node, Void p) {
527 header(node, compress(node.getBody()));
528 return null;
529 }
531 public Void visitThrows(ThrowsTree node, Void p) {
532 header(node);
533 indent(+1);
534 print("exceptionName", node.getExceptionName());
535 print("description", node.getDescription());
536 indent(-1);
537 indent();
538 out.println("]");
539 return null;
540 }
542 public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
543 header(node);
544 indent(+1);
545 indent();
546 out.println("tag:" + node.getTagName());
547 print("content", node.getContent());
548 indent(-1);
549 indent();
550 out.println("]");
551 return null;
552 }
554 public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) {
555 header(node);
556 indent(+1);
557 indent();
558 out.println("tag:" + node.getTagName());
559 print("content", node.getContent());
560 indent(-1);
561 indent();
562 out.println("]");
563 return null;
564 }
566 public Void visitValue(ValueTree node, Void p) {
567 header(node);
568 indent(+1);
569 print("reference", node.getReference());
570 indent(-1);
571 indent();
572 out.println("]");
573 return null;
574 }
576 public Void visitVersion(VersionTree node, Void p) {
577 header(node);
578 indent(+1);
579 print("body", node.getBody());
580 indent(-1);
581 indent();
582 out.println("]");
583 return null;
584 }
586 public Void visitOther(DocTree node, Void p) {
587 throw new UnsupportedOperationException("Not supported yet.");
588 }
590 void header(DocTree node) {
591 indent();
592 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos);
593 }
595 void header(DocTree node, String rest) {
596 indent();
597 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos
598 + (rest.isEmpty() ? "" : ", " + rest)
599 + "]");
600 }
602 String simpleClassName(DocTree node) {
603 return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1");
604 }
606 void print(String name, DocTree item) {
607 indent();
608 if (item == null)
609 out.println(name + ": null");
610 else {
611 out.println(name + ":");
612 indent(+1);
613 item.accept(this, null);
614 indent(-1);
615 }
616 }
618 void print(String name, String s) {
619 indent();
620 out.println(name + ": " + s);
621 }
623 void print(String name, List<? extends DocTree> list) {
624 indent();
625 if (list == null)
626 out.println(name + ": null");
627 else if (list.isEmpty())
628 out.println(name + ": empty");
629 else {
630 out.println(name + ": " + list.size());
631 indent(+1);
632 for (DocTree tree: list) {
633 tree.accept(this, null);
634 }
635 indent(-1);
636 }
637 }
639 int indent = 0;
641 void indent() {
642 for (int i = 0; i < indent; i++) {
643 out.print(" ");
644 }
645 }
647 void indent(int n) {
648 indent += n;
649 }
651 String compress(String s) {
652 s = s.replace("\n", "|").replace(" ", "_");
653 return (s.length() < 32)
654 ? s
655 : s.substring(0, 16) + "..." + s.substring(16);
656 }
658 String quote(String s) {
659 if (s.contains("\""))
660 return "'" + s + "'";
661 else if (s.contains("'") || s.contains(" "))
662 return '"' + s + '"';
663 else
664 return s;
665 }
668 }
669 }
671 /**
672 * Verify the reported tree positions by comparing the characters found
673 * at and after the reported position with the beginning of the pretty-
674 * printed text.
675 */
676 static class PosChecker extends Checker {
677 PosChecker(DocCommentTester test, DocTrees t) {
678 test.super(t);
679 }
681 @Override
682 void check(TreePath path, Name name) throws Exception {
683 JavaFileObject fo = path.getCompilationUnit().getSourceFile();
684 final CharSequence cs = fo.getCharContent(true);
686 final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
687 DCTree t = (DCTree) trees.getDocCommentTree(path);
689 DocTreeScanner scanner = new DocTreeScanner<Void,Void>() {
690 @Override
691 public Void scan(DocTree node, Void ignore) {
692 if (node != null) {
693 try {
694 String expect = getExpectText(node);
695 long pos = ((DCTree) node).getSourcePosition(dc);
696 String found = getFoundText(cs, (int) pos, expect.length());
697 if (!found.equals(expect)) {
698 System.err.println("expect: " + expect);
699 System.err.println("found: " + found);
700 error("mismatch");
701 }
703 } catch (StringIndexOutOfBoundsException e) {
704 error(node.getClass() + ": " + e.toString());
705 e.printStackTrace();
706 }
707 }
708 return super.scan(node, ignore);
709 }
710 };
712 scanner.scan(t, null);
713 }
715 String getExpectText(DocTree t) {
716 StringWriter sw = new StringWriter();
717 DocPretty p = new DocPretty(sw);
718 try { p.print(t); } catch (IOException never) { }
719 String s = sw.toString();
720 if (s.length() <= 1)
721 return s;
722 int ws = s.replaceAll("\\s+", " ").indexOf(" ");
723 if (ws != -1) s = s.substring(0, ws);
724 return (s.length() < 5) ? s : s.substring(0, 5);
725 }
727 String getFoundText(CharSequence cs, int pos, int len) {
728 return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString();
729 }
730 }
732 /**
733 * Verify the pretty printed text against a normalized form of the
734 * original doc comment.
735 */
736 static class PrettyChecker extends Checker {
738 PrettyChecker(DocCommentTester test, DocTrees t) {
739 test.super(t);
740 }
742 @Override
743 void check(TreePath path, Name name) throws Exception {
744 String raw = trees.getDocComment(path);
745 String normRaw = normalize(raw);
747 StringWriter out = new StringWriter();
748 DocPretty dp = new DocPretty(out);
749 dp.print(trees.getDocCommentTree(path));
750 String pretty = out.toString();
752 if (!pretty.equals(normRaw)) {
753 error("mismatch");
754 System.err.println("*** expected:");
755 System.err.println(normRaw.replace(" ", "_"));
756 System.err.println("*** found:");
757 System.err.println(pretty.replace(" ", "_"));
758 // throw new Error();
759 }
760 }
762 /**
763 * Normalize white space in places where the tree does not preserve it.
764 */
765 String normalize(String s) {
766 return s.trim()
767 .replaceFirst("\\.\\s++([^@])", ". $1")
768 .replaceFirst("\\.\\s*\\n *@", ".\n@")
769 .replaceFirst("\\s+<(/?p|pre|h[1-6])>", " <$1>")
770 .replaceAll("\\{@docRoot\\s+\\}", "{@docRoot}")
771 .replaceAll("\\{@inheritDoc\\s+\\}", "{@inheritDoc}")
772 .replaceAll("(\\{@value\\s+[^}]+)\\s+(\\})", "$1$2")
773 .replaceAll("\n[ \t]+@", "\n@");
774 }
776 }
778 }