--- a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java Fri Dec 08 16:28:14 2017 +0100
+++ b/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java Mon Dec 11 18:33:53 2017 +0100
@@ -77,6 +77,7 @@
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
+import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
@@ -194,6 +195,8 @@
String docComment = trees.getDocComment(el);
if (docComment == null && element.getKind() == ElementKind.METHOD) {
+ //if a method does not have a javadoc,
+ //try to use javadoc from the methods overridden by this method:
ExecutableElement executableElement = (ExecutableElement) element;
Iterable<Element> superTypes =
() -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
@@ -215,27 +218,65 @@
}
}
+ if (docComment == null)
+ return null;
+
Pair<DocCommentTree, Integer> parsed = parseDocComment(task, docComment);
DocCommentTree docCommentTree = parsed.fst;
int offset = parsed.snd;
IOException[] exception = new IOException[1];
- Map<int[], String> replace = new TreeMap<>((span1, span2) -> span2[0] - span1[0]);
+ Comparator<int[]> spanComp =
+ (span1, span2) -> span1[0] != span2[0] ? span2[0] - span1[0]
+ : span2[1] - span1[0];
+ //spans in the docComment that should be replaced with the given Strings:
+ Map<int[], List<String>> replace = new TreeMap<>(spanComp);
+ DocSourcePositions sp = trees.getSourcePositions();
+ //fill in missing elements and resolve {@inheritDoc}
+ //if an element is (silently) missing in the javadoc, a synthetic {@inheritDoc}
+ //is created for it.
new DocTreeScanner<Void, Void>() {
+ /* enclosing doctree that may contain {@inheritDoc} (explicit or synthetic)*/
private Stack<DocTree> interestingParent = new Stack<>();
+ /* current top-level DocCommentTree*/
private DocCommentTree dcTree;
- private JavacTask inheritedJavacTask;
- private TreePath inheritedTreePath;
+ /* javadoc from a super method from which we may copy elements.*/
private String inherited;
+ /* JavacTask from which inherited originates.*/
+ private JavacTask inheritedJavacTask;
+ /* TreePath to the super method from which inherited originates.*/
+ private TreePath inheritedTreePath;
+ /* Synthetic trees that contain {@inheritDoc} and
+ * texts which which they should be replaced.*/
private Map<DocTree, String> syntheticTrees = new IdentityHashMap<>();
- private long lastPos = 0;
+ /* Position on which the synthetic trees should be inserted.*/
+ private long insertPos = offset;
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitDocComment(DocCommentTree node, Void p) {
dcTree = node;
interestingParent.push(node);
try {
- scan(node.getFirstSentence(), p);
- scan(node.getBody(), p);
+ if (node.getFullBody().isEmpty()) {
+ //there is no body in the javadoc, add synthetic {@inheritDoc}, which
+ //will be automatically filled in visitInheritDoc:
+ DocCommentTree dc = parseDocComment(task, "{@inheritDoc}").fst;
+ syntheticTrees.put(dc, "*\n");
+ interestingParent.push(dc);
+ boolean prevInSynthetic = inSynthetic;
+ try {
+ inSynthetic = true;
+ scan(dc.getFirstSentence(), p);
+ scan(dc.getBody(), p);
+ } finally {
+ inSynthetic = prevInSynthetic;
+ interestingParent.pop();
+ }
+ } else {
+ scan(node.getFirstSentence(), p);
+ scan(node.getBody(), p);
+ }
+ //add missing @param, @throws and @return, augmented with {@inheritDoc}
+ //which will be resolved in visitInheritDoc:
List<DocTree> augmentedBlockTags = new ArrayList<>(node.getBlockTags());
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
@@ -269,19 +310,19 @@
for (String missingParam : missingParams) {
DocTree syntheticTag = parseBlockTag(task, "@param " + missingParam + " {@inheritDoc}");
- syntheticTrees.put(syntheticTag, "@param " + missingParam + " ");
+ syntheticTrees.put(syntheticTag, "@param " + missingParam + " *\n");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
for (String missingThrow : missingThrows) {
DocTree syntheticTag = parseBlockTag(task, "@throws " + missingThrow + " {@inheritDoc}");
- syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " ");
+ syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " *\n");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
if (!hasReturn) {
DocTree syntheticTag = parseBlockTag(task, "@return {@inheritDoc}");
- syntheticTrees.put(syntheticTag, "@return ");
+ syntheticTrees.put(syntheticTag, "@return *\n");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
}
@@ -320,26 +361,32 @@
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitInheritDoc(InheritDocTree node, Void p) {
+ //replace (schedule replacement into the replace map)
+ //{@inheritDoc} with the corresponding text from an overridden method
+
+ //first, fill in inherited, inheritedJavacTask and inheritedTreePath if not
+ //done yet:
if (inherited == null) {
try {
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
- Iterable<Element> superTypes = () -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
- OUTER: for (Element sup : superTypes) {
- for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) {
- if (task.getElements().overrides(executableElement, supMethod, (TypeElement) executableElement.getEnclosingElement())) {
- Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod);
+ Iterable<ExecutableElement> superMethods =
+ () -> superMethodsForInheritDoc(task, executableElement).
+ iterator();
+ for (Element supMethod : superMethods) {
+ Pair<JavacTask, TreePath> source =
+ getSourceElement(task, supMethod);
- if (source != null) {
- String overriddenComment = getResolvedDocComment(source.fst, source.snd);
+ if (source != null) {
+ String overriddenComment =
+ getResolvedDocComment(source.fst,
+ source.snd);
- if (overriddenComment != null) {
- inheritedJavacTask = source.fst;
- inheritedTreePath = source.snd;
- inherited = overriddenComment;
- break OUTER;
- }
- }
+ if (overriddenComment != null) {
+ inheritedJavacTask = source.fst;
+ inheritedTreePath = source.snd;
+ inherited = overriddenComment;
+ break;
}
}
}
@@ -357,6 +404,8 @@
DocCommentTree inheritedDocTree = parsed.fst;
int offset = parsed.snd;
List<List<? extends DocTree>> inheritedText = new ArrayList<>();
+ //find the corresponding piece in the inherited javadoc
+ //(interesting parent keeps the enclosing tree):
DocTree parent = interestingParent.peek();
switch (parent.getKind()) {
case DOC_COMMENT:
@@ -401,18 +450,29 @@
long end = Long.MIN_VALUE;
for (DocTree t : inheritedText.get(0)) {
- start = Math.min(start, trees.getSourcePositions().getStartPosition(null, inheritedDocTree, t) - offset);
- end = Math.max(end, trees.getSourcePositions().getEndPosition(null, inheritedDocTree, t) - offset);
+ start = Math.min(start,
+ sp.getStartPosition(null, inheritedDocTree, t) - offset);
+ end = Math.max(end,
+ sp.getEndPosition(null, inheritedDocTree, t) - offset);
}
- String text = inherited.substring((int) start, (int) end);
+ String text = end >= 0 ? inherited.substring((int) start, (int) end) : "";
if (syntheticTrees.containsKey(parent)) {
- replace.put(new int[] {(int) lastPos + 1, (int) lastPos}, "\n" + syntheticTrees.get(parent) + text);
+ //if the {@inheritDoc} is inside a synthetic tree, don't delete anything,
+ //but insert the required text
+ //(insertPos is the position at which new stuff should be added):
+ int[] span = new int[] {(int) insertPos, (int) insertPos};
+ replace.computeIfAbsent(span, s -> new ArrayList<>())
+ .add(syntheticTrees.get(parent).replace("*", text));
} else {
- long inheritedStart = trees.getSourcePositions().getStartPosition(null, dcTree, node);
- long inheritedEnd = trees.getSourcePositions().getEndPosition(null, dcTree, node);
+ //replace the {@inheritDoc} with the full text from
+ //the overridden method:
+ long inheritedStart = sp.getStartPosition(null, dcTree, node);
+ long inheritedEnd = sp.getEndPosition(null, dcTree, node);
+ int[] span = new int[] {(int) inheritedStart, (int) inheritedEnd};
- replace.put(new int[] {(int) inheritedStart, (int) inheritedEnd}, text);
+ replace.computeIfAbsent(span, s -> new ArrayList<>())
+ .add(text);
}
}
return super.visitInheritDoc(node, p);
@@ -428,13 +488,31 @@
inSynthetic |= syntheticTrees.containsKey(tree);
return super.scan(tree, p);
} finally {
- if (!inSynthetic) {
- lastPos = trees.getSourcePositions().getEndPosition(null, dcTree, tree);
+ if (!inSynthetic && tree != null) {
+ //for nonsynthetic trees, preserve the ending position as the future
+ //insertPos (as future missing elements should be inserted behind
+ //this tree)
+ //if there is a newline immediately behind this tree, insert behind
+ //the newline:
+ long endPos = sp.getEndPosition(null, dcTree, tree);
+ if (endPos >= 0) {
+ if (endPos - offset + 1 < docComment.length() &&
+ docComment.charAt((int) (endPos - offset + 1)) == '\n') {
+ endPos++;
+ }
+ if (endPos - offset < docComment.length()) {
+ insertPos = endPos + 1;
+ } else {
+ insertPos = endPos;
+ }
+ }
}
inSynthetic = prevInSynthetic;
}
}
+ /* Insert a synthetic tag (toInsert) into the list of tags at
+ * an appropriate position.*/
private void insertTag(List<DocTree> tags, DocTree toInsert, List<String> parameters, List<String> throwsTypes) {
Comparator<DocTree> comp = (tag1, tag2) -> {
if (tag1.getKind() == tag2.getKind()) {
@@ -479,16 +557,30 @@
if (replace.isEmpty())
return docComment;
+ //do actually replace {@inheritDoc} with the new text (as scheduled by the visitor
+ //above):
StringBuilder replacedInheritDoc = new StringBuilder(docComment);
- for (Entry<int[], String> e : replace.entrySet()) {
- replacedInheritDoc.delete(e.getKey()[0] - offset, e.getKey()[1] - offset + 1);
- replacedInheritDoc.insert(e.getKey()[0] - offset, e.getValue());
+ for (Entry<int[], List<String>> e : replace.entrySet()) {
+ replacedInheritDoc.delete(e.getKey()[0] - offset, e.getKey()[1] - offset);
+ replacedInheritDoc.insert(e.getKey()[0] - offset,
+ e.getValue().stream().collect(Collectors.joining("")));
}
return replacedInheritDoc.toString();
}
+ /* Find methods from which the given method may inherit javadoc, in the proper order.*/
+ private Stream<ExecutableElement> superMethodsForInheritDoc(JavacTask task,
+ ExecutableElement method) {
+ TypeElement type = (TypeElement) method.getEnclosingElement();
+
+ return this.superTypeForInheritDoc(task, type)
+ .flatMap(sup -> ElementFilter.methodsIn(sup.getEnclosedElements()).stream())
+ .filter(supMethod -> task.getElements().overrides(method, supMethod, type));
+ }
+
+ /* Find types from which methods in type may inherit javadoc, in the proper order.*/
private Stream<Element> superTypeForInheritDoc(JavacTask task, Element type) {
TypeElement clazz = (TypeElement) type;
Stream<Element> result = interfaces(clazz);
@@ -529,7 +621,7 @@
};
DocCommentTree tree = trees.getDocCommentTree(fo);
int offset = (int) trees.getSourcePositions().getStartPosition(null, tree, tree);
- offset += "<body>".length() + 1;
+ offset += "<body>".length();
return Pair.of(tree, offset);
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
--- a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java Fri Dec 08 16:28:14 2017 +0100
+++ b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java Mon Dec 11 18:33:53 2017 +0100
@@ -23,28 +23,37 @@
/*
* @test
- * @bug 8131019 8190552
+ * @bug 8131019 8189778 8190552
* @summary Test JavadocHelper
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/jdk.internal.shellsupport.doc
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
- * @run testng JavadocHelperTest
+ * @run testng/timeout=900/othervm JavadocHelperTest
*/
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import javax.lang.model.element.Element;
+import javax.lang.model.element.ModuleElement;
+import javax.lang.model.element.ModuleElement.ExportsDirective;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticListener;
@@ -70,15 +79,15 @@
"Top level. ");
doTestJavadoc("",
t -> getFirstMethod(t, "test.Super"),
- " javadoc1A\n" +
+ " javadoc1\n" +
"\n" +
- " @param p1 param1A\n" +
- " @param p2 param2A\n" +
- " @param p3 param3A\n" +
- " @throws IllegalStateException exc1A\n" +
- " @throws IllegalArgumentException exc2A\n" +
- " @throws IllegalAccessException exc3A\n" +
- " @return valueA\n");
+ " @param p1 param1\n" +
+ " @param p2 param2\n" +
+ " @param p3 param3\n" +
+ " @throws IllegalStateException exc1\n" +
+ " @throws IllegalArgumentException exc2\n" +
+ " @throws IllegalAccessException exc3\n" +
+ " @return value\n");
}
private Element getFirstMethod(JavacTask task, String typeName) {
@@ -90,15 +99,15 @@
public void testInheritNoJavadoc() throws Exception {
doTestJavadoc("",
getSubTest,
- " javadoc1A\n" +
+ " javadoc1\n" +
"\n" +
- " @param p1 param1A\n" +
- " @param p2 param2A\n" +
- " @param p3 param3A\n" +
- " @throws IllegalStateException exc1A\n" +
- " @throws IllegalArgumentException exc2A\n" +
- " @throws IllegalAccessException exc3A\n" +
- " @return valueA\n");
+ " @param p1 param1\n" +
+ " @param p2 param2\n" +
+ " @param p3 param3\n" +
+ " @throws IllegalStateException exc1\n" +
+ " @throws IllegalArgumentException exc2\n" +
+ " @throws IllegalAccessException exc3\n" +
+ " @return value\n");
}
public void testInheritFull() throws Exception {
@@ -140,7 +149,7 @@
" Prefix javadoc1 suffix.\n" +
"\n" +
" @param p1 prefix param1 suffix\n" +
- "@param p2 param2\n" +
+ "@param p2 param2\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
@@ -161,8 +170,8 @@
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
- "@param p1 param1\n" +
"\n" +
+ "@param p1 param1\n" +
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
@@ -189,7 +198,7 @@
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
- "@throws java.lang.IllegalArgumentException exc2\n" +
+ "@throws java.lang.IllegalArgumentException exc2\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
" @return prefix value suffix\n");
}
@@ -214,11 +223,101 @@
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
- "@return value\n");
+ "@return value\n");
+ }
+
+ public void testInheritAllButOne() throws Exception {
+ doTestJavadoc(" /**\n" +
+ " * @throws IllegalArgumentException {@inheritDoc}\n" +
+ " */\n",
+ getSubTest,
+ "javadoc1\n" +
+ "@param p1 param1\n" +
+ "@param p2 param2\n" +
+ "@param p3 param3\n" +
+ "@throws java.lang.IllegalStateException exc1\n" +
+ " @throws IllegalArgumentException exc2\n" +
+ "@throws java.lang.IllegalAccessException exc3\n" +
+ "@return value\n");
+ }
+
+ public void testInheritEmpty() throws Exception {
+ doTestJavadoc(" /**\n" +
+ " */\n",
+ " /**@param p1\n" +
+ " * @param p2\n" +
+ " * @param p3\n" +
+ " * @throws IllegalStateException\n" +
+ " * @throws IllegalArgumentException\n" +
+ " * @throws IllegalAccessException\n" +
+ " * @return\n" +
+ " */\n",
+ getSubTest,
+ "\n" +
+ "@param p1 \n" +
+ "@param p2 \n" +
+ "@param p3 \n" +
+ "@throws java.lang.IllegalStateException \n" +
+ "@throws java.lang.IllegalArgumentException \n" +
+ "@throws java.lang.IllegalAccessException \n" +
+ "@return \n");
}
+ public void testEmptyValue() throws Exception {
+ doTestJavadoc(" /**\n" +
+ " */\n",
+ " /**@param p1 {@value}\n" +
+ " * @param p2\n" +
+ " * @param p3\n" +
+ " * @throws IllegalStateException\n" +
+ " * @throws IllegalArgumentException\n" +
+ " * @throws IllegalAccessException\n" +
+ " * @return\n" +
+ " */\n",
+ getSubTest,
+ "\n" +
+ "@param p1 {@value}\n" +
+ "@param p2 \n" +
+ "@param p3 \n" +
+ "@throws java.lang.IllegalStateException \n" +
+ "@throws java.lang.IllegalArgumentException \n" +
+ "@throws java.lang.IllegalAccessException \n" +
+ "@return \n");
+ }
+
+ public void testShortComment() throws Exception {
+ doTestJavadoc(" /**Test.*/\n",
+ getSubTest,
+ "Test." +
+ "@param p1 param1\n" +
+ "@param p2 param2\n" +
+ "@param p3 param3\n" +
+ "@throws java.lang.IllegalStateException exc1\n" +
+ "@throws java.lang.IllegalArgumentException exc2\n" +
+ "@throws java.lang.IllegalAccessException exc3\n" +
+ "@return value\n");
+ }
private void doTestJavadoc(String origJavadoc, Function<JavacTask, Element> getElement, String expectedJavadoc) throws Exception {
+ doTestJavadoc(origJavadoc,
+ " /**\n" +
+ " * javadoc1\n" +
+ " *\n" +
+ " * @param p1 param1\n" +
+ " * @param p2 param2\n" +
+ " * @param p3 param3\n" +
+ " * @throws IllegalStateException exc1\n" +
+ " * @throws IllegalArgumentException exc2\n" +
+ " * @throws IllegalAccessException exc3\n" +
+ " * @return value\n" +
+ " */\n",
+ getElement, expectedJavadoc);
+ }
+
+ private void doTestJavadoc(String origJavadoc,
+ String superJavadoc,
+ Function<JavacTask, Element> getElement,
+ String expectedJavadoc) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String subClass =
"package test;\n" +
@@ -231,17 +330,7 @@
"/**Top level." +
" */\n" +
"public class Super {\n" +
- " /**\n" +
- " * javadoc1A\n" +
- " *\n" +
- " * @param p1 param1A\n" +
- " * @param p2 param2A\n" +
- " * @param p3 param3A\n" +
- " * @throws IllegalStateException exc1A\n" +
- " * @throws IllegalArgumentException exc2A\n" +
- " * @throws IllegalAccessException exc3A\n" +
- " * @return valueA\n" +
- " */\n" +
+ superJavadoc +
" public String test(int p1, int p2, int p3) throws IllegalStateException, IllegalArgumentException, IllegalAccessException { return null;} \n" +
"}\n";
@@ -293,4 +382,53 @@
}
}
+
+ public void testAllDocs() throws IOException {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ DiagnosticListener<? super JavaFileObject> noErrors = d -> {
+ if (d.getKind() == Kind.ERROR) {
+ throw new AssertionError(d.getMessage(null));
+ }
+ };
+
+ List<Path> sources = new ArrayList<>();
+ Path home = Paths.get(System.getProperty("java.home"));
+ Path srcZip = home.resolve("lib").resolve("src.zip");
+ if (Files.isReadable(srcZip)) {
+ URI uri = URI.create("jar:" + srcZip.toUri());
+ try (FileSystem zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
+ Path root = zipFO.getRootDirectories().iterator().next();
+
+ //modular format:
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+ for (Path p : ds) {
+ if (Files.isDirectory(p)) {
+ sources.add(p);
+ }
+ }
+ }
+ try (StandardJavaFileManager fm =
+ compiler.getStandardFileManager(null, null, null)) {
+ JavacTask task =
+ (JavacTask) compiler.getTask(null, fm, noErrors, null, null, null);
+ task.getElements().getTypeElement("java.lang.Object");
+ for (ModuleElement me : task.getElements().getAllModuleElements()) {
+ List<ExportsDirective> exports =
+ ElementFilter.exportsIn(me.getDirectives());
+ for (ExportsDirective ed : exports) {
+ try (JavadocHelper helper = JavadocHelper.create(task, sources)) {
+ List<? extends Element> content =
+ ed.getPackage().getEnclosedElements();
+ for (TypeElement clazz : ElementFilter.typesIn(content)) {
+ for (Element el : clazz.getEnclosedElements()) {
+ helper.getResolvedDocComment(el);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}