8131019: jshell tool: access javadoc from tool

Adding internal support to resolve {@inheritDoc} and format javadoc to plain text for use by jdk.jshell and jdk.scripting.nashorn.shell, enhancing Shift-<tab> documentation in JShell with ability to show javadoc.

Reviewed-by: jjg, rfield
This commit is contained in:
Jan Lahoda 2016-11-02 07:38:37 +01:00
parent 293d086bd9
commit 5d215e5425
16 changed files with 2551 additions and 224 deletions

View File

@ -297,17 +297,21 @@ public enum Entity {
rsaquo(8250),
euro(8364);
int code;
public final int code;
private Entity(int code) {
this.code = code;
}
static boolean isValid(String name) {
public static boolean isValid(String name) {
return names.containsKey(name);
}
static boolean isValid(int code) {
public static Entity get(String name) {
return names.get(name);
}
public static boolean isValid(int code) {
// allow numeric codes for standard ANSI characters
return codes.containsKey(code) || ( 32 <= code && code < 2127);
}

View File

@ -0,0 +1,706 @@
/*
* Copyright (c) 2016, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.internal.shellsupport.doc;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Stack;
import javax.lang.model.element.Name;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
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.EndElementTree;
import com.sun.source.doctree.EntityTree;
import com.sun.source.doctree.InlineTagTree;
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.ReturnTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.JavacTask;
import com.sun.tools.doclint.Entity;
import com.sun.tools.doclint.HtmlTag;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.StringUtils;
/**A javadoc to plain text formatter.
*
*/
public class JavadocFormatter {
private static final String CODE_RESET = "\033[0m";
private static final String CODE_HIGHLIGHT = "\033[1m";
private static final String CODE_UNDERLINE = "\033[4m";
private final int lineLimit;
private final boolean escapeSequencesSupported;
/** Construct the formatter.
*
* @param lineLimit maximum line length
* @param escapeSequencesSupported whether escape sequences are supported
*/
public JavadocFormatter(int lineLimit, boolean escapeSequencesSupported) {
this.lineLimit = lineLimit;
this.escapeSequencesSupported = escapeSequencesSupported;
}
private static final int MAX_LINE_LENGTH = 95;
private static final int SHORTEST_LINE = 30;
private static final int INDENT = 4;
/**Format javadoc to plain text.
*
* @param header element caption that should be used
* @param javadoc to format
* @return javadoc formatted to plain text
*/
public String formatJavadoc(String header, String javadoc) {
try {
StringBuilder result = new StringBuilder();
result.append(escape(CODE_HIGHLIGHT)).append(header).append(escape(CODE_RESET)).append("\n");
if (javadoc == null) {
return result.toString();
}
JavacTask task = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, null);
DocTrees trees = DocTrees.instance(task);
DocCommentTree docComment = trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) {
@Override @DefinedBy(Api.COMPILER)
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return "<body>" + javadoc + "</body>";
}
});
new FormatJavadocScanner(result, task).scan(docComment, null);
addNewLineIfNeeded(result);
return result.toString();
} catch (URISyntaxException ex) {
throw new InternalError("Unexpected exception", ex);
}
}
private class FormatJavadocScanner extends DocTreeScanner<Object, Object> {
private final StringBuilder result;
private final JavacTask task;
private int reflownTo;
private int indent;
private int limit = Math.min(lineLimit, MAX_LINE_LENGTH);
private boolean pre;
private Map<StartElementTree, Integer> tableColumns;
public FormatJavadocScanner(StringBuilder result, JavacTask task) {
this.result = result;
this.task = task;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitDocComment(DocCommentTree node, Object p) {
tableColumns = countTableColumns(node);
reflownTo = result.length();
scan(node.getFirstSentence(), p);
scan(node.getBody(), p);
reflow(result, reflownTo, indent, limit);
for (Sections current : docSections.keySet()) {
boolean seenAny = false;
for (DocTree t : node.getBlockTags()) {
if (current.matches(t)) {
if (!seenAny) {
seenAny = true;
if (result.charAt(result.length() - 1) != '\n')
result.append("\n");
result.append("\n");
result.append(escape(CODE_UNDERLINE))
.append(docSections.get(current))
.append(escape(CODE_RESET))
.append("\n");
}
scan(t, null);
}
}
}
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitText(TextTree node, Object p) {
String text = node.getBody();
if (!pre) {
text = text.replaceAll("[ \t\r\n]+", " ").trim();
if (text.isEmpty()) {
text = " ";
}
} else {
text = text.replaceAll("\n", "\n" + indentString(indent));
}
result.append(text);
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitLink(LinkTree node, Object p) {
if (!node.getLabel().isEmpty()) {
scan(node.getLabel(), p);
} else {
result.append(node.getReference().getSignature());
}
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitParam(ParamTree node, Object p) {
return formatDef(node.getName().getName(), node.getDescription());
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitThrows(ThrowsTree node, Object p) {
return formatDef(node.getExceptionName().getSignature(), node.getDescription());
}
public Object formatDef(CharSequence name, List<? extends DocTree> description) {
result.append(name);
result.append(" - ");
reflownTo = result.length();
indent = name.length() + 3;
if (limit - indent < SHORTEST_LINE) {
result.append("\n");
result.append(indentString(INDENT));
indent = INDENT;
reflownTo += INDENT;
}
try {
return scan(description, null);
} finally {
reflow(result, reflownTo, indent, limit);
result.append("\n");
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitLiteral(LiteralTree node, Object p) {
return scan(node.getBody(), p);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitReturn(ReturnTree node, Object p) {
reflownTo = result.length();
try {
return super.visitReturn(node, p);
} finally {
reflow(result, reflownTo, 0, limit);
}
}
Stack<Integer> listStack = new Stack<>();
Stack<Integer> defStack = new Stack<>();
Stack<Integer> tableStack = new Stack<>();
Stack<List<Integer>> cellsStack = new Stack<>();
Stack<List<Boolean>> headerStack = new Stack<>();
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitStartElement(StartElementTree node, Object p) {
switch (HtmlTag.get(node.getName())) {
case P:
if (lastNode!= null && lastNode.getKind() == DocTree.Kind.START_ELEMENT &&
HtmlTag.get(((StartElementTree) lastNode).getName()) == HtmlTag.LI) {
//ignore
break;
}
reflowTillNow();
addNewLineIfNeeded(result);
result.append(indentString(indent));
reflownTo = result.length();
break;
case BLOCKQUOTE:
reflowTillNow();
indent += INDENT;
break;
case PRE:
reflowTillNow();
pre = true;
break;
case UL:
reflowTillNow();
listStack.push(-1);
indent += INDENT;
break;
case OL:
reflowTillNow();
listStack.push(1);
indent += INDENT;
break;
case DL:
reflowTillNow();
defStack.push(indent);
break;
case LI:
reflowTillNow();
if (!listStack.empty()) {
addNewLineIfNeeded(result);
int top = listStack.pop();
if (top == (-1)) {
result.append(indentString(indent - 2));
result.append("* ");
} else {
result.append(indentString(indent - 3));
result.append("" + top++ + ". ");
}
listStack.push(top);
reflownTo = result.length();
}
break;
case DT:
reflowTillNow();
if (!defStack.isEmpty()) {
addNewLineIfNeeded(result);
indent = defStack.peek();
result.append(escape(CODE_HIGHLIGHT));
}
break;
case DD:
reflowTillNow();
if (!defStack.isEmpty()) {
if (indent == defStack.peek()) {
result.append(escape(CODE_RESET));
}
addNewLineIfNeeded(result);
indent = defStack.peek() + INDENT;
result.append(indentString(indent));
}
break;
case H1: case H2: case H3:
case H4: case H5: case H6:
reflowTillNow();
addNewLineIfNeeded(result);
result.append("\n")
.append(escape(CODE_UNDERLINE));
reflownTo = result.length();
break;
case TABLE:
int columns = tableColumns.get(node);
if (columns == 0) {
break; //broken input
}
reflowTillNow();
addNewLineIfNeeded(result);
reflownTo = result.length();
tableStack.push(limit);
limit = (limit - 1) / columns - 3;
for (int sep = 0; sep < (limit + 3) * columns + 1; sep++) {
result.append("-");
}
result.append("\n");
break;
case TR:
if (cellsStack.size() >= tableStack.size()) {
//unclosed <tr>:
handleEndElement(node.getName());
}
cellsStack.push(new ArrayList<>());
headerStack.push(new ArrayList<>());
break;
case TH:
case TD:
if (cellsStack.isEmpty()) {
//broken code
break;
}
reflowTillNow();
result.append("\n");
reflownTo = result.length();
cellsStack.peek().add(result.length());
headerStack.peek().add(HtmlTag.get(node.getName()) == HtmlTag.TH);
break;
case IMG:
for (DocTree attr : node.getAttributes()) {
if (attr.getKind() != DocTree.Kind.ATTRIBUTE) {
continue;
}
AttributeTree at = (AttributeTree) attr;
if ("alt".equals(StringUtils.toLowerCase(at.getName().toString()))) {
addSpaceIfNeeded(result);
scan(at.getValue(), null);
addSpaceIfNeeded(result);
break;
}
}
break;
default:
addSpaceIfNeeded(result);
break;
}
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitEndElement(EndElementTree node, Object p) {
handleEndElement(node.getName());
return super.visitEndElement(node, p);
}
private void handleEndElement(Name name) {
switch (HtmlTag.get(name)) {
case BLOCKQUOTE:
indent -= INDENT;
break;
case PRE:
pre = false;
addNewLineIfNeeded(result);
reflownTo = result.length();
break;
case UL: case OL:
if (listStack.isEmpty()) { //ignore stray closing tag
break;
}
reflowTillNow();
listStack.pop();
indent -= INDENT;
addNewLineIfNeeded(result);
break;
case DL:
if (defStack.isEmpty()) {//ignore stray closing tag
break;
}
reflowTillNow();
if (indent == defStack.peek()) {
result.append(escape(CODE_RESET));
}
indent = defStack.pop();
addNewLineIfNeeded(result);
break;
case H1: case H2: case H3:
case H4: case H5: case H6:
reflowTillNow();
result.append(escape(CODE_RESET))
.append("\n");
reflownTo = result.length();
break;
case TABLE:
if (cellsStack.size() >= tableStack.size()) {
//unclosed <tr>:
handleEndElement(task.getElements().getName("tr"));
}
if (tableStack.isEmpty()) {
break;
}
limit = tableStack.pop();
break;
case TR:
if (cellsStack.isEmpty()) {
break;
}
reflowTillNow();
List<Integer> cells = cellsStack.pop();
List<Boolean> headerFlags = headerStack.pop();
List<String[]> content = new ArrayList<>();
int maxLines = 0;
result.append("\n");
while (!cells.isEmpty()) {
int currentCell = cells.remove(cells.size() - 1);
String[] lines = result.substring(currentCell, result.length()).split("\n");
result.delete(currentCell - 1, result.length());
content.add(lines);
maxLines = Math.max(maxLines, lines.length);
}
Collections.reverse(content);
for (int line = 0; line < maxLines; line++) {
for (int column = 0; column < content.size(); column++) {
String[] lines = content.get(column);
String currentLine = line < lines.length ? lines[line] : "";
result.append("| ");
boolean header = headerFlags.get(column);
if (header) {
result.append(escape(CODE_HIGHLIGHT));
}
result.append(currentLine);
if (header) {
result.append(escape(CODE_RESET));
}
int padding = limit - currentLine.length();
if (padding > 0)
result.append(indentString(padding));
result.append(" ");
}
result.append("|\n");
}
for (int sep = 0; sep < (limit + 3) * content.size() + 1; sep++) {
result.append("-");
}
result.append("\n");
reflownTo = result.length();
break;
case TD:
case TH:
break;
default:
addSpaceIfNeeded(result);
break;
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitEntity(EntityTree node, Object p) {
String name = node.getName().toString();
int code = -1;
if (name.startsWith("#")) {
try {
int v = StringUtils.toLowerCase(name).startsWith("#x")
? Integer.parseInt(name.substring(2), 16)
: Integer.parseInt(name.substring(1), 10);
if (Entity.isValid(v)) {
code = v;
}
} catch (NumberFormatException ex) {
//ignore
}
} else {
Entity entity = Entity.get(name);
if (entity != null) {
code = entity.code;
}
}
if (code != (-1)) {
result.appendCodePoint(code);
} else {
result.append(node.toString());
}
return super.visitEntity(node, p);
}
private DocTree lastNode;
@Override @DefinedBy(Api.COMPILER_TREE)
public Object scan(DocTree node, Object p) {
if (node instanceof InlineTagTree) {
addSpaceIfNeeded(result);
}
try {
return super.scan(node, p);
} finally {
if (node instanceof InlineTagTree) {
addSpaceIfNeeded(result);
}
lastNode = node;
}
}
private void reflowTillNow() {
while (result.length() > 0 && result.charAt(result.length() - 1) == ' ')
result.delete(result.length() - 1, result.length());
reflow(result, reflownTo, indent, limit);
reflownTo = result.length();
}
};
private String escape(String sequence) {
return this.escapeSequencesSupported ? sequence : "";
}
private static final Map<Sections, String> docSections = new LinkedHashMap<>();
static {
ResourceBundle bundle =
ResourceBundle.getBundle("jdk.internal.shellsupport.doc.resources.javadocformatter");
docSections.put(Sections.TYPE_PARAMS, bundle.getString("CAP_TypeParameters"));
docSections.put(Sections.PARAMS, bundle.getString("CAP_Parameters"));
docSections.put(Sections.RETURNS, bundle.getString("CAP_Returns"));
docSections.put(Sections.THROWS, bundle.getString("CAP_Thrown_Exceptions"));
}
private static String indentString(int indent) {
char[] content = new char[indent];
Arrays.fill(content, ' ');
return new String(content);
}
private static void reflow(StringBuilder text, int from, int indent, int limit) {
int lineStart = from;
while (lineStart > 0 && text.charAt(lineStart - 1) != '\n') {
lineStart--;
}
int lineChars = from - lineStart;
int pointer = from;
int lastSpace = -1;
while (pointer < text.length()) {
if (text.charAt(pointer) == ' ')
lastSpace = pointer;
if (lineChars >= limit) {
if (lastSpace != (-1)) {
text.setCharAt(lastSpace, '\n');
text.insert(lastSpace + 1, indentString(indent));
lineChars = indent + pointer - lastSpace - 1;
pointer += indent;
lastSpace = -1;
}
}
lineChars++;
pointer++;
}
}
private static void addNewLineIfNeeded(StringBuilder text) {
if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
text.append("\n");
}
}
private static void addSpaceIfNeeded(StringBuilder text) {
if (text.length() == 0)
return ;
char last = text.charAt(text.length() - 1);
if (last != ' ' && last != '\n') {
text.append(" ");
}
}
private static Map<StartElementTree, Integer> countTableColumns(DocCommentTree dct) {
Map<StartElementTree, Integer> result = new IdentityHashMap<>();
new DocTreeScanner<Void, Void>() {
private StartElementTree currentTable;
private int currentMaxColumns;
private int currentRowColumns;
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitStartElement(StartElementTree node, Void p) {
switch (HtmlTag.get(node.getName())) {
case TABLE: currentTable = node; break;
case TR:
currentMaxColumns = Math.max(currentMaxColumns, currentRowColumns);
currentRowColumns = 0;
break;
case TD:
case TH: currentRowColumns++; break;
}
return super.visitStartElement(node, p);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitEndElement(EndElementTree node, Void p) {
if (HtmlTag.get(node.getName()) == HtmlTag.TABLE) {
closeTable();
}
return super.visitEndElement(node, p);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitDocComment(DocCommentTree node, Void p) {
try {
return super.visitDocComment(node, p);
} finally {
closeTable();
}
}
private void closeTable() {
if (currentTable != null) {
result.put(currentTable, Math.max(currentMaxColumns, currentRowColumns));
currentTable = null;
}
}
}.scan(dct, null);
return result;
}
private enum Sections {
TYPE_PARAMS {
@Override public boolean matches(DocTree t) {
return t.getKind() == DocTree.Kind.PARAM && ((ParamTree) t).isTypeParameter();
}
},
PARAMS {
@Override public boolean matches(DocTree t) {
return t.getKind() == DocTree.Kind.PARAM && !((ParamTree) t).isTypeParameter();
}
},
RETURNS {
@Override public boolean matches(DocTree t) {
return t.getKind() == DocTree.Kind.RETURN;
}
},
THROWS {
@Override public boolean matches(DocTree t) {
return t.getKind() == DocTree.Kind.THROWS;
}
};
public abstract boolean matches(DocTree t);
}
}

View File

@ -0,0 +1,661 @@
/*
* Copyright (c) 2016, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.internal.shellsupport.doc;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.InheritDocTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.ReturnTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.Pair;
/**Helper to find javadoc and resolve @inheritDoc.
*/
public abstract class JavadocHelper implements AutoCloseable {
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/**Create the helper.
*
* @param mainTask JavacTask from which the further Elements originate
* @param sourceLocations paths where source files should be searched
* @return a JavadocHelper
*/
public static JavadocHelper create(JavacTask mainTask, Collection<? extends Path> sourceLocations) {
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
try {
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourceLocations);
return new OnDemandJavadocHelper(mainTask, fm);
} catch (IOException ex) {
try {
fm.close();
} catch (IOException closeEx) {
}
return new JavadocHelper() {
@Override
public String getResolvedDocComment(Element forElement) throws IOException {
return null;
}
@Override
public Element getSourceElement(Element forElement) throws IOException {
return forElement;
}
@Override
public void close() throws IOException {}
};
}
}
/**Returns javadoc for the given element, if it can be found, or null otherwise. The javadoc
* will have @inheritDoc resolved.
*
* @param forElement element for which the javadoc should be searched
* @return javadoc if found, null otherwise
* @throws IOException if something goes wrong in the search
*/
public abstract String getResolvedDocComment(Element forElement) throws IOException;
/**Returns an element representing the same given program element, but the returned element will
* be resolved from source, if it can be found. Returns the original element if the source for
* the given element cannot be found.
*
* @param forElement element for which the source element should be searched
* @return source element if found, the original element otherwise
* @throws IOException if something goes wrong in the search
*/
public abstract Element getSourceElement(Element forElement) throws IOException;
/**Closes the helper.
*
* @throws IOException if something foes wrong during the close
*/
@Override
public abstract void close() throws IOException;
private static final class OnDemandJavadocHelper extends JavadocHelper {
private final JavacTask mainTask;
private final JavaFileManager baseFileManager;
private final StandardJavaFileManager fm;
private final Map<String, Pair<JavacTask, TreePath>> signature2Source = new HashMap<>();
private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm) {
this.mainTask = mainTask;
this.baseFileManager = ((JavacTaskImpl) mainTask).getContext().get(JavaFileManager.class);
this.fm = fm;
}
@Override
public String getResolvedDocComment(Element forElement) throws IOException {
Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement);
if (sourceElement == null)
return null;
return getResolvedDocComment(sourceElement.fst, sourceElement.snd);
}
@Override
public Element getSourceElement(Element forElement) throws IOException {
Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement);
if (sourceElement == null)
return forElement;
Element result = Trees.instance(sourceElement.fst).getElement(sourceElement.snd);
if (result == null)
return forElement;
return result;
}
private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException {
DocTrees trees = DocTrees.instance(task);
Element element = trees.getElement(el);
String docComment = trees.getDocComment(el);
if (docComment == null && element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
Iterable<Element> superTypes =
() -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
for (Element sup : superTypes) {
for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) {
TypeElement clazz = (TypeElement) executableElement.getEnclosingElement();
if (task.getElements().overrides(executableElement, supMethod, clazz)) {
Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod);
if (source != null) {
String overriddenComment = getResolvedDocComment(source.fst, source.snd);
if (overriddenComment != null) {
return overriddenComment;
}
}
}
}
}
}
DocCommentTree docCommentTree = parseDocComment(task, docComment);
IOException[] exception = new IOException[1];
Map<int[], String> replace = new TreeMap<>((span1, span2) -> span2[0] - span1[0]);
new DocTreeScanner<Void, Void>() {
private Stack<DocTree> interestingParent = new Stack<>();
private DocCommentTree dcTree;
private JavacTask inheritedJavacTask;
private TreePath inheritedTreePath;
private String inherited;
private Map<DocTree, String> syntheticTrees = new IdentityHashMap<>();
private long lastPos = 0;
@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);
List<DocTree> augmentedBlockTags = new ArrayList<>(node.getBlockTags());
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
List<String> parameters =
executableElement.getParameters()
.stream()
.map(param -> param.getSimpleName().toString())
.collect(Collectors.toList());
List<String> throwsList =
executableElement.getThrownTypes()
.stream()
.map(exc -> exc.toString())
.collect(Collectors.toList());
Set<String> missingParams = new HashSet<>(parameters);
Set<String> missingThrows = new HashSet<>(throwsList);
boolean hasReturn = false;
for (DocTree dt : augmentedBlockTags) {
switch (dt.getKind()) {
case PARAM:
missingParams.remove(((ParamTree) dt).getName().getName().toString());
break;
case THROWS:
missingThrows.remove(getThrownException(task, el, docCommentTree, (ThrowsTree) dt));
break;
case RETURN:
hasReturn = true;
break;
}
}
for (String missingParam : missingParams) {
DocTree syntheticTag = parseBlockTag(task, "@param " + missingParam + " {@inheritDoc}");
syntheticTrees.put(syntheticTag, "@param " + missingParam + " ");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
for (String missingThrow : missingThrows) {
DocTree syntheticTag = parseBlockTag(task, "@throws " + missingThrow + " {@inheritDoc}");
syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " ");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
if (!hasReturn) {
DocTree syntheticTag = parseBlockTag(task, "@return {@inheritDoc}");
syntheticTrees.put(syntheticTag, "@return ");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
}
scan(augmentedBlockTags, p);
return null;
} finally {
interestingParent.pop();
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitParam(ParamTree node, Void p) {
interestingParent.push(node);
try {
return super.visitParam(node, p);
} finally {
interestingParent.pop();
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitThrows(ThrowsTree node, Void p) {
interestingParent.push(node);
try {
return super.visitThrows(node, p);
} finally {
interestingParent.pop();
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitReturn(ReturnTree node, Void p) {
interestingParent.push(node);
try {
return super.visitReturn(node, p);
} finally {
interestingParent.pop();
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitInheritDoc(InheritDocTree node, Void p) {
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);
if (source != null) {
String overriddenComment = getResolvedDocComment(source.fst, source.snd);
if (overriddenComment != null) {
inheritedJavacTask = source.fst;
inheritedTreePath = source.snd;
inherited = overriddenComment;
break OUTER;
}
}
}
}
}
}
} catch (IOException ex) {
exception[0] = ex;
return null;
}
}
if (inherited == null) {
return null;
}
DocCommentTree inheritedDocTree = parseDocComment(inheritedJavacTask, inherited);
List<List<? extends DocTree>> inheritedText = new ArrayList<>();
DocTree parent = interestingParent.peek();
switch (parent.getKind()) {
case DOC_COMMENT:
inheritedText.add(inheritedDocTree.getFullBody());
break;
case PARAM:
String paramName = ((ParamTree) parent).getName().getName().toString();
new DocTreeScanner<Void, Void>() {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitParam(ParamTree node, Void p) {
if (node.getName().getName().contentEquals(paramName)) {
inheritedText.add(node.getDescription());
}
return super.visitParam(node, p);
}
}.scan(inheritedDocTree, null);
break;
case THROWS:
String thrownName = getThrownException(task, el, docCommentTree, (ThrowsTree) parent);
new DocTreeScanner<Void, Void>() {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitThrows(ThrowsTree node, Void p) {
if (Objects.equals(getThrownException(inheritedJavacTask, inheritedTreePath, inheritedDocTree, node), thrownName)) {
inheritedText.add(node.getDescription());
}
return super.visitThrows(node, p);
}
}.scan(inheritedDocTree, null);
break;
case RETURN:
new DocTreeScanner<Void, Void>() {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitReturn(ReturnTree node, Void p) {
inheritedText.add(node.getDescription());
return super.visitReturn(node, p);
}
}.scan(inheritedDocTree, null);
break;
}
if (!inheritedText.isEmpty()) {
long offset = trees.getSourcePositions().getStartPosition(null, inheritedDocTree, inheritedDocTree);
long start = Long.MAX_VALUE;
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);
}
String text = inherited.substring((int) start, (int) end);
if (syntheticTrees.containsKey(parent)) {
replace.put(new int[] {(int) lastPos + 1, (int) lastPos}, "\n" + syntheticTrees.get(parent) + text);
} else {
long inheritedStart = trees.getSourcePositions().getStartPosition(null, dcTree, node);
long inheritedEnd = trees.getSourcePositions().getEndPosition(null, dcTree, node);
replace.put(new int[] {(int) inheritedStart, (int) inheritedEnd}, text);
}
}
return super.visitInheritDoc(node, p);
}
private boolean inSynthetic;
@Override @DefinedBy(Api.COMPILER_TREE)
public Void scan(DocTree tree, Void p) {
if (exception[0] != null) {
return null;
}
boolean prevInSynthetic = inSynthetic;
try {
inSynthetic |= syntheticTrees.containsKey(tree);
return super.scan(tree, p);
} finally {
if (!inSynthetic) {
lastPos = trees.getSourcePositions().getEndPosition(null, dcTree, tree);
}
inSynthetic = prevInSynthetic;
}
}
private void insertTag(List<DocTree> tags, DocTree toInsert, List<String> parameters, List<String> throwsTypes) {
Comparator<DocTree> comp = (tag1, tag2) -> {
if (tag1.getKind() == tag2.getKind()) {
switch (toInsert.getKind()) {
case PARAM: {
ParamTree p1 = (ParamTree) tag1;
ParamTree p2 = (ParamTree) tag2;
int i1 = parameters.indexOf(p1.getName().getName().toString());
int i2 = parameters.indexOf(p2.getName().getName().toString());
return i1 - i2;
}
case THROWS: {
ThrowsTree t1 = (ThrowsTree) tag1;
ThrowsTree t2 = (ThrowsTree) tag2;
int i1 = throwsTypes.indexOf(getThrownException(task, el, docCommentTree, t1));
int i2 = throwsTypes.indexOf(getThrownException(task, el, docCommentTree, t2));
return i1 - i2;
}
}
}
int i1 = tagOrder.indexOf(tag1.getKind());
int i2 = tagOrder.indexOf(tag2.getKind());
return i1 - i2;
};
for (int i = 0; i < tags.size(); i++) {
if (comp.compare(tags.get(i), toInsert) >= 0) {
tags.add(i, toInsert);
return ;
}
}
tags.add(toInsert);
}
private final List<DocTree.Kind> tagOrder = Arrays.asList(DocTree.Kind.PARAM, DocTree.Kind.THROWS, DocTree.Kind.RETURN);
}.scan(docCommentTree, null);
if (replace.isEmpty())
return docComment;
StringBuilder replacedInheritDoc = new StringBuilder(docComment);
int offset = (int) trees.getSourcePositions().getStartPosition(null, docCommentTree, docCommentTree);
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());
}
return replacedInheritDoc.toString();
}
private Stream<Element> superTypeForInheritDoc(JavacTask task, Element type) {
TypeElement clazz = (TypeElement) type;
Stream<Element> result = interfaces(clazz);
result = Stream.concat(result, interfaces(clazz).flatMap(el -> superTypeForInheritDoc(task, el)));
if (clazz.getSuperclass().getKind() == TypeKind.DECLARED) {
Element superClass = ((DeclaredType) clazz.getSuperclass()).asElement();
result = Stream.concat(result, Stream.of(superClass));
result = Stream.concat(result, superTypeForInheritDoc(task, superClass));
}
return result;
}
//where:
private Stream<Element> interfaces(TypeElement clazz) {
return clazz.getInterfaces()
.stream()
.filter(tm -> tm.getKind() == TypeKind.DECLARED)
.map(tm -> ((DeclaredType) tm).asElement());
}
private DocTree parseBlockTag(JavacTask task, String blockTag) {
DocCommentTree dc = parseDocComment(task, blockTag);
return dc.getBlockTags().get(0);
}
private DocCommentTree parseDocComment(JavacTask task, String javadoc) {
DocTrees trees = DocTrees.instance(task);
try {
return trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), javax.tools.JavaFileObject.Kind.HTML) {
@Override @DefinedBy(Api.COMPILER)
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return "<body>" + javadoc + "</body>";
}
});
} catch (URISyntaxException ex) {
return null;
}
}
private String getThrownException(JavacTask task, TreePath rootOn, DocCommentTree comment, ThrowsTree tt) {
DocTrees trees = DocTrees.instance(task);
Element exc = trees.getElement(new DocTreePath(new DocTreePath(rootOn, comment), tt.getExceptionName()));
return exc != null ? exc.toString() : null;
}
private Pair<JavacTask, TreePath> getSourceElement(JavacTask origin, Element el) throws IOException {
String handle = elementSignature(el);
Pair<JavacTask, TreePath> cached = signature2Source.get(handle);
if (cached != null) {
return cached.fst != null ? cached : null;
}
TypeElement type = topLevelType(el);
if (type == null)
return null;
String binaryName = origin.getElements().getBinaryName(type).toString();
Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName);
if (source == null)
return null;
fillElementCache(source.fst, source.snd);
cached = signature2Source.get(handle);
if (cached != null) {
return cached;
} else {
signature2Source.put(handle, Pair.of(null, null));
return null;
}
}
//where:
private String elementSignature(Element el) {
switch (el.getKind()) {
case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE:
return ((TypeElement) el).getQualifiedName().toString();
case FIELD:
return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
case ENUM_CONSTANT:
return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName();
case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
return el.getSimpleName() + ":" + el.asType();
case CONSTRUCTOR: case METHOD:
StringBuilder header = new StringBuilder();
header.append(elementSignature(el.getEnclosingElement()));
if (el.getKind() == ElementKind.METHOD) {
header.append(".");
header.append(el.getSimpleName());
}
header.append("(");
String sep = "";
ExecutableElement method = (ExecutableElement) el;
for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) {
VariableElement p = i.next();
header.append(sep);
header.append(p.asType());
sep = ", ";
}
header.append(")");
return header.toString();
default:
return el.toString();
}
}
private TypeElement topLevelType(Element el) {
if (el.getKind() == ElementKind.PACKAGE)
return null;
while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
el = el.getEnclosingElement();
}
return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null;
}
private void fillElementCache(JavacTask task, CompilationUnitTree cut) throws IOException {
Trees trees = Trees.instance(task);
new TreePathScanner<Void, Void>() {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitMethod(MethodTree node, Void p) {
handleDeclaration();
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitClass(ClassTree node, Void p) {
handleDeclaration();
return super.visitClass(node, p);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitVariable(VariableTree node, Void p) {
handleDeclaration();
return super.visitVariable(node, p);
}
private void handleDeclaration() {
Element currentElement = trees.getElement(getCurrentPath());
if (currentElement != null) {
signature2Source.put(elementSignature(currentElement), Pair.of(task, getCurrentPath()));
}
}
}.scan(cut, null);
}
private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException {
JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH,
binaryName,
JavaFileObject.Kind.SOURCE);
if (jfo == null)
return null;
List<JavaFileObject> jfos = Arrays.asList(jfo);
JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, baseFileManager, d -> {}, null, null, jfos);
Iterable<? extends CompilationUnitTree> cuts = task.parse();
task.enter();
return Pair.of(task, cuts.iterator().next());
}
@Override
public void close() throws IOException {
fm.close();
}
}
}

