8131019: jshell tool: access javadoc from tool
authorjlahoda
Wed, 02 Nov 2016 07:38:37 +0100
changeset 41865 3ef02797070d
parent 41864 f7dbab23003a
child 41866 dd1b42a0fb46
8131019: jshell tool: access javadoc from tool Summary: Adding internal support to resolve {@inheritDoc} and format javadoc to plain text for use by jdk.jshell and jdk.scripting.nashorn.shell, enhancing Shift-<tab> documentation in JShell with ability to show javadoc. Reviewed-by: jjg, rfield
langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Entity.java
langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java
langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java
langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties
langtools/src/jdk.compiler/share/classes/module-info.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties
langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java
langtools/test/jdk/internal/shellsupport/doc/JavadocFormatterTest.java
langtools/test/jdk/internal/shellsupport/doc/JavadocHelperTest.java
langtools/test/jdk/jshell/CompletionSuggestionTest.java
langtools/test/jdk/jshell/JavadocTest.java
langtools/test/jdk/jshell/KullaTesting.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Entity.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Entity.java	Wed Nov 02 07:38:37 2016 +0100
@@ -297,17 +297,21 @@
     rsaquo(8250),
     euro(8364);
 
-    int code;
+    public final int code;
 
     private Entity(int code) {
         this.code = code;
     }
 
-    static boolean isValid(String name) {
+    public static boolean isValid(String name) {
         return names.containsKey(name);
     }
 
-    static boolean isValid(int code) {
+    public static Entity get(String name) {
+        return names.get(name);
+    }
+
+    public static boolean isValid(int code) {
         // allow numeric codes for standard ANSI characters
         return codes.containsKey(code) || ( 32 <= code && code < 2127);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java	Wed Nov 02 07:38:37 2016 +0100
@@ -0,0 +1,706 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.shellsupport.doc;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Stack;
+
+import javax.lang.model.element.Name;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+import com.sun.source.doctree.AttributeTree;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.EndElementTree;
+import com.sun.source.doctree.EntityTree;
+import com.sun.source.doctree.InlineTagTree;
+import com.sun.source.doctree.LinkTree;
+import com.sun.source.doctree.LiteralTree;
+import com.sun.source.doctree.ParamTree;
+import com.sun.source.doctree.ReturnTree;
+import com.sun.source.doctree.StartElementTree;
+import com.sun.source.doctree.TextTree;
+import com.sun.source.doctree.ThrowsTree;
+import com.sun.source.util.DocTreeScanner;
+import com.sun.source.util.DocTrees;
+import com.sun.source.util.JavacTask;
+import com.sun.tools.doclint.Entity;
+import com.sun.tools.doclint.HtmlTag;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+import com.sun.tools.javac.util.StringUtils;
+
+/**A javadoc to plain text formatter.
+ *
+ */
+public class JavadocFormatter {
+
+    private static final String CODE_RESET = "\033[0m";
+    private static final String CODE_HIGHLIGHT = "\033[1m";
+    private static final String CODE_UNDERLINE = "\033[4m";
+
+    private final int lineLimit;
+    private final boolean escapeSequencesSupported;
+
+    /** Construct the formatter.
+     *
+     * @param lineLimit maximum line length
+     * @param escapeSequencesSupported whether escape sequences are supported
+     */
+    public JavadocFormatter(int lineLimit, boolean escapeSequencesSupported) {
+        this.lineLimit = lineLimit;
+        this.escapeSequencesSupported = escapeSequencesSupported;
+    }
+
+    private static final int MAX_LINE_LENGTH = 95;
+    private static final int SHORTEST_LINE = 30;
+    private static final int INDENT = 4;
+
+    /**Format javadoc to plain text.
+     *
+     * @param header element caption that should be used
+     * @param javadoc to format
+     * @return javadoc formatted to plain text
+     */
+    public String formatJavadoc(String header, String javadoc) {
+        try {
+            StringBuilder result = new StringBuilder();
+
+            result.append(escape(CODE_HIGHLIGHT)).append(header).append(escape(CODE_RESET)).append("\n");
+
+            if (javadoc == null) {
+                return result.toString();
+            }
+
+            JavacTask task = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, null);
+            DocTrees trees = DocTrees.instance(task);
+            DocCommentTree docComment = trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) {
+                @Override @DefinedBy(Api.COMPILER)
+                public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+                    return "<body>" + javadoc + "</body>";
+                }
+            });
+
+            new FormatJavadocScanner(result, task).scan(docComment, null);
+
+            addNewLineIfNeeded(result);
+
+            return result.toString();
+        } catch (URISyntaxException ex) {
+            throw new InternalError("Unexpected exception", ex);
+        }
+    }
+
+    private class FormatJavadocScanner extends DocTreeScanner<Object, Object> {
+        private final StringBuilder result;
+        private final JavacTask task;
+        private int reflownTo;
+        private int indent;
+        private int limit = Math.min(lineLimit, MAX_LINE_LENGTH);
+        private boolean pre;
+        private Map<StartElementTree, Integer> tableColumns;
+
+        public FormatJavadocScanner(StringBuilder result, JavacTask task) {
+            this.result = result;
+            this.task = task;
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitDocComment(DocCommentTree node, Object p) {
+            tableColumns = countTableColumns(node);
+            reflownTo = result.length();
+            scan(node.getFirstSentence(), p);
+            scan(node.getBody(), p);
+            reflow(result, reflownTo, indent, limit);
+            for (Sections current : docSections.keySet()) {
+                boolean seenAny = false;
+                for (DocTree t : node.getBlockTags()) {
+                    if (current.matches(t)) {
+                        if (!seenAny) {
+                            seenAny = true;
+                            if (result.charAt(result.length() - 1) != '\n')
+                                result.append("\n");
+                            result.append("\n");
+                            result.append(escape(CODE_UNDERLINE))
+                                  .append(docSections.get(current))
+                                  .append(escape(CODE_RESET))
+                                  .append("\n");
+                        }
+
+                        scan(t, null);
+                    }
+                }
+            }
+            return null;
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitText(TextTree node, Object p) {
+            String text = node.getBody();
+            if (!pre) {
+                text = text.replaceAll("[ \t\r\n]+", " ").trim();
+                if (text.isEmpty()) {
+                    text = " ";
+                }
+            } else {
+                text = text.replaceAll("\n", "\n" + indentString(indent));
+            }
+            result.append(text);
+            return null;
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitLink(LinkTree node, Object p) {
+            if (!node.getLabel().isEmpty()) {
+                scan(node.getLabel(), p);
+            } else {
+                result.append(node.getReference().getSignature());
+            }
+            return null;
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitParam(ParamTree node, Object p) {
+            return formatDef(node.getName().getName(), node.getDescription());
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitThrows(ThrowsTree node, Object p) {
+            return formatDef(node.getExceptionName().getSignature(), node.getDescription());
+        }
+
+        public Object formatDef(CharSequence name, List<? extends DocTree> description) {
+            result.append(name);
+            result.append(" - ");
+            reflownTo = result.length();
+            indent = name.length() + 3;
+
+            if (limit - indent < SHORTEST_LINE) {
+                result.append("\n");
+                result.append(indentString(INDENT));
+                indent = INDENT;
+                reflownTo += INDENT;
+            }
+            try {
+                return scan(description, null);
+            } finally {
+                reflow(result, reflownTo, indent, limit);
+                result.append("\n");
+            }
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitLiteral(LiteralTree node, Object p) {
+            return scan(node.getBody(), p);
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitReturn(ReturnTree node, Object p) {
+            reflownTo = result.length();
+            try {
+                return super.visitReturn(node, p);
+            } finally {
+                reflow(result, reflownTo, 0, limit);
+            }
+        }
+
+        Stack<Integer> listStack = new Stack<>();
+        Stack<Integer> defStack = new Stack<>();
+        Stack<Integer> tableStack = new Stack<>();
+        Stack<List<Integer>> cellsStack = new Stack<>();
+        Stack<List<Boolean>> headerStack = new Stack<>();
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitStartElement(StartElementTree node, Object p) {
+            switch (HtmlTag.get(node.getName())) {
+                case P:
+                    if (lastNode!= null && lastNode.getKind() == DocTree.Kind.START_ELEMENT &&
+                        HtmlTag.get(((StartElementTree) lastNode).getName()) == HtmlTag.LI) {
+                        //ignore
+                        break;
+                    }
+                    reflowTillNow();
+                    addNewLineIfNeeded(result);
+                    result.append(indentString(indent));
+                    reflownTo = result.length();
+                    break;
+                case BLOCKQUOTE:
+                    reflowTillNow();
+                    indent += INDENT;
+                    break;
+                case PRE:
+                    reflowTillNow();
+                    pre = true;
+                    break;
+                case UL:
+                    reflowTillNow();
+                    listStack.push(-1);
+                    indent += INDENT;
+                    break;
+                case OL:
+                    reflowTillNow();
+                    listStack.push(1);
+                    indent += INDENT;
+                    break;
+                case DL:
+                    reflowTillNow();
+                    defStack.push(indent);
+                    break;
+                case LI:
+                    reflowTillNow();
+                    if (!listStack.empty()) {
+                        addNewLineIfNeeded(result);
+
+                        int top = listStack.pop();
+
+                        if (top == (-1)) {
+                            result.append(indentString(indent - 2));
+                            result.append("* ");
+                        } else {
+                            result.append(indentString(indent - 3));
+                            result.append("" + top++ + ". ");
+                        }
+
+                        listStack.push(top);
+
+                        reflownTo = result.length();
+                    }
+                    break;
+                case DT:
+                    reflowTillNow();
+                    if (!defStack.isEmpty()) {
+                        addNewLineIfNeeded(result);
+                        indent = defStack.peek();
+                        result.append(escape(CODE_HIGHLIGHT));
+                    }
+                    break;
+                case DD:
+                    reflowTillNow();
+                    if (!defStack.isEmpty()) {
+                        if (indent == defStack.peek()) {
+                            result.append(escape(CODE_RESET));
+                        }
+                        addNewLineIfNeeded(result);
+                        indent = defStack.peek() + INDENT;
+                        result.append(indentString(indent));
+                    }
+                    break;
+                case H1: case H2: case H3:
+                case H4: case H5: case H6:
+                    reflowTillNow();
+                    addNewLineIfNeeded(result);
+                    result.append("\n")
+                          .append(escape(CODE_UNDERLINE));
+                    reflownTo = result.length();
+                    break;
+                case TABLE:
+                    int columns = tableColumns.get(node);
+
+                    if (columns == 0) {
+                        break; //broken input
+                    }
+
+                    reflowTillNow();
+                    addNewLineIfNeeded(result);
+                    reflownTo = result.length();
+
+                    tableStack.push(limit);
+
+                    limit = (limit - 1) / columns - 3;
+
+                    for (int sep = 0; sep < (limit + 3) * columns + 1; sep++) {
+                        result.append("-");
+                    }
+
+                    result.append("\n");
+
+                    break;
+                case TR:
+                    if (cellsStack.size() >= tableStack.size()) {
+                        //unclosed <tr>:
+                        handleEndElement(node.getName());
+                    }
+                    cellsStack.push(new ArrayList<>());
+                    headerStack.push(new ArrayList<>());
+                    break;
+                case TH:
+                case TD:
+                    if (cellsStack.isEmpty()) {
+                        //broken code
+                        break;
+                    }
+                    reflowTillNow();
+                    result.append("\n");
+                    reflownTo = result.length();
+                    cellsStack.peek().add(result.length());
+                    headerStack.peek().add(HtmlTag.get(node.getName()) == HtmlTag.TH);
+                    break;
+                case IMG:
+                    for (DocTree attr : node.getAttributes()) {
+                        if (attr.getKind() != DocTree.Kind.ATTRIBUTE) {
+                            continue;
+                        }
+                        AttributeTree at = (AttributeTree) attr;
+                        if ("alt".equals(StringUtils.toLowerCase(at.getName().toString()))) {
+                            addSpaceIfNeeded(result);
+                            scan(at.getValue(), null);
+                            addSpaceIfNeeded(result);
+                            break;
+                        }
+                    }
+                    break;
+                default:
+                    addSpaceIfNeeded(result);
+                    break;
+            }
+            return null;
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitEndElement(EndElementTree node, Object p) {
+            handleEndElement(node.getName());
+            return super.visitEndElement(node, p);
+        }
+
+        private void handleEndElement(Name name) {
+            switch (HtmlTag.get(name)) {
+                case BLOCKQUOTE:
+                    indent -= INDENT;
+                    break;
+                case PRE:
+                    pre = false;
+                    addNewLineIfNeeded(result);
+                    reflownTo = result.length();
+                    break;
+                case UL: case OL:
+                    if (listStack.isEmpty()) { //ignore stray closing tag
+                        break;
+                    }
+                    reflowTillNow();
+                    listStack.pop();
+                    indent -= INDENT;
+                    addNewLineIfNeeded(result);
+                    break;
+                case DL:
+                    if (defStack.isEmpty()) {//ignore stray closing tag
+                        break;
+                    }
+                    reflowTillNow();
+                    if (indent == defStack.peek()) {
+                        result.append(escape(CODE_RESET));
+                    }
+                    indent = defStack.pop();
+                    addNewLineIfNeeded(result);
+                    break;
+                case H1: case H2: case H3:
+                case H4: case H5: case H6:
+                    reflowTillNow();
+                    result.append(escape(CODE_RESET))
+                          .append("\n");
+                    reflownTo = result.length();
+                    break;
+                case TABLE:
+                    if (cellsStack.size() >= tableStack.size()) {
+                        //unclosed <tr>:
+                        handleEndElement(task.getElements().getName("tr"));
+                    }
+
+                    if (tableStack.isEmpty()) {
+                        break;
+                    }
+
+                    limit = tableStack.pop();
+                    break;
+                case TR:
+                    if (cellsStack.isEmpty()) {
+                        break;
+                    }
+
+                    reflowTillNow();
+
+                    List<Integer> cells = cellsStack.pop();
+                    List<Boolean> headerFlags = headerStack.pop();
+                    List<String[]> content = new ArrayList<>();
+                    int maxLines = 0;
+
+                    result.append("\n");
+
+                    while (!cells.isEmpty()) {
+                        int currentCell = cells.remove(cells.size() - 1);
+                        String[] lines = result.substring(currentCell, result.length()).split("\n");
+
+                        result.delete(currentCell - 1, result.length());
+
+                        content.add(lines);
+                        maxLines = Math.max(maxLines, lines.length);
+                    }
+
+                    Collections.reverse(content);
+
+                    for (int line = 0; line < maxLines; line++) {
+                        for (int column = 0; column < content.size(); column++) {
+                            String[] lines = content.get(column);
+                            String currentLine = line < lines.length ? lines[line] : "";
+                            result.append("| ");
+                            boolean header = headerFlags.get(column);
+                            if (header) {
+                                result.append(escape(CODE_HIGHLIGHT));
+                            }
+                            result.append(currentLine);
+                            if (header) {
+                                result.append(escape(CODE_RESET));
+                            }
+                            int padding = limit - currentLine.length();
+                            if (padding > 0)
+                                result.append(indentString(padding));
+                            result.append(" ");
+                        }
+                        result.append("|\n");
+                    }
+
+                    for (int sep = 0; sep < (limit + 3) * content.size() + 1; sep++) {
+                        result.append("-");
+                    }
+
+                    result.append("\n");
+
+                    reflownTo = result.length();
+                    break;
+                case TD:
+                case TH:
+                    break;
+                default:
+                    addSpaceIfNeeded(result);
+                    break;
+            }
+        }
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object visitEntity(EntityTree node, Object p) {
+            String name = node.getName().toString();
+            int code = -1;
+            if (name.startsWith("#")) {
+                try {
+                    int v = StringUtils.toLowerCase(name).startsWith("#x")
+                            ? Integer.parseInt(name.substring(2), 16)
+                            : Integer.parseInt(name.substring(1), 10);
+                    if (Entity.isValid(v)) {
+                        code = v;
+                    }
+                } catch (NumberFormatException ex) {
+                    //ignore
+                }
+            } else {
+                Entity entity = Entity.get(name);
+                if (entity != null) {
+                    code = entity.code;
+                }
+            }
+            if (code != (-1)) {
+                result.appendCodePoint(code);
+            } else {
+                result.append(node.toString());
+            }
+            return super.visitEntity(node, p);
+        }
+
+        private DocTree lastNode;
+
+        @Override @DefinedBy(Api.COMPILER_TREE)
+        public Object scan(DocTree node, Object p) {
+            if (node instanceof InlineTagTree) {
+                addSpaceIfNeeded(result);
+            }
+            try {
+                return super.scan(node, p);
+            } finally {
+                if (node instanceof InlineTagTree) {
+                    addSpaceIfNeeded(result);
+                }
+                lastNode = node;
+            }
+        }
+
+        private void reflowTillNow() {
+            while (result.length() > 0 && result.charAt(result.length() - 1) == ' ')
+                result.delete(result.length() - 1, result.length());
+            reflow(result, reflownTo, indent, limit);
+            reflownTo = result.length();
+        }
+    };
+
+    private String escape(String sequence) {
+        return this.escapeSequencesSupported ? sequence : "";
+    }
+
+    private static final Map<Sections, String> docSections = new LinkedHashMap<>();
+
+    static {
+        ResourceBundle bundle =
+                ResourceBundle.getBundle("jdk.internal.shellsupport.doc.resources.javadocformatter");
+        docSections.put(Sections.TYPE_PARAMS, bundle.getString("CAP_TypeParameters"));
+        docSections.put(Sections.PARAMS, bundle.getString("CAP_Parameters"));
+        docSections.put(Sections.RETURNS, bundle.getString("CAP_Returns"));
+        docSections.put(Sections.THROWS, bundle.getString("CAP_Thrown_Exceptions"));
+    }
+
+    private static String indentString(int indent) {
+        char[] content = new char[indent];
+        Arrays.fill(content, ' ');
+        return new String(content);
+    }
+
+    private static void reflow(StringBuilder text, int from, int indent, int limit) {
+        int lineStart = from;
+
+        while (lineStart > 0 && text.charAt(lineStart - 1) != '\n') {
+            lineStart--;
+        }
+
+        int lineChars = from - lineStart;
+        int pointer = from;
+        int lastSpace = -1;
+
+        while (pointer < text.length()) {
+            if (text.charAt(pointer) == ' ')
+                lastSpace = pointer;
+            if (lineChars >= limit) {
+                if (lastSpace != (-1)) {
+                    text.setCharAt(lastSpace, '\n');
+                    text.insert(lastSpace + 1, indentString(indent));
+                    lineChars = indent + pointer - lastSpace - 1;
+                    pointer += indent;
+                    lastSpace = -1;
+                }
+            }
+            lineChars++;
+            pointer++;
+        }
+    }
+
+    private static void addNewLineIfNeeded(StringBuilder text) {
+        if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
+            text.append("\n");
+        }
+    }
+
+    private static void addSpaceIfNeeded(StringBuilder text) {
+        if (text.length() == 0)
+            return ;
+
+        char last = text.charAt(text.length() - 1);
+
+        if (last != ' ' && last != '\n') {
+            text.append(" ");
+        }
+    }
+
+    private static Map<StartElementTree, Integer> countTableColumns(DocCommentTree dct) {
+        Map<StartElementTree, Integer> result = new IdentityHashMap<>();
+
+        new DocTreeScanner<Void, Void>() {
+            private StartElementTree currentTable;
+            private int currentMaxColumns;
+            private int currentRowColumns;
+
+            @Override @DefinedBy(Api.COMPILER_TREE)
+            public Void visitStartElement(StartElementTree node, Void p) {
+                switch (HtmlTag.get(node.getName())) {
+                    case TABLE: currentTable = node; break;
+                    case TR:
+                        currentMaxColumns = Math.max(currentMaxColumns, currentRowColumns);
+                        currentRowColumns = 0;
+                        break;
+                    case TD:
+                    case TH: currentRowColumns++; break;
+                }
+                return super.visitStartElement(node, p);
+            }
+
+            @Override @DefinedBy(Api.COMPILER_TREE)
+            public Void visitEndElement(EndElementTree node, Void p) {
+                if (HtmlTag.get(node.getName()) == HtmlTag.TABLE) {
+                    closeTable();
+                }
+                return super.visitEndElement(node, p);
+            }
+
+            @Override @DefinedBy(Api.COMPILER_TREE)
+            public Void visitDocComment(DocCommentTree node, Void p) {
+                try {
+                    return super.visitDocComment(node, p);
+                } finally {
+                    closeTable();
+                }
+            }
+
+            private void closeTable() {
+                if (currentTable != null) {
+                    result.put(currentTable, Math.max(currentMaxColumns, currentRowColumns));
+                    currentTable = null;
+                }
+            }
+        }.scan(dct, null);
+
+        return result;
+    }
+
+    private enum Sections {
+        TYPE_PARAMS {
+            @Override public boolean matches(DocTree t) {
+                return t.getKind() == DocTree.Kind.PARAM && ((ParamTree) t).isTypeParameter();
+            }
+        },
+        PARAMS {
+            @Override public boolean matches(DocTree t) {
+                return t.getKind() == DocTree.Kind.PARAM && !((ParamTree) t).isTypeParameter();
+            }
+        },
+        RETURNS {
+            @Override public boolean matches(DocTree t) {
+                return t.getKind() == DocTree.Kind.RETURN;
+            }
+        },
+        THROWS {
+            @Override public boolean matches(DocTree t) {
+                return t.getKind() == DocTree.Kind.THROWS;
+            }
+        };
+
+        public abstract boolean matches(DocTree t);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java	Wed Nov 02 07:38:37 2016 +0100
@@ -0,0 +1,661 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.shellsupport.doc;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.InheritDocTree;
+import com.sun.source.doctree.ParamTree;
+import com.sun.source.doctree.ReturnTree;
+import com.sun.source.doctree.ThrowsTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.DocTreePath;
+import com.sun.source.util.DocTreeScanner;
+import com.sun.source.util.DocTrees;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacTaskImpl;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+import com.sun.tools.javac.util.Pair;
+
+/**Helper to find javadoc and resolve @inheritDoc.
+ */
+public abstract class JavadocHelper implements AutoCloseable {
+    private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+
+    /**Create the helper.
+     *
+     * @param mainTask JavacTask from which the further Elements originate
+     * @param sourceLocations paths where source files should be searched
+     * @return a JavadocHelper
+     */
+    public static JavadocHelper create(JavacTask mainTask, Collection<? extends Path> sourceLocations) {
+        StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
+        try {
+            fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourceLocations);
+            return new OnDemandJavadocHelper(mainTask, fm);
+        } catch (IOException ex) {
+            try {
+                fm.close();
+            } catch (IOException closeEx) {
+            }
+            return new JavadocHelper() {
+                @Override
+                public String getResolvedDocComment(Element forElement) throws IOException {
+                    return null;
+                }
+                @Override
+                public Element getSourceElement(Element forElement) throws IOException {
+                    return forElement;
+                }
+                @Override
+                public void close() throws IOException {}
+            };
+        }
+    }
+
+    /**Returns javadoc for the given element, if it can be found, or null otherwise. The javadoc
+     * will have @inheritDoc resolved.
+     *
+     * @param forElement element for which the javadoc should be searched
+     * @return javadoc if found, null otherwise
+     * @throws IOException if something goes wrong in the search
+     */
+    public abstract String getResolvedDocComment(Element forElement) throws IOException;
+
+    /**Returns an element representing the same given program element, but the returned element will
+     * be resolved from source, if it can be found. Returns the original element if the source for
+     * the given element cannot be found.
+     *
+     * @param forElement element for which the source element should be searched
+     * @return source element if found, the original element otherwise
+     * @throws IOException if something goes wrong in the search
+     */
+    public abstract Element getSourceElement(Element forElement) throws IOException;
+
+    /**Closes the helper.
+     *
+     * @throws IOException if something foes wrong during the close
+     */
+    @Override
+    public abstract void close() throws IOException;
+
+    private static final class OnDemandJavadocHelper extends JavadocHelper {
+        private final JavacTask mainTask;
+        private final JavaFileManager baseFileManager;
+        private final StandardJavaFileManager fm;
+        private final Map<String, Pair<JavacTask, TreePath>> signature2Source = new HashMap<>();
+
+        private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm) {
+            this.mainTask = mainTask;
+            this.baseFileManager = ((JavacTaskImpl) mainTask).getContext().get(JavaFileManager.class);
+            this.fm = fm;
+        }
+
+        @Override
+        public String getResolvedDocComment(Element forElement) throws IOException {
+            Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement);
+
+            if (sourceElement == null)
+                return null;
+
+            return getResolvedDocComment(sourceElement.fst, sourceElement.snd);
+        }
+
+        @Override
+        public Element getSourceElement(Element forElement) throws IOException {
+            Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement);
+
+            if (sourceElement == null)
+                return forElement;
+
+            Element result = Trees.instance(sourceElement.fst).getElement(sourceElement.snd);
+
+            if (result == null)
+                return forElement;
+
+            return result;
+        }
+
+        private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException {
+            DocTrees trees = DocTrees.instance(task);
+            Element element = trees.getElement(el);
+            String docComment = trees.getDocComment(el);
+
+            if (docComment == null && element.getKind() == ElementKind.METHOD) {
+                ExecutableElement executableElement = (ExecutableElement) element;
+                Iterable<Element> superTypes =
+                        () -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
+                for (Element sup : superTypes) {
+                   for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) {
+                       TypeElement clazz = (TypeElement) executableElement.getEnclosingElement();
+                       if (task.getElements().overrides(executableElement, supMethod, clazz)) {
+                           Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod);
+
+                           if (source != null) {
+                               String overriddenComment = getResolvedDocComment(source.fst, source.snd);
+
+                               if (overriddenComment != null) {
+                                   return overriddenComment;
+                               }
+                           }
+                       }
+                   }
+                }
+            }
+
+            DocCommentTree docCommentTree = parseDocComment(task, docComment);
+            IOException[] exception = new IOException[1];
+            Map<int[], String> replace = new TreeMap<>((span1, span2) -> span2[0] - span1[0]);
+
+            new DocTreeScanner<Void, Void>() {
+                private Stack<DocTree> interestingParent = new Stack<>();
+                private DocCommentTree dcTree;
+                private JavacTask inheritedJavacTask;
+                private TreePath inheritedTreePath;
+                private String inherited;
+                private Map<DocTree, String> syntheticTrees = new IdentityHashMap<>();
+                private long lastPos = 0;
+                @Override @DefinedBy(Api.COMPILER_TREE)
+                public Void visitDocComment(DocCommentTree node, Void p) {
+                    dcTree = node;
+                    interestingParent.push(node);
+                    try {
+                        scan(node.getFirstSentence(), p);
+                        scan(node.getBody(), p);
+                        List<DocTree> augmentedBlockTags = new ArrayList<>(node.getBlockTags());
+                        if (element.getKind() == ElementKind.METHOD) {
+                            ExecutableElement executableElement = (ExecutableElement) element;
+                            List<String> parameters =
+                                    executableElement.getParameters()
+                                                     .stream()
+                                                     .map(param -> param.getSimpleName().toString())
+                                                     .collect(Collectors.toList());
+                            List<String> throwsList =
+                                    executableElement.getThrownTypes()
+                                                     .stream()
+                                                     .map(exc -> exc.toString())
+                                                     .collect(Collectors.toList());
+                            Set<String> missingParams = new HashSet<>(parameters);
+                            Set<String> missingThrows = new HashSet<>(throwsList);
+                            boolean hasReturn = false;
+
+                            for (DocTree dt : augmentedBlockTags) {
+                                switch (dt.getKind()) {
+                                    case PARAM:
+                                        missingParams.remove(((ParamTree) dt).getName().getName().toString());
+                                        break;
+                                    case THROWS:
+                                        missingThrows.remove(getThrownException(task, el, docCommentTree, (ThrowsTree) dt));
+                                        break;
+                                    case RETURN:
+                                        hasReturn = true;
+                                        break;
+                                }
+                            }
+
+                            for (String missingParam : missingParams) {
+                                DocTree syntheticTag = parseBlockTag(task, "@param " + missingParam + " {@inheritDoc}");
+                                syntheticTrees.put(syntheticTag, "@param " + missingParam + " ");
+                                insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
+                            }
+
+                            for (String missingThrow : missingThrows) {
+                                DocTree syntheticTag = parseBlockTag(task, "@throws " + missingThrow + " {@inheritDoc}");
+                                syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " ");
+                                insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
+                            }
+
+                            if (!hasReturn) {
+                                DocTree syntheticTag = parseBlockTag(task, "@return {@inheritDoc}");
+                                syntheticTrees.put(syntheticTag, "@return ");
+                                insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
+                            }
+                        }
+                        scan(augmentedBlockTags, p);
+                        return null;
+                    } finally {
+                        interestingParent.pop();
+                    }
+                }
+                @Override @DefinedBy(Api.COMPILER_TREE)
+                public Void visitParam(ParamTree node, Void p) {
+                    interestingParent.push(node);
+                    try {
+                        return super.visitParam(node, p);
+                    } finally {
+                        interestingParent.pop();
+                    }
+                }
+                @Override @DefinedBy(Api.COMPILER_TREE)
+                public Void visitThrows(ThrowsTree node, Void p) {
+                    interestingParent.push(node);
+                    try {
+                        return super.visitThrows(node, p);
+                    } finally {
+                        interestingParent.pop();
+                    }
+                }
+                @Override @DefinedBy(Api.COMPILER_TREE)
+                public Void visitReturn(ReturnTree node, Void p) {
+                    interestingParent.push(node);
+                    try {
+                        return super.visitReturn(node, p);
+                    } finally {
+                        interestingParent.pop();
+                    }
+                }
+                @Override @DefinedBy(Api.COMPILER_TREE)
+                public Void visitInheritDoc(InheritDocTree node, Void p) {
+                    if (inherited == null) {
+                        try {
+                            if (element.getKind() == ElementKind.METHOD) {
+                                ExecutableElement executableElement = (ExecutableElement) element;
+                                Iterable<Element> superTypes = () -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
+                                OUTER: for (Element sup : superTypes) {
+                                   for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) {
+                                       if (task.getElements().overrides(executableElement, supMethod, (TypeElement) executableElement.getEnclosingElement())) {
+                                           Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod);
+
+                                           if (source != null) {
+                                               String overriddenComment = getResolvedDocComment(source.fst, source.snd);
+
+                                               if (overriddenComment != null) {
+                                                   inheritedJavacTask = source.fst;
+                                                   inheritedTreePath = source.snd;
+                                                   inherited = overriddenComment;
+                                                   break OUTER;
+                                               }
+                                           }
+                                       }
+                                   }
+                                }
+                            }
+                        } catch (IOException ex) {
+                            exception[0] = ex;
+                            return null;
+                        }
+                    }
+                    if (inherited == null) {
+                        return null;
+                    }
+                    DocCommentTree inheritedDocTree = parseDocComment(inheritedJavacTask, inherited);
+                    List<List<? extends DocTree>> inheritedText = new ArrayList<>();
+                    DocTree parent = interestingParent.peek();
+                    switch (parent.getKind()) {
+                        case DOC_COMMENT:
+                            inheritedText.add(inheritedDocTree.getFullBody());
+                            break;
+                        case PARAM:
+                            String paramName = ((ParamTree) parent).getName().getName().toString();
+                            new DocTreeScanner<Void, Void>() {
+                                @Override @DefinedBy(Api.COMPILER_TREE)
+                                public Void visitParam(ParamTree node, Void p) {
+                                    if (node.getName().getName().contentEquals(paramName)) {
+                                        inheritedText.add(node.getDescription());
+                                    }
+                                    return super.visitParam(node, p);
+                                }
+                            }.scan(inheritedDocTree, null);
+                            break;
+                        case THROWS:
+                            String thrownName = getThrownException(task, el, docCommentTree, (ThrowsTree) parent);
+                            new DocTreeScanner<Void, Void>() {
+                                @Override @DefinedBy(Api.COMPILER_TREE)
+                                public Void visitThrows(ThrowsTree node, Void p) {
+                                    if (Objects.equals(getThrownException(inheritedJavacTask, inheritedTreePath, inheritedDocTree, node), thrownName)) {
+                                        inheritedText.add(node.getDescription());
+                                    }
+                                    return super.visitThrows(node, p);
+                                }
+                            }.scan(inheritedDocTree, null);
+                            break;
+                        case RETURN:
+                            new DocTreeScanner<Void, Void>() {
+                                @Override @DefinedBy(Api.COMPILER_TREE)
+                                public Void visitReturn(ReturnTree node, Void p) {
+                                    inheritedText.add(node.getDescription());
+                                    return super.visitReturn(node, p);
+                                }
+                            }.scan(inheritedDocTree, null);
+                            break;
+                    }
+                    if (!inheritedText.isEmpty()) {
+                        long offset = trees.getSourcePositions().getStartPosition(null, inheritedDocTree, inheritedDocTree);
+                        long start = Long.MAX_VALUE;
+                        long end = Long.MIN_VALUE;
+
+                        for (DocTree t : inheritedText.get(0)) {
+                            start = Math.min(start, trees.getSourcePositions().getStartPosition(null, inheritedDocTree, t) - offset);
+                            end   = Math.max(end,   trees.getSourcePositions().getEndPosition(null, inheritedDocTree, t) - offset);
+                        }
+                        String text = inherited.substring((int) start, (int) end);
+
+                        if (syntheticTrees.containsKey(parent)) {
+                            replace.put(new int[] {(int) lastPos + 1, (int) lastPos}, "\n" + syntheticTrees.get(parent) + text);
+                        } else {
+                            long inheritedStart = trees.getSourcePositions().getStartPosition(null, dcTree, node);
+                            long inheritedEnd   = trees.getSourcePositions().getEndPosition(null, dcTree, node);
+
+                            replace.put(new int[] {(int) inheritedStart, (int) inheritedEnd}, text);
+                        }
+                    }
+                    return super.visitInheritDoc(node, p);
+                }
+                private boolean inSynthetic;
+                @Override @DefinedBy(Api.COMPILER_TREE)
+                public Void scan(DocTree tree, Void p) {
+                    if (exception[0] != null) {
+                        return null;
+                    }
+                    boolean prevInSynthetic = inSynthetic;
+                    try {
+                        inSynthetic |= syntheticTrees.containsKey(tree);
+                        return super.scan(tree, p);
+                    } finally {
+                        if (!inSynthetic) {
+                            lastPos = trees.getSourcePositions().getEndPosition(null, dcTree, tree);
+                        }
+                        inSynthetic = prevInSynthetic;
+                    }
+                }
+
+                private void insertTag(List<DocTree> tags, DocTree toInsert, List<String> parameters, List<String> throwsTypes) {
+                    Comparator<DocTree> comp = (tag1, tag2) -> {
+                        if (tag1.getKind() == tag2.getKind()) {
+                            switch (toInsert.getKind()) {
+                                case PARAM: {
+                                    ParamTree p1 = (ParamTree) tag1;
+                                    ParamTree p2 = (ParamTree) tag2;
+                                    int i1 = parameters.indexOf(p1.getName().getName().toString());
+                                    int i2 = parameters.indexOf(p2.getName().getName().toString());
+
+                                    return i1 - i2;
+                                }
+                                case THROWS: {
+                                    ThrowsTree t1 = (ThrowsTree) tag1;
+                                    ThrowsTree t2 = (ThrowsTree) tag2;
+                                    int i1 = throwsTypes.indexOf(getThrownException(task, el, docCommentTree, t1));
+                                    int i2 = throwsTypes.indexOf(getThrownException(task, el, docCommentTree, t2));
+
+                                    return i1 - i2;
+                                }
+                            }
+                        }
+
+                        int i1 = tagOrder.indexOf(tag1.getKind());
+                        int i2 = tagOrder.indexOf(tag2.getKind());
+
+                        return i1 - i2;
+                    };
+
+                    for (int i = 0; i < tags.size(); i++) {
+                        if (comp.compare(tags.get(i), toInsert) >= 0) {
+                            tags.add(i, toInsert);
+                            return ;
+                        }
+                    }
+                    tags.add(toInsert);
+                }
+
+                private final List<DocTree.Kind> tagOrder = Arrays.asList(DocTree.Kind.PARAM, DocTree.Kind.THROWS, DocTree.Kind.RETURN);
+            }.scan(docCommentTree, null);
+
+            if (replace.isEmpty())
+                return docComment;
+
+            StringBuilder replacedInheritDoc = new StringBuilder(docComment);
+            int offset = (int) trees.getSourcePositions().getStartPosition(null, docCommentTree, docCommentTree);
+
+            for (Entry<int[], String> e : replace.entrySet()) {
+                replacedInheritDoc.delete(e.getKey()[0] - offset, e.getKey()[1] - offset + 1);
+                replacedInheritDoc.insert(e.getKey()[0] - offset, e.getValue());
+            }
+
+            return replacedInheritDoc.toString();
+        }
+
+        private Stream<Element> superTypeForInheritDoc(JavacTask task, Element type) {
+            TypeElement clazz = (TypeElement) type;
+            Stream<Element> result = interfaces(clazz);
+            result = Stream.concat(result, interfaces(clazz).flatMap(el -> superTypeForInheritDoc(task, el)));
+
+            if (clazz.getSuperclass().getKind() == TypeKind.DECLARED) {
+                Element superClass = ((DeclaredType) clazz.getSuperclass()).asElement();
+                result = Stream.concat(result, Stream.of(superClass));
+                result = Stream.concat(result, superTypeForInheritDoc(task, superClass));
+            }
+
+            return result;
+        }
+        //where:
+            private Stream<Element> interfaces(TypeElement clazz) {
+                return clazz.getInterfaces()
+                            .stream()
+                            .filter(tm -> tm.getKind() == TypeKind.DECLARED)
+                            .map(tm -> ((DeclaredType) tm).asElement());
+            }
+
+         private DocTree parseBlockTag(JavacTask task, String blockTag) {
+            DocCommentTree dc = parseDocComment(task, blockTag);
+
+            return dc.getBlockTags().get(0);
+        }
+
+        private DocCommentTree parseDocComment(JavacTask task, String javadoc) {
+            DocTrees trees = DocTrees.instance(task);
+            try {
+                return trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), javax.tools.JavaFileObject.Kind.HTML) {
+                    @Override @DefinedBy(Api.COMPILER)
+                    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+                        return "<body>" + javadoc + "</body>";
+                    }
+                });
+            } catch (URISyntaxException ex) {
+                return null;
+            }
+        }
+
+        private String getThrownException(JavacTask task, TreePath rootOn, DocCommentTree comment, ThrowsTree tt) {
+            DocTrees trees = DocTrees.instance(task);
+            Element exc = trees.getElement(new DocTreePath(new DocTreePath(rootOn, comment), tt.getExceptionName()));
+            return exc != null ? exc.toString() : null;
+        }
+
+        private Pair<JavacTask, TreePath> getSourceElement(JavacTask origin, Element el) throws IOException {
+            String handle = elementSignature(el);
+            Pair<JavacTask, TreePath> cached = signature2Source.get(handle);
+
+            if (cached != null) {
+                return cached.fst != null ? cached : null;
+            }
+
+            TypeElement type = topLevelType(el);
+
+            if (type == null)
+                return null;
+
+            String binaryName = origin.getElements().getBinaryName(type).toString();
+            Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName);
+
+            if (source == null)
+                return null;
+
+            fillElementCache(source.fst, source.snd);
+
+            cached = signature2Source.get(handle);
+
+            if (cached != null) {
+                return cached;
+            } else {
+                signature2Source.put(handle, Pair.of(null, null));
+                return null;
+            }
+        }
+        //where:
+            private String elementSignature(Element el) {
+                switch (el.getKind()) {
+                    case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE:
+                        return ((TypeElement) el).getQualifiedName().toString();
+                    case FIELD:
+                        return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
+                    case ENUM_CONSTANT:
+                        return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName();
+                    case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
+                        return el.getSimpleName() + ":" + el.asType();
+                    case CONSTRUCTOR: case METHOD:
+                        StringBuilder header = new StringBuilder();
+                        header.append(elementSignature(el.getEnclosingElement()));
+                        if (el.getKind() == ElementKind.METHOD) {
+                            header.append(".");
+                            header.append(el.getSimpleName());
+                        }
+                        header.append("(");
+                        String sep = "";
+                        ExecutableElement method = (ExecutableElement) el;
+                        for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) {
+                            VariableElement p = i.next();
+                            header.append(sep);
+                            header.append(p.asType());
+                            sep = ", ";
+                        }
+                        header.append(")");
+                        return header.toString();
+                   default:
+                        return el.toString();
+                }
+            }
+
+            private TypeElement topLevelType(Element el) {
+                if (el.getKind() == ElementKind.PACKAGE)
+                    return null;
+
+                while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
+                    el = el.getEnclosingElement();
+                }
+
+                return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null;
+            }
+
+            private void fillElementCache(JavacTask task, CompilationUnitTree cut) throws IOException {
+                Trees trees = Trees.instance(task);
+
+                new TreePathScanner<Void, Void>() {
+                    @Override @DefinedBy(Api.COMPILER_TREE)
+                    public Void visitMethod(MethodTree node, Void p) {
+                        handleDeclaration();
+                        return null;
+                    }
+
+                    @Override @DefinedBy(Api.COMPILER_TREE)
+                    public Void visitClass(ClassTree node, Void p) {
+                        handleDeclaration();
+                        return super.visitClass(node, p);
+                    }
+
+                    @Override @DefinedBy(Api.COMPILER_TREE)
+                    public Void visitVariable(VariableTree node, Void p) {
+                        handleDeclaration();
+                        return super.visitVariable(node, p);
+                    }
+
+                    private void handleDeclaration() {
+                        Element currentElement = trees.getElement(getCurrentPath());
+
+                        if (currentElement != null) {
+                            signature2Source.put(elementSignature(currentElement), Pair.of(task, getCurrentPath()));
+                        }
+                    }
+                }.scan(cut, null);
+            }
+
+        private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException {
+            JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH,
+                                                        binaryName,
+                                                        JavaFileObject.Kind.SOURCE);
+
+            if (jfo == null)
+                return null;
+
+            List<JavaFileObject> jfos = Arrays.asList(jfo);
+            JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, baseFileManager, d -> {}, null, null, jfos);
+            Iterable<? extends CompilationUnitTree> cuts = task.parse();
+
+            task.enter();
+
+            return Pair.of(task, cuts.iterator().next());
+        }
+
+        @Override
+        public void close() throws IOException {
+            fm.close();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties	Wed Nov 02 07:38:37 2016 +0100
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+CAP_TypeParameters=Type Parameters:
+CAP_Parameters=Parameters:
+CAP_Returns=Returns:
+CAP_Thrown_Exceptions=Thrown Exceptions:
--- a/langtools/src/jdk.compiler/share/classes/module-info.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.compiler/share/classes/module-info.java	Wed Nov 02 07:38:37 2016 +0100
@@ -65,6 +65,9 @@
         jdk.jdeps,
         jdk.javadoc,
         jdk.jshell;
+    exports jdk.internal.shellsupport.doc to
+        jdk.jshell,
+        jdk.scripting.nashorn.shell;
 
     uses javax.annotation.processing.Processor;
     uses com.sun.source.util.Plugin;
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Wed Nov 02 07:38:37 2016 +0100
@@ -25,6 +25,7 @@
 
 package jdk.internal.jshell.tool;
 
+import jdk.jshell.SourceCodeAnalysis.Documentation;
 import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
 import jdk.jshell.SourceCodeAnalysis.Suggestion;
 
@@ -34,27 +35,30 @@
 import java.io.InterruptedIOException;
 import java.io.PrintStream;
 import java.io.UncheckedIOException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.function.Supplier;
+import java.util.function.Function;
 import java.util.prefs.BackingStoreException;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import jdk.internal.shellsupport.doc.JavadocFormatter;
 import jdk.internal.jline.NoInterruptUnixTerminal;
 import jdk.internal.jline.Terminal;
 import jdk.internal.jline.TerminalFactory;
 import jdk.internal.jline.TerminalSupport;
 import jdk.internal.jline.WindowsTerminal;
 import jdk.internal.jline.console.ConsoleReader;
+import jdk.internal.jline.console.CursorBuffer;
 import jdk.internal.jline.console.KeyMap;
 import jdk.internal.jline.console.UserInterruptException;
 import jdk.internal.jline.console.completer.Completer;
@@ -259,22 +263,118 @@
         "\u001BO3P" //Alt-F1 (Linux)
     };
 
+    private String lastDocumentationBuffer;
+    private int lastDocumentationCursor = (-1);
+
     private void documentation(JShellTool repl) {
         String buffer = in.getCursorBuffer().buffer.toString();
         int cursor = in.getCursorBuffer().cursor;
-        String doc;
+        boolean firstInvocation = !buffer.equals(lastDocumentationBuffer) || cursor != lastDocumentationCursor;
+        lastDocumentationBuffer = buffer;
+        lastDocumentationCursor = cursor;
+        List<String> doc;
+        String seeMore;
+        Terminal term = in.getTerminal();
         if (prefix.isEmpty() && buffer.trim().startsWith("/")) {
-            doc = repl.commandDocumentation(buffer, cursor);
+            doc = Arrays.asList(repl.commandDocumentation(buffer, cursor, firstInvocation));
+            seeMore = "jshell.console.see.help";
         } else {
-            doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length());
+            JavadocFormatter formatter = new JavadocFormatter(term.getWidth(),
+                                                              term.isAnsiSupported());
+            Function<Documentation, String> convertor;
+            if (firstInvocation) {
+                convertor = d -> d.signature();
+            } else {
+                convertor = d -> formatter.formatJavadoc(d.signature(),
+                                                         d.javadoc() != null ? d.javadoc()
+                                                                             : repl.messageFormat("jshell.console.no.javadoc"));
+            }
+            doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length(), !firstInvocation)
+                               .stream()
+                               .map(convertor)
+                               .collect(Collectors.toList());
+            seeMore = "jshell.console.see.javadoc";
         }
 
         try {
-            if (doc != null) {
-                in.println();
-                in.println(doc);
-                in.redrawLine();
-                in.flush();
+            if (doc != null && !doc.isEmpty()) {
+                if (firstInvocation) {
+                    in.println();
+                    in.println(doc.stream().collect(Collectors.joining("\n")));
+                    in.println(repl.messageFormat(seeMore));
+                    in.redrawLine();
+                    in.flush();
+                } else {
+                    in.println();
+
+                    int height = term.getHeight();
+                    String lastNote = "";
+
+                    PRINT_DOC: for (Iterator<String> docIt = doc.iterator(); docIt.hasNext(); ) {
+                        String currentDoc = docIt.next();
+                        String[] lines = currentDoc.split("\n");
+                        int firstLine = 0;
+
+                        PRINT_PAGE: while (true) {
+                            int toPrint = height - 1;
+
+                            while (toPrint > 0 && firstLine < lines.length) {
+                                in.println(lines[firstLine++]);
+                                toPrint--;
+                            }
+
+                            if (firstLine >= lines.length) {
+                                break;
+                            }
+
+                            lastNote = repl.getResourceString("jshell.console.see.next.page");
+                            in.print(lastNote + ConsoleReader.RESET_LINE);
+                            in.flush();
+
+                            while (true) {
+                                int r = in.readCharacter();
+
+                                switch (r) {
+                                    case ' ': continue PRINT_PAGE;
+                                    case 'q':
+                                    case 3:
+                                        break PRINT_DOC;
+                                    default:
+                                        in.beep();
+                                        break;
+                                }
+                            }
+                        }
+
+                        if (docIt.hasNext()) {
+                            lastNote = repl.getResourceString("jshell.console.see.next.javadoc");
+                            in.print(lastNote + ConsoleReader.RESET_LINE);
+                            in.flush();
+
+                            while (true) {
+                                int r = in.readCharacter();
+
+                                switch (r) {
+                                    case ' ': continue PRINT_DOC;
+                                    case 'q':
+                                    case 3:
+                                        break PRINT_DOC;
+                                    default:
+                                        in.beep();
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                    //clear the "press space" line:
+                    in.getCursorBuffer().buffer.replace(0, buffer.length(), lastNote);
+                    in.getCursorBuffer().cursor = 0;
+                    in.killLine();
+                    in.getCursorBuffer().buffer.append(buffer);
+                    in.getCursorBuffer().cursor = cursor;
+                    in.redrawLine();
+                    in.flush();
+                }
             } else {
                 in.beep();
             }
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Wed Nov 02 07:38:37 2016 +0100
@@ -1308,7 +1308,7 @@
         return commandCompletions.completionSuggestions(code, cursor, anchor);
     }
 
-    public String commandDocumentation(String code, int cursor) {
+    public String commandDocumentation(String code, int cursor, boolean shortDescription) {
         code = code.substring(0, cursor);
         int space = code.indexOf(' ');
 
@@ -1316,7 +1316,7 @@
             String cmd = code.substring(0, space);
             Command command = commands.get(cmd);
             if (command != null) {
-                return getResourceString(command.helpKey + ".summary");
+                return getResourceString(command.helpKey + (shortDescription ? ".summary" : ""));
             }
         }
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Wed Nov 02 07:38:37 2016 +0100
@@ -145,6 +145,11 @@
 jshell.err.retained.mode.failure = Failure in retained modes (modes cleared) -- {0} {1}
 
 jshell.console.see.more = <press tab to see more>
+jshell.console.see.javadoc = <press shift-tab again to see javadoc>
+jshell.console.see.help = <press shift-tab again to see detailed help>
+jshell.console.see.next.page = -- Press space for next page, Q to quit. --
+jshell.console.see.next.javadoc = -- Press space for next javadoc, Q to quit. --
+jshell.console.no.javadoc = <no javadoc found>
 jshell.console.do.nothing = Do nothing
 jshell.console.choice = Choice: \
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Wed Nov 02 07:38:37 2016 +0100
@@ -506,6 +506,9 @@
         if (!closed) {
             closeDown();
             executionControl().close();
+            if (sourceCodeAnalysis != null) {
+                sourceCodeAnalysis.close();
+            }
         }
     }
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java	Wed Nov 02 07:38:37 2016 +0100
@@ -63,12 +63,16 @@
     public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
 
     /**
-     * Compute a description/help string for the given user's input.
+     * Compute documentation for the given user's input. Multiple {@code Documentation} objects may
+     * be returned when multiple elements match the user's input (like for overloaded methods).
      * @param input the snippet the user wrote so far
      * @param cursor the current position of the cursors in the given {@code input} text
-     * @return description/help string for the given user's input
+     * @param computeJavadoc true if the javadoc for the given input should be computed in
+     *                       addition to the signature
+     * @return the documentations for the given user's input, if multiple elements match the input,
+     *         multiple {@code Documentation} objects are returned.
      */
-    public abstract String documentation(String input, int cursor);
+    public abstract List<Documentation> documentation(String input, int cursor, boolean computeJavadoc);
 
     /**
      * Infer the type of the given expression. The expression spans from the beginning of {@code code}
@@ -266,6 +270,26 @@
     }
 
     /**
+     * A documentation for a candidate for continuation of the given user's input.
+     */
+    public interface Documentation {
+
+        /**
+         * The signature of the given element.
+         *
+         * @return the signature
+         */
+        String signature();
+
+        /**
+         * The javadoc of the given element.
+         *
+         * @return the javadoc, or null if not found or not requested
+         */
+        String javadoc();
+    }
+
+    /**
      * List of possible qualified names.
      */
     public static final class QualifiedNames {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Wed Nov 02 07:38:37 2016 +0100
@@ -42,19 +42,17 @@
 import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.TypeParameterTree;
 import com.sun.source.tree.VariableTree;
-import com.sun.source.util.JavacTask;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.TreePathScanner;
-import com.sun.source.util.Trees;
 import com.sun.tools.javac.api.JavacScope;
-import com.sun.tools.javac.api.JavacTaskImpl;
 import com.sun.tools.javac.code.Flags;
 import com.sun.tools.javac.code.Symbol.CompletionFailure;
 import com.sun.tools.javac.code.Symbol.VarSymbol;
 import com.sun.tools.javac.code.Symtab;
 import com.sun.tools.javac.code.Type;
 import com.sun.tools.javac.code.Type.ClassType;
+import jdk.internal.shellsupport.doc.JavadocHelper;
 import com.sun.tools.javac.util.Name;
 import com.sun.tools.javac.util.Names;
 import com.sun.tools.javac.util.Pair;
@@ -105,6 +103,7 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toCollection;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
@@ -123,15 +122,10 @@
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.util.ElementFilter;
 import javax.lang.model.util.Types;
-import javax.tools.JavaCompiler;
 import javax.tools.JavaFileManager.Location;
-import javax.tools.JavaFileObject;
-import javax.tools.StandardJavaFileManager;
 import javax.tools.StandardLocation;
-import javax.tools.ToolProvider;
 
 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
-import static java.util.stream.Collectors.joining;
 import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
 import static jdk.jshell.TreeDissector.printType;
 
@@ -151,6 +145,7 @@
 
     private final JShell proc;
     private final CompletenessAnalyzer ca;
+    private final List<AutoCloseable> closeables = new ArrayList<>();
     private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
     private int indexVersion;
     private int classpathVersion;
@@ -1097,10 +1092,10 @@
     }
 
     @Override
-    public String documentation(String code, int cursor) {
+    public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) {
         suspendIndexing();
         try {
-            return documentationImpl(code, cursor);
+            return documentationImpl(code, cursor, computeJavadoc);
         } finally {
             resumeIndexing();
         }
@@ -1112,14 +1107,14 @@
         "-parameters"
     };
 
-    private String documentationImpl(String code, int cursor) {
+    private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
         code = code.substring(0, cursor);
         if (code.trim().isEmpty()) { //TODO: comment handling
             code += ";";
         }
 
         if (guessKind(code) == Kind.IMPORT)
-            return null;
+            return Collections.<Documentation>emptyList();
 
         OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
         AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
@@ -1128,46 +1123,120 @@
         TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
 
         if (tp == null)
-            return null;
+            return Collections.<Documentation>emptyList();
 
         TreePath prevPath = null;
-        while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) {
+        while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION &&
+               tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER &&
+               tp.getLeaf().getKind() != Kind.MEMBER_SELECT) {
             prevPath = tp;
             tp = tp.getParentPath();
         }
 
         if (tp == null)
-            return null;
+            return Collections.<Documentation>emptyList();
 
+        Stream<Element> elements;
         Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
         List<? extends ExpressionTree> arguments;
 
-        if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
-            MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
-            candidates = methodCandidates(at, tp);
-            arguments = mit.getArguments();
+        if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
+            if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
+                MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
+                candidates = methodCandidates(at, tp);
+                arguments = mit.getArguments();
+            } else {
+                NewClassTree nct = (NewClassTree) tp.getLeaf();
+                candidates = newClassCandidates(at, tp);
+                arguments = nct.getArguments();
+            }
+
+            if (!isEmptyArgumentsContext(arguments)) {
+                List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
+                List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
+
+                candidates =
+                        this.filterExecutableTypesByArguments(at, candidates, fullActuals)
+                            .stream()
+                            .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
+                            .collect(Collectors.toList());
+            }
+
+            elements = Util.stream(candidates).map(method -> method.fst);
+        } else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
+            Element el = at.trees().getElement(tp);
+
+            if (el == null ||
+                el.asType().getKind() == TypeKind.ERROR ||
+                (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
+                //erroneous element:
+                return Collections.<Documentation>emptyList();
+            }
+
+            elements = Stream.of(el);
         } else {
-            NewClassTree nct = (NewClassTree) tp.getLeaf();
-            candidates = newClassCandidates(at, tp);
-            arguments = nct.getArguments();
+            return Collections.<Documentation>emptyList();
+        }
+
+        List<Documentation> result = Collections.emptyList();
+
+        try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
+            result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
+                             .filter(r -> r != null)
+                             .collect(Collectors.toList());
+        } catch (IOException ex) {
+            proc.debug(ex, "JavadocHelper.close()");
         }
 
-        if (!isEmptyArgumentsContext(arguments)) {
-            List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
-            List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
+        return result;
+    }
+
+    private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) {
+        String javadoc = null;
+        try {
+            if (hasSyntheticParameterNames(el)) {
+                el = helper.getSourceElement(el);
+            }
+            if (computeJavadoc) {
+                javadoc = helper.getResolvedDocComment(el);
+            }
+        } catch (IOException ex) {
+            proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
+        }
+        String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true));
+        return new DocumentationImpl(signature,  javadoc);
+    }
 
-            candidates =
-                    this.filterExecutableTypesByArguments(at, candidates, fullActuals)
-                        .stream()
-                        .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
-                        .collect(Collectors.toList());
+    public void close() {
+        for (AutoCloseable closeable : closeables) {
+            try {
+                closeable.close();
+            } catch (Exception ex) {
+                proc.debug(ex, "SourceCodeAnalysisImpl.close()");
+            }
+        }
+    }
+
+    private static final class DocumentationImpl implements Documentation {
+
+        private final String signature;
+        private final String javadoc;
+
+        public DocumentationImpl(String signature, String javadoc) {
+            this.signature = signature;
+            this.javadoc = javadoc;
         }
 
-        try (SourceCache sourceCache = new SourceCache(at)) {
-            return Util.stream(candidates)
-                    .map(method -> Util.expunge(element2String(sourceCache, method.fst)))
-                    .collect(joining("\n"));
+        @Override
+        public String signature() {
+            return signature;
         }
+
+        @Override
+        public String javadoc() {
+            return javadoc;
+        }
+
     }
 
     private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
@@ -1178,18 +1247,6 @@
         return false;
     }
 
-    private String element2String(SourceCache sourceCache, Element el) {
-        try {
-            if (hasSyntheticParameterNames(el)) {
-                el = sourceCache.getSourceMethod(el);
-            }
-        } catch (IOException ex) {
-            proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
-        }
-
-        return Util.expunge(elementHeader(sourceCache.originalTask, el, !hasSyntheticParameterNames(el)));
-    }
-
     private boolean hasSyntheticParameterNames(Element el) {
         if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
             return false;
@@ -1204,119 +1261,6 @@
                  .allMatch(param -> param.getSimpleName().toString().startsWith("arg"));
     }
 
-    private final class SourceCache implements AutoCloseable {
-        private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
-        private final Map<String, Map<String, Element>> topLevelName2Signature2Method = new HashMap<>();
-        private final AnalyzeTask originalTask;
-        private final StandardJavaFileManager fm;
-
-        public SourceCache(AnalyzeTask originalTask) {
-            this.originalTask = originalTask;
-            List<Path> sources = findSources();
-            if (sources.iterator().hasNext()) {
-                StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
-                try {
-                    fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sources);
-                } catch (IOException ex) {
-                    proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.<init>(...)");
-                    try {
-                        fm.close();
-                    } catch (IOException closeEx) {
-                        proc.debug(closeEx, "SourceCodeAnalysisImpl.SourceCache.close()");
-                    }
-                    fm = null;
-                }
-                this.fm = fm;
-            } else {
-                //don't waste time if there are no sources
-                this.fm = null;
-            }
-        }
-
-        public Element getSourceMethod(Element method) throws IOException {
-            if (fm == null)
-                return method;
-
-            TypeElement type = topLevelType(method);
-
-            if (type == null)
-                return method;
-
-            String binaryName = originalTask.task.getElements().getBinaryName(type).toString();
-
-            Map<String, Element> cache = topLevelName2Signature2Method.get(binaryName);
-
-            if (cache == null) {
-                topLevelName2Signature2Method.put(binaryName, cache = createMethodCache(binaryName));
-            }
-
-            String handle = elementHeader(originalTask, method, false);
-
-            return cache.getOrDefault(handle, method);
-        }
-
-        private TypeElement topLevelType(Element el) {
-            while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
-                el = el.getEnclosingElement();
-            }
-
-            return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null;
-        }
-
-        private Map<String, Element> createMethodCache(String binaryName) throws IOException {
-            Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName);
-
-            if (source == null)
-                return Collections.emptyMap();
-
-            Map<String, Element> signature2Method = new HashMap<>();
-            Trees trees = Trees.instance(source.fst);
-
-            new TreePathScanner<Void, Void>() {
-                @Override
-                public Void visitMethod(MethodTree node, Void p) {
-                    Element currentMethod = trees.getElement(getCurrentPath());
-
-                    if (currentMethod != null) {
-                        signature2Method.put(elementHeader(originalTask, currentMethod, false), currentMethod);
-                    }
-
-                    return null;
-                }
-            }.scan(source.snd, null);
-
-            return signature2Method;
-        }
-
-        private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException {
-            JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH,
-                                                        binaryName,
-                                                        JavaFileObject.Kind.SOURCE);
-
-            if (jfo == null)
-                return null;
-
-            List<JavaFileObject> jfos = Arrays.asList(jfo);
-            JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, d -> {}, null, null, jfos);
-            Iterable<? extends CompilationUnitTree> cuts = task.parse();
-
-            task.enter();
-
-            return Pair.of(task, cuts.iterator().next());
-        }
-
-        @Override
-        public void close() {
-            try {
-                if (fm != null) {
-                    fm.close();
-                }
-            } catch (IOException ex) {
-                proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.close()");
-            }
-        }
-    }
-
     private List<Path> availableSources;
 
     private List<Path> findSources() {
@@ -1328,25 +1272,59 @@
         Path srcZip = home.resolve("src.zip");
         if (!Files.isReadable(srcZip))
             srcZip = home.getParent().resolve("src.zip");
-        if (Files.isReadable(srcZip))
-            result.add(srcZip);
+        if (Files.isReadable(srcZip)) {
+            boolean keepOpen = false;
+            FileSystem zipFO = null;
+
+            try {
+                URI uri = URI.create("jar:" + srcZip.toUri());
+                zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap());
+                Path root = zipFO.getRootDirectories().iterator().next();
+
+                if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
+                    //non-modular format:
+                    result.add(srcZip);
+                } else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
+                    //modular format:
+                    try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+                        for (Path p : ds) {
+                            if (Files.isDirectory(p)) {
+                                result.add(p);
+                            }
+                        }
+                    }
+
+                    keepOpen = true;
+                }
+            } catch (IOException ex) {
+                proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
+            } finally {
+                if (zipFO != null) {
+                    if (keepOpen) {
+                        closeables.add(zipFO);
+                    } else {
+                        try {
+                            zipFO.close();
+                        } catch (IOException ex) {
+                            proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
+                        }
+                    }
+                }
+            }
+        }
         return availableSources = result;
     }
 
