jjg@1409: /* jjg@1409: * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. jjg@1409: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@1409: * jjg@1409: * This code is free software; you can redistribute it and/or modify it jjg@1409: * under the terms of the GNU General Public License version 2 only, as jjg@1409: * published by the Free Software Foundation. jjg@1409: * jjg@1409: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@1409: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@1409: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@1409: * version 2 for more details (a copy is included in the LICENSE file that jjg@1409: * accompanied this code). jjg@1409: * jjg@1409: * You should have received a copy of the GNU General Public License version jjg@1409: * 2 along with this work; if not, write to the Free Software Foundation, jjg@1409: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@1409: * jjg@1409: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@1409: * or visit www.oracle.com if you need additional information or have any jjg@1409: * questions. jjg@1409: */ jjg@1409: jjg@1409: import java.io.File; jjg@1409: import java.io.FileWriter; jjg@1409: import java.io.IOException; jjg@1409: import java.io.PrintWriter; jjg@1409: import java.io.StringWriter; jjg@1409: import java.io.Writer; jjg@1409: import java.util.ArrayList; jjg@1409: import java.util.List; jjg@1409: import java.util.regex.Matcher; jjg@1409: import java.util.regex.Pattern; jjg@1409: jjg@1409: import javax.lang.model.element.Name; jjg@1409: import javax.tools.JavaFileObject; jjg@1409: import javax.tools.StandardJavaFileManager; jjg@1409: jjg@1409: import com.sun.source.doctree.*; jjg@1409: import com.sun.source.tree.ClassTree; jjg@1409: import com.sun.source.tree.CompilationUnitTree; jjg@1409: import com.sun.source.tree.MethodTree; jjg@1409: import com.sun.source.tree.Tree; jjg@1409: import com.sun.source.tree.VariableTree; jjg@1409: import com.sun.source.util.DocTreeScanner; jjg@1409: import com.sun.source.util.DocTrees; jjg@1409: import com.sun.source.util.JavacTask; jjg@1409: import com.sun.source.util.TreePath; jjg@1409: import com.sun.source.util.TreePathScanner; jjg@1409: import com.sun.tools.javac.api.JavacTool; jjg@1409: import com.sun.tools.javac.tree.DCTree; jjg@1409: import com.sun.tools.javac.tree.DCTree.DCDocComment; jjg@1409: import com.sun.tools.javac.tree.DCTree.DCErroneous; jjg@1409: import com.sun.tools.javac.tree.DocPretty; jjg@1409: jjg@1409: public class DocCommentTester { jjg@1409: jjg@1409: public static void main(String... args) throws Exception { jjg@1409: new DocCommentTester().run(args); jjg@1409: } jjg@1409: jjg@1409: public void run(String... args) throws Exception { jjg@1409: String testSrc = System.getProperty("test.src"); jjg@1409: jjg@1409: List files = new ArrayList(); jjg@1409: for (String arg: args) jjg@1409: files.add(new File(testSrc, arg)); jjg@1409: jjg@1409: JavacTool javac = JavacTool.create(); jjg@1409: StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null); jjg@1409: jjg@1409: Iterable fos = fm.getJavaFileObjectsFromFiles(files); jjg@1409: jjg@1409: JavacTask t = javac.getTask(null, fm, null, null, null, fos); jjg@1409: final DocTrees trees = DocTrees.instance(t); jjg@1409: jjg@1409: final Checker[] checkers = { jjg@1409: new ASTChecker(this, trees), jjg@1409: new PosChecker(this, trees), jjg@1409: new PrettyChecker(this, trees) jjg@1409: }; jjg@1409: jjg@1409: DeclScanner d = new DeclScanner() { jjg@1409: @Override jjg@1409: public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) { jjg@1409: for (Checker c: checkers) jjg@1409: c.visitCompilationUnit(tree); jjg@1409: return super.visitCompilationUnit(tree, ignore); jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: void visitDecl(Tree tree, Name name) { jjg@1409: TreePath path = getCurrentPath(); jjg@1409: String dc = trees.getDocComment(path); jjg@1409: if (dc != null) { jjg@1409: for (Checker c : checkers) { jjg@1409: try { jjg@1409: System.err.println(path.getLeaf().getKind() jjg@1409: + " " + name jjg@1409: + " " + c.getClass().getSimpleName()); jjg@1409: jjg@1409: c.check(path, name); jjg@1409: jjg@1409: System.err.println(); jjg@1409: } catch (Exception e) { jjg@1409: error("Exception " + e); jjg@1409: e.printStackTrace(System.err); jjg@1409: } jjg@1409: } jjg@1409: } jjg@1409: } jjg@1409: }; jjg@1409: jjg@1409: Iterable units = t.parse(); jjg@1409: for (CompilationUnitTree unit: units) { jjg@1409: d.scan(unit, null); jjg@1409: } jjg@1409: jjg@1409: if (errors > 0) jjg@1409: throw new Exception(errors + " errors occurred"); jjg@1409: } jjg@1409: jjg@1409: static abstract class DeclScanner extends TreePathScanner { jjg@1409: abstract void visitDecl(Tree tree, Name name); jjg@1409: jjg@1409: @Override jjg@1409: public Void visitClass(ClassTree tree, Void ignore) { jjg@1409: super.visitClass(tree, ignore); jjg@1409: visitDecl(tree, tree.getSimpleName()); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: public Void visitMethod(MethodTree tree, Void ignore) { jjg@1409: super.visitMethod(tree, ignore); jjg@1409: visitDecl(tree, tree.getName()); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: public Void visitVariable(VariableTree tree, Void ignore) { jjg@1409: super.visitVariable(tree, ignore); jjg@1409: visitDecl(tree, tree.getName()); jjg@1409: return null; jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: /** jjg@1409: * Base class for checkers to check the doc comment on a declaration jjg@1409: * (when present.) jjg@1409: */ jjg@1409: abstract class Checker { jjg@1409: final DocTrees trees; jjg@1409: jjg@1409: Checker(DocTrees trees) { jjg@1409: this.trees = trees; jjg@1409: } jjg@1409: jjg@1409: void visitCompilationUnit(CompilationUnitTree tree) { } jjg@1409: jjg@1409: abstract void check(TreePath tree, Name name) throws Exception; jjg@1409: jjg@1409: void error(String msg) { jjg@1409: DocCommentTester.this.error(msg); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: void error(String msg) { jjg@1409: System.err.println("Error: " + msg); jjg@1409: errors++; jjg@1409: } jjg@1409: jjg@1409: int errors; jjg@1409: jjg@1409: /** jjg@1409: * Verify the structure of the DocTree AST by comparing it against golden text. jjg@1409: */ jjg@1409: static class ASTChecker extends Checker { jjg@1419: static final String NEWLINE = System.getProperty("line.separator"); jjg@1409: Printer printer = new Printer(); jjg@1409: String source; jjg@1409: jjg@1409: ASTChecker(DocCommentTester test, DocTrees t) { jjg@1409: test.super(t); jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: void visitCompilationUnit(CompilationUnitTree tree) { jjg@1409: try { jjg@1409: source = tree.getSourceFile().getCharContent(true).toString(); jjg@1409: } catch (IOException e) { jjg@1409: source = ""; jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: void check(TreePath path, Name name) { jjg@1409: StringWriter out = new StringWriter(); jjg@1409: DocCommentTree dc = trees.getDocCommentTree(path); jjg@1409: printer.print(dc, out); jjg@1409: out.flush(); jjg@1419: String found = out.toString().replace(NEWLINE, "\n"); jjg@1409: jjg@1409: // Look for the first block comment after the first occurrence of name jjg@1409: int start = source.indexOf("\n/*\n", findName(source, name)); jjg@1409: int end = source.indexOf("\n*/\n", start); jjg@1409: String expect = source.substring(start + 4, end + 1); jjg@1409: if (!found.equals(expect)) { jjg@1409: System.err.println("Expect:\n" + expect); jjg@1409: System.err.println("Found:\n" + found); jjg@1409: error("AST mismatch for " + name); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: /** jjg@1409: * This main program is to set up the golden comments used by this jjg@1409: * checker. jjg@1409: * Usage: jjg@1409: * java DocCommentTester$ASTChecker -o dir file... jjg@1409: * The given files are written to the output directory with their jjg@1409: * golden comments updated. The intent is that the files should jjg@1409: * then be compared with the originals, e.g. with meld, and if the jjg@1409: * changes are approved, the new files can be used to replace the old. jjg@1409: */ jjg@1409: public static void main(String... args) throws Exception { jjg@1409: List files = new ArrayList(); jjg@1409: File o = null; jjg@1409: for (int i = 0; i < args.length; i++) { jjg@1409: String arg = args[i]; jjg@1409: if (arg.equals("-o")) jjg@1409: o = new File(args[++i]); jjg@1409: else if (arg.startsWith("-")) jjg@1409: throw new IllegalArgumentException(arg); jjg@1409: else { jjg@1409: files.add(new File(arg)); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: if (o == null) jjg@1409: throw new IllegalArgumentException("no output dir specified"); jjg@1409: final File outDir = o; jjg@1409: jjg@1409: JavacTool javac = JavacTool.create(); jjg@1409: StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null); jjg@1409: Iterable fos = fm.getJavaFileObjectsFromFiles(files); jjg@1409: jjg@1409: JavacTask t = javac.getTask(null, fm, null, null, null, fos); jjg@1409: final DocTrees trees = DocTrees.instance(t); jjg@1409: jjg@1409: DeclScanner d = new DeclScanner() { jjg@1409: Printer p = new Printer(); jjg@1409: String source; jjg@1409: jjg@1409: @Override jjg@1409: public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) { jjg@1409: System.err.println("processing " + tree.getSourceFile().getName()); jjg@1409: try { jjg@1409: source = tree.getSourceFile().getCharContent(true).toString(); jjg@1409: } catch (IOException e) { jjg@1409: source = ""; jjg@1409: } jjg@1409: jjg@1409: // remove existing gold by removing all block comments after the first '{'. jjg@1409: int start = source.indexOf("{"); jjg@1409: while ((start = source.indexOf("\n/*\n", start)) != -1) { jjg@1409: int end = source.indexOf("\n*/\n"); jjg@1409: source = source.substring(0, start + 1) + source.substring(end + 4); jjg@1409: } jjg@1409: jjg@1409: // process decls in compilation unit jjg@1409: super.visitCompilationUnit(tree, ignore); jjg@1409: jjg@1409: // write the modified source jjg@1409: File f = new File(tree.getSourceFile().getName()); jjg@1409: File outFile = new File(outDir, f.getName()); jjg@1409: try { jjg@1409: FileWriter out = new FileWriter(outFile); jjg@1409: try { jjg@1409: out.write(source); jjg@1409: } finally { jjg@1409: out.close(); jjg@1409: } jjg@1409: } catch (IOException e) { jjg@1409: System.err.println("Can't write " + tree.getSourceFile().getName() jjg@1409: + " to " + outFile + ": " + e); jjg@1409: } jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: void visitDecl(Tree tree, Name name) { jjg@1409: DocTree dc = trees.getDocCommentTree(getCurrentPath()); jjg@1409: if (dc != null) { jjg@1409: StringWriter out = new StringWriter(); jjg@1409: p.print(dc, out); jjg@1409: String found = out.toString(); jjg@1409: jjg@1409: // Look for the empty line after the first occurrence of name jjg@1409: int pos = source.indexOf("\n\n", findName(source, name)); jjg@1409: jjg@1409: // Insert the golden comment jjg@1409: source = source.substring(0, pos) jjg@1409: + "\n/*\n" jjg@1409: + found jjg@1409: + "*/" jjg@1409: + source.substring(pos); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: }; jjg@1409: jjg@1409: Iterable units = t.parse(); jjg@1409: for (CompilationUnitTree unit: units) { jjg@1409: d.scan(unit, null); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: static int findName(String source, Name name) { jjg@1409: Pattern p = Pattern.compile("\\s" + name + "[(;]"); jjg@1409: Matcher m = p.matcher(source); jjg@1409: if (!m.find()) jjg@1409: throw new Error("cannot find " + name); jjg@1409: return m.start(); jjg@1409: } jjg@1409: jjg@1409: static class Printer implements DocTreeVisitor { jjg@1409: PrintWriter out; jjg@1409: jjg@1409: void print(DocTree tree, Writer out) { jjg@1409: this.out = (out instanceof PrintWriter) jjg@1409: ? (PrintWriter) out : new PrintWriter(out); jjg@1409: tree.accept(this, null); jjg@1409: this.out.flush(); jjg@1409: } jjg@1409: jjg@1409: public Void visitAttribute(AttributeTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("name", node.getName().toString()); jjg@1409: print("vkind", node.getValueKind().toString()); jjg@1409: print("value", node.getValue()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitAuthor(AuthorTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("name", node.getName()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitComment(CommentTree node, Void p) { jjg@1409: header(node, compress(node.getBody())); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitDeprecated(DeprecatedTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("body", node.getBody()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitDocComment(DocCommentTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("firstSentence", node.getFirstSentence()); jjg@1409: print("body", node.getBody()); jjg@1409: print("block tags", node.getBlockTags()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitDocRoot(DocRootTree node, Void p) { jjg@1409: header(node, ""); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitEndElement(EndElementTree node, Void p) { jjg@1409: header(node, node.getName().toString()); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitEntity(EntityTree node, Void p) { jjg@1409: header(node, node.getName().toString()); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitErroneous(ErroneousTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("code", ((DCErroneous) node).diag.getCode()); jjg@1409: print("body", compress(node.getBody())); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitIdentifier(IdentifierTree node, Void p) { jjg@1409: header(node, compress(node.getName().toString())); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitInheritDoc(InheritDocTree node, Void p) { jjg@1409: header(node, ""); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitLink(LinkTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("reference", node.getReference()); jjg@1409: print("body", node.getLabel()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitLiteral(LiteralTree node, Void p) { jjg@1409: header(node, compress(node.getBody().getBody())); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitParam(ParamTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("name", node.getName()); jjg@1409: print("description", node.getDescription()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitReference(ReferenceTree node, Void p) { jjg@1409: header(node, compress(node.getSignature())); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitReturn(ReturnTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("description", node.getDescription()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitSee(SeeTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("reference", node.getReference()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitSerial(SerialTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("description", node.getDescription()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitSerialData(SerialDataTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("description", node.getDescription()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitSerialField(SerialFieldTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("name", node.getName()); jjg@1409: print("type", node.getType()); jjg@1409: print("description", node.getDescription()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitSince(SinceTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("body", node.getBody()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitStartElement(StartElementTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: indent(); jjg@1409: out.println("name:" + node.getName()); jjg@1409: print("attributes", node.getAttributes()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitText(TextTree node, Void p) { jjg@1409: header(node, compress(node.getBody())); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitThrows(ThrowsTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("exceptionName", node.getExceptionName()); jjg@1409: print("description", node.getDescription()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: indent(); jjg@1409: out.println("tag:" + node.getTagName()); jjg@1409: print("content", node.getContent()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: indent(); jjg@1409: out.println("tag:" + node.getTagName()); jjg@1409: print("content", node.getContent()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitValue(ValueTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("reference", node.getReference()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitVersion(VersionTree node, Void p) { jjg@1409: header(node); jjg@1409: indent(+1); jjg@1409: print("body", node.getBody()); jjg@1409: indent(-1); jjg@1409: indent(); jjg@1409: out.println("]"); jjg@1409: return null; jjg@1409: } jjg@1409: jjg@1409: public Void visitOther(DocTree node, Void p) { jjg@1409: throw new UnsupportedOperationException("Not supported yet."); jjg@1409: } jjg@1409: jjg@1409: void header(DocTree node) { jjg@1409: indent(); jjg@1409: out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos); jjg@1409: } jjg@1409: jjg@1409: void header(DocTree node, String rest) { jjg@1409: indent(); jjg@1409: out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos jjg@1409: + (rest.isEmpty() ? "" : ", " + rest) jjg@1409: + "]"); jjg@1409: } jjg@1409: jjg@1409: String simpleClassName(DocTree node) { jjg@1409: return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1"); jjg@1409: } jjg@1409: jjg@1409: void print(String name, DocTree item) { jjg@1409: indent(); jjg@1409: if (item == null) jjg@1409: out.println(name + ": null"); jjg@1409: else { jjg@1409: out.println(name + ":"); jjg@1409: indent(+1); jjg@1409: item.accept(this, null); jjg@1409: indent(-1); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: void print(String name, String s) { jjg@1409: indent(); jjg@1409: out.println(name + ": " + s); jjg@1409: } jjg@1409: jjg@1409: void print(String name, List list) { jjg@1409: indent(); jjg@1409: if (list == null) jjg@1409: out.println(name + ": null"); jjg@1409: else if (list.isEmpty()) jjg@1409: out.println(name + ": empty"); jjg@1409: else { jjg@1409: out.println(name + ": " + list.size()); jjg@1409: indent(+1); jjg@1409: for (DocTree tree: list) { jjg@1409: tree.accept(this, null); jjg@1409: } jjg@1409: indent(-1); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: int indent = 0; jjg@1409: jjg@1409: void indent() { jjg@1409: for (int i = 0; i < indent; i++) { jjg@1409: out.print(" "); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: void indent(int n) { jjg@1409: indent += n; jjg@1409: } jjg@1409: jjg@1409: String compress(String s) { jjg@1409: s = s.replace("\n", "|").replace(" ", "_"); jjg@1409: return (s.length() < 32) jjg@1409: ? s jjg@1409: : s.substring(0, 16) + "..." + s.substring(16); jjg@1409: } jjg@1409: jjg@1409: String quote(String s) { jjg@1409: if (s.contains("\"")) jjg@1409: return "'" + s + "'"; jjg@1409: else if (s.contains("'") || s.contains(" ")) jjg@1409: return '"' + s + '"'; jjg@1409: else jjg@1409: return s; jjg@1409: } jjg@1409: jjg@1409: jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: /** jjg@1409: * Verify the reported tree positions by comparing the characters found jjg@1409: * at and after the reported position with the beginning of the pretty- jjg@1409: * printed text. jjg@1409: */ jjg@1409: static class PosChecker extends Checker { jjg@1409: PosChecker(DocCommentTester test, DocTrees t) { jjg@1409: test.super(t); jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: void check(TreePath path, Name name) throws Exception { jjg@1409: JavaFileObject fo = path.getCompilationUnit().getSourceFile(); jjg@1409: final CharSequence cs = fo.getCharContent(true); jjg@1409: jjg@1409: final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path); jjg@1409: DCTree t = (DCTree) trees.getDocCommentTree(path); jjg@1409: jjg@1409: DocTreeScanner scanner = new DocTreeScanner() { jjg@1409: @Override jjg@1409: public Void scan(DocTree node, Void ignore) { jjg@1409: if (node != null) { jjg@1409: try { jjg@1409: String expect = getExpectText(node); jjg@1409: long pos = ((DCTree) node).getSourcePosition(dc); jjg@1409: String found = getFoundText(cs, (int) pos, expect.length()); jjg@1409: if (!found.equals(expect)) { jjg@1409: System.err.println("expect: " + expect); jjg@1409: System.err.println("found: " + found); jjg@1409: error("mismatch"); jjg@1409: } jjg@1409: jjg@1409: } catch (StringIndexOutOfBoundsException e) { jjg@1409: error(node.getClass() + ": " + e.toString()); jjg@1409: e.printStackTrace(); jjg@1409: } jjg@1409: } jjg@1409: return super.scan(node, ignore); jjg@1409: } jjg@1409: }; jjg@1409: jjg@1409: scanner.scan(t, null); jjg@1409: } jjg@1409: jjg@1409: String getExpectText(DocTree t) { jjg@1409: StringWriter sw = new StringWriter(); jjg@1409: DocPretty p = new DocPretty(sw); jjg@1409: try { p.print(t); } catch (IOException never) { } jjg@1409: String s = sw.toString(); jjg@1409: if (s.length() <= 1) jjg@1409: return s; jjg@1409: int ws = s.replaceAll("\\s+", " ").indexOf(" "); jjg@1409: if (ws != -1) s = s.substring(0, ws); jjg@1409: return (s.length() < 5) ? s : s.substring(0, 5); jjg@1409: } jjg@1409: jjg@1409: String getFoundText(CharSequence cs, int pos, int len) { jjg@1409: return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString(); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: /** jjg@1409: * Verify the pretty printed text against a normalized form of the jjg@1409: * original doc comment. jjg@1409: */ jjg@1409: static class PrettyChecker extends Checker { jjg@1409: jjg@1409: PrettyChecker(DocCommentTester test, DocTrees t) { jjg@1409: test.super(t); jjg@1409: } jjg@1409: jjg@1409: @Override jjg@1409: void check(TreePath path, Name name) throws Exception { jjg@1409: String raw = trees.getDocComment(path); jjg@1409: String normRaw = normalize(raw); jjg@1409: jjg@1409: StringWriter out = new StringWriter(); jjg@1409: DocPretty dp = new DocPretty(out); jjg@1409: dp.print(trees.getDocCommentTree(path)); jjg@1409: String pretty = out.toString(); jjg@1409: jjg@1409: if (!pretty.equals(normRaw)) { jjg@1409: error("mismatch"); jjg@1409: System.err.println("*** expected:"); jjg@1409: System.err.println(normRaw.replace(" ", "_")); jjg@1409: System.err.println("*** found:"); jjg@1409: System.err.println(pretty.replace(" ", "_")); jjg@1409: // throw new Error(); jjg@1409: } jjg@1409: } jjg@1409: jjg@1409: /** jjg@1409: * Normalize white space in places where the tree does not preserve it. jjg@1409: */ jjg@1409: String normalize(String s) { jjg@1409: return s.trim() jjg@1409: .replaceFirst("\\.\\s++([^@])", ". $1") jjg@1409: .replaceFirst("\\.\\s*\\n *@", ".\n@") jjg@1409: .replaceFirst("\\s+<(/?p|pre|h[1-6])>", " <$1>") jjg@1409: .replaceAll("\\{@docRoot\\s+\\}", "{@docRoot}") jjg@1409: .replaceAll("\\{@inheritDoc\\s+\\}", "{@inheritDoc}") jjg@1409: .replaceAll("(\\{@value\\s+[^}]+)\\s+(\\})", "$1$2") jjg@1409: .replaceAll("\n[ \t]+@", "\n@"); jjg@1409: } jjg@1409: jjg@1409: } jjg@1409: jjg@1409: } jjg@1409: