8278795: Create test library and tests for langtools snippets
Reviewed-by: hannesw
This commit is contained in:
parent
7aff03aee1
commit
9df200f749
test/langtools
jdk/javadoc/doclet/testDocletExample
tools
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8272944
|
||||
* @summary Use snippets in jdk.javadoc documentation
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.javadoc/jdk.javadoc.internal.tool
|
||||
* @build snippets.SnippetUtils toolbox.JavacTask toolbox.ToolBox javadoc.tester.*
|
||||
* @run main TestDocletExample
|
||||
*/
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import snippets.SnippetUtils;
|
||||
import toolbox.Task;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
|
||||
public class TestDocletExample extends TestRunner {
|
||||
public static void main(String... args) throws Exception {
|
||||
var t = new TestDocletExample();
|
||||
t.runTests(m -> new Object[] { Path.of(m.getName()) });
|
||||
}
|
||||
|
||||
SnippetUtils snippets = new SnippetUtils("jdk.javadoc");
|
||||
ToolBox tb = new ToolBox();
|
||||
|
||||
TestDocletExample() {
|
||||
super(System.out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryPoint(Path base) throws Exception {
|
||||
var docletPkg = snippets.getElements().getPackageElement("jdk.javadoc.doclet");
|
||||
var dc = snippets.getDocTrees().getDocCommentTree(docletPkg);
|
||||
var entryPointSnippet = snippets.getSnippetById(dc, "entry-point");
|
||||
var entryPointCode = entryPointSnippet.getBody().getBody();
|
||||
var code = """
|
||||
class C {
|
||||
%s { }
|
||||
}
|
||||
""".formatted(entryPointCode);
|
||||
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
|
||||
snippets.parse(code, null, collector);
|
||||
var diags = collector.getDiagnostics();
|
||||
if (diags.isEmpty()) {
|
||||
out.println("parsed entry point snippet");
|
||||
} else {
|
||||
diags.forEach(out::println);
|
||||
throw new Exception("parse failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocletExample(Path base) throws Exception {
|
||||
|
||||
// get source code
|
||||
var docletPkg = snippets.getElements().getPackageElement("jdk.javadoc.doclet");
|
||||
var dc = snippets.getDocTrees().getDocCommentTree(docletPkg);
|
||||
var exampleSnippet = snippets.getSnippetById(dc, "Example.java");
|
||||
var exampleCode = exampleSnippet.getBody().getBody();
|
||||
|
||||
// compile it
|
||||
Path src = base.resolve("src");
|
||||
Path classes = base.resolve("classes");
|
||||
Files.createDirectories(classes);
|
||||
|
||||
tb.writeJavaFiles(src, exampleCode);
|
||||
new toolbox.JavacTask(tb)
|
||||
.outdir(classes)
|
||||
.files(tb.findJavaFiles(src))
|
||||
.run(Task.Expect.SUCCESS)
|
||||
.writeAll();
|
||||
|
||||
// get demo command
|
||||
var cmdSnippet = snippets.getSnippetById(dc, "run-doclet");
|
||||
var cmd = cmdSnippet.getBody().getBody()
|
||||
.replaceAll("\\s+//.*", "") // remove markup
|
||||
.replaceAll("\\\\\n", " ") // join lines
|
||||
.trim();
|
||||
out.println(cmd);
|
||||
|
||||
tb.writeFile(src.resolve("overview.html"),
|
||||
"""
|
||||
<!doctype html>
|
||||
<html><title>Overview</title>
|
||||
<body>
|
||||
Overview
|
||||
</body>
|
||||
</html>
|
||||
""");
|
||||
|
||||
var cmdWords = Stream.of(cmd.split("\\s+"))
|
||||
.map(s -> s.replace("source-location", src.toString()))
|
||||
.map(s -> s.replace("doclet-classes", classes.toString()))
|
||||
.toList();
|
||||
var toolName = cmdWords.get(0);
|
||||
var toolArgs = cmdWords.subList(1, cmdWords.size());
|
||||
|
||||
ToolProvider tool = ToolProvider.findFirst(toolName)
|
||||
.orElseThrow(() -> new Exception("tool not found: " + toolName));
|
||||
int rc = tool.run(System.out, System.err, toolArgs.toArray(new String[0]));
|
||||
if (rc != 0) {
|
||||
throw new Exception("ecommand return code: " + rc);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8272944
|
||||
* @summary Use snippets in java.compiler documentation
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* @build snippets.SnippetUtils toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox
|
||||
* @run main TestJavaxToolsSnippets
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import com.sun.source.doctree.SnippetTree;
|
||||
|
||||
import snippets.SnippetUtils;
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
/**
|
||||
* Tests the snippets in the {@code javax.tools} package, by compiling the
|
||||
* external snippets and parsing the internal Java snippets.
|
||||
*/
|
||||
public class TestJavaxToolsSnippets extends TestRunner {
|
||||
public static void main(String... args) throws Exception {
|
||||
new TestJavaxToolsSnippets().runTests(m -> new Object[] { Path.of(m.getName()) });
|
||||
}
|
||||
|
||||
SnippetUtils snippets = new SnippetUtils("java.compiler");
|
||||
ToolBox tb = new ToolBox();
|
||||
|
||||
TestJavaxToolsSnippets() {
|
||||
super(System.err);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalSnippets(Path base) throws Exception {
|
||||
Path snippetFilesDir = snippets.getSourceDir()
|
||||
.resolve("java.compiler") // module
|
||||
.resolve("share").resolve("classes")
|
||||
.resolve("javax.tools".replace(".", File.separator)) // package
|
||||
.resolve("snippet-files");
|
||||
new JavacTask(tb)
|
||||
.files(tb.findJavaFiles(snippetFilesDir))
|
||||
.outdir(Files.createDirectories(base.resolve("classes")))
|
||||
.run(Task.Expect.SUCCESS)
|
||||
.writeAll();
|
||||
out.println("Compilation succeeded");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavaCompilerSnippets(Path base) {
|
||||
TypeElement te = snippets.getElements().getTypeElement("javax.tools.JavaCompiler");
|
||||
snippets.scan(te, this::handleSnippet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavaFileManagerSnippets(Path base) {
|
||||
TypeElement te = snippets.getElements().getTypeElement("javax.tools.JavaFileManager");
|
||||
snippets.scan(te, this::handleSnippet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardJavaFileManagerSnippets(Path base) {
|
||||
TypeElement te = snippets.getElements().getTypeElement("javax.tools.StandardJavaFileManager");
|
||||
snippets.scan(te, this::handleSnippet);
|
||||
}
|
||||
|
||||
void handleSnippet(Element e, SnippetTree tree) {
|
||||
String lang = snippets.getAttr(tree, "lang");
|
||||
if (Objects.equals(lang, "java")) {
|
||||
String body = snippets.getBody(tree);
|
||||
if (body != null) {
|
||||
String id = snippets.getAttr(tree, "id");
|
||||
try {
|
||||
out.println("parsing snippet " + e + ":" + id);
|
||||
if (snippets.parse(body, out::println)) {
|
||||
out.println("parsed snippet");
|
||||
} else {
|
||||
error("parse failed");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
543
test/langtools/tools/lib/snippets/SnippetUtils.java
Normal file
543
test/langtools/tools/lib/snippets/SnippetUtils.java
Normal file
@ -0,0 +1,543 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package snippets;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.ModuleElement;
|
||||
import javax.lang.model.element.PackageElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.SimpleElementVisitor14;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import com.sun.source.doctree.AttributeTree;
|
||||
import com.sun.source.doctree.DocCommentTree;
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.SnippetTree;
|
||||
import com.sun.source.doctree.TextTree;
|
||||
import com.sun.source.util.DocTreeScanner;
|
||||
import com.sun.source.util.DocTrees;
|
||||
import com.sun.source.util.JavacTask;
|
||||
|
||||
/**
|
||||
* Utilities for analyzing snippets.
|
||||
*
|
||||
* Support is provided for the following:
|
||||
* <ul>
|
||||
* <li>creating an instance of {@link JavacTask} suitable for looking up
|
||||
* elements by name, in order to access any corresponding documentation comment,
|
||||
* <li>scanning elements to find all associated snippets,
|
||||
* <li>locating instances of snippets by their {@code id},
|
||||
* <li>parsing snippets, and
|
||||
* <li>accessing the body of snippets, for any additional analysis.
|
||||
* </ul>
|
||||
*
|
||||
* @apiNote
|
||||
* The utilities do not provide support for compiling and running snippets,
|
||||
* because in general, this requires too much additional context. However,
|
||||
* the utilities do provide support for locating snippets in various ways,
|
||||
* and accessing the body of those snippets, to simplify the task of writing
|
||||
* code to compile and run snippets, where that is appropriate.
|
||||
*/
|
||||
public class SnippetUtils {
|
||||
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
private final StandardJavaFileManager fileManager;
|
||||
private final Path srcDir;
|
||||
private final JavacTask javacTask;
|
||||
private final Elements elements;
|
||||
private final DocTrees docTrees;
|
||||
|
||||
/**
|
||||
* Creates an instance for analysing snippets in one or more JDK modules.
|
||||
*
|
||||
* The source for the modules is derived from the value of the
|
||||
* {@code test.src} system property.
|
||||
*
|
||||
* Any messages, including error messages, will be written to {@code System.err}.
|
||||
*
|
||||
* @param modules the modules
|
||||
*
|
||||
* @throws IllegalArgumentException if no modules are specified
|
||||
*/
|
||||
public SnippetUtils(String... modules) {
|
||||
this(findSourceDir(), null, null, Set.of(modules));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for analysing snippets in one or more modules.
|
||||
*
|
||||
* @param srcDir the location for the source of the modules;
|
||||
* the location for the source of a specific module should be
|
||||
* in <em>srcDir</em>{@code /}<em>module</em>{@code /share/module}
|
||||
*
|
||||
* @param pw a writer for any text messages that may be generated;
|
||||
* if null, messages will be written to {@code System.err}
|
||||
*
|
||||
* @param dl a diagnostic listener for any diagnostic messages that may be generated;
|
||||
* if null, messages will be written to {@code System.err}
|
||||
*
|
||||
* @param modules the modules
|
||||
*
|
||||
* @throws IllegalArgumentException if no modules are specified
|
||||
*/
|
||||
public SnippetUtils(Path srcDir, PrintWriter pw, DiagnosticListener<JavaFileObject> dl, Set<String> modules) {
|
||||
if (modules.isEmpty()) {
|
||||
throw new IllegalArgumentException("no modules specified");
|
||||
}
|
||||
|
||||
this.srcDir = srcDir;
|
||||
fileManager = compiler.getStandardFileManager(dl, null, null);
|
||||
|
||||
List<String> opts = new ArrayList<>();
|
||||
opts.addAll(List.of("--add-modules", String.join(",", modules))); // could use CompilationTask.addModules
|
||||
modules.forEach(m -> opts.addAll(List.of("--patch-module", m + "=" + getModuleSourceDir(m))));
|
||||
opts.add("-proc:only");
|
||||
|
||||
javacTask = (JavacTask) compiler.getTask(pw, fileManager, dl, opts, null, null);
|
||||
elements = javacTask.getElements();
|
||||
elements.getModuleElement("java.base"); // forces module graph to be instantiated, etc
|
||||
|
||||
docTrees = DocTrees.instance(javacTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the source directory for the task used to access snippets}
|
||||
*/
|
||||
public Path getSourceDir() {
|
||||
return srcDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the file manager for the task used to access snippets}
|
||||
*/
|
||||
public StandardJavaFileManager getFileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the instance of {@code Elements} for the task used to access snippets}
|
||||
*/
|
||||
public Elements getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the instance of {@code DocTrees} for the task used to access snippets}
|
||||
*/
|
||||
public DocTrees getDocTrees() {
|
||||
return docTrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the doc comment tree for an element}
|
||||
*
|
||||
* @param element the element
|
||||
*/
|
||||
public DocCommentTree getDocCommentTree(Element element) {
|
||||
return docTrees.getDocCommentTree(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the snippet with a given id in a doc comment tree}
|
||||
*
|
||||
* @param tree the doc comment tree
|
||||
* @param id the id
|
||||
*/
|
||||
public SnippetTree getSnippetById(DocCommentTree tree, String id) {
|
||||
return new SnippetFinder().scan(tree, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the snippet with a given id in the doc comment tree for an element}
|
||||
*
|
||||
* @param element the element
|
||||
* @param id the id
|
||||
*/
|
||||
public SnippetTree getSnippetById(Element element, String id) {
|
||||
DocCommentTree tree = getDocCommentTree(element);
|
||||
return new SnippetFinder().scan(tree, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A scanner to locate the tree for a snippet with a given id.
|
||||
* Note: the scanner is use-once.
|
||||
*/
|
||||
private static class SnippetFinder extends DocTreeScanner<SnippetTree,String> {
|
||||
private SnippetTree result;
|
||||
private SnippetTree inSnippet;
|
||||
|
||||
@Override
|
||||
public SnippetTree scan(DocTree tree, String id) {
|
||||
// stop scanning once the result has been found
|
||||
return result != null ? result : super.scan(tree, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SnippetTree visitSnippet(SnippetTree tree, String id) {
|
||||
inSnippet = tree;
|
||||
try {
|
||||
return super.visitSnippet(tree, id);
|
||||
} finally {
|
||||
inSnippet = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SnippetTree visitAttribute(AttributeTree tree, String id) {
|
||||
if (tree.getName().contentEquals("id")
|
||||
&& tree.getValue().toString().equals(id)) {
|
||||
result = inSnippet;
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans an element and appropriate enclosed elements for doc comments,
|
||||
* and call a handler to handle any snippet trees in those doc comments.
|
||||
*
|
||||
* Only the public and protected members of type elements are scanned.
|
||||
* The enclosed elements of modules and packages are <em>not</em> scanned.
|
||||
*
|
||||
* @param element the element
|
||||
* @param handler the handler
|
||||
* @throws IllegalArgumentException if any inappropriate element is scanned
|
||||
*/
|
||||
public void scan(Element element, BiConsumer<Element, SnippetTree> handler) {
|
||||
new ElementScanner(docTrees).scan(element, handler);
|
||||
}
|
||||
|
||||
private static class ElementScanner extends SimpleElementVisitor14<Void, DocTreeScanner<Void, Element>> {
|
||||
private final DocTrees trees;
|
||||
|
||||
public ElementScanner(DocTrees trees) {
|
||||
this.trees = trees;
|
||||
}
|
||||
|
||||
public void scan(Element e, BiConsumer<Element, SnippetTree> snippetHandler) {
|
||||
visit(e, new DocTreeScanner<>() {
|
||||
@Override
|
||||
public Void visitSnippet(SnippetTree tree, Element e) {
|
||||
snippetHandler.accept(e, tree);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitModule(ModuleElement me, DocTreeScanner<Void, Element> treeScanner) {
|
||||
scanDocComment(me, treeScanner);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPackage(PackageElement pe, DocTreeScanner<Void, Element> treeScanner) {
|
||||
scanDocComment(pe, treeScanner);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitType(TypeElement te, DocTreeScanner<Void, Element> treeScanner) {
|
||||
scanDocComment(te, treeScanner);
|
||||
for (Element e : te.getEnclosedElements()) {
|
||||
Set<Modifier> mods = e.getModifiers();
|
||||
if (mods.contains(Modifier.PUBLIC) || mods.contains(Modifier.PROTECTED)) {
|
||||
e.accept(this, treeScanner);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitExecutable(ExecutableElement ee, DocTreeScanner<Void, Element> treeScanner) {
|
||||
scanDocComment(ee, treeScanner);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariable(VariableElement ve, DocTreeScanner<Void, Element> treeScanner) {
|
||||
switch (ve.getKind()) {
|
||||
case ENUM_CONSTANT, FIELD -> scanDocComment(ve, treeScanner);
|
||||
default -> defaultAction(ve, treeScanner);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void defaultAction(Element e, DocTreeScanner<Void, Element> treeScanner) {
|
||||
throw new IllegalArgumentException(e.getKind() + " " + e.getSimpleName());
|
||||
}
|
||||
|
||||
private void scanDocComment(Element e, DocTreeScanner<Void, Element> treeScanner) {
|
||||
DocCommentTree dc = trees.getDocCommentTree(e);
|
||||
if (dc != null) {
|
||||
treeScanner.scan(dc, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the string content of an inline or hybrid snippet, or {@code null} for an external snippet}
|
||||
*
|
||||
* @param tree the snippet
|
||||
*/
|
||||
public String getBody(SnippetTree tree) {
|
||||
TextTree body = tree.getBody();
|
||||
return body == null ? null : body.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the string content of an external or inline snippet}
|
||||
*
|
||||
* @param element the element whose documentation contains the snippet
|
||||
* @param tree the snippet
|
||||
*/
|
||||
public String getBody(Element element, SnippetTree tree) throws IOException {
|
||||
Path externalSnippetPath = getExternalSnippetPath(element, tree);
|
||||
return externalSnippetPath == null ? getBody(tree) : Files.readString(externalSnippetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the path for the {@code snippet-files} directory for an element}
|
||||
*
|
||||
* @param element the element
|
||||
*
|
||||
* @return the path
|
||||
*/
|
||||
public Path getSnippetFilesDir(Element element) {
|
||||
var moduleElem = elements.getModuleOf(element);
|
||||
var modulePath = getModuleSourceDir(moduleElem);
|
||||
|
||||
var packageElem = elements.getPackageOf(element); // null for a module
|
||||
var packagePath = packageElem == null
|
||||
? modulePath
|
||||
: modulePath.resolve(packageElem.getQualifiedName().toString().replace(".", File.separator));
|
||||
|
||||
return packagePath.resolve("snippet-files");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the path for an external snippet, or {@code null} if the snippet is inline}
|
||||
*
|
||||
* @param element the element whose documentation contains the snippet
|
||||
* @param tree the snippet
|
||||
*/
|
||||
public Path getExternalSnippetPath(Element element, SnippetTree tree) {
|
||||
var classAttr = getAttr(tree, "class");
|
||||
String file = (classAttr != null)
|
||||
? classAttr.replace(".", "/") + ".java"
|
||||
: getAttr(tree, "file");
|
||||
return file == null ? null : getSnippetFilesDir(element).resolve(file.replace("/", File.separator));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the value of an attribute defined by a snippet}
|
||||
*
|
||||
* @param tree the snippet
|
||||
* @param name the name of the attribute
|
||||
*/
|
||||
public String getAttr(SnippetTree tree, String name) {
|
||||
for (DocTree t : tree.getAttributes()) {
|
||||
if (t instanceof AttributeTree at && at.getName().contentEquals(name)) {
|
||||
return at.getValue().toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the primary source directory for a module}
|
||||
*
|
||||
* The directory is <em>srcDir</em>/<em>module-name</em>/share/classes.
|
||||
*
|
||||
* @param e the module
|
||||
*/
|
||||
public Path getModuleSourceDir(ModuleElement e) {
|
||||
return getModuleSourceDir(e.getQualifiedName().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the primary source directory for a module}
|
||||
*
|
||||
* The directory is <em>srcDir</em>/<em>moduleName</em>/share/classes.
|
||||
*
|
||||
* @param moduleName the module name
|
||||
*/
|
||||
public Path getModuleSourceDir(String moduleName) {
|
||||
return srcDir.resolve(moduleName).resolve("share").resolve("classes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Kinds of fragments of source code.
|
||||
*/
|
||||
public enum SourceKind {
|
||||
/** A module declaration. */
|
||||
MODULE_INFO,
|
||||
/** A package declaration. */
|
||||
PACKAGE_INFO,
|
||||
/** A class or interface declaration. */
|
||||
TYPE_DECL,
|
||||
/** A member declaration for a class or interface. */
|
||||
MEMBER_DECL,
|
||||
/** A statement, expression or other kind of fragment. */
|
||||
OTHER
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a fragment of source code, after trying to infer the kind of the fragment.
|
||||
*
|
||||
* @param body the string to be parsed
|
||||
* @param showDiag a function to handle any diagnostics that may be generated
|
||||
* @return {@code true} if the parse succeeded, and {@code false} otherwise
|
||||
*
|
||||
* @throws IOException if an IO exception occurs
|
||||
*/
|
||||
public boolean parse(String body, Consumer<? super Diagnostic<? extends JavaFileObject>> showDiag) throws IOException {
|
||||
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
|
||||
parse(body, null, collector);
|
||||
var diags = collector.getDiagnostics();
|
||||
diags.forEach(showDiag);
|
||||
return diags.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a fragment of source code, after trying to infer the kind of the fragment.
|
||||
*
|
||||
* @param body the string to be parsed
|
||||
* @param pw a stream for diagnostics, or {@code null} to use {@code System.err}
|
||||
* @param dl a diagnostic listener, or {@code null} to report diagnostics to {@code pw} or {@code System.err}
|
||||
* @throws IOException if an IO exception occurs
|
||||
*/
|
||||
public void parse(String body, PrintWriter pw, DiagnosticListener<JavaFileObject> dl)
|
||||
throws IOException {
|
||||
parse(inferSourceKind(body), body, pw, dl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a fragment of source code of a given kind.
|
||||
*
|
||||
* @param kind the kind of code to be parsed
|
||||
* @param body the string to be parsed
|
||||
* @param pw a stream for diagnostics, or {@code null} to use {@code System.err}
|
||||
* @param dl a diagnostic listener, or {@code null} to report diagnostics to {@code pw} or {@code System.err}.
|
||||
* @throws IOException if an IO exception occurs
|
||||
*/
|
||||
public void parse(SourceKind kind, String body, PrintWriter pw, DiagnosticListener<JavaFileObject> dl)
|
||||
throws IOException {
|
||||
String fileBase = switch (kind) {
|
||||
case MODULE_INFO -> "module-info";
|
||||
case PACKAGE_INFO -> "package-info";
|
||||
default -> "C"; // the exact name doesn't matter if just parsing (the filename check for public types comes later on)
|
||||
};
|
||||
URI uri = URI.create("mem://%s.java".formatted(fileBase));
|
||||
|
||||
String compUnit = switch (kind) {
|
||||
case MODULE_INFO, PACKAGE_INFO, TYPE_DECL -> body;
|
||||
case MEMBER_DECL -> """
|
||||
class C {
|
||||
%s
|
||||
}""".formatted(body);
|
||||
case OTHER -> """
|
||||
class C {
|
||||
void m() {
|
||||
%s
|
||||
;
|
||||
}
|
||||
}""".formatted(body);
|
||||
};
|
||||
JavaFileObject fo = new SimpleJavaFileObject(uri, JavaFileObject.Kind.SOURCE) {
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return compUnit;
|
||||
}
|
||||
};
|
||||
|
||||
JavaFileManager fm = compiler.getStandardFileManager(dl, null, null);
|
||||
|
||||
List<String> opts = new ArrayList<>();
|
||||
JavacTask javacTask = (JavacTask) compiler.getTask(pw, fm, dl, opts, null, List.of(fo));
|
||||
|
||||
javacTask.parse();
|
||||
}
|
||||
|
||||
public SourceKind inferSourceKind(String s) {
|
||||
Pattern typeDecl = Pattern.compile("(?s)(^|\\R)([A-Za-z0-9_$ ])*\\b(?<kw>module|package|class|interface|record|enum)\\s+(?<name>[A-Za-z0-9_$]+)");
|
||||
Matcher m1 = typeDecl.matcher(s);
|
||||
if (m1.find()) {
|
||||
return switch (m1.group("kw")) {
|
||||
case "module" -> SourceKind.MODULE_INFO;
|
||||
case "package" -> m1.find() ? SourceKind.TYPE_DECL : SourceKind.PACKAGE_INFO;
|
||||
default -> SourceKind.TYPE_DECL;
|
||||
};
|
||||
}
|
||||
|
||||
Pattern methodDecl = Pattern.compile("(?s)(^|\\R)([A-Za-z0-9<>,]+ )+\\b(?<name>[A-Za-z0-9_$]+)([(;]| +=)");
|
||||
Matcher m2 = methodDecl.matcher(s);
|
||||
if (m2.find()) {
|
||||
return SourceKind.MEMBER_DECL;
|
||||
}
|
||||
|
||||
return SourceKind.OTHER;
|
||||
}
|
||||
|
||||
private static Path findSourceDir() {
|
||||
String testSrc = System.getProperty("test.src");
|
||||
Path p = Path.of(testSrc).toAbsolutePath();
|
||||
while (p.getParent() != null) {
|
||||
Path srcDir = p.resolve("src");
|
||||
if (Files.exists(srcDir.resolve("java.base"))) {
|
||||
return srcDir;
|
||||
}
|
||||
p = p.getParent();
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find src/ from " + testSrc);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user