-    private String elementHeader(AnalyzeTask at, Element el) {
-        return elementHeader(at, el, true);
-    }
-
-    private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames) {
+    private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) {
         switch (el.getKind()) {
             case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
                 TypeElement type = (TypeElement)el;
                 String fullname = type.getQualifiedName().toString();
                 Element pkg = at.getElements().getPackageOf(el);
-                String name = pkg == null ? fullname :
+                String name = pkg == null || useFQN ? fullname :
                         proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString());
 
-                return name + typeParametersOpt(at, type.getTypeParameters());
+                return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames);
             }
             case TYPE_PARAMETER: {
                 TypeParameterElement tp = (TypeParameterElement)el;
@@ -1363,9 +1341,9 @@
                                 .collect(joining(" & "));
             }
             case FIELD:
-                return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
+                return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName() + ":" + el.asType();
             case ENUM_CONSTANT:
-                return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName();
+                return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName();
             case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
                 return el.getSimpleName() + ":" + el.asType();
             case CONSTRUCTOR: case METHOD: {
@@ -1379,20 +1357,20 @@
                     header.append(printType(at, proc, method.getReturnType())).append(" ");
                 } else {
                     // type parameters for the constructor
-                    String typeParameters = typeParametersOpt(at, method.getTypeParameters());
+                    String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames);
                     if (!typeParameters.isEmpty()) {
                         header.append(typeParameters).append(" ");
                     }
                 }
 
                 // receiver type
-                String clazz = elementHeader(at, el.getEnclosingElement());
+                String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false);
                 header.append(clazz);
 
                 if (isMethod) {
                     //method name with type parameters
                     (clazz.isEmpty() ? header : header.append("."))
-                            .append(typeParametersOpt(at, method.getTypeParameters()))
+                            .append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames))
                             .append(el.getSimpleName());
                 }
 
@@ -1435,10 +1413,10 @@
         }
         return arrayType;
     }
