mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-19 02:24:40 +02:00
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
16 changed files with 2551 additions and 224 deletions
|
@ -297,17 +297,21 @@ public enum Entity {
|
||||||
rsaquo(8250),
|
rsaquo(8250),
|
||||||
euro(8364);
|
euro(8364);
|
||||||
|
|
||||||
int code;
|
public final int code;
|
||||||
|
|
||||||
private Entity(int code) {
|
private Entity(int code) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isValid(String name) {
|
public static boolean isValid(String name) {
|
||||||
return names.containsKey(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
|
// allow numeric codes for standard ANSI characters
|
||||||
return codes.containsKey(code) || ( 32 <= code && code < 2127);
|
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.jdeps,
|
||||||
jdk.javadoc,
|
jdk.javadoc,
|
||||||
jdk.jshell;
|
jdk.jshell;
|
||||||
|
exports jdk.internal.shellsupport.doc to
|
||||||
|
jdk.jshell,
|
||||||
|
jdk.scripting.nashorn.shell;
|
||||||
|
|
||||||
uses javax.annotation.processing.Processor;
|
uses javax.annotation.processing.Processor;
|
||||||
uses com.sun.source.util.Plugin;
|
uses com.sun.source.util.Plugin;
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
package jdk.internal.jshell.tool;
|
package jdk.internal.jshell.tool;
|
||||||
|
|
||||||
|
import jdk.jshell.SourceCodeAnalysis.Documentation;
|
||||||
import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
|
import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
|
||||||
import jdk.jshell.SourceCodeAnalysis.Suggestion;
|
import jdk.jshell.SourceCodeAnalysis.Suggestion;
|
||||||
|
|
||||||
|
@ -34,27 +35,30 @@ import java.io.InputStream;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Function;
|
||||||
import java.util.prefs.BackingStoreException;
|
import java.util.prefs.BackingStoreException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import jdk.internal.shellsupport.doc.JavadocFormatter;
|
||||||
import jdk.internal.jline.NoInterruptUnixTerminal;
|
import jdk.internal.jline.NoInterruptUnixTerminal;
|
||||||
import jdk.internal.jline.Terminal;
|
import jdk.internal.jline.Terminal;
|
||||||
import jdk.internal.jline.TerminalFactory;
|
import jdk.internal.jline.TerminalFactory;
|
||||||
import jdk.internal.jline.TerminalSupport;
|
import jdk.internal.jline.TerminalSupport;
|
||||||
import jdk.internal.jline.WindowsTerminal;
|
import jdk.internal.jline.WindowsTerminal;
|
||||||
import jdk.internal.jline.console.ConsoleReader;
|
import jdk.internal.jline.console.ConsoleReader;
|
||||||
|
import jdk.internal.jline.console.CursorBuffer;
|
||||||
import jdk.internal.jline.console.KeyMap;
|
import jdk.internal.jline.console.KeyMap;
|
||||||
import jdk.internal.jline.console.UserInterruptException;
|
import jdk.internal.jline.console.UserInterruptException;
|
||||||
import jdk.internal.jline.console.completer.Completer;
|
import jdk.internal.jline.console.completer.Completer;
|
||||||
|
@ -259,22 +263,118 @@ class ConsoleIOContext extends IOContext {
|
||||||
"\u001BO3P" //Alt-F1 (Linux)
|
"\u001BO3P" //Alt-F1 (Linux)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private String lastDocumentationBuffer;
|
||||||
|
private int lastDocumentationCursor = (-1);
|
||||||
|
|
||||||
private void documentation(JShellTool repl) {
|
private void documentation(JShellTool repl) {
|
||||||
String buffer = in.getCursorBuffer().buffer.toString();
|
String buffer = in.getCursorBuffer().buffer.toString();
|
||||||
int cursor = in.getCursorBuffer().cursor;
|
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("/")) {
|
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 {
|
} 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 {
|
try {
|
||||||
if (doc != null) {
|
if (doc != null && !doc.isEmpty()) {
|
||||||
|
if (firstInvocation) {
|
||||||
in.println();
|
in.println();
|
||||||
in.println(doc);
|
in.println(doc.stream().collect(Collectors.joining("\n")));
|
||||||
|
in.println(repl.messageFormat(seeMore));
|
||||||
in.redrawLine();
|
in.redrawLine();
|
||||||
in.flush();
|
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 {
|
} else {
|
||||||
in.beep();
|
in.beep();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1308,7 +1308,7 @@ public class JShellTool implements MessageHandler {
|
||||||
return commandCompletions.completionSuggestions(code, cursor, anchor);
|
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);
|
code = code.substring(0, cursor);
|
||||||
int space = code.indexOf(' ');
|
int space = code.indexOf(' ');
|
||||||
|
|
||||||
|
@ -1316,7 +1316,7 @@ public class JShellTool implements MessageHandler {
|
||||||
String cmd = code.substring(0, space);
|
String cmd = code.substring(0, space);
|
||||||
Command command = commands.get(cmd);
|
Command command = commands.get(cmd);
|
||||||
if (command != null) {
|
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.err.retained.mode.failure = Failure in retained modes (modes cleared) -- {0} {1}
|
||||||
|
|
||||||
jshell.console.see.more = <press tab to see more>
|
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.do.nothing = Do nothing
|
||||||
jshell.console.choice = Choice: \
|
jshell.console.choice = Choice: \
|
||||||
|
|
||||||
|
|
|
@ -506,6 +506,9 @@ public class JShell implements AutoCloseable {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
closeDown();
|
closeDown();
|
||||||
executionControl().close();
|
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);
|
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 input the snippet the user wrote so far
|
||||||
* @param cursor the current position of the cursors in the given {@code input} text
|
* @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}
|
* 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();
|
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.
|
* 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.Tree.Kind;
|
||||||
import com.sun.source.tree.TypeParameterTree;
|
import com.sun.source.tree.TypeParameterTree;
|
||||||
import com.sun.source.tree.VariableTree;
|
import com.sun.source.tree.VariableTree;
|
||||||
import com.sun.source.util.JavacTask;
|
|
||||||
import com.sun.source.util.SourcePositions;
|
import com.sun.source.util.SourcePositions;
|
||||||
import com.sun.source.util.TreePath;
|
import com.sun.source.util.TreePath;
|
||||||
import com.sun.source.util.TreePathScanner;
|
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.JavacScope;
|
||||||
import com.sun.tools.javac.api.JavacTaskImpl;
|
|
||||||
import com.sun.tools.javac.code.Flags;
|
import com.sun.tools.javac.code.Flags;
|
||||||
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
||||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||||
import com.sun.tools.javac.code.Symtab;
|
import com.sun.tools.javac.code.Symtab;
|
||||||
import com.sun.tools.javac.code.Type;
|
import com.sun.tools.javac.code.Type;
|
||||||
import com.sun.tools.javac.code.Type.ClassType;
|
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.Name;
|
||||||
import com.sun.tools.javac.util.Names;
|
import com.sun.tools.javac.util.Names;
|
||||||
import com.sun.tools.javac.util.Pair;
|
import com.sun.tools.javac.util.Pair;
|
||||||
|
@ -105,6 +103,7 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import static java.util.stream.Collectors.collectingAndThen;
|
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.toCollection;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static java.util.stream.Collectors.toSet;
|
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.type.TypeKind;
|
||||||
import javax.lang.model.util.ElementFilter;
|
import javax.lang.model.util.ElementFilter;
|
||||||
import javax.lang.model.util.Types;
|
import javax.lang.model.util.Types;
|
||||||
import javax.tools.JavaCompiler;
|
|
||||||
import javax.tools.JavaFileManager.Location;
|
import javax.tools.JavaFileManager.Location;
|
||||||
import javax.tools.JavaFileObject;
|
|
||||||
import javax.tools.StandardJavaFileManager;
|
|
||||||
import javax.tools.StandardLocation;
|
import javax.tools.StandardLocation;
|
||||||
import javax.tools.ToolProvider;
|
|
||||||
|
|
||||||
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
|
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.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
|
||||||
import static jdk.jshell.TreeDissector.printType;
|
import static jdk.jshell.TreeDissector.printType;
|
||||||
|
|
||||||
|
@ -151,6 +145,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
|
|
||||||
private final JShell proc;
|
private final JShell proc;
|
||||||
private final CompletenessAnalyzer ca;
|
private final CompletenessAnalyzer ca;
|
||||||
|
private final List<AutoCloseable> closeables = new ArrayList<>();
|
||||||
private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
|
private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
|
||||||
private int indexVersion;
|
private int indexVersion;
|
||||||
private int classpathVersion;
|
private int classpathVersion;
|
||||||
|
@ -1097,10 +1092,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String documentation(String code, int cursor) {
|
public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) {
|
||||||
suspendIndexing();
|
suspendIndexing();
|
||||||
try {
|
try {
|
||||||
return documentationImpl(code, cursor);
|
return documentationImpl(code, cursor, computeJavadoc);
|
||||||
} finally {
|
} finally {
|
||||||
resumeIndexing();
|
resumeIndexing();
|
||||||
}
|
}
|
||||||
|
@ -1112,14 +1107,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
"-parameters"
|
"-parameters"
|
||||||
};
|
};
|
||||||
|
|
||||||
private String documentationImpl(String code, int cursor) {
|
private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
|
||||||
code = code.substring(0, cursor);
|
code = code.substring(0, cursor);
|
||||||
if (code.trim().isEmpty()) { //TODO: comment handling
|
if (code.trim().isEmpty()) { //TODO: comment handling
|
||||||
code += ";";
|
code += ";";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (guessKind(code) == Kind.IMPORT)
|
if (guessKind(code) == Kind.IMPORT)
|
||||||
return null;
|
return Collections.<Documentation>emptyList();
|
||||||
|
|
||||||
OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
|
OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
|
||||||
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
|
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
|
||||||
|
@ -1128,20 +1123,24 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
|
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
|
||||||
|
|
||||||
if (tp == null)
|
if (tp == null)
|
||||||
return null;
|
return Collections.<Documentation>emptyList();
|
||||||
|
|
||||||
TreePath prevPath = null;
|
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;
|
prevPath = tp;
|
||||||
tp = tp.getParentPath();
|
tp = tp.getParentPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tp == null)
|
if (tp == null)
|
||||||
return null;
|
return Collections.<Documentation>emptyList();
|
||||||
|
|
||||||
|
Stream<Element> elements;
|
||||||
Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
|
Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
|
||||||
List<? extends ExpressionTree> arguments;
|
List<? extends ExpressionTree> arguments;
|
||||||
|
|
||||||
|
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
|
||||||
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
|
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
|
||||||
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
|
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
|
||||||
candidates = methodCandidates(at, tp);
|
candidates = methodCandidates(at, tp);
|
||||||
|
@ -1163,11 +1162,81 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
try (SourceCache sourceCache = new SourceCache(at)) {
|
elements = Util.stream(candidates).map(method -> method.fst);
|
||||||
return Util.stream(candidates)
|
} else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
|
||||||
.map(method -> Util.expunge(element2String(sourceCache, method.fst)))
|
Element el = at.trees().getElement(tp);
|
||||||
.collect(joining("\n"));
|
|
||||||
|
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 {
|
||||||
|
return Collections.<Documentation>emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Documentation> result = Collections.emptyList();
|
||||||
|
|
||||||
|
try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
|
||||||
|
result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
|
||||||
|
.filter(r -> r != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
proc.debug(ex, "JavadocHelper.close()");
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
|
||||||
|
@ -1178,18 +1247,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
return false;
|
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) {
|
private boolean hasSyntheticParameterNames(Element el) {
|
||||||
if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
|
if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
|
||||||
return false;
|
return false;
|
||||||
|
@ -1204,119 +1261,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
.allMatch(param -> param.getSimpleName().toString().startsWith("arg"));
|
.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> availableSources;
|
||||||
|
|
||||||
private List<Path> findSources() {
|
private List<Path> findSources() {
|
||||||
|
@ -1328,25 +1272,59 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
Path srcZip = home.resolve("src.zip");
|
Path srcZip = home.resolve("src.zip");
|
||||||
if (!Files.isReadable(srcZip))
|
if (!Files.isReadable(srcZip))
|
||||||
srcZip = home.getParent().resolve("src.zip");
|
srcZip = home.getParent().resolve("src.zip");
|
||||||
if (Files.isReadable(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);
|
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;
|
return availableSources = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String elementHeader(AnalyzeTask at, Element el) {
|
private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) {
|
||||||
return elementHeader(at, el, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames) {
|
|
||||||
switch (el.getKind()) {
|
switch (el.getKind()) {
|
||||||
case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
|
case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
|
||||||
TypeElement type = (TypeElement)el;
|
TypeElement type = (TypeElement)el;
|
||||||
String fullname = type.getQualifiedName().toString();
|
String fullname = type.getQualifiedName().toString();
|
||||||
Element pkg = at.getElements().getPackageOf(el);
|
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());
|
proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString());
|
||||||
|
|
||||||
return name + typeParametersOpt(at, type.getTypeParameters());
|
return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames);
|
||||||
}
|
}
|
||||||
case TYPE_PARAMETER: {
|
case TYPE_PARAMETER: {
|
||||||
TypeParameterElement tp = (TypeParameterElement)el;
|
TypeParameterElement tp = (TypeParameterElement)el;
|
||||||
|
@ -1363,9 +1341,9 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
.collect(joining(" & "));
|
.collect(joining(" & "));
|
||||||
}
|
}
|
||||||
case FIELD:
|
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:
|
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:
|
case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
|
||||||
return el.getSimpleName() + ":" + el.asType();
|
return el.getSimpleName() + ":" + el.asType();
|
||||||
case CONSTRUCTOR: case METHOD: {
|
case CONSTRUCTOR: case METHOD: {
|
||||||
|
@ -1379,20 +1357,20 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
header.append(printType(at, proc, method.getReturnType())).append(" ");
|
header.append(printType(at, proc, method.getReturnType())).append(" ");
|
||||||
} else {
|
} else {
|
||||||
// type parameters for the constructor
|
// type parameters for the constructor
|
||||||
String typeParameters = typeParametersOpt(at, method.getTypeParameters());
|
String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames);
|
||||||
if (!typeParameters.isEmpty()) {
|
if (!typeParameters.isEmpty()) {
|
||||||
header.append(typeParameters).append(" ");
|
header.append(typeParameters).append(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// receiver type
|
// receiver type
|
||||||
String clazz = elementHeader(at, el.getEnclosingElement());
|
String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false);
|
||||||
header.append(clazz);
|
header.append(clazz);
|
||||||
|
|
||||||
if (isMethod) {
|
if (isMethod) {
|
||||||
//method name with type parameters
|
//method name with type parameters
|
||||||
(clazz.isEmpty() ? header : header.append("."))
|
(clazz.isEmpty() ? header : header.append("."))
|
||||||
.append(typeParametersOpt(at, method.getTypeParameters()))
|
.append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames))
|
||||||
.append(el.getSimpleName());
|
.append(el.getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1435,10 +1413,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
|
||||||
}
|
}
|
||||||
return arrayType;
|
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() ? ""
|
return typeParameters.isEmpty() ? ""
|
||||||
: typeParameters.stream()
|
: typeParameters.stream()
|
||||||
.map(tp -> elementHeader(at, tp))
|
.map(tp -> elementHeader(at, tp, includeParameterNames, false))
|
||||||
.collect(joining(", ", "<", ">"));
|
.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
|
* @test
|
||||||
* @bug 8131025 8141092 8153761 8145263
|
* @bug 8131025 8141092 8153761 8145263 8131019
|
||||||
* @summary Test Completion and Documentation
|
* @summary Test Completion and Documentation
|
||||||
|
* @library /tools/lib
|
||||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||||
* jdk.compiler/com.sun.tools.javac.main
|
* jdk.compiler/com.sun.tools.javac.main
|
||||||
* jdk.jdeps/com.sun.tools.javap
|
* jdk.jdeps/com.sun.tools.javap
|
||||||
* @library /tools/lib
|
|
||||||
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
|
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
|
||||||
* @build KullaTesting TestingInputStream Compiler
|
* @build KullaTesting TestingInputStream Compiler
|
||||||
* @run testng CompletionSuggestionTest
|
* @run testng CompletionSuggestionTest
|
||||||
|
@ -305,26 +305,26 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||||
|
|
||||||
public void testDocumentation() throws Exception {
|
public void testDocumentation() throws Exception {
|
||||||
dontReadParameterNamesFromClassFile();
|
dontReadParameterNamesFromClassFile();
|
||||||
assertDocumentation("System.getProperty(|",
|
assertSignature("System.getProperty(|",
|
||||||
"String System.getProperty(String key)",
|
"String System.getProperty(String key)",
|
||||||
"String System.getProperty(String key, String def)");
|
"String System.getProperty(String key, String def)");
|
||||||
assertEval("char[] chars = null;");
|
assertEval("char[] chars = null;");
|
||||||
assertDocumentation("new String(chars, |",
|
assertSignature("new String(chars, |",
|
||||||
"String(char[], int, int)");
|
"String(char[], int, int)");
|
||||||
assertDocumentation("String.format(|",
|
assertSignature("String.format(|",
|
||||||
"String String.format(String, Object...)",
|
"String String.format(String, Object...)",
|
||||||
"String String.format(java.util.Locale, 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(String) throws java.io.UnsupportedEncodingException",
|
||||||
"byte[] String.getBytes(java.nio.charset.Charset)");
|
"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(String) throws java.io.UnsupportedEncodingException",
|
||||||
"byte[] String.getBytes(java.nio.charset.Charset)");
|
"byte[] String.getBytes(java.nio.charset.Charset)");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testMethodsWithNoArguments() throws Exception {
|
public void testMethodsWithNoArguments() throws Exception {
|
||||||
dontReadParameterNamesFromClassFile();
|
dontReadParameterNamesFromClassFile();
|
||||||
assertDocumentation("System.out.println(|",
|
assertSignature("System.out.println(|",
|
||||||
"void java.io.PrintStream.println()",
|
"void java.io.PrintStream.println()",
|
||||||
"void java.io.PrintStream.println(boolean)",
|
"void java.io.PrintStream.println(boolean)",
|
||||||
"void java.io.PrintStream.println(char)",
|
"void java.io.PrintStream.println(char)",
|
||||||
|
@ -339,6 +339,7 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||||
|
|
||||||
public void testErroneous() {
|
public void testErroneous() {
|
||||||
assertCompletion("Undefined.|");
|
assertCompletion("Undefined.|");
|
||||||
|
assertSignature("does.not.exist|");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClinit() {
|
public void testClinit() {
|
||||||
|
@ -474,59 +475,63 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||||
|
|
||||||
public void testDocumentationOfUserDefinedMethods() {
|
public void testDocumentationOfUserDefinedMethods() {
|
||||||
assertEval("void f() {}");
|
assertEval("void f() {}");
|
||||||
assertDocumentation("f(|", "void f()");
|
assertSignature("f(|", "void f()");
|
||||||
assertEval("void f(int i) {}");
|
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);
|
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("class A {}");
|
||||||
assertEval("void f(A 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() {
|
public void testDocumentationOfUserDefinedConstructors() {
|
||||||
Snippet a = classKey(assertEval("class A {}"));
|
Snippet a = classKey(assertEval("class A {}"));
|
||||||
assertDocumentation("new A(|", "A()");
|
assertSignature("new A(|", "A()");
|
||||||
Snippet a2 = classKey(assertEval("class A { A() {} A(int i) {}}",
|
Snippet a2 = classKey(assertEval("class A { A() {} A(int i) {}}",
|
||||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
|
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) {}}",
|
assertEval("class A<T> { A(T a) {} A(int i) {} <U> A(T t, U u) {}}",
|
||||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||||
ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
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 {
|
public void testDocumentationOfOverriddenMethods() throws Exception {
|
||||||
dontReadParameterNamesFromClassFile();
|
dontReadParameterNamesFromClassFile();
|
||||||
assertDocumentation("\"\".wait(|",
|
assertSignature("\"\".wait(|",
|
||||||
"void Object.wait(long) throws InterruptedException",
|
"void Object.wait(long) throws InterruptedException",
|
||||||
"void Object.wait(long, int) throws InterruptedException",
|
"void Object.wait(long, int) throws InterruptedException",
|
||||||
"void Object.wait() throws InterruptedException");
|
"void Object.wait() throws InterruptedException");
|
||||||
assertEval("class Base {void method() {}}");
|
assertEval("class Base {void method() {}}");
|
||||||
Snippet e = classKey(assertEval("class Extend extends Base {}"));
|
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() {}}",
|
assertEval("class Extend extends Base {void method() {}}",
|
||||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||||
ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||||
assertDocumentation("new Extend().method(|", "void Extend.method()");
|
assertSignature("new Extend().method(|", "void Extend.method()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDocumentationOfInvisibleMethods() {
|
public void testDocumentationOfInvisibleMethods() {
|
||||||
assertDocumentation("Object.wait(|", "");
|
assertSignature("Object.wait(|");
|
||||||
assertDocumentation("\"\".indexOfSupplementary(|", "");
|
assertSignature("\"\".indexOfSupplementary(|");
|
||||||
Snippet a = classKey(assertEval("class A {void method() {}}"));
|
Snippet a = classKey(assertEval("class A {void method() {}}"));
|
||||||
assertDocumentation("A.method(|", "");
|
assertSignature("A.method(|");
|
||||||
assertEval("class A {private void method() {}}",
|
assertEval("class A {private void method() {}}",
|
||||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||||
assertDocumentation("new A().method(|", "");
|
assertSignature("new A().method(|");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDocumentationOfInvisibleConstructors() {
|
public void testDocumentationOfInvisibleConstructors() {
|
||||||
assertDocumentation("new Compiler(|", "");
|
assertSignature("new Compiler(|");
|
||||||
assertEval("class A { private A() {} }");
|
assertEval("class A { private A() {} }");
|
||||||
assertDocumentation("new A(|", "");
|
assertSignature("new A(|");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDocumentationWithBoxing() {
|
public void testDocumentationWithBoxing() {
|
||||||
|
@ -535,13 +540,13 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||||
assertEval("Object object = null;");
|
assertEval("Object object = null;");
|
||||||
assertEval("void method(int n, Object o) { }");
|
assertEval("void method(int n, Object o) { }");
|
||||||
assertEval("void method(Object n, int o) { }");
|
assertEval("void method(Object n, int o) { }");
|
||||||
assertDocumentation("method(primitive,|",
|
assertSignature("method(primitive,|",
|
||||||
"void method(int n, Object o)",
|
"void method(int n, Object o)",
|
||||||
"void method(Object n, int o)");
|
"void method(Object n, int o)");
|
||||||
assertDocumentation("method(boxed,|",
|
assertSignature("method(boxed,|",
|
||||||
"void method(int n, Object o)",
|
"void method(int n, Object o)",
|
||||||
"void method(Object n, int o)");
|
"void method(Object n, int o)");
|
||||||
assertDocumentation("method(object,|",
|
assertSignature("method(object,|",
|
||||||
"void method(Object n, int o)");
|
"void method(Object n, int o)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,7 +572,7 @@ public class CompletionSuggestionTest extends KullaTesting {
|
||||||
|
|
||||||
void assertDoc(String generics, String expectedGenerics) {
|
void assertDoc(String generics, String expectedGenerics) {
|
||||||
assertEval(evalFormatter.apply(generics, count));
|
assertEval(evalFormatter.apply(generics, count));
|
||||||
assertDocumentation(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
|
assertSignature(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
|
||||||
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 org.testng.annotations.BeforeMethod;
|
||||||
|
|
||||||
import jdk.jshell.Diag;
|
import jdk.jshell.Diag;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static java.util.stream.Collectors.toSet;
|
import static java.util.stream.Collectors.toSet;
|
||||||
|
|
||||||
import static jdk.jshell.Snippet.Status.*;
|
import static jdk.jshell.Snippet.Status.*;
|
||||||
import static org.testng.Assert.*;
|
import static org.testng.Assert.*;
|
||||||
import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
|
import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
|
||||||
|
import jdk.jshell.SourceCodeAnalysis.Documentation;
|
||||||
|
|
||||||
public class KullaTesting {
|
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('|');
|
int cursor = code.indexOf('|');
|
||||||
code = code.replace("|", "");
|
code = code.replace("|", "");
|
||||||
assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
|
assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
|
||||||
String documentation = getAnalysis().documentation(code, cursor);
|
List<Documentation> documentation = getAnalysis().documentation(code, cursor, false);
|
||||||
Set<String> docSet = Stream.of(documentation.split("\r?\n")).collect(Collectors.toSet());
|
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());
|
Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
|
||||||
assertEquals(docSet, expectedSet, "Input: " + code);
|
assertEquals(docSet, expectedSet, "Input: " + code);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue