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