-    private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters) {
+    private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters, boolean includeParameterNames) {
         return typeParameters.isEmpty() ? ""
                 : typeParameters.stream()
-                        .map(tp -> elementHeader(at, tp))
+                        .map(tp -> elementHeader(at, tp, includeParameterNames, false))
                         .collect(joining(", ", "<", ">"));
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/internal/shellsupport/doc/JavadocFormatterTest.java	Wed Nov 02 07:38:37 2016 +0100
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8131019
+ * @summary Test JavadocFormatter
+ * @library /tools/lib
+ * @modules jdk.compiler/jdk.internal.shellsupport.doc
+ * @run testng JavadocFormatterTest
+ */
+
+import java.util.Objects;
+
+import jdk.internal.shellsupport.doc.JavadocFormatter;
+import org.testng.annotations.Test;
+
+@Test
+public class JavadocFormatterTest {
+
+    private static final String CODE_RESET = "\033[0m";
+    private static final String CODE_HIGHLIGHT = "\033[1m";
+    private static final String CODE_UNDERLINE = "\033[4m";
+
+    public void testReflow() {
+        String actual;
+        String expected;
+
+        actual = new JavadocFormatter(25, true).formatJavadoc(
+                "test",
+                "1234 1234\n1234\n1234 12345 123456789012345678901234567890 1234 1234\n1234 {@code 1234} 1234 1234\n1234 1234 123456 123456\n<b>123456</b>\n123456 123456 {@link String string} 1");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "1234 1234 1234 1234 12345\n" +
+                   "123456789012345678901234567890\n" +
+                   "1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 123456\n" +
+                   "123456 123456 123456\n" +
+                   "123456 string 1\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "@param <T> 51234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                "@param <E> 61234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                "@param shortName 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
+                "@param aVeryLongName1234567890123456789012345678901234567890 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "\n" +
+                   CODE_UNDERLINE + "Type Parameters:" + CODE_RESET + "\n" +
+                   "T - 51234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "E - 61234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "\n" +
+                   CODE_UNDERLINE + "Parameters:" + CODE_RESET + "\n" +
+                   "shortName - 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234\n" +
+                   "aVeryLongName1234567890123456789012345678901234567890 - \n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "@throws ShortExcp 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
+                "@throws aVeryLongException1234567890123456789012345678901234567890 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "\n" +
+                   CODE_UNDERLINE + "Thrown Exceptions:" + CODE_RESET + "\n" +
+                   "ShortExcp - 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234\n" +
+                   "aVeryLongException1234567890123456789012345678901234567890 - \n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "@return 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "\n" +
+                   CODE_UNDERLINE + "Returns:" + CODE_RESET + "\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //handling of <p>, <pre>:
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 <p>1234 1234 <p>1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 <p>1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
+                "<blockquote><pre>\n" +
+                "for (String data : content) {\n" +
+                "    System.err.println(data);\n" +
+                "}\n" +
+                "</pre></blockquote>\n");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234\n" +
+                   "1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234\n" +
+                   "    for (String data : content) {\n" +
+                   "        System.err.println(data);\n" +
+                   "    }\n" +
+                   "    \n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //list handling:
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "<ul>" +
+                "    <li>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</li>" +
+                "    <li>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "    <li>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234<ol>" +
+                "        <li>D 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</li>" +
+                "        <li>E 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234<ul>" +
+                "            <li>F 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234<ol>" +
+                "                <li>G 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "            </ol>" +
+                "        </ul>" +
+                "    </OL>" +
+                "    <LI><p>H 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 <p>1234 1234 1234 1234 1234 1234 1234<ul>" +
+                "        <li>I 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "    </ul>" +
+                "</ul> followup" +
+                "<dl>" +
+                "<dt>Term1</dt>" +
+                "<dd>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</dd>" +
+                "<dt>Term2" +
+                "<dd>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "<dt>Term3" +
+                "<dd>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "</dl>" +
+                "<dl>" +
+                "<dt>TermUnfinished" +
+                "</dl> followup");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "  * A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234\n" +
+                   "  * B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234\n" +
+                   "  * C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234\n" +
+                   "     1. D 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "        1234 1234 1234 1234 1234 1234\n" +
+                   "     2. E 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "        1234 1234 1234 1234 1234 1234\n" +
+                   "          * F 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234\n" +
+                   "             1. G 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "                1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "  * H 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234\n" +
+                   "      * I 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "        1234 1234 1234 1234 1234 1234\n" +
+                   "followup\n" +
+                   CODE_HIGHLIGHT + "Term1" + CODE_RESET + "\n" +
+                   "    A 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   CODE_HIGHLIGHT + "Term2" + CODE_RESET + "\n" +
+                   "    B 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   CODE_HIGHLIGHT + "Term3" + CODE_RESET + "\n" +
+                   "    C 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   CODE_HIGHLIGHT + "TermUnfinished" + CODE_RESET + "\n" +
+                   "followup\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //sections:
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "text 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "<h3>1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</h3>" +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "text 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "\n" +
+                   CODE_UNDERLINE + "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234" + CODE_RESET + "\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "1234 1234 1234 1234 1234 1234 1234 1234\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //table:
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "<table>" +
+                "<tr>" +
+                "<th>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</th>" +
+                "<th>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</th>" +
+                "<th>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</th>" +
+                "</tr>" +
+                "<tr>" +
+                "<td>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>  \n" +
+                "<td>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "<td>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "<tr>" +
+                "<td>A 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
+                "<td>B 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
+                "<td>C 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
+                "</tr>" +
+                "<tr>" +
+                "<td>1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
+                "<td>1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234</td>" +
+                "</table>");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "----------------------------------------------------------------\n" +
+                   "| " + CODE_HIGHLIGHT + "A 1234 1234 1234" + CODE_RESET + "   | " + CODE_HIGHLIGHT + "B 1234 1234 1234" + CODE_RESET + "   | " + CODE_HIGHLIGHT + "C 1234 1234 1234" + CODE_RESET + "   |\n" +
+                   "| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     |\n" +
+                   "| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     |\n" +
+                   "| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     |\n" +
+                   "| " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     | " + CODE_HIGHLIGHT + "1234 1234 1234" + CODE_RESET + "     |\n" +
+                   "| " + CODE_HIGHLIGHT + "1234 1234" + CODE_RESET + "          | " + CODE_HIGHLIGHT + "1234 1234" + CODE_RESET + "          | " + CODE_HIGHLIGHT + "1234 1234" + CODE_RESET + "          |\n" +
+                   "----------------------------------------------------------------\n" +
+                   "| A 1234 1234 1234   | B 1234 1234 1234   | C 1234 1234 1234   |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234          | 1234 1234          | 1234 1234          |\n" +
+                   "----------------------------------------------------------------\n" +
+                   "| A 1234 1234 1234   | B 1234 1234 1234   | C 1234 1234 1234   |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234          | 1234 1234          | 1234 1234          |\n" +
+                   "----------------------------------------------------------------\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234 1234     | 1234 1234 1234     |\n" +
+                   "| 1234 1234          | 1234 1234          |\n" +
+                   "-------------------------------------------\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //no escape sequences:
+        actual = new JavadocFormatter(66, false).formatJavadoc("test",
+                "@param shortName 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n" +
+                "@param aVeryLongName1234567890123456789012345678901234567890 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 " +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234" +
+                "                 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 \n");
+
+        expected = "test\n" +
+                   "\n" +
+                   "Parameters:\n" +
+                   "shortName - 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "            1234 1234 1234 1234\n" +
+                   "aVeryLongName1234567890123456789012345678901234567890 - \n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
+                   "    1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //null javadoc:
+        actual = new JavadocFormatter(66, true).formatJavadoc("test", null);
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //stray tags:
+        for (String tag : new String[] {"li", "ol", "h3", "table", "tr", "td", "dl", "dt", "dd"}) {
+            for (boolean closing : new boolean[] {false, true}) {
+                actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                        "<" + (closing ? "/" : "") + tag + ">text");
+
+                if (!actual.contains("text")) {
+                    throw new AssertionError("Incorrect output: " + actual);
+                }
+            }
+        }
+
+        //entities:
+        actual = new JavadocFormatter(66, false).formatJavadoc("test",
+                "&alpha; &lt; &#65; &#X42; &gt; &broken; &#xFFFFFFFF; &#xFFFFFFF;\n");
+
+        expected = "test\n" +
+                   "\u03b1 < A B > &broken; &#xFFFFFFFF; &#xFFFFFFF;\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+
+        //img:
+        actual = new JavadocFormatter(66, true).formatJavadoc("test",
+                "1234 <img src='any.png' alt='text'/> 1234");
+
+        expected = CODE_HIGHLIGHT + "test" + CODE_RESET + "\n" +
+                   "1234 text 1234\n";
+
+        if (!Objects.equals(actual, expected)) {
+            throw new AssertionError("Incorrect output: " + actual);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/internal/shellsupport/doc/JavadocHelperTest.java	Wed Nov 02 07:38:37 2016 +0100
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8131019
+ * @summary Test JavadocHelper
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ *          jdk.compiler/jdk.internal.shellsupport.doc
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @run testng JavadocHelperTest
+ */
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import com.sun.source.util.JavacTask;
+import jdk.internal.shellsupport.doc.JavadocHelper;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class JavadocHelperTest {
+
+    public void testJavadoc() throws Exception {
+        doTestJavadoc("",
+                      t -> t.getElements().getTypeElement("test.Super"),
+                      "Top level. ");
+        doTestJavadoc("",
+                      t -> getFirstMethod(t, "test.Super"),
+                      " javadoc1A\n" +
+                      "\n" +
+                      " @param p1 param1A\n" +
+                      " @param p2 param2A\n" +
+                      " @param p3 param3A\n" +
+                      " @throws IllegalStateException exc1A\n" +
+                      " @throws IllegalArgumentException exc2A\n" +
+                      " @throws IllegalAccessException exc3A\n" +
+                      " @return valueA\n");
+    }
+
+    private Element getFirstMethod(JavacTask task, String typeName) {
+        return ElementFilter.methodsIn(task.getElements().getTypeElement(typeName).getEnclosedElements()).get(0);
+    }
+
+    private Function<JavacTask, Element> getSubTest = t -> getFirstMethod(t, "test.Sub");
+
+    public void testInheritNoJavadoc() throws Exception {
+        doTestJavadoc("",
+                      getSubTest,
+                      " javadoc1A\n" +
+                      "\n" +
+                      " @param p1 param1A\n" +
+                      " @param p2 param2A\n" +
+                      " @param p3 param3A\n" +
+                      " @throws IllegalStateException exc1A\n" +
+                      " @throws IllegalArgumentException exc2A\n" +
+                      " @throws IllegalAccessException exc3A\n" +
+                      " @return valueA\n");
+    }
+
+    public void testInheritFull() throws Exception {
+        doTestJavadoc("    /**\n" +
+                      "     * Prefix {@inheritDoc} suffix.\n" +
+                      "     *\n" +
+                      "     * @param p1 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p2 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p3 prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
+                      "     * @return prefix {@inheritDoc} suffix\n" +
+                      "     */\n",
+                      getSubTest,
+                      " Prefix javadoc1 suffix.\n" +
+                      "\n" +
+                      " @param p1 prefix param1 suffix\n" +
+                      " @param p2 prefix param2 suffix\n" +
+                      " @param p3 prefix param3 suffix\n" +
+                      " @throws IllegalStateException prefix exc1 suffix\n" +
+                      " @throws IllegalArgumentException prefix exc2 suffix\n" +
+                      " @throws IllegalAccessException prefix exc3 suffix\n" +
+                      " @return prefix value suffix\n");
+    }
+
+    public void testInheritMissingParam() throws Exception {
+        doTestJavadoc("    /**\n" +
+                      "     * Prefix {@inheritDoc} suffix.\n" +
+                      "     *\n" +
+                      "     * @param p1 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p3 prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
+                      "     * @return prefix {@inheritDoc} suffix\n" +
+                      "     */\n",
+                      getSubTest,
+                      " Prefix javadoc1 suffix.\n" +
+                      "\n" +
+                      " @param p1 prefix param1 suffix\n" +
+                      "@param p2  param2\n" +
+                      " @param p3 prefix param3 suffix\n" +
+                      " @throws IllegalStateException prefix exc1 suffix\n" +
+                      " @throws IllegalArgumentException prefix exc2 suffix\n" +
+                      " @throws IllegalAccessException prefix exc3 suffix\n" +
+                      " @return prefix value suffix\n");
+    }
+
+    public void testInheritMissingFirstParam() throws Exception {
+        doTestJavadoc("    /**\n" +
+                      "     * Prefix {@inheritDoc} suffix.\n" +
+                      "     *\n" +
+                      "     * @param p2 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p3 prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
+                      "     * @return prefix {@inheritDoc} suffix\n" +
+                      "     */\n",
+                      getSubTest,
+                      " Prefix javadoc1 suffix.\n" +
+                      "@param p1  param1\n" +
+                      "\n" +
+                      " @param p2 prefix param2 suffix\n" +
+                      " @param p3 prefix param3 suffix\n" +
+                      " @throws IllegalStateException prefix exc1 suffix\n" +
+                      " @throws IllegalArgumentException prefix exc2 suffix\n" +
+                      " @throws IllegalAccessException prefix exc3 suffix\n" +
+                      " @return prefix value suffix\n");
+    }
+
+    public void testInheritMissingThrows() throws Exception {
+        doTestJavadoc("    /**\n" +
+                      "     * Prefix {@inheritDoc} suffix.\n" +
+                      "     *\n" +
+                      "     * @param p1 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p2 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p3 prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
+                      "     * @return prefix {@inheritDoc} suffix\n" +
+                      "     */\n",
+                      getSubTest,
+                      " Prefix javadoc1 suffix.\n" +
+                      "\n" +
+                      " @param p1 prefix param1 suffix\n" +
+                      " @param p2 prefix param2 suffix\n" +
+                      " @param p3 prefix param3 suffix\n" +
+                      " @throws IllegalStateException prefix exc1 suffix\n" +
+                      "@throws java.lang.IllegalArgumentException  exc2\n" +
+                      " @throws IllegalAccessException prefix exc3 suffix\n" +
+                      " @return prefix value suffix\n");
+    }
+
+    public void testInheritMissingReturn() throws Exception {
+        doTestJavadoc("    /**\n" +
+                      "     * Prefix {@inheritDoc} suffix.\n" +
+                      "     *\n" +
+                      "     * @param p1 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p2 prefix {@inheritDoc} suffix\n" +
+                      "     * @param p3 prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalStateException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalArgumentException prefix {@inheritDoc} suffix\n" +
+                      "     * @throws IllegalAccessException prefix {@inheritDoc} suffix\n" +
+                      "     */\n",
+                      getSubTest,
+                      " Prefix javadoc1 suffix.\n" +
+                      "\n" +
+                      " @param p1 prefix param1 suffix\n" +
+                      " @param p2 prefix param2 suffix\n" +
+                      " @param p3 prefix param3 suffix\n" +
+                      " @throws IllegalStateException prefix exc1 suffix\n" +
+                      " @throws IllegalArgumentException prefix exc2 suffix\n" +
+                      " @throws IllegalAccessException prefix exc3 suffix\n" +
+                      "@return  value\n");
+    }
+
+
+    private void doTestJavadoc(String origJavadoc, Function<JavacTask, Element> getElement, String expectedJavadoc) throws Exception {
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        String subClass =
+                "package test;\n" +
+                "public class Sub extends Super {\n" +
+                origJavadoc +
+                "    public String test(int p1, int p2, int p3) throws IllegalStateException, IllegalArgumentException, IllegalAccessException { return null;} \n" +
+                "}\n";
+        String superClass =
+                "package test;\n" +
+                "/**Top level." +
+                " */\n" +
+                "public class Super {\n" +
+                "    /**\n" +
+                "     * javadoc1A\n" +
+                "     *\n" +
+                "     * @param p1 param1A\n" +
+                "     * @param p2 param2A\n" +
+                "     * @param p3 param3A\n" +
+                "     * @throws IllegalStateException exc1A\n" +
+                "     * @throws IllegalArgumentException exc2A\n" +
+                "     * @throws IllegalAccessException exc3A\n" +
+                "     * @return valueA\n" +
+                "     */\n" +
+                "    public String test(int p1, int p2, int p3) throws IllegalStateException, IllegalArgumentException, IllegalAccessException { return null;} \n" +
+                "}\n";
+
+        Path srcZip = Paths.get("src.zip");
+
+        try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
+            out.putNextEntry(new JarEntry("test/Sub.java"));
+            out.write(subClass.getBytes());
+            out.putNextEntry(new JarEntry("test/Super.java"));
+            out.write(superClass.getBytes());
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+
+        DiagnosticListener<? super JavaFileObject> noErrors = d -> {
+            if (d.getKind() == Kind.ERROR) {
+                throw new AssertionError(d.getMessage(null));
+            }
+        };
+
+        assertTrue(compiler.getTask(null, null, noErrors, Arrays.asList("-d", "."), null, Arrays.asList(new JFOImpl("Super", superClass), new JFOImpl("Sub", subClass))).call());
+
+        try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
+            fm.setLocationFromPaths(StandardLocation.CLASS_PATH, Arrays.asList(Paths.get(".").toAbsolutePath()));
+            JavacTask task = (JavacTask) compiler.getTask(null, fm, noErrors, null, null, null);
+
+            Element el = getElement.apply(task);
+
+            try (JavadocHelper helper = JavadocHelper.create(task, Arrays.asList(srcZip))) {
+                String javadoc = helper.getResolvedDocComment(el);
+
+                assertEquals(javadoc, expectedJavadoc);
+            }
+        }
+    }
+
+    private static final class JFOImpl extends SimpleJavaFileObject {
+
+        private final String code;
+
+        public JFOImpl(String name, String code) throws URISyntaxException {
+            super(new URI("mem:///" + name + ".java"), Kind.SOURCE);
+            this.code = code;
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+            return code;
+        }
+
+    }
+}
--- a/langtools/test/jdk/jshell/CompletionSuggestionTest.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/test/jdk/jshell/CompletionSuggestionTest.java	Wed Nov 02 07:38:37 2016 +0100
@@ -23,12 +23,12 @@
 
 /*
  * @test
- * @bug 8131025 8141092 8153761 8145263
+ * @bug 8131025 8141092 8153761 8145263 8131019
  * @summary Test Completion and Documentation
+ * @library /tools/lib
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
  *          jdk.jdeps/com.sun.tools.javap
- * @library /tools/lib
  * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
  * @build KullaTesting TestingInputStream Compiler
  * @run testng CompletionSuggestionTest
@@ -305,26 +305,26 @@
 
     public void testDocumentation() throws Exception {
         dontReadParameterNamesFromClassFile();
-        assertDocumentation("System.getProperty(|",
+        assertSignature("System.getProperty(|",
                 "String System.getProperty(String key)",
                 "String System.getProperty(String key, String def)");
         assertEval("char[] chars = null;");
-        assertDocumentation("new String(chars, |",
+        assertSignature("new String(chars, |",
                 "String(char[], int, int)");
-        assertDocumentation("String.format(|",
+        assertSignature("String.format(|",
                 "String String.format(String, Object...)",
                 "String String.format(java.util.Locale, String, Object...)");
-        assertDocumentation("\"\".getBytes(\"\"|", "void String.getBytes(int, int, byte[], int)",
+        assertSignature("\"\".getBytes(\"\"|", "void String.getBytes(int, int, byte[], int)",
                                                     "byte[] String.getBytes(String) throws java.io.UnsupportedEncodingException",
                                                     "byte[] String.getBytes(java.nio.charset.Charset)");
-        assertDocumentation("\"\".getBytes(\"\" |", "void String.getBytes(int, int, byte[], int)",
+        assertSignature("\"\".getBytes(\"\" |", "void String.getBytes(int, int, byte[], int)",
                                                      "byte[] String.getBytes(String) throws java.io.UnsupportedEncodingException",
                                                      "byte[] String.getBytes(java.nio.charset.Charset)");
     }
 
     public void testMethodsWithNoArguments() throws Exception {
         dontReadParameterNamesFromClassFile();
-        assertDocumentation("System.out.println(|",
+        assertSignature("System.out.println(|",
                 "void java.io.PrintStream.println()",
                 "void java.io.PrintStream.println(boolean)",
                 "void java.io.PrintStream.println(char)",
@@ -339,6 +339,7 @@
 
     public void testErroneous() {
         assertCompletion("Undefined.|");
+        assertSignature("does.not.exist|");
     }
 
     public void testClinit() {
@@ -474,59 +475,63 @@
 
     public void testDocumentationOfUserDefinedMethods() {
         assertEval("void f() {}");
-        assertDocumentation("f(|", "void f()");
+        assertSignature("f(|", "void f()");
         assertEval("void f(int i) {}");
-        assertDocumentation("f(|", "void f()", "void f(int i)");
+        assertSignature("f(|", "void f()", "void f(int i)");
         assertEval("<T> void f(T... ts) {}", DiagCheck.DIAG_WARNING, DiagCheck.DIAG_OK);
-        assertDocumentation("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)");
+        assertSignature("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)");
         assertEval("class A {}");
         assertEval("void f(A a) {}");
-        assertDocumentation("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)", "void f(A a)");
+        assertSignature("f(|", "void f()", "void f(int i)", "void <T>f(T... ts)", "void f(A a)");
+    }
+
+    public void testClass() {
+        assertSignature("String|", "java.lang.String");
     }
 
     public void testDocumentationOfUserDefinedConstructors() {
         Snippet a = classKey(assertEval("class A {}"));
-        assertDocumentation("new A(|", "A()");
+        assertSignature("new A(|", "A()");
         Snippet a2 = classKey(assertEval("class A { A() {} A(int i) {}}",
                 ste(MAIN_SNIPPET, VALID, VALID, true, null),
                 ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
-        assertDocumentation("new A(|", "A()", "A(int i)");
+        assertSignature("new A(|", "A()", "A(int i)");
         assertEval("class A<T> { A(T a) {} A(int i) {} <U> A(T t, U u) {}}",
                 ste(MAIN_SNIPPET, VALID, VALID, true, null),
                 ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
-        assertDocumentation("new A(|", "A<T>(T a)", "A<T>(int i)", "<U> A<T>(T t, U u)");
+        assertSignature("new A(|", "A<T>(T a)", "A<T>(int i)", "<U> A<T>(T t, U u)");
     }
 
     public void testDocumentationOfOverriddenMethods() throws Exception {
         dontReadParameterNamesFromClassFile();
-        assertDocumentation("\"\".wait(|",
+        assertSignature("\"\".wait(|",
             "void Object.wait(long) throws InterruptedException",
             "void Object.wait(long, int) throws InterruptedException",
             "void Object.wait() throws InterruptedException");
         assertEval("class Base {void method() {}}");
         Snippet e = classKey(assertEval("class Extend extends Base {}"));
-        assertDocumentation("new Extend().method(|", "void Base.method()");
+        assertSignature("new Extend().method(|", "void Base.method()");
         assertEval("class Extend extends Base {void method() {}}",
                 ste(MAIN_SNIPPET, VALID, VALID, true, null),
                 ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
-        assertDocumentation("new Extend().method(|", "void Extend.method()");
+        assertSignature("new Extend().method(|", "void Extend.method()");
     }
 
     public void testDocumentationOfInvisibleMethods() {
-        assertDocumentation("Object.wait(|", "");
-        assertDocumentation("\"\".indexOfSupplementary(|", "");
+        assertSignature("Object.wait(|");
+        assertSignature("\"\".indexOfSupplementary(|");
         Snippet a = classKey(assertEval("class A {void method() {}}"));
-        assertDocumentation("A.method(|", "");
+        assertSignature("A.method(|");
         assertEval("class A {private void method() {}}",
                 ste(MAIN_SNIPPET, VALID, VALID, true, null),
                 ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
-        assertDocumentation("new A().method(|", "");
+        assertSignature("new A().method(|");
     }
 
     public void testDocumentationOfInvisibleConstructors() {
-        assertDocumentation("new Compiler(|", "");
+        assertSignature("new Compiler(|");
         assertEval("class A { private A() {} }");
-        assertDocumentation("new A(|", "");
+        assertSignature("new A(|");
     }
 
     public void testDocumentationWithBoxing() {
@@ -535,13 +540,13 @@
         assertEval("Object object = null;");
         assertEval("void method(int n, Object o) { }");
         assertEval("void method(Object n, int o) { }");
-        assertDocumentation("method(primitive,|",
+        assertSignature("method(primitive,|",
                 "void method(int n, Object o)",
                 "void method(Object n, int o)");
-        assertDocumentation("method(boxed,|",
+        assertSignature("method(boxed,|",
                 "void method(int n, Object o)",
                 "void method(Object n, int o)");
-        assertDocumentation("method(object,|",
+        assertSignature("method(object,|",
                 "void method(Object n, int o)");
     }
 
@@ -567,7 +572,7 @@
 
             void assertDoc(String generics, String expectedGenerics) {
                 assertEval(evalFormatter.apply(generics, count));
-                assertDocumentation(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
+                assertSignature(codeFacotry.apply(count), docFormatter.apply(expectedGenerics, count));
                 count++;
             }
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/JavadocTest.java	Wed Nov 02 07:38:37 2016 +0100
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8131019
+ * @summary Test Javadoc
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ *          jdk.jshell
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @build KullaTesting TestingInputStream Compiler
+ * @run testng JavadocTest
+ */
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.testng.annotations.Test;
+
+@Test
+public class JavadocTest extends KullaTesting {
+
+    private final Compiler compiler = new Compiler();
+
+    public void testJavadoc() {
+        prepareZip();
+        assertJavadoc("test.Clazz|", "test.Clazz\n" +
+                                     "Top level. ");
+        assertEval("test.Clazz clz = null;");
+        assertJavadoc("clz.test(|", "String test.Clazz.test(int p) throws IllegalStateException\n" +
+                                    " javadoc1A\n" +
+                                    "\n" +
+                                    " @param p param\n" +
+                                    " @throws IllegalStateException exc\n" +
+                                    " @return value\n");
+        //undefined handling:
+        assertJavadoc("clz.undef|");
+    }
+
+    private void prepareZip() {
+        String clazz =
+                "package test;\n" +
+                "/**Top level." +
+                " */\n" +
+                "public class Clazz {\n" +
+                "    /**\n" +
+                "     * javadoc1A\n" +
+                "     *\n" +
+                "     * @param p param\n" +
+                "     * @throws IllegalStateException exc\n" +
+                "     * @return value\n" +
+                "     */\n" +
+                "    public String test(int p) throws IllegalStateException { return null;}\n" +
+                "}\n";
+
+        Path srcZip = Paths.get("src.zip");
+
+        try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
+            out.putNextEntry(new JarEntry("test/Clazz.java"));
+            out.write(clazz.getBytes());
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+
+        compiler.compile(clazz);
+
+        try {
+            Field availableSources = getAnalysis().getClass().getDeclaredField("availableSources");
+            availableSources.setAccessible(true);
+            availableSources.set(getAnalysis(), Arrays.asList(srcZip));
+        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
+            throw new IllegalStateException(ex);
+        }
+        addToClasspath(compiler.getClassDir());
+    }
+
+}
--- a/langtools/test/jdk/jshell/KullaTesting.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/test/jdk/jshell/KullaTesting.java	Wed Nov 02 07:38:37 2016 +0100
@@ -72,11 +72,14 @@
 import org.testng.annotations.BeforeMethod;
 
 import jdk.jshell.Diag;