View File

@ -0,0 +1,29 @@
#
# Copyright (c) 2016, 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. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# 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.
#
CAP_TypeParameters=Type Parameters:
CAP_Parameters=Parameters:
CAP_Returns=Returns:
CAP_Thrown_Exceptions=Thrown Exceptions:

View File

@ -65,6 +65,9 @@ module jdk.compiler {
jdk.jdeps,
jdk.javadoc,
jdk.jshell;
exports jdk.internal.shellsupport.doc to
jdk.jshell,
jdk.scripting.nashorn.shell;
uses javax.annotation.processing.Processor;
uses com.sun.source.util.Plugin;

View File

@ -25,6 +25,7 @@
package jdk.internal.jshell.tool;
import jdk.jshell.SourceCodeAnalysis.Documentation;
import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
import jdk.jshell.SourceCodeAnalysis.Suggestion;
@ -34,27 +35,30 @@ import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.prefs.BackingStoreException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.shellsupport.doc.JavadocFormatter;
import jdk.internal.jline.NoInterruptUnixTerminal;
import jdk.internal.jline.Terminal;
import jdk.internal.jline.TerminalFactory;
import jdk.internal.jline.TerminalSupport;
import jdk.internal.jline.WindowsTerminal;
import jdk.internal.jline.console.ConsoleReader;
import jdk.internal.jline.console.CursorBuffer;
import jdk.internal.jline.console.KeyMap;
import jdk.internal.jline.console.UserInterruptException;
import jdk.internal.jline.console.completer.Completer;
@ -259,22 +263,118 @@ class ConsoleIOContext extends IOContext {
"\u001BO3P" //Alt-F1 (Linux)
};
private String lastDocumentationBuffer;
private int lastDocumentationCursor = (-1);
private void documentation(JShellTool repl) {
String buffer = in.getCursorBuffer().buffer.toString();
int cursor = in.getCursorBuffer().cursor;
String doc;
boolean firstInvocation = !buffer.equals(lastDocumentationBuffer) || cursor != lastDocumentationCursor;
lastDocumentationBuffer = buffer;
lastDocumentationCursor = cursor;
List<String> doc;
String seeMore;
Terminal term = in.getTerminal();
if (prefix.isEmpty() && buffer.trim().startsWith("/")) {
doc = repl.commandDocumentation(buffer, cursor);
doc = Arrays.asList(repl.commandDocumentation(buffer, cursor, firstInvocation));
seeMore = "jshell.console.see.help";
} else {
doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length());
JavadocFormatter formatter = new JavadocFormatter(term.getWidth(),
term.isAnsiSupported());
Function<Documentation, String> convertor;
if (firstInvocation) {
convertor = d -> d.signature();
} else {
convertor = d -> formatter.formatJavadoc(d.signature(),
d.javadoc() != null ? d.javadoc()
: repl.messageFormat("jshell.console.no.javadoc"));
}
doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length(), !firstInvocation)
.stream()
.map(convertor)
.collect(Collectors.toList());
seeMore = "jshell.console.see.javadoc";
}
try {
if (doc != null) {
in.println();
in.println(doc);
in.redrawLine();
in.flush();
if (doc != null && !doc.isEmpty()) {
if (firstInvocation) {
in.println();
in.println(doc.stream().collect(Collectors.joining("\n")));
in.println(repl.messageFormat(seeMore));
in.redrawLine();
in.flush();
} else {
in.println();
int height = term.getHeight();
String lastNote = "";
PRINT_DOC: for (Iterator<String> docIt = doc.iterator(); docIt.hasNext(); ) {
String currentDoc = docIt.next();
String[] lines = currentDoc.split("\n");
int firstLine = 0;
PRINT_PAGE: while (true) {
int toPrint = height - 1;
while (toPrint > 0 && firstLine < lines.length) {
in.println(lines[firstLine++]);
toPrint--;
}
if (firstLine >= lines.length) {
break;
}
lastNote = repl.getResourceString("jshell.console.see.next.page");
in.print(lastNote + ConsoleReader.RESET_LINE);
in.flush();
while (true) {
int r = in.readCharacter();
switch (r) {
case ' ': continue PRINT_PAGE;
case 'q':
case 3:
break PRINT_DOC;
default:
in.beep();
break;
}
}
}
if (docIt.hasNext()) {
lastNote = repl.getResourceString("jshell.console.see.next.javadoc");
in.print(lastNote + ConsoleReader.RESET_LINE);
in.flush();
while (true) {
int r = in.readCharacter();
switch (r) {
case ' ': continue PRINT_DOC;
case 'q':
case 3:
break PRINT_DOC;
default:
in.beep();
break;
}
}
}
}
//clear the "press space" line:
in.getCursorBuffer().buffer.replace(0, buffer.length(), lastNote);
in.getCursorBuffer().cursor = 0;
in.killLine();
in.getCursorBuffer().buffer.append(buffer);
in.getCursorBuffer().cursor = cursor;
in.redrawLine();
in.flush();
}
} else {
in.beep();
}

View File

@ -1308,7 +1308,7 @@ public class JShellTool implements MessageHandler {
return commandCompletions.completionSuggestions(code, cursor, anchor);
}
public String commandDocumentation(String code, int cursor) {
public String commandDocumentation(String code, int cursor, boolean shortDescription) {
code = code.substring(0, cursor);
int space = code.indexOf(' ');
@ -1316,7 +1316,7 @@ public class JShellTool implements MessageHandler {
String cmd = code.substring(0, space);
Command command = commands.get(cmd);
if (command != null) {
return getResourceString(command.helpKey + ".summary");
return getResourceString(command.helpKey + (shortDescription ? ".summary" : ""));
}
}

View File

@ -145,6 +145,11 @@ jshell.err.the.snippet.cannot.be.used.with.this.command = This command does not
jshell.err.retained.mode.failure = Failure in retained modes (modes cleared) -- {0} {1}
jshell.console.see.more = <press tab to see more>
jshell.console.see.javadoc = <press shift-tab again to see javadoc>
jshell.console.see.help = <press shift-tab again to see detailed help>
jshell.console.see.next.page = -- Press space for next page, Q to quit. --
jshell.console.see.next.javadoc = -- Press space for next javadoc, Q to quit. --
jshell.console.no.javadoc = <no javadoc found>
jshell.console.do.nothing = Do nothing
jshell.console.choice = Choice: \

View File

@ -506,6 +506,9 @@ public class JShell implements AutoCloseable {
if (!closed) {
closeDown();
executionControl().close();
if (sourceCodeAnalysis != null) {
sourceCodeAnalysis.close();
}
}
}

View File

@ -63,12 +63,16 @@ public abstract class SourceCodeAnalysis {
public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
/**
* Compute a description/help string for the given user's input.
* Compute documentation for the given user's input. Multiple {@code Documentation} objects may
* be returned when multiple elements match the user's input (like for overloaded methods).
* @param input the snippet the user wrote so far
* @param cursor the current position of the cursors in the given {@code input} text
* @return description/help string for the given user's input
* @param computeJavadoc true if the javadoc for the given input should be computed in
* addition to the signature
* @return the documentations for the given user's input, if multiple elements match the input,
* multiple {@code Documentation} objects are returned.
*/
public abstract String documentation(String input, int cursor);
public abstract List<Documentation> documentation(String input, int cursor, boolean computeJavadoc);
/**
* Infer the type of the given expression. The expression spans from the beginning of {@code code}
@ -265,6 +269,26 @@ public abstract class SourceCodeAnalysis {
boolean matchesType();
}
/**
* A documentation for a candidate for continuation of the given user's input.
*/
public interface Documentation {
/**
* The signature of the given element.
*
* @return the signature
*/
String signature();
/**
* The javadoc of the given element.
*
* @return the javadoc, or null if not found or not requested
*/
String javadoc();
}
/**
* List of possible qualified names.
*/

View File

@ -42,19 +42,17 @@ import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacScope;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import jdk.internal.shellsupport.doc.JavadocHelper;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.Pair;
@ -105,6 +103,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@ -123,15 +122,10 @@ import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
import static java.util.stream.Collectors.joining;
import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
import static jdk.jshell.TreeDissector.printType;
@ -151,6 +145,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
private final JShell proc;
private final CompletenessAnalyzer ca;
private final List<AutoCloseable> closeables = new ArrayList<>();
private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
private int indexVersion;
private int classpathVersion;
@ -1097,10 +1092,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
}
@Override
public String documentation(String code, int cursor) {
public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) {
suspendIndexing();
try {
return documentationImpl(code, cursor);
return documentationImpl(code, cursor, computeJavadoc);
} finally {
resumeIndexing();
}
@ -1112,14 +1107,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
"-parameters"
};
private String documentationImpl(String code, int cursor) {
private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
code = code.substring(0, cursor);
if (code.trim().isEmpty()) { //TODO: comment handling
code += ";";
}
if (guessKind(code) == Kind.IMPORT)
return null;
return Collections.<Documentation>emptyList();
OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
@ -1128,46 +1123,120 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
if (tp == null)
return null;
return Collections.<Documentation>emptyList();
TreePath prevPath = null;
while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) {
while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION &&
tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER &&
tp.getLeaf().getKind() != Kind.MEMBER_SELECT) {
prevPath = tp;
tp = tp.getParentPath();
}
if (tp == null)
return null;
return Collections.<Documentation>emptyList();
Stream<Element> elements;
Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
List<? extends ExpressionTree> arguments;
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
candidates = methodCandidates(at, tp);
arguments = mit.getArguments();
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
candidates = methodCandidates(at, tp);
arguments = mit.getArguments();
} else {
NewClassTree nct = (NewClassTree) tp.getLeaf();
candidates = newClassCandidates(at, tp);
arguments = nct.getArguments();
}
if (!isEmptyArgumentsContext(arguments)) {
List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
candidates =
this.filterExecutableTypesByArguments(at, candidates, fullActuals)
.stream()
.filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
.collect(Collectors.toList());
}
elements = Util.stream(candidates).map(method -> method.fst);
} else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
Element el = at.trees().getElement(tp);
if (el == null ||
el.asType().getKind() == TypeKind.ERROR ||
(el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
//erroneous element:
return Collections.<Documentation>emptyList();
}
elements = Stream.of(el);
} else {
NewClassTree nct = (NewClassTree) tp.getLeaf();
candidates = newClassCandidates(at, tp);
arguments = nct.getArguments();
return Collections.<Documentation>emptyList();
}
if (!isEmptyArgumentsContext(arguments)) {
List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
List<Documentation> result = Collections.emptyList();
candidates =
this.filterExecutableTypesByArguments(at, candidates, fullActuals)
.stream()
.filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
.collect(Collectors.toList());
try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
.filter(r -> r != null)
.collect(Collectors.toList());
} catch (IOException ex) {
proc.debug(ex, "JavadocHelper.close()");
}
try (SourceCache sourceCache = new SourceCache(at)) {
return Util.stream(candidates)
.map(method -> Util.expunge(element2String(sourceCache, method.fst)))
.collect(joining("\n"));
return result;
}
private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) {
String javadoc = null;
try {
if (hasSyntheticParameterNames(el)) {
el = helper.getSourceElement(el);
}
if (computeJavadoc) {
javadoc = helper.getResolvedDocComment(el);
}
} catch (IOException ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
}
String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true));
return new DocumentationImpl(signature, javadoc);
}
public void close() {
for (AutoCloseable closeable : closeables) {
try {
closeable.close();
} catch (Exception ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.close()");
}
}
}
private static final class DocumentationImpl implements Documentation {
private final String signature;
private final String javadoc;
public DocumentationImpl(String signature, String javadoc) {
this.signature = signature;
this.javadoc = javadoc;
}
@Override
public String signature() {
return signature;
}
@Override
public String javadoc() {
return javadoc;
}
}
private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
@ -1178,18 +1247,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
return false;
}
private String element2String(SourceCache sourceCache, Element el) {
try {
if (hasSyntheticParameterNames(el)) {
el = sourceCache.getSourceMethod(el);
}
} catch (IOException ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
}
return Util.expunge(elementHeader(sourceCache.originalTask, el, !hasSyntheticParameterNames(el)));
}
private boolean hasSyntheticParameterNames(Element el) {
if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
return false;
@ -1204,119 +1261,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
.allMatch(param -> param.getSimpleName().toString().startsWith("arg"));
}
private final class SourceCache implements AutoCloseable {
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private final Map<String, Map<String, Element>> topLevelName2Signature2Method = new HashMap<>();
private final AnalyzeTask originalTask;
private final StandardJavaFileManager fm;
public SourceCache(AnalyzeTask originalTask) {
this.originalTask = originalTask;
List<Path> sources = findSources();
if (sources.iterator().hasNext()) {
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
try {
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sources);
} catch (IOException ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.<init>(...)");
try {
fm.close();
} catch (IOException closeEx) {
proc.debug(closeEx, "SourceCodeAnalysisImpl.SourceCache.close()");
}
fm = null;
}
this.fm = fm;
} else {
//don't waste time if there are no sources
this.fm = null;
}
}
public Element getSourceMethod(Element method) throws IOException {
if (fm == null)
return method;
TypeElement type = topLevelType(method);
if (type == null)
return method;
String binaryName = originalTask.task.getElements().getBinaryName(type).toString();
Map<String, Element> cache = topLevelName2Signature2Method.get(binaryName);
if (cache == null) {
topLevelName2Signature2Method.put(binaryName, cache = createMethodCache(binaryName));
}
String handle = elementHeader(originalTask, method, false);
return cache.getOrDefault(handle, method);
}
private TypeElement topLevelType(Element el) {
while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
el = el.getEnclosingElement();
}
return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null;
}
private Map<String, Element> createMethodCache(String binaryName) throws IOException {
Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName);
if (source == null)
return Collections.emptyMap();
Map<String, Element> signature2Method = new HashMap<>();
Trees trees = Trees.instance(source.fst);
new TreePathScanner<Void, Void>() {
@Override
public Void visitMethod(MethodTree node, Void p) {
Element currentMethod = trees.getElement(getCurrentPath());
if (currentMethod != null) {
signature2Method.put(elementHeader(originalTask, currentMethod, false), currentMethod);
}
return null;
}
}.scan(source.snd, null);
return signature2Method;
}
private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException {
JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH,
binaryName,
JavaFileObject.Kind.SOURCE);
if (jfo == null)
return null;
List<JavaFileObject> jfos = Arrays.asList(jfo);
JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, d -> {}, null, null, jfos);
Iterable<? extends CompilationUnitTree> cuts = task.parse();
task.enter();
return Pair.of(task, cuts.iterator().next());
}
@Override
public void close() {
try {
if (fm != null) {
fm.close();
}
} catch (IOException ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.close()");
}
}
}
private List<Path> availableSources;
private List<Path> findSources() {
@ -1328,25 +1272,59 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
Path srcZip = home.resolve("src.zip");
if (!Files.isReadable(srcZip))
srcZip = home.getParent().resolve("src.zip");
if (Files.isReadable(srcZip))
result.add(srcZip);
if (Files.isReadable(srcZip)) {
boolean keepOpen = false;
FileSystem zipFO = null;
try {
URI uri = URI.create("jar:" + srcZip.toUri());
zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap());
Path root = zipFO.getRootDirectories().iterator().next();
if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
//non-modular format:
result.add(srcZip);
} else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
//modular format:
try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
for (Path p : ds) {
if (Files.isDirectory(p)) {
result.add(p);
}
}
}
keepOpen = true;
}
} catch (IOException ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
} finally {
if (zipFO != null) {
if (keepOpen) {
closeables.add(zipFO);
} else {
try {
zipFO.close();
} catch (IOException ex) {
proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
}
}
}
}
}
return availableSources = result;
}
private String elementHeader(AnalyzeTask at, Element el) {
return elementHeader(at, el, true);
}
private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames) {
private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) {
switch (el.getKind()) {
case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
TypeElement type = (TypeElement)el;
String fullname = type.getQualifiedName().toString();
Element pkg = at.getElements().getPackageOf(el);
String name = pkg == null ? fullname :
String name = pkg == null || useFQN ? fullname :
proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString());
return name + typeParametersOpt(at, type.getTypeParameters());
return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames);
}
case TYPE_PARAMETER: {
TypeParameterElement tp = (TypeParameterElement)el;
@ -1363,9 +1341,9 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
.collect(joining(" & "));
}
case FIELD:
return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName() + ":" + el.asType();
case ENUM_CONSTANT:
return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName();
return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName();
case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
return el.getSimpleName() + ":" + el.asType();
case CONSTRUCTOR: case METHOD: {
@ -1379,20 +1357,20 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
header.append(printType(at, proc, method.getReturnType())).append(" ");
} else {
// type parameters for the constructor
String typeParameters = typeParametersOpt(at, method.getTypeParameters());
String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames);
if (!typeParameters.isEmpty()) {
header.append(typeParameters).append(" ");
}
}
// receiver type
String clazz = elementHeader(at, el.getEnclosingElement());
String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false);
header.append(clazz);
if (isMethod) {
//method name with type parameters
(clazz.isEmpty() ? header : header.append("."))
.append(typeParametersOpt(at, method.getTypeParameters()))
.append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames))
.append(el.getSimpleName());
}
@ -1435,10 +1413,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
}
return arrayType;
}
private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters) {
private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters, boolean includeParameterNames) {
return typeParameters.isEmpty() ? ""
: typeParameters.stream()
.map(tp -> elementHeader(at, tp))
.map(tp -> elementHeader(at, tp, includeParameterNames, false))
.collect(joining(", ", "<", ">"));
}

