langtools/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java
changeset 41865 3ef02797070d
child 42258 a1aafd5ea6ec
equal deleted inserted replaced
41864:f7dbab23003a 41865:3ef02797070d
       
     1 /*
       
     2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.internal.shellsupport.doc;
       
    26 
       
    27 import java.io.IOException;
       
    28 import java.net.URI;
       
    29 import java.net.URISyntaxException;
       
    30 import java.util.ArrayList;
       
    31 import java.util.Arrays;
       
    32 import java.util.Collections;
       
    33 import java.util.IdentityHashMap;
       
    34 import java.util.LinkedHashMap;
       
    35 import java.util.List;
       
    36 import java.util.Map;
       
    37 import java.util.ResourceBundle;
       
    38 import java.util.Stack;
       
    39 
       
    40 import javax.lang.model.element.Name;
       
    41 import javax.tools.JavaFileObject.Kind;
       
    42 import javax.tools.SimpleJavaFileObject;
       
    43 import javax.tools.ToolProvider;
       
    44 
       
    45 import com.sun.source.doctree.AttributeTree;
       
    46 import com.sun.source.doctree.DocCommentTree;
       
    47 import com.sun.source.doctree.DocTree;
       
    48 import com.sun.source.doctree.EndElementTree;
       
    49 import com.sun.source.doctree.EntityTree;
       
    50 import com.sun.source.doctree.InlineTagTree;
       
    51 import com.sun.source.doctree.LinkTree;
       
    52 import com.sun.source.doctree.LiteralTree;
       
    53 import com.sun.source.doctree.ParamTree;
       
    54 import com.sun.source.doctree.ReturnTree;
       
    55 import com.sun.source.doctree.StartElementTree;
       
    56 import com.sun.source.doctree.TextTree;
       
    57 import com.sun.source.doctree.ThrowsTree;
       
    58 import com.sun.source.util.DocTreeScanner;
       
    59 import com.sun.source.util.DocTrees;
       
    60 import com.sun.source.util.JavacTask;
       
    61 import com.sun.tools.doclint.Entity;
       
    62 import com.sun.tools.doclint.HtmlTag;
       
    63 import com.sun.tools.javac.util.DefinedBy;
       
    64 import com.sun.tools.javac.util.DefinedBy.Api;
       
    65 import com.sun.tools.javac.util.StringUtils;
       
    66 
       
    67 /**A javadoc to plain text formatter.
       
    68  *
       
    69  */
       
    70 public class JavadocFormatter {
       
    71 
       
    72     private static final String CODE_RESET = "\033[0m";
       
    73     private static final String CODE_HIGHLIGHT = "\033[1m";
       
    74     private static final String CODE_UNDERLINE = "\033[4m";
       
    75 
       
    76     private final int lineLimit;
       
    77     private final boolean escapeSequencesSupported;
       
    78 
       
    79     /** Construct the formatter.
       
    80      *
       
    81      * @param lineLimit maximum line length
       
    82      * @param escapeSequencesSupported whether escape sequences are supported
       
    83      */
       
    84     public JavadocFormatter(int lineLimit, boolean escapeSequencesSupported) {
       
    85         this.lineLimit = lineLimit;
       
    86         this.escapeSequencesSupported = escapeSequencesSupported;
       
    87     }
       
    88 
       
    89     private static final int MAX_LINE_LENGTH = 95;
       
    90     private static final int SHORTEST_LINE = 30;
       
    91     private static final int INDENT = 4;
       
    92 
       
    93     /**Format javadoc to plain text.
       
    94      *
       
    95      * @param header element caption that should be used
       
    96      * @param javadoc to format
       
    97      * @return javadoc formatted to plain text
       
    98      */
       
    99     public String formatJavadoc(String header, String javadoc) {
       
   100         try {
       
   101             StringBuilder result = new StringBuilder();
       
   102 
       
   103             result.append(escape(CODE_HIGHLIGHT)).append(header).append(escape(CODE_RESET)).append("\n");
       
   104 
       
   105             if (javadoc == null) {
       
   106                 return result.toString();
       
   107             }
       
   108 
       
   109             JavacTask task = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, null);
       
   110             DocTrees trees = DocTrees.instance(task);
       
   111             DocCommentTree docComment = trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) {
       
   112                 @Override @DefinedBy(Api.COMPILER)
       
   113                 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
       
   114                     return "<body>" + javadoc + "</body>";
       
   115                 }
       
   116             });
       
   117 
       
   118             new FormatJavadocScanner(result, task).scan(docComment, null);
       
   119 
       
   120             addNewLineIfNeeded(result);
       
   121 
       
   122             return result.toString();
       
   123         } catch (URISyntaxException ex) {
       
   124             throw new InternalError("Unexpected exception", ex);
       
   125         }
       
   126     }
       
   127 
       
   128     private class FormatJavadocScanner extends DocTreeScanner<Object, Object> {
       
   129         private final StringBuilder result;
       
   130         private final JavacTask task;
       
   131         private int reflownTo;
       
   132         private int indent;
       
   133         private int limit = Math.min(lineLimit, MAX_LINE_LENGTH);
       
   134         private boolean pre;
       
   135         private Map<StartElementTree, Integer> tableColumns;
       
   136 
       
   137         public FormatJavadocScanner(StringBuilder result, JavacTask task) {
       
   138             this.result = result;
       
   139             this.task = task;
       
   140         }
       
   141 
       
   142         @Override @DefinedBy(Api.COMPILER_TREE)
       
   143         public Object visitDocComment(DocCommentTree node, Object p) {
       
   144             tableColumns = countTableColumns(node);
       
   145             reflownTo = result.length();
       
   146             scan(node.getFirstSentence(), p);
       
   147             scan(node.getBody(), p);
       
   148             reflow(result, reflownTo, indent, limit);
       
   149             for (Sections current : docSections.keySet()) {
       
   150                 boolean seenAny = false;
       
   151                 for (DocTree t : node.getBlockTags()) {
       
   152                     if (current.matches(t)) {
       
   153                         if (!seenAny) {
       
   154                             seenAny = true;
       
   155                             if (result.charAt(result.length() - 1) != '\n')
       
   156                                 result.append("\n");
       
   157                             result.append("\n");
       
   158                             result.append(escape(CODE_UNDERLINE))
       
   159                                   .append(docSections.get(current))
       
   160                                   .append(escape(CODE_RESET))
       
   161                                   .append("\n");
       
   162                         }
       
   163 
       
   164                         scan(t, null);
       
   165                     }
       
   166                 }
       
   167             }
       
   168             return null;
       
   169         }
       
   170 
       
   171         @Override @DefinedBy(Api.COMPILER_TREE)
       
   172         public Object visitText(TextTree node, Object p) {
       
   173             String text = node.getBody();
       
   174             if (!pre) {
       
   175                 text = text.replaceAll("[ \t\r\n]+", " ").trim();
       
   176                 if (text.isEmpty()) {
       
   177                     text = " ";
       
   178                 }
       
   179             } else {
       
   180                 text = text.replaceAll("\n", "\n" + indentString(indent));
       
   181             }
       
   182             result.append(text);
       
   183             return null;
       
   184         }
       
   185 
       
   186         @Override @DefinedBy(Api.COMPILER_TREE)
       
   187         public Object visitLink(LinkTree node, Object p) {
       
   188             if (!node.getLabel().isEmpty()) {
       
   189                 scan(node.getLabel(), p);
       
   190             } else {
       
   191                 result.append(node.getReference().getSignature());
       
   192             }
       
   193             return null;
       
   194         }
       
   195 
       
   196         @Override @DefinedBy(Api.COMPILER_TREE)
       
   197         public Object visitParam(ParamTree node, Object p) {
       
   198             return formatDef(node.getName().getName(), node.getDescription());
       
   199         }
       
   200 
       
   201         @Override @DefinedBy(Api.COMPILER_TREE)
       
   202         public Object visitThrows(ThrowsTree node, Object p) {
       
   203             return formatDef(node.getExceptionName().getSignature(), node.getDescription());
       
   204         }
       
   205 
       
   206         public Object formatDef(CharSequence name, List<? extends DocTree> description) {
       
   207             result.append(name);
       
   208             result.append(" - ");
       
   209             reflownTo = result.length();
       
   210             indent = name.length() + 3;
       
   211 
       
   212             if (limit - indent < SHORTEST_LINE) {
       
   213                 result.append("\n");
       
   214                 result.append(indentString(INDENT));
       
   215                 indent = INDENT;
       
   216                 reflownTo += INDENT;
       
   217             }
       
   218             try {
       
   219                 return scan(description, null);
       
   220             } finally {
       
   221                 reflow(result, reflownTo, indent, limit);
       
   222                 result.append("\n");
       
   223             }
       
   224         }
       
   225 
       
   226         @Override @DefinedBy(Api.COMPILER_TREE)
       
   227         public Object visitLiteral(LiteralTree node, Object p) {
       
   228             return scan(node.getBody(), p);
       
   229         }
       
   230 
       
   231         @Override @DefinedBy(Api.COMPILER_TREE)
       
   232         public Object visitReturn(ReturnTree node, Object p) {
       
   233             reflownTo = result.length();
       
   234             try {
       
   235                 return super.visitReturn(node, p);
       
   236             } finally {
       
   237                 reflow(result, reflownTo, 0, limit);
       
   238             }
       
   239         }
       
   240 
       
   241         Stack<Integer> listStack = new Stack<>();
       
   242         Stack<Integer> defStack = new Stack<>();
       
   243         Stack<Integer> tableStack = new Stack<>();
       
   244         Stack<List<Integer>> cellsStack = new Stack<>();
       
   245         Stack<List<Boolean>> headerStack = new Stack<>();
       
   246 
       
   247         @Override @DefinedBy(Api.COMPILER_TREE)
       
   248         public Object visitStartElement(StartElementTree node, Object p) {
       
   249             switch (HtmlTag.get(node.getName())) {
       
   250                 case P:
       
   251                     if (lastNode!= null && lastNode.getKind() == DocTree.Kind.START_ELEMENT &&
       
   252                         HtmlTag.get(((StartElementTree) lastNode).getName()) == HtmlTag.LI) {
       
   253                         //ignore
       
   254                         break;
       
   255                     }
       
   256                     reflowTillNow();
       
   257                     addNewLineIfNeeded(result);
       
   258                     result.append(indentString(indent));
       
   259                     reflownTo = result.length();
       
   260                     break;
       
   261                 case BLOCKQUOTE:
       
   262                     reflowTillNow();
       
   263                     indent += INDENT;
       
   264                     break;
       
   265                 case PRE:
       
   266                     reflowTillNow();
       
   267                     pre = true;
       
   268                     break;
       
   269                 case UL:
       
   270                     reflowTillNow();
       
   271                     listStack.push(-1);
       
   272                     indent += INDENT;
       
   273                     break;
       
   274                 case OL:
       
   275                     reflowTillNow();
       
   276                     listStack.push(1);
       
   277                     indent += INDENT;
       
   278                     break;
       
   279                 case DL:
       
   280                     reflowTillNow();
       
   281                     defStack.push(indent);
       
   282                     break;
       
   283                 case LI:
       
   284                     reflowTillNow();
       
   285                     if (!listStack.empty()) {
       
   286                         addNewLineIfNeeded(result);
       
   287 
       
   288                         int top = listStack.pop();
       
   289 
       
   290                         if (top == (-1)) {
       
   291                             result.append(indentString(indent - 2));
       
   292                             result.append("* ");
       
   293                         } else {
       
   294                             result.append(indentString(indent - 3));
       
   295                             result.append("" + top++ + ". ");
       
   296                         }
       
   297 
       
   298                         listStack.push(top);
       
   299 
       
   300                         reflownTo = result.length();
       
   301                     }
       
   302                     break;
       
   303                 case DT:
       
   304                     reflowTillNow();
       
   305                     if (!defStack.isEmpty()) {
       
   306                         addNewLineIfNeeded(result);
       
   307                         indent = defStack.peek();
       
   308                         result.append(escape(CODE_HIGHLIGHT));
       
   309                     }
       
   310                     break;
       
   311                 case DD:
       
   312                     reflowTillNow();
       
   313                     if (!defStack.isEmpty()) {
       
   314                         if (indent == defStack.peek()) {
       
   315                             result.append(escape(CODE_RESET));
       
   316                         }
       
   317                         addNewLineIfNeeded(result);
       
   318                         indent = defStack.peek() + INDENT;
       
   319                         result.append(indentString(indent));
       
   320                     }
       
   321                     break;
       
   322                 case H1: case H2: case H3:
       
   323                 case H4: case H5: case H6:
       
   324                     reflowTillNow();
       
   325                     addNewLineIfNeeded(result);
       
   326                     result.append("\n")
       
   327                           .append(escape(CODE_UNDERLINE));
       
   328                     reflownTo = result.length();
       
   329                     break;
       
   330                 case TABLE:
       
   331                     int columns = tableColumns.get(node);
       
   332 
       
   333                     if (columns == 0) {
       
   334                         break; //broken input
       
   335                     }
       
   336 
       
   337                     reflowTillNow();
       
   338                     addNewLineIfNeeded(result);
       
   339                     reflownTo = result.length();
       
   340 
       
   341                     tableStack.push(limit);
       
   342 
       
   343                     limit = (limit - 1) / columns - 3;
       
   344 
       
   345                     for (int sep = 0; sep < (limit + 3) * columns + 1; sep++) {
       
   346                         result.append("-");
       
   347                     }
       
   348 
       
   349                     result.append("\n");
       
   350 
       
   351                     break;
       
   352                 case TR:
       
   353                     if (cellsStack.size() >= tableStack.size()) {
       
   354                         //unclosed <tr>:
       
   355                         handleEndElement(node.getName());
       
   356                     }
       
   357                     cellsStack.push(new ArrayList<>());
       
   358                     headerStack.push(new ArrayList<>());
       
   359                     break;
       
   360                 case TH:
       
   361                 case TD:
       
   362                     if (cellsStack.isEmpty()) {
       
   363                         //broken code
       
   364                         break;
       
   365                     }
       
   366                     reflowTillNow();
       
   367                     result.append("\n");
       
   368                     reflownTo = result.length();
       
   369                     cellsStack.peek().add(result.length());
       
   370                     headerStack.peek().add(HtmlTag.get(node.getName()) == HtmlTag.TH);
       
   371                     break;
       
   372                 case IMG:
       
   373                     for (DocTree attr : node.getAttributes()) {
       
   374                         if (attr.getKind() != DocTree.Kind.ATTRIBUTE) {
       
   375                             continue;
       
   376                         }
       
   377                         AttributeTree at = (AttributeTree) attr;
       
   378                         if ("alt".equals(StringUtils.toLowerCase(at.getName().toString()))) {
       
   379                             addSpaceIfNeeded(result);
       
   380                             scan(at.getValue(), null);
       
   381                             addSpaceIfNeeded(result);
       
   382                             break;
       
   383                         }
       
   384                     }
       
   385                     break;
       
   386                 default:
       
   387                     addSpaceIfNeeded(result);
       
   388                     break;
       
   389             }
       
   390             return null;
       
   391         }
       
   392 
       
   393         @Override @DefinedBy(Api.COMPILER_TREE)
       
   394         public Object visitEndElement(EndElementTree node, Object p) {
       
   395             handleEndElement(node.getName());
       
   396             return super.visitEndElement(node, p);
       
   397         }
       
   398 
       
   399         private void handleEndElement(Name name) {
       
   400             switch (HtmlTag.get(name)) {
       
   401                 case BLOCKQUOTE:
       
   402                     indent -= INDENT;
       
   403                     break;
       
   404                 case PRE:
       
   405                     pre = false;
       
   406                     addNewLineIfNeeded(result);
       
   407                     reflownTo = result.length();
       
   408                     break;
       
   409                 case UL: case OL:
       
   410                     if (listStack.isEmpty()) { //ignore stray closing tag
       
   411                         break;
       
   412                     }
       
   413                     reflowTillNow();
       
   414                     listStack.pop();
       
   415                     indent -= INDENT;
       
   416                     addNewLineIfNeeded(result);
       
   417                     break;
       
   418                 case DL:
       
   419                     if (defStack.isEmpty()) {//ignore stray closing tag
       
   420                         break;
       
   421                     }
       
   422                     reflowTillNow();
       
   423                     if (indent == defStack.peek()) {
       
   424                         result.append(escape(CODE_RESET));
       
   425                     }
       
   426                     indent = defStack.pop();
       
   427                     addNewLineIfNeeded(result);
       
   428                     break;
       
   429                 case H1: case H2: case H3:
       
   430                 case H4: case H5: case H6:
       
   431                     reflowTillNow();
       
   432                     result.append(escape(CODE_RESET))
       
   433                           .append("\n");
       
   434                     reflownTo = result.length();
       
   435                     break;
       
   436                 case TABLE:
       
   437                     if (cellsStack.size() >= tableStack.size()) {
       
   438                         //unclosed <tr>:
       
   439                         handleEndElement(task.getElements().getName("tr"));
       
   440                     }
       
   441 
       
   442                     if (tableStack.isEmpty()) {
       
   443                         break;
       
   444                     }
       
   445 
       
   446                     limit = tableStack.pop();
       
   447                     break;
       
   448                 case TR:
       
   449                     if (cellsStack.isEmpty()) {
       
   450                         break;
       
   451                     }
       
   452 
       
   453                     reflowTillNow();
       
   454 
       
   455                     List<Integer> cells = cellsStack.pop();
       
   456                     List<Boolean> headerFlags = headerStack.pop();
       
   457                     List<String[]> content = new ArrayList<>();
       
   458                     int maxLines = 0;
       
   459 
       
   460                     result.append("\n");
       
   461 
       
   462                     while (!cells.isEmpty()) {
       
   463                         int currentCell = cells.remove(cells.size() - 1);
       
   464                         String[] lines = result.substring(currentCell, result.length()).split("\n");
       
   465 
       
   466                         result.delete(currentCell - 1, result.length());
       
   467 
       
   468                         content.add(lines);
       
   469                         maxLines = Math.max(maxLines, lines.length);
       
   470                     }
       
   471 
       
   472                     Collections.reverse(content);
       
   473 
       
   474                     for (int line = 0; line < maxLines; line++) {
       
   475                         for (int column = 0; column < content.size(); column++) {
       
   476                             String[] lines = content.get(column);
       
   477                             String currentLine = line < lines.length ? lines[line] : "";
       
   478                             result.append("| ");
       
   479                             boolean header = headerFlags.get(column);
       
   480                             if (header) {
       
   481                                 result.append(escape(CODE_HIGHLIGHT));
       
   482                             }
       
   483                             result.append(currentLine);
       
   484                             if (header) {
       
   485                                 result.append(escape(CODE_RESET));
       
   486                             }
       
   487                             int padding = limit - currentLine.length();
       
   488                             if (padding > 0)
       
   489                                 result.append(indentString(padding));
       
   490                             result.append(" ");
       
   491                         }
       
   492                         result.append("|\n");
       
   493                     }
       
   494 
       
   495                     for (int sep = 0; sep < (limit + 3) * content.size() + 1; sep++) {
       
   496                         result.append("-");
       
   497                     }
       
   498 
       
   499                     result.append("\n");
       
   500 
       
   501                     reflownTo = result.length();
       
   502                     break;
       
   503                 case TD:
       
   504                 case TH:
       
   505                     break;
       
   506                 default:
       
   507                     addSpaceIfNeeded(result);
       
   508                     break;
       
   509             }
       
   510         }
       
   511 
       
   512         @Override @DefinedBy(Api.COMPILER_TREE)
       
   513         public Object visitEntity(EntityTree node, Object p) {
       
   514             String name = node.getName().toString();
       
   515             int code = -1;
       
   516             if (name.startsWith("#")) {
       
   517                 try {
       
   518                     int v = StringUtils.toLowerCase(name).startsWith("#x")
       
   519                             ? Integer.parseInt(name.substring(2), 16)
       
   520                             : Integer.parseInt(name.substring(1), 10);
       
   521                     if (Entity.isValid(v)) {
       
   522                         code = v;
       
   523                     }
       
   524                 } catch (NumberFormatException ex) {
       
   525                     //ignore
       
   526                 }
       
   527             } else {
       
   528                 Entity entity = Entity.get(name);
       
   529                 if (entity != null) {
       
   530                     code = entity.code;
       
   531                 }
       
   532             }
       
   533             if (code != (-1)) {
       
   534                 result.appendCodePoint(code);
       
   535             } else {
       
   536                 result.append(node.toString());
       
   537             }
       
   538             return super.visitEntity(node, p);
       
   539         }
       
   540 
       
   541         private DocTree lastNode;
       
   542 
       
   543         @Override @DefinedBy(Api.COMPILER_TREE)
       
   544         public Object scan(DocTree node, Object p) {
       
   545             if (node instanceof InlineTagTree) {
       
   546                 addSpaceIfNeeded(result);
       
   547             }
       
   548             try {
       
   549                 return super.scan(node, p);
       
   550             } finally {
       
   551                 if (node instanceof InlineTagTree) {
       
   552                     addSpaceIfNeeded(result);
       
   553                 }
       
   554                 lastNode = node;
       
   555             }
       
   556         }
       
   557 
       
   558         private void reflowTillNow() {
       
   559             while (result.length() > 0 && result.charAt(result.length() - 1) == ' ')
       
   560                 result.delete(result.length() - 1, result.length());
       
   561             reflow(result, reflownTo, indent, limit);
       
   562             reflownTo = result.length();
       
   563         }
       
   564     };
       
   565 
       
   566     private String escape(String sequence) {
       
   567         return this.escapeSequencesSupported ? sequence : "";
       
   568     }
       
   569 
       
   570     private static final Map<Sections, String> docSections = new LinkedHashMap<>();
       
   571 
       
   572     static {
       
   573         ResourceBundle bundle =
       
   574                 ResourceBundle.getBundle("jdk.internal.shellsupport.doc.resources.javadocformatter");
       
   575         docSections.put(Sections.TYPE_PARAMS, bundle.getString("CAP_TypeParameters"));
       
   576         docSections.put(Sections.PARAMS, bundle.getString("CAP_Parameters"));
       
   577         docSections.put(Sections.RETURNS, bundle.getString("CAP_Returns"));
       
   578         docSections.put(Sections.THROWS, bundle.getString("CAP_Thrown_Exceptions"));
       
   579     }
       
   580 
       
   581     private static String indentString(int indent) {
       
   582         char[] content = new char[indent];
       
   583         Arrays.fill(content, ' ');
       
   584         return new String(content);
       
   585     }
       
   586 
       
   587     private static void reflow(StringBuilder text, int from, int indent, int limit) {
       
   588         int lineStart = from;
       
   589 
       
   590         while (lineStart > 0 && text.charAt(lineStart - 1) != '\n') {
       
   591             lineStart--;
       
   592         }
       
   593 
       
   594         int lineChars = from - lineStart;
       
   595         int pointer = from;
       
   596         int lastSpace = -1;
       
   597 
       
   598         while (pointer < text.length()) {
       
   599             if (text.charAt(pointer) == ' ')
       
   600                 lastSpace = pointer;
       
   601             if (lineChars >= limit) {
       
   602                 if (lastSpace != (-1)) {
       
   603                     text.setCharAt(lastSpace, '\n');
       
   604                     text.insert(lastSpace + 1, indentString(indent));
       
   605                     lineChars = indent + pointer - lastSpace - 1;
       
   606                     pointer += indent;
       
   607                     lastSpace = -1;
       
   608                 }
       
   609             }
       
   610             lineChars++;
       
   611             pointer++;
       
   612         }
       
   613     }
       
   614 
       
   615     private static void addNewLineIfNeeded(StringBuilder text) {
       
   616         if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
       
   617             text.append("\n");
       
   618         }
       
   619     }
       
   620 
       
   621     private static void addSpaceIfNeeded(StringBuilder text) {
       
   622         if (text.length() == 0)
       
   623             return ;
       
   624 
       
   625         char last = text.charAt(text.length() - 1);
       
   626 
       
   627         if (last != ' ' && last != '\n') {
       
   628             text.append(" ");
       
   629         }
       
   630     }
       
   631 
       
   632     private static Map<StartElementTree, Integer> countTableColumns(DocCommentTree dct) {
       
   633         Map<StartElementTree, Integer> result = new IdentityHashMap<>();
       
   634 
       
   635         new DocTreeScanner<Void, Void>() {
       
   636             private StartElementTree currentTable;
       
   637             private int currentMaxColumns;
       
   638             private int currentRowColumns;
       
   639 
       
   640             @Override @DefinedBy(Api.COMPILER_TREE)
       
   641             public Void visitStartElement(StartElementTree node, Void p) {
       
   642                 switch (HtmlTag.get(node.getName())) {
       
   643                     case TABLE: currentTable = node; break;
       
   644                     case TR:
       
   645                         currentMaxColumns = Math.max(currentMaxColumns, currentRowColumns);
       
   646                         currentRowColumns = 0;
       
   647                         break;
       
   648                     case TD:
       
   649                     case TH: currentRowColumns++; break;
       
   650                 }
       
   651                 return super.visitStartElement(node, p);
       
   652             }
       
   653 
       
   654             @Override @DefinedBy(Api.COMPILER_TREE)
       
   655             public Void visitEndElement(EndElementTree node, Void p) {
       
   656                 if (HtmlTag.get(node.getName()) == HtmlTag.TABLE) {
       
   657                     closeTable();
       
   658                 }
       
   659                 return super.visitEndElement(node, p);
       
   660             }
       
   661 
       
   662             @Override @DefinedBy(Api.COMPILER_TREE)
       
   663             public Void visitDocComment(DocCommentTree node, Void p) {
       
   664                 try {
       
   665                     return super.visitDocComment(node, p);
       
   666                 } finally {
       
   667                     closeTable();
       
   668                 }
       
   669             }
       
   670 
       
   671             private void closeTable() {
       
   672                 if (currentTable != null) {
       
   673                     result.put(currentTable, Math.max(currentMaxColumns, currentRowColumns));
       
   674                     currentTable = null;
       
   675                 }
       
   676             }
       
   677         }.scan(dct, null);
       
   678 
       
   679         return result;
       
   680     }
       
   681 
       
   682     private enum Sections {
       
   683         TYPE_PARAMS {
       
   684             @Override public boolean matches(DocTree t) {
       
   685                 return t.getKind() == DocTree.Kind.PARAM && ((ParamTree) t).isTypeParameter();
       
   686             }
       
   687         },
       
   688         PARAMS {
       
   689             @Override public boolean matches(DocTree t) {
       
   690                 return t.getKind() == DocTree.Kind.PARAM && !((ParamTree) t).isTypeParameter();
       
   691             }
       
   692         },
       
   693         RETURNS {
       
   694             @Override public boolean matches(DocTree t) {
       
   695                 return t.getKind() == DocTree.Kind.RETURN;
       
   696             }
       
   697         },
       
   698         THROWS {
       
   699             @Override public boolean matches(DocTree t) {
       
   700                 return t.getKind() == DocTree.Kind.THROWS;
       
   701             }
       
   702         };
       
   703 
       
   704         public abstract boolean matches(DocTree t);
       
   705     }
       
   706 }