+
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
+
 import static jdk.jshell.Snippet.Status.*;
 import static org.testng.Assert.*;
 import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
+import jdk.jshell.SourceCodeAnalysis.Documentation;
 
 public class KullaTesting {
 
@@ -946,12 +949,24 @@
         }
     }
 
-    public void assertDocumentation(String code, String... expected) {
+    public void assertSignature(String code, String... expected) {
         int cursor =  code.indexOf('|');
         code = code.replace("|", "");
         assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
-        String documentation = getAnalysis().documentation(code, cursor);
-        Set<String> docSet = Stream.of(documentation.split("\r?\n")).collect(Collectors.toSet());
+        List<Documentation> documentation = getAnalysis().documentation(code, cursor, false);
+        Set<String> docSet = documentation.stream().map(doc -> doc.signature()).collect(Collectors.toSet());
+        Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
+        assertEquals(docSet, expectedSet, "Input: " + code);
+    }
+
+    public void assertJavadoc(String code, String... expected) {
+        int cursor =  code.indexOf('|');
+        code = code.replace("|", "");
+        assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
+        List<Documentation> documentation = getAnalysis().documentation(code, cursor, true);
+        Set<String> docSet = documentation.stream()
+                                          .map(doc -> doc.signature() + "\n" + doc.javadoc())
+                                          .collect(Collectors.toSet());
         Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
         assertEquals(docSet, expectedSet, "Input: " + code);
     }