View File

@ -0,0 +1,393 @@
/*
* Copyright (c) 2015, 2016, 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 8131019
* @summary Test JavadocFormatter
* @library /tools/lib
* @modules jdk.compiler/jdk.internal.shellsupport.doc
* @run testng JavadocFormatterTest
*/
import java.util.Objects;
import jdk.internal.shellsupport.doc.JavadocFormatter;
import org.testng.annotations.Test;
@Test
public class JavadocFormatterTest {
private static final String CODE_RESET = "\033[0m";
private static final String CODE_HIGHLIGHT = "\033[1m";
private static final String CODE_UNDERLINE = "\033[4m";
public void testReflow() {
String actual;
String expected;
actual = new JavadocFormatter(25, true).formatJavadoc(
"test",
"1234 1234\n1234\n1234 12345 123456789012345678901234567890 1234 1234\n1234 {@code 1234} 1234 1234\n1234 1234 123456 123456\n<b>123456</b>\n123456 123456 {@link String string} 1");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"1234 1234 1234 1234 12345\n" +
"123456789012345678901234567890\n" +
"1234 1234 1234 1234 1234\n" +
"1234 1234 1234 123456\n" +
"123456 123456 123456\n" +
"123456 string 1\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"@param <T> 51234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"@param <E> 61234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"@param shortName 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
"@param aVeryLongName1234567890123456789012345678901234567890 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"\n" +
CODE_UNDERLINE + "Type Parameters:" + CODE_RESET + "\n" +
"T - 51234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"E - 61234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"\n" +
CODE_UNDERLINE + "Parameters:" + CODE_RESET + "\n" +
"shortName - 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234\n" +
"aVeryLongName1234567890123456789012345678901234567890 - \n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"@throws ShortExcp 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
"@throws aVeryLongException1234567890123456789012345678901234567890 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"\n" +
CODE_UNDERLINE + "Thrown Exceptions:" + CODE_RESET + "\n" +
"ShortExcp - 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234\n" +
"aVeryLongException1234567890123456789012345678901234567890 - \n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"@return 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"\n" +
CODE_UNDERLINE + "Returns:" + CODE_RESET + "\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//handling of <p>, <pre>:
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 <p>1234 1234 <p>1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 <p>1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
"<blockquote><pre>\n" +
"for (String data : content) {\n" +
" System.err.println(data);\n" +
"}\n" +
"</pre></blockquote>\n");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234\n" +
"1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234\n" +
" for (String data : content) {\n" +
" System.err.println(data);\n" +
" }\n" +
" \n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//list handling:
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"<ul>" +
" <li>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</li>" +
" <li>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" <li>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234<ol>" +
" <li>D 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</li>" +
" <li>E 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234<ul>" +
" <li>F 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234<ol>" +
" <li>G 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" </ol>" +
" </ul>" +
" </OL>" +
" <LI><p>H 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 <p>1234 1234 1234 1234 1234 1234 1234<ul>" +
" <li>I 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" </ul>" +
"</ul> followup" +
"<dl>" +
"<dt>Term1</dt>" +
"<dd>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</dd>" +
"<dt>Term2" +
"<dd>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
"<dt>Term3" +
"<dd>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
"</dl>" +
"<dl>" +
"<dt>TermUnfinished" +
"</dl> followup");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
" * A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234\n" +
" * B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234\n" +
" * C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234\n" +
" 1. D 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234\n" +
" 2. E 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234\n" +
" * F 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234\n" +
" 1. G 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" * H 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234\n" +
" * I 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234\n" +
"followup\n" +
CODE_HIGHLIGHT + "Term1" + CODE_RESET + "\n" +
" A 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234\n" +
CODE_HIGHLIGHT + "Term2" + CODE_RESET + "\n" +
" B 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234\n" +
CODE_HIGHLIGHT + "Term3" + CODE_RESET + "\n" +
" C 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234\n" +
CODE_HIGHLIGHT + "TermUnfinished" + CODE_RESET + "\n" +
"followup\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//sections:
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"text 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"<h3>1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</h3>" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"text 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"\n" +
CODE_UNDERLINE + "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234" + CODE_RESET + "\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//table:
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"<table>" +
"<tr>" +
"<th>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</th>" +
"<th>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</th>" +
"<th>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</th>" +
"</tr>" +
"<tr>" +
"<td>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td> \n" +
"<td>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
"<td>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
"<tr>" +
"<td>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
"<td>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
"<td>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
"</tr>" +
"<tr>" +
"<td>1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
"<td>1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
"</table>");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"----------------------------------------------------------------\n" +
"| " + CODE_HIGHLIGHT + "A 1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "B 1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "C 1234 1234 1234" + CODE_RESET + " |\n" +
"| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " |\n" +
"| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " |\n" +
"| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " |\n" +
"| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + " |\n" +
"| " + CODE_HIGHLIGHT + "1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234" + CODE_RESET + " | " + CODE_HIGHLIGHT + "1234 1234" + CODE_RESET + " |\n" +
"----------------------------------------------------------------\n" +
"| A 1234 1234 1234 | B 1234 1234 1234 | C 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 | 1234 1234 | 1234 1234 |\n" +
"----------------------------------------------------------------\n" +
"| A 1234 1234 1234 | B 1234 1234 1234 | C 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 | 1234 1234 | 1234 1234 |\n" +
"----------------------------------------------------------------\n" +
"| 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 1234 | 1234 1234 1234 |\n" +
"| 1234 1234 | 1234 1234 |\n" +
"-------------------------------------------\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//no escape sequences:
actual = new JavadocFormatter(66, false).formatJavadoc("test",
"@param shortName 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
"@param aVeryLongName1234567890123456789012345678901234567890 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
expected = "test\n" +
"\n" +
"Parameters:\n" +
"shortName - 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234\n" +
"aVeryLongName1234567890123456789012345678901234567890 - \n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
" 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//null javadoc:
actual = new JavadocFormatter(66, true).formatJavadoc("test", null);
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//stray tags:
for (String tag : new String[] {"li", "ol", "h3", "table", "tr", "td", "dl", "dt", "dd"}) {
for (boolean closing : new boolean[] {false, true}) {
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"<" + (closing ? "/" : "") + tag + ">text");
if (!actual.contains("text")) {
throw new AssertionError("Incorrect output: " + actual);
}
}
}
//entities:
actual = new JavadocFormatter(66, false).formatJavadoc("test",
"&alpha; &lt; &#65; &#X42; &gt; &broken; &#xFFFFFFFF; &#xFFFFFFF;\n");
expected = "test\n" +
"\u03b1 < A B > &broken; &#xFFFFFFFF; &#xFFFFFFF;\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
//img:
actual = new JavadocFormatter(66, true).formatJavadoc("test",
"1234 <img src='any.png' alt='text'/> 1234");
expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
"1234 text 1234\n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);
}
}
}

