JavadocFormatter.java revision 3767:b265444e51db
1127563Sdes/*
2127208Sdes * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3139824Simp * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4139824Simp *
5139824Simp * This code is free software; you can redistribute it and/or modify it
698679Sdes * under the terms of the GNU General Public License version 2 only, as
798679Sdes * published by the Free Software Foundation.  Oracle designates this
898679Sdes * particular file as subject to the "Classpath" exception as provided
998679Sdes * by Oracle in the LICENSE file that accompanied this code.
1098679Sdes *
1198679Sdes * This code is distributed in the hope that it will be useful, but WITHOUT
1298679Sdes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1398679Sdes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1498679Sdes * version 2 for more details (a copy is included in the LICENSE file that
1598679Sdes * accompanied this code).
1698679Sdes *
1798679Sdes * You should have received a copy of the GNU General Public License version
1898679Sdes * 2 along with this work; if not, write to the Free Software Foundation,
1998679Sdes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2098679Sdes *
2198679Sdes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2298679Sdes * or visit www.oracle.com if you need additional information or have any
2398679Sdes * questions.
2498679Sdes */
2598679Sdespackage jdk.internal.shellsupport.doc;
2698679Sdes
2798679Sdesimport java.io.IOException;
2898679Sdesimport java.net.URI;
2998679Sdesimport java.net.URISyntaxException;
3098679Sdesimport java.util.ArrayList;
3198679Sdesimport java.util.Arrays;
3298679Sdesimport java.util.Collections;
3398679Sdesimport java.util.IdentityHashMap;
3498679Sdesimport java.util.LinkedHashMap;
3598679Sdesimport java.util.List;
3698679Sdesimport java.util.Map;
3798679Sdesimport java.util.ResourceBundle;
3898679Sdesimport java.util.Stack;
3998679Sdes
4098679Sdesimport javax.lang.model.element.Name;
4198679Sdesimport javax.tools.JavaFileObject.Kind;
4298679Sdesimport javax.tools.SimpleJavaFileObject;
4398679Sdesimport javax.tools.ToolProvider;
4498679Sdes
4598679Sdesimport com.sun.source.doctree.AttributeTree;
4698679Sdesimport com.sun.source.doctree.DocCommentTree;
4798679Sdesimport com.sun.source.doctree.DocTree;
4898679Sdesimport com.sun.source.doctree.EndElementTree;
4998679Sdesimport com.sun.source.doctree.EntityTree;
5098679Sdesimport com.sun.source.doctree.InlineTagTree;
5198679Sdesimport com.sun.source.doctree.LinkTree;
5298679Sdesimport com.sun.source.doctree.LiteralTree;
5398679Sdesimport com.sun.source.doctree.ParamTree;
5498679Sdesimport com.sun.source.doctree.ReturnTree;
5598679Sdesimport com.sun.source.doctree.StartElementTree;
5698679Sdesimport com.sun.source.doctree.TextTree;
5798679Sdesimport com.sun.source.doctree.ThrowsTree;
5898679Sdesimport com.sun.source.util.DocTreeScanner;
5998679Sdesimport com.sun.source.util.DocTrees;
6098679Sdesimport com.sun.source.util.JavacTask;
6198679Sdesimport com.sun.tools.doclint.Entity;
6298679Sdesimport com.sun.tools.doclint.HtmlTag;
6398679Sdesimport com.sun.tools.javac.util.DefinedBy;
6498679Sdesimport com.sun.tools.javac.util.DefinedBy.Api;
6598679Sdesimport com.sun.tools.javac.util.StringUtils;
6698679Sdes
6798679Sdes/**A javadoc to plain text formatter.
6898679Sdes *
6998679Sdes */
70127403Sdespublic class JavadocFormatter {
7198679Sdes
7298679Sdes    private static final String CODE_RESET = "\033[0m";
7398679Sdes    private static final String CODE_HIGHLIGHT = "\033[1m";
7498679Sdes    private static final String CODE_UNDERLINE = "\033[4m";
7598679Sdes
7698679Sdes    private final int lineLimit;
7798679Sdes    private final boolean escapeSequencesSupported;
7898679Sdes
7998679Sdes    /** Construct the formatter.
8098679Sdes     *
8198679Sdes     * @param lineLimit maximum line length
8298679Sdes     * @param escapeSequencesSupported whether escape sequences are supported
8398679Sdes     */
8498679Sdes    public JavadocFormatter(int lineLimit, boolean escapeSequencesSupported) {
8598679Sdes        this.lineLimit = lineLimit;
8698679Sdes        this.escapeSequencesSupported = escapeSequencesSupported;
8798679Sdes    }
88127403Sdes
8998679Sdes    private static final int MAX_LINE_LENGTH = 95;
9098679Sdes    private static final int SHORTEST_LINE = 30;
9198679Sdes    private static final int INDENT = 4;
9298679Sdes
9398679Sdes    /**Format javadoc to plain text.
94127403Sdes     *
9598679Sdes     * @param header element caption that should be used
9698679Sdes     * @param javadoc to format
9798679Sdes     * @return javadoc formatted to plain text
9898679Sdes     */
9998679Sdes    public String formatJavadoc(String header, String javadoc) {
100127403Sdes        try {
10198679Sdes            StringBuilder result = new StringBuilder();
10298679Sdes
10398679Sdes            result.append(escape(CODE_HIGHLIGHT)).append(header).append(escape(CODE_RESET)).append("\n");
10498679Sdes
10598679Sdes            if (javadoc == null) {
106127403Sdes                return result.toString();
10798679Sdes            }
10898679Sdes
10998679Sdes            JavacTask task = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, null);
11098679Sdes            DocTrees trees = DocTrees.instance(task);
11198679Sdes            DocCommentTree docComment = trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) {
11298679Sdes                @Override @DefinedBy(Api.COMPILER)
113127403Sdes                public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
11498679Sdes                    return "<body>" + javadoc + "</body>";
11598679Sdes                }
11698679Sdes            });
11798679Sdes
11898679Sdes            new FormatJavadocScanner(result, task).scan(docComment, null);
11998679Sdes
12098679Sdes            addNewLineIfNeeded(result);
12198679Sdes
12298679Sdes            return result.toString();
12398679Sdes        } catch (URISyntaxException ex) {
12498679Sdes            throw new InternalError("Unexpected exception", ex);
12598679Sdes        }
12698679Sdes    }
12798679Sdes
12898679Sdes    private class FormatJavadocScanner extends DocTreeScanner<Object, Object> {
12998679Sdes        private final StringBuilder result;
13098679Sdes        private final JavacTask task;
13198679Sdes        private int reflownTo;
13298679Sdes        private int indent;
13398679Sdes        private int limit = Math.min(lineLimit, MAX_LINE_LENGTH);
13498679Sdes        private boolean pre;
13598679Sdes        private Map<StartElementTree, Integer> tableColumns;
13698679Sdes
13798679Sdes        public FormatJavadocScanner(StringBuilder result, JavacTask task) {
13898679Sdes            this.result = result;
13998679Sdes            this.task = task;
14098679Sdes        }
14198679Sdes
14298679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
14398679Sdes        public Object visitDocComment(DocCommentTree node, Object p) {
14498679Sdes            tableColumns = countTableColumns(node);
14598679Sdes            reflownTo = result.length();
14698679Sdes            scan(node.getFirstSentence(), p);
14798679Sdes            scan(node.getBody(), p);
14898679Sdes            reflow(result, reflownTo, indent, limit);
14998679Sdes            for (Sections current : docSections.keySet()) {
15098679Sdes                boolean seenAny = false;
15198679Sdes                for (DocTree t : node.getBlockTags()) {
15298679Sdes                    if (current.matches(t)) {
15398679Sdes                        if (!seenAny) {
15498679Sdes                            seenAny = true;
15598679Sdes                            if (result.charAt(result.length() - 1) != '\n')
15698679Sdes                                result.append("\n");
15798679Sdes                            result.append("\n");
15898679Sdes                            result.append(escape(CODE_UNDERLINE))
15998679Sdes                                  .append(docSections.get(current))
16098679Sdes                                  .append(escape(CODE_RESET))
16198679Sdes                                  .append("\n");
16298679Sdes                        }
16398679Sdes
16498679Sdes                        scan(t, null);
16598679Sdes                    }
16698679Sdes                }
16798679Sdes            }
16898679Sdes            return null;
16998679Sdes        }
17098679Sdes
17198679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
17298679Sdes        public Object visitText(TextTree node, Object p) {
17398679Sdes            String text = node.getBody();
17498679Sdes            if (!pre) {
17598679Sdes                text = text.replaceAll("[ \t\r\n]+", " ").trim();
17698679Sdes                if (text.isEmpty()) {
17798679Sdes                    text = " ";
17898679Sdes                }
17998679Sdes            } else {
18098679Sdes                text = text.replaceAll("\n", "\n" + indentString(indent));
18198679Sdes            }
18298679Sdes            result.append(text);
18398679Sdes            return null;
18498679Sdes        }
18598679Sdes
18698679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
18798679Sdes        public Object visitLink(LinkTree node, Object p) {
18898679Sdes            if (!node.getLabel().isEmpty()) {
18998679Sdes                scan(node.getLabel(), p);
19098679Sdes            } else {
19198679Sdes                result.append(node.getReference().getSignature());
19298679Sdes            }
19398679Sdes            return null;
19498679Sdes        }
19598679Sdes
19698679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
19798679Sdes        public Object visitParam(ParamTree node, Object p) {
19898679Sdes            return formatDef(node.getName().getName(), node.getDescription());
19998679Sdes        }
20098679Sdes
20198679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
20298679Sdes        public Object visitThrows(ThrowsTree node, Object p) {
20398679Sdes            return formatDef(node.getExceptionName().getSignature(), node.getDescription());
20498679Sdes        }
20598679Sdes
20698679Sdes        public Object formatDef(CharSequence name, List<? extends DocTree> description) {
20798679Sdes            result.append(name);
20898679Sdes            result.append(" - ");
20998679Sdes            reflownTo = result.length();
21098679Sdes            indent = name.length() + 3;
21198679Sdes
21298679Sdes            if (limit - indent < SHORTEST_LINE) {
21398679Sdes                result.append("\n");
214127403Sdes                result.append(indentString(INDENT));
21598679Sdes                indent = INDENT;
21698679Sdes                reflownTo += INDENT;
21798679Sdes            }
21898679Sdes            try {
21998679Sdes                return scan(description, null);
22098679Sdes            } finally {
22198679Sdes                reflow(result, reflownTo, indent, limit);
22298679Sdes                result.append("\n");
22398679Sdes            }
22498679Sdes        }
22598679Sdes
22698679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
22798679Sdes        public Object visitLiteral(LiteralTree node, Object p) {
22898679Sdes            return scan(node.getBody(), p);
22998679Sdes        }
23098679Sdes
23198679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
23298679Sdes        public Object visitReturn(ReturnTree node, Object p) {
23398679Sdes            reflownTo = result.length();
23498679Sdes            try {
23598679Sdes                return super.visitReturn(node, p);
23698679Sdes            } finally {
23798679Sdes                reflow(result, reflownTo, 0, limit);
23898679Sdes            }
23998679Sdes        }
24098679Sdes
24198679Sdes        Stack<Integer> listStack = new Stack<>();
24298679Sdes        Stack<Integer> defStack = new Stack<>();
24398679Sdes        Stack<Integer> tableStack = new Stack<>();
24498679Sdes        Stack<List<Integer>> cellsStack = new Stack<>();
24598679Sdes        Stack<List<Boolean>> headerStack = new Stack<>();
24698679Sdes
24798679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
24898679Sdes        public Object visitStartElement(StartElementTree node, Object p) {
24998679Sdes            switch (getHtmlTag(node.getName())) {
25098679Sdes                case P:
25198679Sdes                    if (lastNode!= null && lastNode.getKind() == DocTree.Kind.START_ELEMENT &&
25298679Sdes                        HtmlTag.get(((StartElementTree) lastNode).getName()) == HtmlTag.LI) {
25398679Sdes                        //ignore
25498679Sdes                        break;
25598679Sdes                    }
25698679Sdes                    reflowTillNow();
25798679Sdes                    addNewLineIfNeeded(result);
25898679Sdes                    result.append(indentString(indent));
25998679Sdes                    reflownTo = result.length();
26098679Sdes                    break;
26198679Sdes                case BLOCKQUOTE:
26298679Sdes                    reflowTillNow();
26398679Sdes                    indent += INDENT;
26498679Sdes                    break;
26598679Sdes                case PRE:
26698679Sdes                    reflowTillNow();
26798679Sdes                    pre = true;
26898679Sdes                    break;
26998679Sdes                case UL:
27098679Sdes                    reflowTillNow();
27198679Sdes                    listStack.push(-1);
27298679Sdes                    indent += INDENT;
27398679Sdes                    break;
27498679Sdes                case OL:
27598679Sdes                    reflowTillNow();
27698679Sdes                    listStack.push(1);
27798679Sdes                    indent += INDENT;
27898679Sdes                    break;
27998679Sdes                case DL:
28098679Sdes                    reflowTillNow();
28198679Sdes                    defStack.push(indent);
28298679Sdes                    break;
28398679Sdes                case LI:
28498679Sdes                    reflowTillNow();
28598679Sdes                    if (!listStack.empty()) {
28698679Sdes                        addNewLineIfNeeded(result);
28798679Sdes
28898679Sdes                        int top = listStack.pop();
28998679Sdes
29098679Sdes                        if (top == (-1)) {
29198679Sdes                            result.append(indentString(indent - 2));
29298679Sdes                            result.append("* ");
293127403Sdes                        } else {
29498679Sdes                            result.append(indentString(indent - 3));
29598679Sdes                            result.append("" + top++ + ". ");
29698679Sdes                        }
29798679Sdes
29898679Sdes                        listStack.push(top);
29998679Sdes
30098679Sdes                        reflownTo = result.length();
30198679Sdes                    }
30298679Sdes                    break;
30398679Sdes                case DT:
304127403Sdes                    reflowTillNow();
30598679Sdes                    if (!defStack.isEmpty()) {
30698679Sdes                        addNewLineIfNeeded(result);
30798679Sdes                        indent = defStack.peek();
30898679Sdes                        result.append(escape(CODE_HIGHLIGHT));
30998679Sdes                    }
31098679Sdes                    break;
31198679Sdes                case DD:
31298679Sdes                    reflowTillNow();
31398679Sdes                    if (!defStack.isEmpty()) {
31498679Sdes                        if (indent == defStack.peek()) {
31598679Sdes                            result.append(escape(CODE_RESET));
31698679Sdes                        }
31798679Sdes                        addNewLineIfNeeded(result);
31898679Sdes                        indent = defStack.peek() + INDENT;
31998679Sdes                        result.append(indentString(indent));
32098679Sdes                    }
32198679Sdes                    break;
32298679Sdes                case H1: case H2: case H3:
32398679Sdes                case H4: case H5: case H6:
32498679Sdes                    reflowTillNow();
32598679Sdes                    addNewLineIfNeeded(result);
32698679Sdes                    result.append("\n")
327127403Sdes                          .append(escape(CODE_UNDERLINE));
32898679Sdes                    reflownTo = result.length();
32998679Sdes                    break;
33098679Sdes                case TABLE:
33198679Sdes                    int columns = tableColumns.get(node);
332127403Sdes
33398679Sdes                    if (columns == 0) {
33498679Sdes                        break; //broken input
335147245Sharti                    }
33698679Sdes
33798679Sdes                    reflowTillNow();
33898679Sdes                    addNewLineIfNeeded(result);
33998679Sdes                    reflownTo = result.length();
340127403Sdes
34198679Sdes                    tableStack.push(limit);
34298679Sdes
34398679Sdes                    limit = (limit - 1) / columns - 3;
344127403Sdes
34598679Sdes                    for (int sep = 0; sep < (limit + 3) * columns + 1; sep++) {
34698679Sdes                        result.append("-");
34798679Sdes                    }
34898679Sdes
34998679Sdes                    result.append("\n");
35098679Sdes
35198679Sdes                    break;
35298679Sdes                case TR:
35398679Sdes                    if (cellsStack.size() >= tableStack.size()) {
354127208Sdes                        //unclosed <tr>:
355127208Sdes                        handleEndElement(node.getName());
356127403Sdes                    }
35798679Sdes                    cellsStack.push(new ArrayList<>());
35898679Sdes                    headerStack.push(new ArrayList<>());
35998679Sdes                    break;
360127403Sdes                case TH:
36198679Sdes                case TD:
36298679Sdes                    if (cellsStack.isEmpty()) {
36398679Sdes                        //broken code
364127403Sdes                        break;
36598679Sdes                    }
36698679Sdes                    reflowTillNow();
36798679Sdes                    result.append("\n");
36898679Sdes                    reflownTo = result.length();
36998679Sdes                    cellsStack.peek().add(result.length());
37098679Sdes                    headerStack.peek().add(HtmlTag.get(node.getName()) == HtmlTag.TH);
37198679Sdes                    break;
37298679Sdes                case IMG:
37398679Sdes                    for (DocTree attr : node.getAttributes()) {
374127208Sdes                        if (attr.getKind() != DocTree.Kind.ATTRIBUTE) {
375127208Sdes                            continue;
376127403Sdes                        }
37798679Sdes                        AttributeTree at = (AttributeTree) attr;
37898679Sdes                        if ("alt".equals(StringUtils.toLowerCase(at.getName().toString()))) {
37998679Sdes                            addSpaceIfNeeded(result);
38098679Sdes                            scan(at.getValue(), null);
38198679Sdes                            addSpaceIfNeeded(result);
38298679Sdes                            break;
38398679Sdes                        }
38498679Sdes                    }
385127563Sdes                    break;
38698679Sdes                default:
38798679Sdes                    addSpaceIfNeeded(result);
38898679Sdes                    break;
38998679Sdes            }
39098679Sdes            return null;
39198679Sdes        }
39298679Sdes
39398679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
39498679Sdes        public Object visitEndElement(EndElementTree node, Object p) {
39598679Sdes            handleEndElement(node.getName());
39698679Sdes            return super.visitEndElement(node, p);
397127403Sdes        }
39898679Sdes
39998679Sdes        private void handleEndElement(Name name) {
40098679Sdes            switch (getHtmlTag(name)) {
40198679Sdes                case BLOCKQUOTE:
40298679Sdes                    indent -= INDENT;
40398679Sdes                    break;
40498679Sdes                case PRE:
40598679Sdes                    pre = false;
40698679Sdes                    addNewLineIfNeeded(result);
40798679Sdes                    reflownTo = result.length();
40898679Sdes                    break;
40998679Sdes                case UL: case OL:
41098679Sdes                    if (listStack.isEmpty()) { //ignore stray closing tag
41198679Sdes                        break;
41298679Sdes                    }
41398679Sdes                    reflowTillNow();
41498679Sdes                    listStack.pop();
41598679Sdes                    indent -= INDENT;
41698679Sdes                    addNewLineIfNeeded(result);
41798679Sdes                    break;
41898679Sdes                case DL:
41998679Sdes                    if (defStack.isEmpty()) {//ignore stray closing tag
42098679Sdes                        break;
42198679Sdes                    }
42298679Sdes                    reflowTillNow();
42398679Sdes                    if (indent == defStack.peek()) {
42498679Sdes                        result.append(escape(CODE_RESET));
42598679Sdes                    }
42698679Sdes                    indent = defStack.pop();
42798679Sdes                    addNewLineIfNeeded(result);
42898679Sdes                    break;
42998679Sdes                case H1: case H2: case H3:
43098679Sdes                case H4: case H5: case H6:
43198679Sdes                    reflowTillNow();
43298679Sdes                    result.append(escape(CODE_RESET))
43398679Sdes                          .append("\n");
43498679Sdes                    reflownTo = result.length();
43598679Sdes                    break;
43698679Sdes                case TABLE:
43798679Sdes                    if (cellsStack.size() >= tableStack.size()) {
43898679Sdes                        //unclosed <tr>:
43998679Sdes                        handleEndElement(task.getElements().getName("tr"));
44098679Sdes                    }
44198679Sdes
44298679Sdes                    if (tableStack.isEmpty()) {
44398679Sdes                        break;
44498679Sdes                    }
44598679Sdes
44698679Sdes                    limit = tableStack.pop();
44798679Sdes                    break;
44898679Sdes                case TR:
44998679Sdes                    if (cellsStack.isEmpty()) {
45098679Sdes                        break;
45198679Sdes                    }
45298679Sdes
45398679Sdes                    reflowTillNow();
45498679Sdes
45598679Sdes                    List<Integer> cells = cellsStack.pop();
45698679Sdes                    List<Boolean> headerFlags = headerStack.pop();
45798679Sdes                    List<String[]> content = new ArrayList<>();
45898679Sdes                    int maxLines = 0;
45998679Sdes
46098679Sdes                    result.append("\n");
461127403Sdes
462127403Sdes                    while (!cells.isEmpty()) {
46398679Sdes                        int currentCell = cells.remove(cells.size() - 1);
46498679Sdes                        String[] lines = result.substring(currentCell, result.length()).split("\n");
46598679Sdes
46698679Sdes                        result.delete(currentCell - 1, result.length());
46798679Sdes
46898679Sdes                        content.add(lines);
46998679Sdes                        maxLines = Math.max(maxLines, lines.length);
47098679Sdes                    }
47198679Sdes
47298679Sdes                    Collections.reverse(content);
47398679Sdes
47498679Sdes                    for (int line = 0; line < maxLines; line++) {
47598679Sdes                        for (int column = 0; column < content.size(); column++) {
47698679Sdes                            String[] lines = content.get(column);
47798679Sdes                            String currentLine = line < lines.length ? lines[line] : "";
47898679Sdes                            result.append("| ");
47998679Sdes                            boolean header = headerFlags.get(column);
48098679Sdes                            if (header) {
48198679Sdes                                result.append(escape(CODE_HIGHLIGHT));
48298679Sdes                            }
48398679Sdes                            result.append(currentLine);
48498679Sdes                            if (header) {
48598679Sdes                                result.append(escape(CODE_RESET));
48698679Sdes                            }
48798679Sdes                            int padding = limit - currentLine.length();
48898679Sdes                            if (padding > 0)
48998679Sdes                                result.append(indentString(padding));
49098679Sdes                            result.append(" ");
49198679Sdes                        }
49298679Sdes                        result.append("|\n");
49398679Sdes                    }
494127403Sdes
495127403Sdes                    for (int sep = 0; sep < (limit + 3) * content.size() + 1; sep++) {
49698679Sdes                        result.append("-");
49798679Sdes                    }
49898679Sdes
49998679Sdes                    result.append("\n");
50098679Sdes
50198679Sdes                    reflownTo = result.length();
50298679Sdes                    break;
50398679Sdes                case TD:
50498679Sdes                case TH:
50598679Sdes                    break;
50698679Sdes                default:
50798679Sdes                    addSpaceIfNeeded(result);
50898679Sdes                    break;
50998679Sdes            }
51098679Sdes        }
51198679Sdes
51298679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
51398679Sdes        public Object visitEntity(EntityTree node, Object p) {
51498679Sdes            String name = node.getName().toString();
51598679Sdes            int code = -1;
51698679Sdes            if (name.startsWith("#")) {
51798679Sdes                try {
51898679Sdes                    int v = StringUtils.toLowerCase(name).startsWith("#x")
51998679Sdes                            ? Integer.parseInt(name.substring(2), 16)
52098679Sdes                            : Integer.parseInt(name.substring(1), 10);
52198679Sdes                    if (Entity.isValid(v)) {
52298679Sdes                        code = v;
52398679Sdes                    }
52498679Sdes                } catch (NumberFormatException ex) {
52598679Sdes                    //ignore
52698679Sdes                }
527127403Sdes            } else {
52898679Sdes                Entity entity = Entity.get(name);
52998679Sdes                if (entity != null) {
53098679Sdes                    code = entity.code;
53198679Sdes                }
53298679Sdes            }
53398679Sdes            if (code != (-1)) {
53498679Sdes                result.appendCodePoint(code);
53598679Sdes            } else {
53698679Sdes                result.append(node.toString());
53798679Sdes            }
53898679Sdes            return super.visitEntity(node, p);
53998679Sdes        }
54098679Sdes
54198679Sdes        private DocTree lastNode;
54298679Sdes
54398679Sdes        @Override @DefinedBy(Api.COMPILER_TREE)
54498679Sdes        public Object scan(DocTree node, Object p) {
54598679Sdes            if (node instanceof InlineTagTree) {
54698679Sdes                addSpaceIfNeeded(result);
54798679Sdes            }
54898679Sdes            try {
54998679Sdes                return super.scan(node, p);
55098679Sdes            } finally {
55198679Sdes                if (node instanceof InlineTagTree) {
55298679Sdes                    addSpaceIfNeeded(result);
55398679Sdes                }
55498679Sdes                lastNode = node;
55598679Sdes            }
55698679Sdes        }
55798679Sdes
55898679Sdes        private void reflowTillNow() {
55998679Sdes            while (result.length() > 0 && result.charAt(result.length() - 1) == ' ')
560127403Sdes                result.delete(result.length() - 1, result.length());
56198679Sdes            reflow(result, reflownTo, indent, limit);
56298679Sdes            reflownTo = result.length();
56398679Sdes        }
56498679Sdes    };
56598679Sdes
56698679Sdes    private String escape(String sequence) {
56798679Sdes        return this.escapeSequencesSupported ? sequence : "";
56898679Sdes    }
56998679Sdes
57098679Sdes    private static final Map<Sections, String> docSections = new LinkedHashMap<>();
57198679Sdes
57298679Sdes    static {
57398679Sdes        ResourceBundle bundle =
57498679Sdes                ResourceBundle.getBundle("jdk.internal.shellsupport.doc.resources.javadocformatter");
57598679Sdes        docSections.put(Sections.TYPE_PARAMS, bundle.getString("CAP_TypeParameters"));
57698679Sdes        docSections.put(Sections.PARAMS, bundle.getString("CAP_Parameters"));
57798679Sdes        docSections.put(Sections.RETURNS, bundle.getString("CAP_Returns"));
57898679Sdes        docSections.put(Sections.THROWS, bundle.getString("CAP_Thrown_Exceptions"));
57998679Sdes    }
58098679Sdes
58198679Sdes    private static String indentString(int indent) {
58298679Sdes        char[] content = new char[indent];
58398679Sdes        Arrays.fill(content, ' ');
58498679Sdes        return new String(content);
58598679Sdes    }
58698679Sdes
58798679Sdes    private static void reflow(StringBuilder text, int from, int indent, int limit) {
58898679Sdes        int lineStart = from;
58998679Sdes
59098679Sdes        while (lineStart > 0 && text.charAt(lineStart - 1) != '\n') {
59198679Sdes            lineStart--;
59298679Sdes        }
59398679Sdes
59498679Sdes        int lineChars = from - lineStart;
59598679Sdes        int pointer = from;
59698679Sdes        int lastSpace = -1;
59798679Sdes
59898679Sdes        while (pointer < text.length()) {
59998679Sdes            if (text.charAt(pointer) == ' ')
60098679Sdes                lastSpace = pointer;
60198679Sdes            if (lineChars >= limit) {
60298679Sdes                if (lastSpace != (-1)) {
60398679Sdes                    text.setCharAt(lastSpace, '\n');
60498679Sdes                    text.insert(lastSpace + 1, indentString(indent));
60598679Sdes                    lineChars = indent + pointer - lastSpace - 1;
60698679Sdes                    pointer += indent;
60798679Sdes                    lastSpace = -1;
60898679Sdes                }
60998679Sdes            }
61098679Sdes            lineChars++;
61198679Sdes            pointer++;
61298679Sdes        }
61398679Sdes    }
61498679Sdes
61598679Sdes    private static void addNewLineIfNeeded(StringBuilder text) {
61698679Sdes        if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
61798679Sdes            text.append("\n");
61898679Sdes        }
61998679Sdes    }
62098679Sdes
62198679Sdes    private static void addSpaceIfNeeded(StringBuilder text) {
62298679Sdes        if (text.length() == 0)
62398679Sdes            return ;
62498679Sdes
62598679Sdes        char last = text.charAt(text.length() - 1);
62698679Sdes
62798679Sdes        if (last != ' ' && last != '\n') {
62898679Sdes            text.append(" ");
62998679Sdes        }
63098679Sdes    }
631127403Sdes
63298679Sdes    private static HtmlTag getHtmlTag(Name name) {
633127563Sdes        HtmlTag tag = HtmlTag.get(name);
63498679Sdes
63598679Sdes        return tag != null ? tag : HtmlTag.HTML; //using HtmlTag.HTML as default no-op value
63698679Sdes    }
63798679Sdes
63898679Sdes    private static Map<StartElementTree, Integer> countTableColumns(DocCommentTree dct) {
63998679Sdes        Map<StartElementTree, Integer> result = new IdentityHashMap<>();
64098679Sdes
64198679Sdes        new DocTreeScanner<Void, Void>() {
64298679Sdes            private StartElementTree currentTable;
64398679Sdes            private int currentMaxColumns;
64498679Sdes            private int currentRowColumns;
64598679Sdes
64698679Sdes            @Override @DefinedBy(Api.COMPILER_TREE)
64798679Sdes            public Void visitStartElement(StartElementTree node, Void p) {
64898679Sdes                switch (getHtmlTag(node.getName())) {
64998679Sdes                    case TABLE: currentTable = node; break;
65098679Sdes                    case TR:
65198679Sdes                        currentMaxColumns = Math.max(currentMaxColumns, currentRowColumns);
65298679Sdes                        currentRowColumns = 0;
65398679Sdes                        break;
65498679Sdes                    case TD:
65598679Sdes                    case TH: currentRowColumns++; break;
65698679Sdes                }
65798679Sdes                return super.visitStartElement(node, p);
65898679Sdes            }
65998679Sdes
66098679Sdes            @Override @DefinedBy(Api.COMPILER_TREE)
66198679Sdes            public Void visitEndElement(EndElementTree node, Void p) {
66298679Sdes                if (HtmlTag.get(node.getName()) == HtmlTag.TABLE) {
66398679Sdes                    closeTable();
66498679Sdes                }
66598679Sdes                return super.visitEndElement(node, p);
66698679Sdes            }
66798679Sdes
66898679Sdes            @Override @DefinedBy(Api.COMPILER_TREE)
66998679Sdes            public Void visitDocComment(DocCommentTree node, Void p) {
67098679Sdes                try {
67198679Sdes                    return super.visitDocComment(node, p);
67298679Sdes                } finally {
67398679Sdes                    closeTable();
674127563Sdes                }
67598679Sdes            }
67698679Sdes
67798679Sdes            private void closeTable() {
67898679Sdes                if (currentTable != null) {
67998679Sdes                    result.put(currentTable, Math.max(currentMaxColumns, currentRowColumns));
68098679Sdes                    currentTable = null;
681127563Sdes                }
68298679Sdes            }
68398679Sdes        }.scan(dct, null);
684
685        return result;
686    }
687
688    private enum Sections {
689        TYPE_PARAMS {
690            @Override public boolean matches(DocTree t) {
691                return t.getKind() == DocTree.Kind.PARAM && ((ParamTree) t).isTypeParameter();
692            }
693        },
694        PARAMS {
695            @Override public boolean matches(DocTree t) {
696                return t.getKind() == DocTree.Kind.PARAM && !((ParamTree) t).isTypeParameter();
697            }
698        },
699        RETURNS {
700            @Override public boolean matches(DocTree t) {
701                return t.getKind() == DocTree.Kind.RETURN;
702            }
703        },
704        THROWS {
705            @Override public boolean matches(DocTree t) {
706                return t.getKind() == DocTree.Kind.THROWS;
707            }
708        };
709
710        public abstract boolean matches(DocTree t);
711    }
712}
713