8131019: jshell tool: access javadoc from tool
Summary: 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
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Entity.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Entity.java Wed Nov 02 07:38:37 2016 +0100
@@ -297,17 +297,21 @@
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);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java Wed Nov 02 07:38:37 2016 +0100
@@ -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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java Wed Nov 02 07:38:37 2016 +0100
@@ -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();
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties Wed Nov 02 07:38:37 2016 +0100
@@ -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:
--- a/langtools/src/jdk.compiler/share/classes/module-info.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.compiler/share/classes/module-info.java Wed Nov 02 07:38:37 2016 +0100
@@ -65,6 +65,9 @@
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;
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java Wed Nov 02 07:38:37 2016 +0100
@@ -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.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 @@
"\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();
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Wed Nov 02 07:38:37 2016 +0100
@@ -1308,7 +1308,7 @@
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 @@
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" : ""));
}
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Wed Nov 02 07:38:37 2016 +0100
@@ -145,6 +145,11 @@
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: \
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Wed Nov 02 07:38:37 2016 +0100
@@ -506,6 +506,9 @@
if (!closed) {
closeDown();
executionControl().close();
+ if (sourceCodeAnalysis != null) {
+ sourceCodeAnalysis.close();
+ }
}
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java Wed Nov 02 07:38:37 2016 +0100
@@ -63,12 +63,16 @@
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}
@@ -266,6 +270,26 @@
}
/**
+ * 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.
*/
public static final class QualifiedNames {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java Wed Nov 02 07:38:37 2016 +0100
@@ -42,19 +42,17 @@
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.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.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 @@
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 @@
}
@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 @@
"-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 @@
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();
+ }
+
+ List<Documentation> result = Collections.emptyList();
+
+ 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()");
}
- if (!isEmptyArgumentsContext(arguments)) {
- List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
- List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
+ 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);
+ }
- candidates =
- this.filterExecutableTypesByArguments(at, candidates, fullActuals)
- .stream()
- .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
- .collect(Collectors.toList());
+ 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;
}
- try (SourceCache sourceCache = new SourceCache(at)) {
- return Util.stream(candidates)
- .map(method -> Util.expunge(element2String(sourceCache, method.fst)))
- .collect(joining("\n"));
+ @Override
+ public String signature() {
+ return signature;
}
+
+ @Override
+ public String javadoc() {
+ return javadoc;
+ }
+
}
private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
@@ -1178,18 +1247,6 @@
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 @@
.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 @@
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 @@
.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 @@
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 @@
}
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(", ", "<", ">"));
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/internal/shellsupport/doc/JavadocFormatterTest.java Wed Nov 02 07:38:37 2016 +0100
@@ -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",
+ "α < A B > &broken; � �\n");
+
+ expected = "test\n" +
+ "\u03b1 < A B > &broken; � �\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);
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/internal/shellsupport/doc/JavadocHelperTest.java Wed Nov 02 07:38:37 2016 +0100
@@ -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;
+ }
+
+ }
+}
--- a/langtools/test/jdk/jshell/CompletionSuggestionTest.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/test/jdk/jshell/CompletionSuggestionTest.java Wed Nov 02 07:38:37 2016 +0100
@@ -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 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 void testErroneous() {
assertCompletion("Undefined.|");
+ assertSignature("does.not.exist|");
}
public void testClinit() {
@@ -474,59 +475,63 @@
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 @@
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 @@
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++;
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/JavadocTest.java Wed Nov 02 07:38:37 2016 +0100
@@ -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());
+ }
+
+}
--- a/langtools/test/jdk/jshell/KullaTesting.java Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/test/jdk/jshell/KullaTesting.java Wed Nov 02 07:38:37 2016 +0100
@@ -72,11 +72,14 @@
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 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);
}