View File

@ -0,0 +1,296 @@
/*
* Copyright (c) 2015, 2016, 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 8131019
* @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
*/
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
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.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import com.sun.source.util.JavacTask;
import jdk.internal.shellsupport.doc.JavadocHelper;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@Test
public class JavadocHelperTest {
public void testJavadoc() throws Exception {
doTestJavadoc("",
t -> t.getElements().getTypeElement("test.Super"),
"Top level. ");
doTestJavadoc("",
t -> getFirstMethod(t, "test.Super"),
" 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");
}
private Element getFirstMethod(JavacTask task, String typeName) {
return ElementFilter.methodsIn(task.getElements().getTypeElement(typeName).getEnclosedElements()).get(0);
}
private Function<JavacTask, Element> getSubTest = t -> getFirstMethod(t, "test.Sub");
public void testInheritNoJavadoc() throws Exception {
doTestJavadoc("",
getSubTest,
" 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");
}
public void testInheritFull() throws Exception {
doTestJavadoc(" /**\n" +
" * Prefix {@inheritDoc} suffix.\n" +
" *\n" +
" * @param p1 prefix {@inheritDoc} suffix\n" +
" * @param p2 prefix {@inheritDoc} suffix\n" +
" * @param p3 prefix {@inheritDoc} suffix\n" +
" * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
" * @return prefix {@inheritDoc} suffix\n" +
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
"\n" +
" @param p1 prefix param1 suffix\n" +
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
" @return prefix value suffix\n");
}
public void testInheritMissingParam() throws Exception {
doTestJavadoc(" /**\n" +
" * Prefix {@inheritDoc} suffix.\n" +
" *\n" +
" * @param p1 prefix {@inheritDoc} suffix\n" +
" * @param p3 prefix {@inheritDoc} suffix\n" +
" * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
" * @return prefix {@inheritDoc} suffix\n" +
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
"\n" +
" @param p1 prefix param1 suffix\n" +
"@param p2 param2\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
" @return prefix value suffix\n");
}
public void testInheritMissingFirstParam() throws Exception {
doTestJavadoc(" /**\n" +
" * Prefix {@inheritDoc} suffix.\n" +
" *\n" +
" * @param p2 prefix {@inheritDoc} suffix\n" +
" * @param p3 prefix {@inheritDoc} suffix\n" +
" * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
" * @return prefix {@inheritDoc} suffix\n" +
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
"@param p1 param1\n" +
"\n" +
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
" @return prefix value suffix\n");
}
public void testInheritMissingThrows() throws Exception {
doTestJavadoc(" /**\n" +
" * Prefix {@inheritDoc} suffix.\n" +
" *\n" +
" * @param p1 prefix {@inheritDoc} suffix\n" +
" * @param p2 prefix {@inheritDoc} suffix\n" +
" * @param p3 prefix {@inheritDoc} suffix\n" +
" * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
" * @return prefix {@inheritDoc} suffix\n" +
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
"\n" +
" @param p1 prefix param1 suffix\n" +
" @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 IllegalAccessException prefix exc3 suffix\n" +
" @return prefix value suffix\n");
}
public void testInheritMissingReturn() throws Exception {
doTestJavadoc(" /**\n" +
" * Prefix {@inheritDoc} suffix.\n" +
" *\n" +
" * @param p1 prefix {@inheritDoc} suffix\n" +
" * @param p2 prefix {@inheritDoc} suffix\n" +
" * @param p3 prefix {@inheritDoc} suffix\n" +
" * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
" * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
"\n" +
" @param p1 prefix param1 suffix\n" +
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
"@return value\n");
}
private void doTestJavadoc(String origJavadoc, Function<JavacTask, Element> getElement, String expectedJavadoc) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String subClass =
"package test;\n" +
"public class Sub extends Super {\n" +
origJavadoc +
" public String test(int p1, int p2, int p3) throws IllegalStateException, IllegalArgumentException, IllegalAccessException { return null;} \n" +
"}\n";
String superClass =
"package test;\n" +
"/**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" +
" public String test(int p1, int p2, int p3) throws IllegalStateException, IllegalArgumentException, IllegalAccessException { return null;} \n" +
"}\n";
Path srcZip = Paths.get("src.zip");
try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
out.putNextEntry(new JarEntry("test/Sub.java"));
out.write(subClass.getBytes());
out.putNextEntry(new JarEntry("test/Super.java"));
out.write(superClass.getBytes());
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
DiagnosticListener<? super JavaFileObject> noErrors = d -> {
if (d.getKind() == Kind.ERROR) {
throw new AssertionError(d.getMessage(null));
}
};
assertTrue(compiler.getTask(null, null, noErrors, Arrays.asList("-d", "."), null, Arrays.asList(new JFOImpl("Super", superClass), new JFOImpl("Sub", subClass))).call());
try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
fm.setLocationFromPaths(StandardLocation.CLASS_PATH, Arrays.asList(Paths.get(".").toAbsolutePath()));
JavacTask task = (JavacTask) compiler.getTask(null, fm, noErrors, null, null, null);
Element el = getElement.apply(task);
try (JavadocHelper helper = JavadocHelper.create(task, Arrays.asList(srcZip))) {
String javadoc = helper.getResolvedDocComment(el);
assertEquals(javadoc, expectedJavadoc);
}
}
}
private static final class JFOImpl extends SimpleJavaFileObject {
private final String code;
public JFOImpl(String name, String code) throws URISyntaxException {
super(new URI("mem:///" + name + ".java"), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return code;
}
}
}

