8131019: jshell tool: access javadoc from tool
Adding internal support to resolve {@inheritDoc} and format javadoc to plain text for use by jdk.jshell and jdk.scripting.nashorn.shell, enhancing Shift-<tab> documentation in JShell with ability to show javadoc. Reviewed-by: jjg, rfield
This commit is contained in:
parent
293d086bd9
commit
5d215e5425
@ -297,17 +297,21 @@ public enum Entity {
|
||||
rsaquo(8250),
|
||||
euro(8364);
|
||||
|
||||
int code;
|
||||
public final int code;
|
||||
|
||||
private Entity(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
static boolean isValid(String name) {
|
||||
public static boolean isValid(String name) {
|
||||
return names.containsKey(name);
|
||||
}
|
||||
|
||||
static boolean isValid(int code) {
|
||||
public static Entity get(String name) {
|
||||
return names.get(name);
|
||||
}
|
||||
|
||||
public static boolean isValid(int code) {
|
||||
// allow numeric codes for standard ANSI characters
|
||||
return codes.containsKey(code) || ( 32 <= code && code < 2127);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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:
|
@ -65,6 +65,9 @@ module jdk.compiler {
|
||||
jdk.jdeps,
|
||||
jdk.javadoc,
|
||||
jdk.jshell;
|
||||
exports jdk.internal.shellsupport.doc to
|
||||
jdk.jshell,
|
||||
jdk.scripting.nashorn.shell;
|
||||
|
||||
uses javax.annotation.processing.Processor;
|
||||
uses com.sun.source.util.Plugin;
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package jdk.internal.jshell.tool;
|
||||
|
||||
import jdk.jshell.SourceCodeAnalysis.Documentation;
|
||||
import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
|
||||
import jdk.jshell.SourceCodeAnalysis.Suggestion;
|
||||
|
||||
@ -34,27 +35,30 @@ import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.shellsupport.doc.JavadocFormatter;
|
||||
import jdk.internal.jline.NoInterruptUnixTerminal;
|
||||
import jdk.internal.jline.Terminal;
|
||||
import jdk.internal.jline.TerminalFactory;
|
||||
import jdk.internal.jline.TerminalSupport;
|
||||
import jdk.internal.jline.WindowsTerminal;
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
import jdk.internal.jline.console.CursorBuffer;
|
||||
import jdk.internal.jline.console.KeyMap;
|
||||
import jdk.internal.jline.console.UserInterruptException;
|
||||
import jdk.internal.jline.console.completer.Completer;
|
||||
@ -259,22 +263,118 @@ class ConsoleIOContext extends IOContext {
|
||||
"\u001BO3P" //Alt-F1 (Linux)
|
||||
};
|
||||
|
||||
private String lastDocumentationBuffer;
|
||||
private int lastDocumentationCursor = (-1);
|
||||
|
||||
private void documentation(JShellTool repl) {
|
||||
String buffer = in.getCursorBuffer().buffer.toString();
|
||||
int cursor = in.getCursorBuffer().cursor;
|
||||
String doc;
|
||||
boolean firstInvocation = !buffer.equals(lastDocumentationBuffer) || cursor != lastDocumentationCursor;
|
||||
lastDocumentationBuffer = buffer;
|
||||
lastDocumentationCursor = cursor;
|
||||
List<String> doc;
|
||||
String seeMore;
|
||||
Terminal term = in.getTerminal();
|
||||
if (prefix.isEmpty() && buffer.trim().startsWith("/")) {
|
||||
doc = repl.commandDocumentation(buffer, cursor);
|
||||
doc = Arrays.asList(repl.commandDocumentation(buffer, cursor, firstInvocation));
|
||||
seeMore = "jshell.console.see.help";
|
||||
} else {
|
||||
doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length());
|
||||
JavadocFormatter formatter = new JavadocFormatter(term.getWidth(),
|
||||
term.isAnsiSupported());
|
||||
Function<Documentation, String> convertor;
|
||||
if (firstInvocation) {
|
||||
convertor = d -> d.signature();
|
||||
} else {
|
||||
convertor = d -> formatter.formatJavadoc(d.signature(),
|
||||
d.javadoc() != null ? d.javadoc()
|
||||
: repl.messageFormat("jshell.console.no.javadoc"));
|
||||
}
|
||||
doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length(), !firstInvocation)
|
||||
.stream()
|
||||
.map(convertor)
|
||||
.collect(Collectors.toList());
|
||||
seeMore = "jshell.console.see.javadoc";
|
||||
}
|
||||
|
||||
try {
|
||||
if (doc != null) {
|
||||
in.println();
|
||||
in.println(doc);
|
||||
in.redrawLine();
|
||||
in.flush();
|
||||
if (doc != null && !doc.isEmpty()) {
|
||||
if (firstInvocation) {
|
||||
in.println();
|
||||
in.println(doc.stream().collect(Collectors.joining("\n")));
|
||||
in.println(repl.messageFormat(seeMore));
|
||||
in.redrawLine();
|
||||
in.flush();
|
||||
} else {
|
||||
in.println();
|
||||
|
||||
int height = term.getHeight();
|
||||
String lastNote = "";
|
||||
|
||||
PRINT_DOC: for (Iterator<String> docIt = doc.iterator(); docIt.hasNext(); ) {
|
||||
String currentDoc = docIt.next();
|
||||
String[] lines = currentDoc.split("\n");
|
||||
int firstLine = 0;
|
||||
|
||||
PRINT_PAGE: while (true) {
|
||||
int toPrint = height - 1;
|
||||
|
||||
while (toPrint > 0 && firstLine < lines.length) {
|
||||
in.println(lines[firstLine++]);
|
||||
toPrint--;
|
||||
}
|
||||
|
||||
if (firstLine >= lines.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
lastNote = repl.getResourceString("jshell.console.see.next.page");
|
||||
in.print(lastNote + ConsoleReader.RESET_LINE);
|
||||
in.flush();
|
||||
|
||||
while (true) {
|
||||
int r = in.readCharacter();
|
||||
|
||||
switch (r) {
|
||||
case ' ': continue PRINT_PAGE;
|
||||
case 'q':
|
||||
case 3:
|
||||
break PRINT_DOC;
|
||||
default:
|
||||
in.beep();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (docIt.hasNext()) {
|
||||
lastNote = repl.getResourceString("jshell.console.see.next.javadoc");
|
||||
in.print(lastNote + ConsoleReader.RESET_LINE);
|
||||
in.flush();
|
||||
|
||||
while (true) {
|
||||
int r = in.readCharacter();
|
||||
|
||||
switch (r) {
|
||||
case ' ': continue PRINT_DOC;
|
||||
case 'q':
|
||||
case 3:
|
||||
break PRINT_DOC;
|
||||
default:
|
||||
in.beep();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//clear the "press space" line:
|
||||
in.getCursorBuffer().buffer.replace(0, buffer.length(), lastNote);
|
||||
in.getCursorBuffer().cursor = 0;
|
||||
in.killLine();
|
||||
in.getCursorBuffer().buffer.append(buffer);
|
||||
in.getCursorBuffer().cursor = cursor;
|
||||
in.redrawLine();
|
||||
in.flush();
|
||||
}
|
||||
} else {
|
||||
in.beep();
|
||||
}
|
||||
|
@ -1308,7 +1308,7 @@ public class JShellTool implements MessageHandler {
|
||||
return commandCompletions.completionSuggestions(code, cursor, anchor);
|
||||
}
|
||||
|
||||
public String commandDocumentation(String code, int cursor) {
|
||||
public String commandDocumentation(String code, int cursor, boolean shortDescription) {
|
||||
code = code.substring(0, cursor);
|
||||
int space = code.indexOf(' ');
|
||||
|
||||
@ -1316,7 +1316,7 @@ public class JShellTool implements MessageHandler {
|
||||
String cmd = code.substring(0, space);
|
||||
Command command = commands.get(cmd);
|
||||
if (command != null) {
|
||||
return getResourceString(command.helpKey + ".summary");
|
||||
return getResourceString(command.helpKey + (shortDescription ? ".summary" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,11 @@ jshell.err.the.snippet.cannot.be.used.with.this.command = This command does not
|
||||
jshell.err.retained.mode.failure = Failure in retained modes (modes cleared) -- {0} {1}
|
||||
|
||||
jshell.console.see.more = <press tab to see more>
|
||||
jshell.console.see.javadoc = <press shift-tab again to see javadoc>
|
||||
jshell.console.see.help = <press shift-tab again to see detailed help>
|
||||
jshell.console.see.next.page = -- Press space for next page, Q to quit. --
|
||||
jshell.console.see.next.javadoc = -- Press space for next javadoc, Q to quit. --
|
||||
jshell.console.no.javadoc = <no javadoc found>
|
||||
jshell.console.do.nothing = Do nothing
|
||||
jshell.console.choice = Choice: \
|
||||
|
||||
|
@ -506,6 +506,9 @@ public class JShell implements AutoCloseable {
|
||||
if (!closed) {
|
||||
closeDown();
|
||||
executionControl().close();
|
||||
if (sourceCodeAnalysis != null) {
|
||||
sourceCodeAnalysis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,12 +63,16 @@ public abstract class SourceCodeAnalysis {
|
||||
public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
|
||||
|
||||
/**
|
||||
* Compute a description/help string for the given user's input.
|
||||
* Compute documentation for the given user's input. Multiple {@code Documentation} objects may
|
||||
* be returned when multiple elements match the user's input (like for overloaded methods).
|
||||
* @param input the snippet the user wrote so far
|
||||
* @param cursor the current position of the cursors in the given {@code input} text
|
||||
* @return description/help string for the given user's input
|
||||
* @param computeJavadoc true if the javadoc for the given input should be computed in
|
||||
* addition to the signature
|
||||
* @return the documentations for the given user's input, if multiple elements match the input,
|
||||
* multiple {@code Documentation} objects are returned.
|
||||
*/
|
||||
public abstract String documentation(String input, int cursor);
|
||||
public abstract List<Documentation> documentation(String input, int cursor, boolean computeJavadoc);
|
||||
|
||||
/**
|
||||
* Infer the type of the given expression. The expression spans from the beginning of {@code code}
|
||||
@ -265,6 +269,26 @@ public abstract class SourceCodeAnalysis {
|
||||
boolean matchesType();
|
||||
}
|
||||
|
||||
/**
|
||||
* A documentation for a candidate for continuation of the given user's input.
|
||||
*/
|
||||
public interface Documentation {
|
||||
|
||||
/**
|
||||
* The signature of the given element.
|
||||
*
|
||||
* @return the signature
|
||||
*/
|
||||
String signature();
|
||||
|
||||
/**
|
||||
* The javadoc of the given element.
|
||||
*
|
||||
* @return the javadoc, or null if not found or not requested
|
||||
*/
|
||||
String javadoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of possible qualified names.
|
||||
*/
|
||||
|
@ -42,19 +42,17 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.tree.TypeParameterTree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.api.JavacScope;
|
||||
import com.sun.tools.javac.api.JavacTaskImpl;
|
||||
import com.sun.tools.javac.code.Flags;
|
||||
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
import com.sun.tools.javac.code.Symtab;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.code.Type.ClassType;
|
||||
import jdk.internal.shellsupport.doc.JavadocHelper;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
import com.sun.tools.javac.util.Names;
|
||||
import com.sun.tools.javac.util.Pair;
|
||||
@ -105,6 +103,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import static java.util.stream.Collectors.collectingAndThen;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
@ -123,15 +122,10 @@ import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.StandardLocation;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
|
||||
import static jdk.jshell.TreeDissector.printType;
|
||||
|
||||
@ -151,6 +145,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
|
||||
private final JShell proc;
|
||||
private final CompletenessAnalyzer ca;
|
||||
private final List<AutoCloseable> closeables = new ArrayList<>();
|
||||
private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
|
||||
private int indexVersion;
|
||||
private int classpathVersion;
|
||||
@ -1097,10 +1092,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String documentation(String code, int cursor) {
|
||||
public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) {
|
||||
suspendIndexing();
|
||||
try {
|
||||
return documentationImpl(code, cursor);
|
||||
return documentationImpl(code, cursor, computeJavadoc);
|
||||
} finally {
|
||||
resumeIndexing();
|
||||
}
|
||||
@ -1112,14 +1107,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
"-parameters"
|
||||
};
|
||||
|
||||
private String documentationImpl(String code, int cursor) {
|
||||
private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
|
||||
code = code.substring(0, cursor);
|
||||
if (code.trim().isEmpty()) { //TODO: comment handling
|
||||
code += ";";
|
||||
}
|
||||
|
||||
if (guessKind(code) == Kind.IMPORT)
|
||||
return null;
|
||||
return Collections.<Documentation>emptyList();
|
||||
|
||||
OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
|
||||
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
|
||||
@ -1128,46 +1123,120 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
|
||||
|
||||
if (tp == null)
|
||||
return null;
|
||||
return Collections.<Documentation>emptyList();
|
||||
|
||||
TreePath prevPath = null;
|
||||
while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) {
|
||||
while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION &&
|
||||
tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER &&
|
||||
tp.getLeaf().getKind() != Kind.MEMBER_SELECT) {
|
||||
prevPath = tp;
|
||||
tp = tp.getParentPath();
|
||||
}
|
||||
|
||||
if (tp == null)
|
||||
return null;
|
||||
return Collections.<Documentation>emptyList();
|
||||
|
||||
Stream<Element> elements;
|
||||
Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
|
||||
List<? extends ExpressionTree> arguments;
|
||||
|
||||
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
|
||||
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
|
||||
candidates = methodCandidates(at, tp);
|
||||
arguments = mit.getArguments();
|
||||
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
|
||||
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
|
||||
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
|
||||
candidates = methodCandidates(at, tp);
|
||||
arguments = mit.getArguments();
|
||||
} else {
|
||||
NewClassTree nct = (NewClassTree) tp.getLeaf();
|
||||
candidates = newClassCandidates(at, tp);
|
||||
arguments = nct.getArguments();
|
||||
}
|
||||
|
||||
if (!isEmptyArgumentsContext(arguments)) {
|
||||
List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
|
||||
List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
|
||||
|
||||
candidates =
|
||||
this.filterExecutableTypesByArguments(at, candidates, fullActuals)
|
||||
.stream()
|
||||
.filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
elements = Util.stream(candidates).map(method -> method.fst);
|
||||
} else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
|
||||
Element el = at.trees().getElement(tp);
|
||||
|
||||
if (el == null ||
|
||||
el.asType().getKind() == TypeKind.ERROR ||
|
||||
(el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
|
||||
//erroneous element:
|
||||
return Collections.<Documentation>emptyList();
|
||||
}
|
||||
|
||||
elements = Stream.of(el);
|
||||
} else {
|
||||
NewClassTree nct = (NewClassTree) tp.getLeaf();
|
||||
candidates = newClassCandidates(at, tp);
|
||||
arguments = nct.getArguments();
|
||||
return Collections.<Documentation>emptyList();
|
||||
}
|
||||
|
||||
if (!isEmptyArgumentsContext(arguments)) {
|
||||
List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
|
||||
List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
|
||||
List<Documentation> result = Collections.emptyList();
|
||||
|
||||
candidates =
|
||||
this.filterExecutableTypesByArguments(at, candidates, fullActuals)
|
||||
.stream()
|
||||
.filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
|
||||
.collect(Collectors.toList());
|
||||
try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
|
||||
result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
|
||||
.filter(r -> r != null)
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "JavadocHelper.close()");
|
||||
}
|
||||
|
||||
try (SourceCache sourceCache = new SourceCache(at)) {
|
||||
return Util.stream(candidates)
|
||||
.map(method -> Util.expunge(element2String(sourceCache, method.fst)))
|
||||
.collect(joining("\n"));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) {
|
||||
String javadoc = null;
|
||||
try {
|
||||
if (hasSyntheticParameterNames(el)) {
|
||||
el = helper.getSourceElement(el);
|
||||
}
|
||||
if (computeJavadoc) {
|
||||
javadoc = helper.getResolvedDocComment(el);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
|
||||
}
|
||||
String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true));
|
||||
return new DocumentationImpl(signature, javadoc);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (AutoCloseable closeable : closeables) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.close()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DocumentationImpl implements Documentation {
|
||||
|
||||
private final String signature;
|
||||
private final String javadoc;
|
||||
|
||||
public DocumentationImpl(String signature, String javadoc) {
|
||||
this.signature = signature;
|
||||
this.javadoc = javadoc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String signature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String javadoc() {
|
||||
return javadoc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
|
||||
@ -1178,18 +1247,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
return false;
|
||||
}
|
||||
|
||||
private String element2String(SourceCache sourceCache, Element el) {
|
||||
try {
|
||||
if (hasSyntheticParameterNames(el)) {
|
||||
el = sourceCache.getSourceMethod(el);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
|
||||
}
|
||||
|
||||
return Util.expunge(elementHeader(sourceCache.originalTask, el, !hasSyntheticParameterNames(el)));
|
||||
}
|
||||
|
||||
private boolean hasSyntheticParameterNames(Element el) {
|
||||
if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
|
||||
return false;
|
||||
@ -1204,119 +1261,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
.allMatch(param -> param.getSimpleName().toString().startsWith("arg"));
|
||||
}
|
||||
|
||||
private final class SourceCache implements AutoCloseable {
|
||||
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
private final Map<String, Map<String, Element>> topLevelName2Signature2Method = new HashMap<>();
|
||||
private final AnalyzeTask originalTask;
|
||||
private final StandardJavaFileManager fm;
|
||||
|
||||
public SourceCache(AnalyzeTask originalTask) {
|
||||
this.originalTask = originalTask;
|
||||
List<Path> sources = findSources();
|
||||
if (sources.iterator().hasNext()) {
|
||||
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
|
||||
try {
|
||||
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sources);
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.<init>(...)");
|
||||
try {
|
||||
fm.close();
|
||||
} catch (IOException closeEx) {
|
||||
proc.debug(closeEx, "SourceCodeAnalysisImpl.SourceCache.close()");
|
||||
}
|
||||
fm = null;
|
||||
}
|
||||
this.fm = fm;
|
||||
} else {
|
||||
//don't waste time if there are no sources
|
||||
this.fm = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Element getSourceMethod(Element method) throws IOException {
|
||||
if (fm == null)
|
||||
return method;
|
||||
|
||||
TypeElement type = topLevelType(method);
|
||||
|
||||
if (type == null)
|
||||
return method;
|
||||
|
||||
String binaryName = originalTask.task.getElements().getBinaryName(type).toString();
|
||||
|
||||
Map<String, Element> cache = topLevelName2Signature2Method.get(binaryName);
|
||||
|
||||
if (cache == null) {
|
||||
topLevelName2Signature2Method.put(binaryName, cache = createMethodCache(binaryName));
|
||||
}
|
||||
|
||||
String handle = elementHeader(originalTask, method, false);
|
||||
|
||||
return cache.getOrDefault(handle, method);
|
||||
}
|
||||
|
||||
private TypeElement topLevelType(Element el) {
|
||||
while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
|
||||
el = el.getEnclosingElement();
|
||||
}
|
||||
|
||||
return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null;
|
||||
}
|
||||
|
||||
private Map<String, Element> createMethodCache(String binaryName) throws IOException {
|
||||
Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName);
|
||||
|
||||
if (source == null)
|
||||
return Collections.emptyMap();
|
||||
|
||||
Map<String, Element> signature2Method = new HashMap<>();
|
||||
Trees trees = Trees.instance(source.fst);
|
||||
|
||||
new TreePathScanner<Void, Void>() {
|
||||
@Override
|
||||
public Void visitMethod(MethodTree node, Void p) {
|
||||
Element currentMethod = trees.getElement(getCurrentPath());
|
||||
|
||||
if (currentMethod != null) {
|
||||
signature2Method.put(elementHeader(originalTask, currentMethod, false), currentMethod);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}.scan(source.snd, null);
|
||||
|
||||
return signature2Method;
|
||||
}
|
||||
|
||||
private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException {
|
||||
JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH,
|
||||
binaryName,
|
||||
JavaFileObject.Kind.SOURCE);
|
||||
|
||||
if (jfo == null)
|
||||
return null;
|
||||
|
||||
List<JavaFileObject> jfos = Arrays.asList(jfo);
|
||||
JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, d -> {}, null, null, jfos);
|
||||
Iterable<? extends CompilationUnitTree> cuts = task.parse();
|
||||
|
||||
task.enter();
|
||||
|
||||
return Pair.of(task, cuts.iterator().next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (fm != null) {
|
||||
fm.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.close()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Path> availableSources;
|
||||
|
||||
private List<Path> findSources() {
|
||||
@ -1328,25 +1272,59 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
Path srcZip = home.resolve("src.zip");
|
||||
if (!Files.isReadable(srcZip))
|
||||
srcZip = home.getParent().resolve("src.zip");
|
||||
if (Files.isReadable(srcZip))
|
||||
result.add(srcZip);
|
||||
if (Files.isReadable(srcZip)) {
|
||||
boolean keepOpen = false;
|
||||
FileSystem zipFO = null;
|
||||
|
||||
try {
|
||||
URI uri = URI.create("jar:" + srcZip.toUri());
|
||||
zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap());
|
||||
Path root = zipFO.getRootDirectories().iterator().next();
|
||||
|
||||
if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
|
||||
//non-modular format:
|
||||
result.add(srcZip);
|
||||
} else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
|
||||
//modular format:
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
|
||||
for (Path p : ds) {
|
||||
if (Files.isDirectory(p)) {
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keepOpen = true;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
|
||||
} finally {
|
||||
if (zipFO != null) {
|
||||
if (keepOpen) {
|
||||
closeables.add(zipFO);
|
||||
} else {
|
||||
try {
|
||||
zipFO.close();
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return availableSources = result;
|
||||
}
|
||||
|
||||
private String elementHeader(AnalyzeTask at, Element el) {
|
||||
return elementHeader(at, el, true);
|
||||
}
|
||||
|
||||
private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames) {
|
||||
private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) {
|
||||
switch (el.getKind()) {
|
||||
case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
|
||||
TypeElement type = (TypeElement)el;
|
||||
String fullname = type.getQualifiedName().toString();
|
||||
Element pkg = at.getElements().getPackageOf(el);
|
||||
String name = pkg == null ? fullname :
|
||||
String name = pkg == null || useFQN ? fullname :
|
||||
proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString());
|
||||
|
||||
return name + typeParametersOpt(at, type.getTypeParameters());
|
||||
return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames);
|
||||
}
|
||||
case TYPE_PARAMETER: {
|
||||
TypeParameterElement tp = (TypeParameterElement)el;
|
||||
@ -1363,9 +1341,9 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
.collect(joining(" & "));
|
||||
}
|
||||
case FIELD:
|
||||
return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
|
||||
return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName() + ":" + el.asType();
|
||||
case ENUM_CONSTANT:
|
||||
return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName();
|
||||
return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName();
|
||||
case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
|
||||
return el.getSimpleName() + ":" + el.asType();
|
||||
case CONSTRUCTOR: case METHOD: {
|
||||
@ -1379,20 +1357,20 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
header.append(printType(at, proc, method.getReturnType())).append(" ");
|
||||
} else {
|
||||
// type parameters for the constructor
|
||||
String typeParameters = typeParametersOpt(at, method.getTypeParameters());
|
||||
String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames);
|
||||
if (!typeParameters.isEmpty()) {
|
||||
header.append(typeParameters).append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// receiver type
|
||||
String clazz = elementHeader(at, el.getEnclosingElement());
|
||||
String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false);
|
||||
header.append(clazz);
|
||||
|
||||
if (isMethod) {
|
||||
//method name with type parameters
|
||||
(clazz.isEmpty() ? header : header.append("."))
|
||||
.append(typeParametersOpt(at, method.getTypeParameters()))
|
||||
.append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames))
|
||||
.append(el.getSimpleName());
|
||||
}
|
||||
|
||||
@ -1435,10 +1413,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||
}
|
||||
return arrayType;
|
||||
}
|
||||
private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters) {
|
||||
private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters, boolean includeParameterNames) {
|
||||
return typeParameters.isEmpty() ? ""
|
||||
: typeParameters.stream()
|
||||
.map(tp -> elementHeader(at, tp))
|
||||
.map(tp -> elementHeader(at, tp, includeParameterNames, false))
|
||||
.collect(joining(", ", "<", ">"));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -23,12 +23,12 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8131025 8141092 8153761 8145263
|
||||
* @bug 8131025 8141092 8153761 8145263 8131019
|
||||
* @summary Test Completion and Documentation
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.jdeps/com.sun.tools.javap
|
||||
* @library /tools/lib
|
||||
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
|
||||
* @build KullaTesting TestingInputStream Compiler
|
||||
* @run testng CompletionSuggestionTest
|
||||
@ -305,26 +305,26 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||
|
||||
public void testDocumentation() throws Exception {
|
||||
dontReadParameterNamesFromClassFile();
|
||||
assertDocumentation("System.getProperty(|",
|
||||
assertSignature("System.getProperty(|",
|
||||
"String System.getProperty(String key)",
|
||||
"String System.getProperty(String key, String def)");
|
||||
assertEval("char[] chars = null;");
|
||||
assertDocumentation("new String(chars, |",
|
||||
assertSignature("new String(chars, |",
|
||||
"String(char[], int, int)");
|
||||
assertDocumentation("String.format(|",
|
||||
assertSignature("String.format(|",
|
||||
"String String.format(String, Object...)",
|
||||
"String String.format(java.util.Locale, String, Object...)");
|
||||
assertDocumentation("\"\".getBytes(\"\"|", "void String.getBytes(int, int, byte[], int)",
|
||||
assertSignature("\"\".getBytes(\"\"|", "void String.getBytes(int, int, byte[], int)",
|
||||
"byte[] String.getBytes(String) throws java.io.UnsupportedEncodingException",
|
||||
"byte[] String.getBytes(java.nio.charset.Charset)");
|
||||
assertDocumentation("\"\".getBytes(\"\" |", "void String.getBytes(int, int, byte[], int)",
|
||||
assertSignature("\"\".getBytes(\"\" |", "void String.getBytes(int, int, byte[], int)",
|
||||
"byte[] String.getBytes(String) throws java.io.UnsupportedEncodingException",
|
||||
"byte[] String.getBytes(java.nio.charset.Charset)");
|
||||
}
|
||||
|
||||
public void testMethodsWithNoArguments() throws Exception {
|
||||
dontReadParameterNamesFromClassFile();
|
||||
assertDocumentation("System.out.println(|",
|
||||
assertSignature("System.out.println(|",
|
||||
"void java.io.PrintStream.println()",
|
||||
"void java.io.PrintStream.println(boolean)",
|
||||
"void java.io.PrintStream.println(char)",
|
||||
@ -339,6 +339,7 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||
|
||||
public void testErroneous() {
|
||||
assertCompletion("Undefined.|");
|
||||
assertSignature("does.not.exist|");
|
||||
}
|
||||
|
||||
public void testClinit() {
|
||||
@ -474,59 +475,63 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||
|
||||
public void testDocumentationOfUserDefinedMethods() {
|
||||
assertEval("void f() {}");
|
||||
assertDocumentation("f(|", "void f()");
|
||||
assertSignature("f(|", "void f()");
|
||||
assertEval("void f(int i) {}");
|
||||
assertDocumentation("f(|", "void f()", "void f(int i)");
|
||||
assertSignature("f(|", "void f()", "void f(int i)");
|
||||
assertEval("<T> void f(T... ts) {}", DiagCheck.DIAG_WARNING, DiagCheck.DIAG_OK);
|
||||
assertDocumentation("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)");
|
||||
assertSignature("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)");
|
||||
assertEval("class A {}");
|
||||
assertEval("void f(A a) {}");
|
||||
assertDocumentation("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)", "void f(A a)");
|
||||
assertSignature("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)", "void f(A a)");
|
||||
}
|
||||
|
||||
public void testClass() {
|
||||
assertSignature("String|", "java.lang.String");
|
||||
}
|
||||
|
||||
public void testDocumentationOfUserDefinedConstructors() {
|
||||
Snippet a = classKey(assertEval("class A {}"));
|
||||
assertDocumentation("new A(|", "A()");
|
||||
assertSignature("new A(|", "A()");
|
||||
Snippet a2 = classKey(assertEval("class A { A() {} A(int i) {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertDocumentation("new A(|", "A()", "A(int i)");
|
||||
assertSignature("new A(|", "A()", "A(int i)");
|
||||
assertEval("class A<T> { A(T a) {} A(int i) {} <U> A(T t, U u) {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertDocumentation("new A(|", "A<T>(T a)", "A<T>(int i)", "<U> A<T>(T t, U u)");
|
||||
assertSignature("new A(|", "A<T>(T a)", "A<T>(int i)", "<U> A<T>(T t, U u)");
|
||||
}
|
||||
|
||||
public void testDocumentationOfOverriddenMethods() throws Exception {
|
||||
dontReadParameterNamesFromClassFile();
|
||||
assertDocumentation("\"\".wait(|",
|
||||
assertSignature("\"\".wait(|",
|
||||
"void Object.wait(long) throws InterruptedException",
|
||||
"void Object.wait(long, int) throws InterruptedException",
|
||||
"void Object.wait() throws InterruptedException");
|
||||
assertEval("class Base {void method() {}}");
|
||||
Snippet e = classKey(assertEval("class Extend extends Base {}"));
|
||||
assertDocumentation("new Extend().method(|", "void Base.method()");
|
||||
assertSignature("new Extend().method(|", "void Base.method()");
|
||||
assertEval("class Extend extends Base {void method() {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertDocumentation("new Extend().method(|", "void Extend.method()");
|
||||
assertSignature("new Extend().method(|", "void Extend.method()");
|
||||
}
|
||||
|
||||
public void testDocumentationOfInvisibleMethods() {
|
||||
assertDocumentation("Object.wait(|", "");
|
||||
assertDocumentation("\"\".indexOfSupplementary(|", "");
|
||||
assertSignature("Object.wait(|");
|
||||
assertSignature("\"\".indexOfSupplementary(|");
|
||||
Snippet a = classKey(assertEval("class A {void method() {}}"));
|
||||
assertDocumentation("A.method(|", "");
|
||||
assertSignature("A.method(|");
|
||||
assertEval("class A {private void method() {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertDocumentation("new A().method(|", "");
|
||||
assertSignature("new A().method(|");
|
||||
}
|
||||
|
||||
public void testDocumentationOfInvisibleConstructors() {
|
||||
assertDocumentation("new Compiler(|", "");
|
||||
assertSignature("new Compiler(|");
|
||||
assertEval("class A { private A() {} }");
|
||||
assertDocumentation("new A(|", "");
|
||||
assertSignature("new A(|");
|
||||
}
|
||||
|
||||
public void testDocumentationWithBoxing() {
|
||||
@ -535,13 +540,13 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||
assertEval("Object object = null;");
|
||||
assertEval("void method(int n, Object o) { }");
|
||||
assertEval("void method(Object n, int o) { }");
|
||||
assertDocumentation("method(primitive,|",
|
||||
assertSignature("method(primitive,|",
|
||||
"void method(int n, Object o)",
|
||||
"void method(Object n, int o)");
|
||||
assertDocumentation("method(boxed,|",
|
||||
assertSignature("method(boxed,|",
|
||||
"void method(int n, Object o)",
|
||||
"void method(Object n, int o)");
|
||||
assertDocumentation("method(object,|",
|
||||
assertSignature("method(object,|",
|
||||
"void method(Object n, int o)");
|
||||
}
|
||||
|
||||
@ -567,7 +572,7 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||
|
||||
void assertDoc(String generics, String expectedGenerics) {
|
||||
assertEval(evalFormatter.apply(generics, count));
|
||||
assertDocumentation(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
|
||||
assertSignature(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
105
langtools/test/jdk/jshell/JavadocTest.java
Normal file
105
langtools/test/jdk/jshell/JavadocTest.java
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8131019
|
||||
* @summary Test Javadoc
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.jshell
|
||||
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
|
||||
* @build KullaTesting TestingInputStream Compiler
|
||||
* @run testng JavadocTest
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class JavadocTest extends KullaTesting {
|
||||
|
||||
private final Compiler compiler = new Compiler();
|
||||
|
||||
public void testJavadoc() {
|
||||
prepareZip();
|
||||
assertJavadoc("test.Clazz|", "test.Clazz\n" +
|
||||
"Top level. ");
|
||||
assertEval("test.Clazz clz = null;");
|
||||
assertJavadoc("clz.test(|", "String test.Clazz.test(int p) throws IllegalStateException\n" +
|
||||
" javadoc1A\n" +
|
||||
"\n" +
|
||||
" @param p param\n" +
|
||||
" @throws IllegalStateException exc\n" +
|
||||
" @return value\n");
|
||||
//undefined handling:
|
||||
assertJavadoc("clz.undef|");
|
||||
}
|
||||
|
||||
private void prepareZip() {
|
||||
String clazz =
|
||||
"package test;\n" +
|
||||
"/**Top level." +
|
||||
" */\n" +
|
||||
"public class Clazz {\n" +
|
||||
" /**\n" +
|
||||
" * javadoc1A\n" +
|
||||
" *\n" +
|
||||
" * @param p param\n" +
|
||||
" * @throws IllegalStateException exc\n" +
|
||||
" * @return value\n" +
|
||||
" */\n" +
|
||||
" public String test(int p) throws IllegalStateException { return null;}\n" +
|
||||
"}\n";
|
||||
|
||||
Path srcZip = Paths.get("src.zip");
|
||||
|
||||
try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
|
||||
out.putNextEntry(new JarEntry("test/Clazz.java"));
|
||||
out.write(clazz.getBytes());
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
compiler.compile(clazz);
|
||||
|
||||
try {
|
||||
Field availableSources = getAnalysis().getClass().getDeclaredField("availableSources");
|
||||
availableSources.setAccessible(true);
|
||||
availableSources.set(getAnalysis(), Arrays.asList(srcZip));
|
||||
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
addToClasspath(compiler.getClassDir());
|
||||
}
|
||||
|
||||
}
|
@ -72,11 +72,14 @@ import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
import jdk.jshell.Diag;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
import static jdk.jshell.Snippet.Status.*;
|
||||
import static org.testng.Assert.*;
|
||||
import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
|
||||
import jdk.jshell.SourceCodeAnalysis.Documentation;
|
||||
|
||||
public class KullaTesting {
|
||||
|
||||
@ -946,12 +949,24 @@ public class KullaTesting {
|
||||
}
|
||||
}
|
||||
|
||||
public void assertDocumentation(String code, String... expected) {
|
||||
public void assertSignature(String code, String... expected) {
|
||||
int cursor = code.indexOf('|');
|
||||
code = code.replace("|", "");
|
||||
assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
|
||||
String documentation = getAnalysis().documentation(code, cursor);
|
||||
Set<String> docSet = Stream.of(documentation.split("\r?\n")).collect(Collectors.toSet());
|
||||
List<Documentation> documentation = getAnalysis().documentation(code, cursor, false);
|
||||
Set<String> docSet = documentation.stream().map(doc -> doc.signature()).collect(Collectors.toSet());
|
||||
Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
|
||||
assertEquals(docSet, expectedSet, "Input: " + code);
|
||||
}
|
||||
|
||||
public void assertJavadoc(String code, String... expected) {
|
||||
int cursor = code.indexOf('|');
|
||||
code = code.replace("|", "");
|
||||
assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
|
||||
List<Documentation> documentation = getAnalysis().documentation(code, cursor, true);
|
||||
Set<String> docSet = documentation.stream()
|
||||
.map(doc -> doc.signature() + "\n" + doc.javadoc())
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
|
||||
assertEquals(docSet, expectedSet, "Input: " + code);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user