View File

@ -23,12 +23,12 @@
/*
* @test
* @bug 8131025 8141092 8153761 8145263
* @bug 8131025 8141092 8153761 8145263 8131019
* @summary Test Completion and Documentation
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.jdeps/com.sun.tools.javap
* @library /tools/lib
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
* @build KullaTesting TestingInputStream Compiler
* @run testng CompletionSuggestionTest
@ -305,26 +305,26 @@ public class CompletionSuggestionTest extends KullaTesting {
public void testDocumentation() throws Exception {
dontReadParameterNamesFromClassFile();
assertDocumentation("System.getProperty(|",
assertSignature("System.getProperty(|",
"String System.getProperty(String key)",
"String System.getProperty(String key, String def)");
assertEval("char[] chars = null;");
assertDocumentation("new String(chars, |",
assertSignature("new String(chars, |",
"String(char[], int, int)");
assertDocumentation("String.format(|",
assertSignature("String.format(|",
"String String.format(String, Object...)",
"String String.format(java.util.Locale, String, Object...)");
assertDocumentation("\"\".getBytes(\"\"|", "void String.getBytes(int, int, byte[], int)",
assertSignature("\"\".getBytes(\"\"|", "void String.getBytes(int, int, byte[], int)",
"byte[] String.getBytes(String) throws java.io.UnsupportedEncodingException",
"byte[] String.getBytes(java.nio.charset.Charset)");
assertDocumentation("\"\".getBytes(\"\" |", "void String.getBytes(int, int, byte[], int)",
assertSignature("\"\".getBytes(\"\" |", "void String.getBytes(int, int, byte[], int)",
"byte[] String.getBytes(String) throws java.io.UnsupportedEncodingException",
"byte[] String.getBytes(java.nio.charset.Charset)");
}
public void testMethodsWithNoArguments() throws Exception {
dontReadParameterNamesFromClassFile();
assertDocumentation("System.out.println(|",
assertSignature("System.out.println(|",
"void java.io.PrintStream.println()",
"void java.io.PrintStream.println(boolean)",
"void java.io.PrintStream.println(char)",
@ -339,6 +339,7 @@ public class CompletionSuggestionTest extends KullaTesting {
public void testErroneous() {
assertCompletion("Undefined.|");
assertSignature("does.not.exist|");
}
public void testClinit() {
@ -474,59 +475,63 @@ public class CompletionSuggestionTest extends KullaTesting {
public void testDocumentationOfUserDefinedMethods() {
assertEval("void f() {}");
assertDocumentation("f(|", "void f()");
assertSignature("f(|", "void f()");
assertEval("void f(int i) {}");
assertDocumentation("f(|", "void f()", "void f(int i)");
assertSignature("f(|", "void f()", "void f(int i)");
assertEval("<T> void f(T... ts) {}", DiagCheck.DIAG_WARNING, DiagCheck.DIAG_OK);
assertDocumentation("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)");
assertSignature("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)");
assertEval("class A {}");
assertEval("void f(A a) {}");
assertDocumentation("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)", "void f(A a)");
assertSignature("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)", "void f(A a)");
}
public void testClass() {
assertSignature("String|", "java.lang.String");
}
public void testDocumentationOfUserDefinedConstructors() {
Snippet a = classKey(assertEval("class A {}"));
assertDocumentation("new A(|", "A()");
assertSignature("new A(|", "A()");
Snippet a2 = classKey(assertEval("class A { A() {} A(int i) {}}",
ste(MAIN_SNIPPET, VALID, VALID, true, null),
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
assertDocumentation("new A(|", "A()", "A(int i)");
assertSignature("new A(|", "A()", "A(int i)");
assertEval("class A<T> { A(T a) {} A(int i) {} <U> A(T t, U u) {}}",
ste(MAIN_SNIPPET, VALID, VALID, true, null),
ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
assertDocumentation("new A(|", "A<T>(T a)", "A<T>(int i)", "<U> A<T>(T t, U u)");
assertSignature("new A(|", "A<T>(T a)", "A<T>(int i)", "<U> A<T>(T t, U u)");
}
public void testDocumentationOfOverriddenMethods() throws Exception {
dontReadParameterNamesFromClassFile();
assertDocumentation("\"\".wait(|",
assertSignature("\"\".wait(|",
"void Object.wait(long) throws InterruptedException",
"void Object.wait(long, int) throws InterruptedException",
"void Object.wait() throws InterruptedException");
assertEval("class Base {void method() {}}");
Snippet e = classKey(assertEval("class Extend extends Base {}"));
assertDocumentation("new Extend().method(|", "void Base.method()");
assertSignature("new Extend().method(|", "void Base.method()");
assertEval("class Extend extends Base {void method() {}}",
ste(MAIN_SNIPPET, VALID, VALID, true, null),
ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
assertDocumentation("new Extend().method(|", "void Extend.method()");
assertSignature("new Extend().method(|", "void Extend.method()");
}
public void testDocumentationOfInvisibleMethods() {
assertDocumentation("Object.wait(|", "");
assertDocumentation("\"\".indexOfSupplementary(|", "");
assertSignature("Object.wait(|");
assertSignature("\"\".indexOfSupplementary(|");
Snippet a = classKey(assertEval("class A {void method() {}}"));
assertDocumentation("A.method(|", "");
assertSignature("A.method(|");
assertEval("class A {private void method() {}}",
ste(MAIN_SNIPPET, VALID, VALID, true, null),
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
assertDocumentation("new A().method(|", "");
assertSignature("new A().method(|");
}
public void testDocumentationOfInvisibleConstructors() {
assertDocumentation("new Compiler(|", "");
assertSignature("new Compiler(|");
assertEval("class A { private A() {} }");
assertDocumentation("new A(|", "");
assertSignature("new A(|");
}
public void testDocumentationWithBoxing() {
@ -535,13 +540,13 @@ public class CompletionSuggestionTest extends KullaTesting {
assertEval("Object object = null;");
assertEval("void method(int n, Object o) { }");
assertEval("void method(Object n, int o) { }");
assertDocumentation("method(primitive,|",
assertSignature("method(primitive,|",
"void method(int n, Object o)",
"void method(Object n, int o)");
assertDocumentation("method(boxed,|",
assertSignature("method(boxed,|",
"void method(int n, Object o)",
"void method(Object n, int o)");
assertDocumentation("method(object,|",
assertSignature("method(object,|",
"void method(Object n, int o)");
}
@ -567,7 +572,7 @@ public class CompletionSuggestionTest extends KullaTesting {
void assertDoc(String generics, String expectedGenerics) {
assertEval(evalFormatter.apply(generics, count));
assertDocumentation(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
assertSignature(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
count++;
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2015, 2016, 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 8131019
* @summary Test Javadoc
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.jshell
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
* @build KullaTesting TestingInputStream Compiler
* @run testng JavadocTest
*/
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import org.testng.annotations.Test;
@Test
public class JavadocTest extends KullaTesting {
private final Compiler compiler = new Compiler();
public void testJavadoc() {
prepareZip();
assertJavadoc("test.Clazz|", "test.Clazz\n" +
"Top level. ");
assertEval("test.Clazz clz = null;");
assertJavadoc("clz.test(|", "String test.Clazz.test(int p) throws IllegalStateException\n" +
" javadoc1A\n" +
"\n" +
" @param p param\n" +
" @throws IllegalStateException exc\n" +
" @return value\n");
//undefined handling:
assertJavadoc("clz.undef|");
}
private void prepareZip() {
String clazz =
"package test;\n" +
"/**Top level." +
" */\n" +
"public class Clazz {\n" +
" /**\n" +
" * javadoc1A\n" +
" *\n" +
" * @param p param\n" +
" * @throws IllegalStateException exc\n" +
" * @return value\n" +
" */\n" +
" public String test(int p) throws IllegalStateException { return null;}\n" +
"}\n";
Path srcZip = Paths.get("src.zip");
try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
out.putNextEntry(new JarEntry("test/Clazz.java"));
out.write(clazz.getBytes());
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
compiler.compile(clazz);
try {
Field availableSources = getAnalysis().getClass().getDeclaredField("availableSources");
availableSources.setAccessible(true);
availableSources.set(getAnalysis(), Arrays.asList(srcZip));
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
addToClasspath(compiler.getClassDir());
}
}

View File

@ -72,11 +72,14 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import jdk.jshell.Diag;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static jdk.jshell.Snippet.Status.*;
import static org.testng.Assert.*;
import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
import jdk.jshell.SourceCodeAnalysis.Documentation;
public class KullaTesting {
@ -946,12 +949,24 @@ public class KullaTesting {
}
}
public void assertDocumentation(String code, String... expected) {
public void assertSignature(String code, String... expected) {
int cursor = code.indexOf('|');
code = code.replace("|", "");
assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
String documentation = getAnalysis().documentation(code, cursor);
Set<String> docSet = Stream.of(documentation.split("\r?\n")).collect(Collectors.toSet());
List<Documentation> documentation = getAnalysis().documentation(code, cursor, false);
Set<String> docSet = documentation.stream().map(doc -> doc.signature()).collect(Collectors.toSet());
Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
assertEquals(docSet, expectedSet, "Input: " + code);
}
public void assertJavadoc(String code, String... expected) {
int cursor = code.indexOf('|');
code = code.replace("|", "");
assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
List<Documentation> documentation = getAnalysis().documentation(code, cursor, true);
Set<String> docSet = documentation.stream()
.map(doc -> doc.signature() + "\n" + doc.javadoc())
.collect(Collectors.toSet());
Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
assertEquals(docSet, expectedSet, "Input: " + code);
}