1 /*
   2  * Copyright (c) 2011, 2017, 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 
  26 package com.sun.tools.javac.tree;
  27 
  28 import java.text.BreakIterator;
  29 import java.util.ArrayList;
  30 import java.util.Collection;
  31 import java.util.Collections;
  32 import java.util.EnumSet;
  33 import java.util.List;
  34 import java.util.ListIterator;
  35 
  36 import javax.lang.model.element.Name;
  37 import javax.tools.Diagnostic;
  38 import javax.tools.JavaFileObject;
  39 
  40 import com.sun.source.doctree.AttributeTree.ValueKind;
  41 import com.sun.source.doctree.DocCommentTree;
  42 import com.sun.source.doctree.DocTree;
  43 import com.sun.source.doctree.DocTree.Kind;
  44 import com.sun.source.doctree.EndElementTree;
  45 import com.sun.source.doctree.IdentifierTree;
  46 import com.sun.source.doctree.ReferenceTree;
  47 import com.sun.source.doctree.StartElementTree;
  48 import com.sun.source.doctree.TextTree;
  49 import com.sun.source.doctree.ProvidesTree;
  50 import com.sun.source.doctree.UsesTree;
  51 import com.sun.source.util.DocTreeFactory;
  52 import com.sun.tools.doclint.HtmlTag;
  53 import com.sun.tools.javac.api.JavacTrees;
  54 import com.sun.tools.javac.parser.ParserFactory;
  55 import com.sun.tools.javac.parser.ReferenceParser;
  56 import com.sun.tools.javac.parser.Tokens.Comment;
  57 import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
  58 import com.sun.tools.javac.tree.DCTree.DCAttribute;
  59 import com.sun.tools.javac.tree.DCTree.DCAuthor;
  60 import com.sun.tools.javac.tree.DCTree.DCComment;
  61 import com.sun.tools.javac.tree.DCTree.DCDeprecated;
  62 import com.sun.tools.javac.tree.DCTree.DCDocComment;
  63 import com.sun.tools.javac.tree.DCTree.DCDocRoot;
  64 import com.sun.tools.javac.tree.DCTree.DCDocType;
  65 import com.sun.tools.javac.tree.DCTree.DCEndElement;
  66 import com.sun.tools.javac.tree.DCTree.DCEntity;
  67 import com.sun.tools.javac.tree.DCTree.DCErroneous;
  68 import com.sun.tools.javac.tree.DCTree.DCHidden;
  69 import com.sun.tools.javac.tree.DCTree.DCIdentifier;
  70 import com.sun.tools.javac.tree.DCTree.DCIndex;
  71 import com.sun.tools.javac.tree.DCTree.DCInheritDoc;
  72 import com.sun.tools.javac.tree.DCTree.DCLink;
  73 import com.sun.tools.javac.tree.DCTree.DCLiteral;
  74 import com.sun.tools.javac.tree.DCTree.DCParam;
  75 import com.sun.tools.javac.tree.DCTree.DCProvides;
  76 import com.sun.tools.javac.tree.DCTree.DCReference;
  77 import com.sun.tools.javac.tree.DCTree.DCReturn;
  78 import com.sun.tools.javac.tree.DCTree.DCSee;
  79 import com.sun.tools.javac.tree.DCTree.DCSerial;
  80 import com.sun.tools.javac.tree.DCTree.DCSerialData;
  81 import com.sun.tools.javac.tree.DCTree.DCSerialField;
  82 import com.sun.tools.javac.tree.DCTree.DCSince;
  83 import com.sun.tools.javac.tree.DCTree.DCStartElement;
  84 import com.sun.tools.javac.tree.DCTree.DCSummary;
  85 import com.sun.tools.javac.tree.DCTree.DCSystemProperty;
  86 import com.sun.tools.javac.tree.DCTree.DCText;
  87 import com.sun.tools.javac.tree.DCTree.DCThrows;
  88 import com.sun.tools.javac.tree.DCTree.DCUnknownBlockTag;
  89 import com.sun.tools.javac.tree.DCTree.DCUnknownInlineTag;
  90 import com.sun.tools.javac.tree.DCTree.DCUses;
  91 import com.sun.tools.javac.tree.DCTree.DCValue;
  92 import com.sun.tools.javac.tree.DCTree.DCVersion;
  93 import com.sun.tools.javac.util.Context;
  94 import com.sun.tools.javac.util.DefinedBy;
  95 import com.sun.tools.javac.util.DefinedBy.Api;
  96 import com.sun.tools.javac.util.DiagnosticSource;
  97 import com.sun.tools.javac.util.JCDiagnostic;
  98 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
  99 import com.sun.tools.javac.util.ListBuffer;
 100 import com.sun.tools.javac.util.Pair;
 101 import com.sun.tools.javac.util.Position;
 102 
 103 import static com.sun.tools.doclint.HtmlTag.*;
 104 
 105 /**
 106  *
 107  *  <p><b>This is NOT part of any supported API.
 108  *  If you write code that depends on this, you do so at your own risk.
 109  *  This code and its internal interfaces are subject to change or
 110  *  deletion without notice.</b>
 111  */
 112 public class DocTreeMaker implements DocTreeFactory {
 113 
 114     /** The context key for the tree factory. */
 115     protected static final Context.Key<DocTreeMaker> treeMakerKey = new Context.Key<>();
 116 
 117     // A subset of block tags, which acts as sentence breakers, appearing
 118     // anywhere but the zero'th position in the first sentence.
 119     final EnumSet<HtmlTag> sentenceBreakTags;
 120 
 121     /** Get the TreeMaker instance. */
 122     public static DocTreeMaker instance(Context context) {
 123         DocTreeMaker instance = context.get(treeMakerKey);
 124         if (instance == null)
 125             instance = new DocTreeMaker(context);
 126         return instance;
 127     }
 128 
 129     /** The position at which subsequent trees will be created.
 130      */
 131     public int pos = Position.NOPOS;
 132 
 133     /** Access to diag factory for ErroneousTrees. */
 134     private final JCDiagnostic.Factory diags;
 135 
 136     private final JavacTrees trees;
 137 
 138     /** Utility class to parse reference signatures. */
 139     private final ReferenceParser referenceParser;
 140 
 141     /** Create a tree maker with NOPOS as initial position.
 142      */
 143     protected DocTreeMaker(Context context) {
 144         context.put(treeMakerKey, this);
 145         diags = JCDiagnostic.Factory.instance(context);
 146         this.pos = Position.NOPOS;
 147         trees = JavacTrees.instance(context);
 148         referenceParser = new ReferenceParser(ParserFactory.instance(context));
 149         sentenceBreakTags = EnumSet.of(H1, H2, H3, H4, H5, H6, PRE, P);
 150     }
 151 
 152     /** Reassign current position.
 153      */
 154     @Override @DefinedBy(Api.COMPILER_TREE)
 155     public DocTreeMaker at(int pos) {
 156         this.pos = pos;
 157         return this;
 158     }
 159 
 160     /** Reassign current position.
 161      */
 162     public DocTreeMaker at(DiagnosticPosition pos) {
 163         this.pos = (pos == null ? Position.NOPOS : pos.getStartPosition());
 164         return this;
 165     }
 166 
 167     @Override @DefinedBy(Api.COMPILER_TREE)
 168     public DCAttribute newAttributeTree(javax.lang.model.element.Name name, ValueKind vkind, java.util.List<? extends DocTree> value) {
 169         DCAttribute tree = new DCAttribute(name, vkind, cast(value));
 170         tree.pos = pos;
 171         return tree;
 172     }
 173 
 174     @Override @DefinedBy(Api.COMPILER_TREE)
 175     public DCAuthor newAuthorTree(java.util.List<? extends DocTree> name) {
 176         DCAuthor tree = new DCAuthor(cast(name));
 177         tree.pos = pos;
 178         return tree;
 179     }
 180 
 181     @Override @DefinedBy(Api.COMPILER_TREE)
 182     public DCLiteral newCodeTree(TextTree text) {
 183         DCLiteral tree = new DCLiteral(Kind.CODE, (DCText) text);
 184         tree.pos = pos;
 185         return tree;
 186     }
 187 
 188     @Override @DefinedBy(Api.COMPILER_TREE)
 189     public DCComment newCommentTree(String text) {
 190         DCComment tree = new DCComment(text);
 191         tree.pos = pos;
 192         return tree;
 193     }
 194 
 195     @Override @DefinedBy(Api.COMPILER_TREE)
 196     public DCDeprecated newDeprecatedTree(List<? extends DocTree> text) {
 197         DCDeprecated tree = new DCDeprecated(cast(text));
 198         tree.pos = pos;
 199         return tree;
 200     }
 201 
 202     @Override @DefinedBy(Api.COMPILER_TREE)
 203     public DCDocComment newDocCommentTree(List<? extends DocTree> fullBody, List<? extends DocTree> tags) {
 204         Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
 205         List<DCTree> preamble = Collections.emptyList();
 206         List<DCTree> postamble = Collections.emptyList();
 207 
 208         return newDocCommentTree(fullBody, tags, preamble, postamble);
 209     }
 210 
 211     public DCDocComment newDocCommentTree(Comment comment,
 212                                           List<? extends DocTree> fullBody,
 213                                           List<? extends DocTree> tags,
 214                                           List<? extends DocTree> preamble,
 215                                           List<? extends DocTree> postamble) {
 216         Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
 217         DCDocComment tree = new DCDocComment(comment, cast(fullBody), pair.fst, pair.snd,
 218                 cast(tags), cast(preamble), cast(postamble));
 219         tree.pos = pos;
 220         return tree;
 221     }
 222 
 223     /*
 224      * Primarily to produce a DocCommenTree when given a
 225      * first sentence and a body, this is useful, in cases
 226      * where the trees are being synthesized by a tool.
 227      */
 228     @Override @DefinedBy(Api.COMPILER_TREE)
 229     public DCDocComment newDocCommentTree(List<? extends DocTree> fullBody,
 230                                           List<? extends DocTree> tags,
 231                                           List<? extends DocTree> preamble,
 232                                           List<? extends DocTree> postamble) {
 233         ListBuffer<DCTree> lb = new ListBuffer<>();
 234         lb.addAll(cast(fullBody));
 235         List<DCTree> fBody = lb.toList();
 236 
 237         // A dummy comment to keep the diagnostics logic happy.
 238         Comment c = new Comment() {
 239             @Override
 240             public String getText() {
 241                 return null;
 242             }
 243 
 244             @Override
 245             public int getSourcePos(int index) {
 246                 return Position.NOPOS;
 247             }
 248 
 249             @Override
 250             public CommentStyle getStyle() {
 251                 return CommentStyle.JAVADOC;
 252             }
 253 
 254             @Override
 255             public boolean isDeprecated() {
 256                 return false;
 257             }
 258         };
 259         Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
 260         DCDocComment tree = new DCDocComment(c, fBody, pair.fst, pair.snd, cast(tags),
 261                                              cast(preamble), cast(postamble));
 262         return tree;
 263     }
 264 
 265     @Override @DefinedBy(Api.COMPILER_TREE)
 266     public DCDocRoot newDocRootTree() {
 267         DCDocRoot tree = new DCDocRoot();
 268         tree.pos = pos;
 269         return tree;
 270     }
 271 
 272     @Override @DefinedBy(Api.COMPILER_TREE)
 273     public DCDocType newDocTypeTree(String text) {
 274         DCDocType tree = new DCDocType(text);
 275         tree.pos = pos;
 276         return tree;
 277     }
 278 
 279     @Override @DefinedBy(Api.COMPILER_TREE)
 280     public DCEndElement newEndElementTree(Name name) {
 281         DCEndElement tree = new DCEndElement(name);
 282         tree.pos = pos;
 283         return tree;
 284     }
 285 
 286     @Override @DefinedBy(Api.COMPILER_TREE)
 287     public DCEntity newEntityTree(Name name) {
 288         DCEntity tree = new DCEntity(name);
 289         tree.pos = pos;
 290         return tree;
 291     }
 292 
 293     @Override @DefinedBy(Api.COMPILER_TREE)
 294     public DCErroneous newErroneousTree(String text, Diagnostic<JavaFileObject> diag) {
 295         DCErroneous tree = new DCErroneous(text, (JCDiagnostic) diag);
 296         tree.pos = pos;
 297         return tree;
 298     }
 299 
 300     public DCErroneous newErroneousTree(String text, DiagnosticSource diagSource, String code, Object... args) {
 301         DCErroneous tree = new DCErroneous(text, diags, diagSource, code, args);
 302         tree.pos = pos;
 303         return tree;
 304     }
 305 
 306     @Override @DefinedBy(Api.COMPILER_TREE)
 307     public DCThrows newExceptionTree(ReferenceTree name, List<? extends DocTree> description) {
 308         // TODO: verify the reference is just to a type (not a field or method)
 309         DCThrows tree = new DCThrows(Kind.EXCEPTION, (DCReference) name, cast(description));
 310         tree.pos = pos;
 311         return tree;
 312     }
 313 
 314     @Override @DefinedBy(Api.COMPILER_TREE)
 315     public DCHidden newHiddenTree(List<? extends DocTree> text) {
 316         DCHidden tree = new DCHidden(cast(text));
 317         tree.pos = pos;
 318         return tree;
 319     }
 320 
 321     @Override @DefinedBy(Api.COMPILER_TREE)
 322     public DCIdentifier newIdentifierTree(Name name) {
 323         DCIdentifier tree = new DCIdentifier(name);
 324         tree.pos = pos;
 325         return tree;
 326     }
 327 
 328     @Override @DefinedBy(Api.COMPILER_TREE)
 329     public DCIndex newIndexTree(DocTree term, List<? extends DocTree> description) {
 330         DCIndex tree = new DCIndex((DCTree) term, cast(description));
 331         tree.pos = pos;
 332         return tree;
 333     }
 334 
 335     @Override @DefinedBy(Api.COMPILER_TREE)
 336     public DCInheritDoc newInheritDocTree() {
 337         DCInheritDoc tree = new DCInheritDoc();
 338         tree.pos = pos;
 339         return tree;
 340     }
 341 
 342     @Override @DefinedBy(Api.COMPILER_TREE)
 343     public DCLink newLinkTree(ReferenceTree ref, List<? extends DocTree> label) {
 344         DCLink tree = new DCLink(Kind.LINK, (DCReference) ref, cast(label));
 345         tree.pos = pos;
 346         return tree;
 347     }
 348 
 349     @Override @DefinedBy(Api.COMPILER_TREE)
 350     public DCLink newLinkPlainTree(ReferenceTree ref, List<? extends DocTree> label) {
 351         DCLink tree = new DCLink(Kind.LINK_PLAIN, (DCReference) ref, cast(label));
 352         tree.pos = pos;
 353         return tree;
 354     }
 355 
 356     @Override @DefinedBy(Api.COMPILER_TREE)
 357     public DCLiteral newLiteralTree(TextTree text) {
 358         DCLiteral tree = new DCLiteral(Kind.LITERAL, (DCText) text);
 359         tree.pos = pos;
 360         return tree;
 361     }
 362 
 363     @Override @DefinedBy(Api.COMPILER_TREE)
 364     public DCParam newParamTree(boolean isTypeParameter, IdentifierTree name, List<? extends DocTree> description) {
 365         DCParam tree = new DCParam(isTypeParameter, (DCIdentifier) name, cast(description));
 366         tree.pos = pos;
 367         return tree;
 368     }
 369 
 370     @Override @DefinedBy(Api.COMPILER_TREE)
 371     public DCProvides newProvidesTree(ReferenceTree name, List<? extends DocTree> description) {
 372         DCProvides tree = new DCProvides((DCReference) name, cast(description));
 373         tree.pos = pos;
 374         return tree;
 375     }
 376 
 377     @Override @DefinedBy(Api.COMPILER_TREE)
 378     public DCReference newReferenceTree(String signature) {
 379         try {
 380             ReferenceParser.Reference ref = referenceParser.parse(signature);
 381             DCReference tree = new DCReference(signature, ref.qualExpr, ref.member, ref.paramTypes);
 382             tree.pos = pos;
 383             return tree;
 384         } catch (ReferenceParser.ParseException e) {
 385             throw new IllegalArgumentException("invalid signature", e);
 386         }
 387     }
 388 
 389     public DCReference newReferenceTree(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
 390         DCReference tree = new DCReference(signature, qualExpr, member, paramTypes);
 391         tree.pos = pos;
 392         return tree;
 393     }
 394 
 395     @Override @DefinedBy(Api.COMPILER_TREE)
 396     public DCReturn newReturnTree(List<? extends DocTree> description) {
 397         DCReturn tree = new DCReturn(cast(description));
 398         tree.pos = pos;
 399         return tree;
 400     }
 401 
 402     @Override @DefinedBy(Api.COMPILER_TREE)
 403     public DCSee newSeeTree(List<? extends DocTree> reference) {
 404         DCSee tree = new DCSee(cast(reference));
 405         tree.pos = pos;
 406         return tree;
 407     }
 408 
 409     @Override @DefinedBy(Api.COMPILER_TREE)
 410     public DCSerial newSerialTree(List<? extends DocTree> description) {
 411         DCSerial tree = new DCSerial(cast(description));
 412         tree.pos = pos;
 413         return tree;
 414     }
 415 
 416     @Override @DefinedBy(Api.COMPILER_TREE)
 417     public DCSerialData newSerialDataTree(List<? extends DocTree> description) {
 418         DCSerialData tree = new DCSerialData(cast(description));
 419         tree.pos = pos;
 420         return tree;
 421     }
 422 
 423     @Override @DefinedBy(Api.COMPILER_TREE)
 424     public DCSerialField newSerialFieldTree(IdentifierTree name, ReferenceTree type, List<? extends DocTree> description) {
 425         DCSerialField tree = new DCSerialField((DCIdentifier) name, (DCReference) type, cast(description));
 426         tree.pos = pos;
 427         return tree;
 428     }
 429 
 430     @Override @DefinedBy(Api.COMPILER_TREE)
 431     public DCSince newSinceTree(List<? extends DocTree> text) {
 432         DCSince tree = new DCSince(cast(text));
 433         tree.pos = pos;
 434         return tree;
 435     }
 436 
 437     @Override @DefinedBy(Api.COMPILER_TREE)
 438     public DCStartElement newStartElementTree(Name name, List<? extends DocTree> attrs, boolean selfClosing) {
 439         DCStartElement tree = new DCStartElement(name, cast(attrs), selfClosing);
 440         tree.pos = pos;
 441         return tree;
 442     }
 443 
 444     @Override @DefinedBy(Api.COMPILER_TREE)
 445     public DCSummary newSummaryTree(List<? extends DocTree> text) {
 446         DCSummary tree = new DCSummary(cast(text));
 447         tree.pos = pos;
 448         return tree;
 449     }
 450 
 451     @Override @DefinedBy(Api.COMPILER_TREE)
 452     public DCSystemProperty newSystemPropertyTree(Name propertyName) {
 453         DCSystemProperty tree = new DCSystemProperty(propertyName);
 454         tree.pos = pos;
 455         return tree;
 456     }
 457 
 458     @Override @DefinedBy(Api.COMPILER_TREE)
 459     public DCText newTextTree(String text) {
 460         DCText tree = new DCText(text);
 461         tree.pos = pos;
 462         return tree;
 463     }
 464 
 465     @Override @DefinedBy(Api.COMPILER_TREE)
 466     public DCThrows newThrowsTree(ReferenceTree name, List<? extends DocTree> description) {
 467         // TODO: verify the reference is just to a type (not a field or method)
 468         DCThrows tree = new DCThrows(Kind.THROWS, (DCReference) name, cast(description));
 469         tree.pos = pos;
 470         return tree;
 471     }
 472 
 473     @Override @DefinedBy(Api.COMPILER_TREE)
 474     public DCUnknownBlockTag newUnknownBlockTagTree(Name name, List<? extends DocTree> content) {
 475         DCUnknownBlockTag tree = new DCUnknownBlockTag(name, cast(content));
 476         tree.pos = pos;
 477         return tree;
 478     }
 479 
 480     @Override @DefinedBy(Api.COMPILER_TREE)
 481     public DCUnknownInlineTag newUnknownInlineTagTree(Name name, List<? extends DocTree> content) {
 482         DCUnknownInlineTag tree = new DCUnknownInlineTag(name, cast(content));
 483         tree.pos = pos;
 484         return tree;
 485     }
 486 
 487     @Override @DefinedBy(Api.COMPILER_TREE)
 488     public DCUses newUsesTree(ReferenceTree name, List<? extends DocTree> description) {
 489         DCUses tree = new DCUses((DCReference) name, cast(description));
 490         tree.pos = pos;
 491         return tree;
 492     }
 493 
 494     @Override @DefinedBy(Api.COMPILER_TREE)
 495     public DCValue newValueTree(ReferenceTree ref) {
 496         // TODO: verify the reference is to a constant value
 497         DCValue tree = new DCValue((DCReference) ref);
 498         tree.pos = pos;
 499         return tree;
 500     }
 501 
 502     @Override @DefinedBy(Api.COMPILER_TREE)
 503     public DCVersion newVersionTree(List<? extends DocTree> text) {
 504         DCVersion tree = new DCVersion(cast(text));
 505         tree.pos = pos;
 506         return tree;
 507     }
 508 
 509     @Override @DefinedBy(Api.COMPILER_TREE)
 510     public java.util.List<DocTree> getFirstSentence(java.util.List<? extends DocTree> list) {
 511         Pair<List<DCTree>, List<DCTree>> pair = splitBody(list);
 512         return new ArrayList<>(pair.fst);
 513     }
 514 
 515     /*
 516      * Breaks up the body tags into the first sentence and its successors.
 517      * The first sentence is determined with the presence of a period,
 518      * block tag, or a sentence break, as returned by the BreakIterator.
 519      * Trailing whitespaces are trimmed.
 520      */
 521     private Pair<List<DCTree>, List<DCTree>> splitBody(Collection<? extends DocTree> list) {
 522         // pos is modified as we create trees, therefore
 523         // we save the pos and restore it later.
 524         final int savedpos = this.pos;
 525         try {
 526             ListBuffer<DCTree> body = new ListBuffer<>();
 527             // split body into first sentence and body
 528             ListBuffer<DCTree> fs = new ListBuffer<>();
 529             if (list.isEmpty()) {
 530                 return new Pair<>(fs.toList(), body.toList());
 531             }
 532             boolean foundFirstSentence = false;
 533             ArrayList<DocTree> alist = new ArrayList<>(list);
 534             ListIterator<DocTree> itr = alist.listIterator();
 535             while (itr.hasNext()) {
 536                 boolean isFirst = !itr.hasPrevious();
 537                 DocTree dt = itr.next();
 538                 int spos = ((DCTree) dt).pos;
 539                 if (foundFirstSentence) {
 540                     body.add((DCTree) dt);
 541                     continue;
 542                 }
 543                 switch (dt.getKind()) {
 544                     case SUMMARY:
 545                         foundFirstSentence = true;
 546                         break;
 547                     case TEXT:
 548                         DCText tt = (DCText) dt;
 549                         String s = tt.getBody();
 550                         DocTree peekedNext = itr.hasNext()
 551                                 ? alist.get(itr.nextIndex())
 552                                 : null;
 553                         int sbreak = getSentenceBreak(s, peekedNext);
 554                         if (sbreak > 0) {
 555                             s = removeTrailingWhitespace(s.substring(0, sbreak));
 556                             DCText text = this.at(spos).newTextTree(s);
 557                             fs.add(text);
 558                             foundFirstSentence = true;
 559                             int nwPos = skipWhiteSpace(tt.getBody(), sbreak);
 560                             if (nwPos > 0) {
 561                                 DCText text2 = this.at(spos + nwPos).newTextTree(tt.getBody().substring(nwPos));
 562                                 body.add(text2);
 563                             }
 564                             continue;
 565                         } else if (itr.hasNext()) {
 566                             // if the next doctree is a break, remove trailing spaces
 567                             peekedNext = alist.get(itr.nextIndex());
 568                             boolean sbrk = isSentenceBreak(peekedNext, false);
 569                             if (sbrk) {
 570                                 DocTree next = itr.next();
 571                                 s = removeTrailingWhitespace(s);
 572                                 DCText text = this.at(spos).newTextTree(s);
 573                                 fs.add(text);
 574                                 body.add((DCTree) next);
 575                                 foundFirstSentence = true;
 576                                 continue;
 577                             }
 578                         }
 579                         break;
 580                     default:
 581                         if (isSentenceBreak(dt, isFirst)) {
 582                             body.add((DCTree) dt);
 583                             foundFirstSentence = true;
 584                             continue;
 585                         }
 586                         break;
 587                 }
 588                 fs.add((DCTree) dt);
 589             }
 590             return new Pair<>(fs.toList(), body.toList());
 591         } finally {
 592             this.pos = savedpos;
 593         }
 594     }
 595 
 596     private boolean isTextTree(DocTree tree) {
 597         return tree.getKind() == Kind.TEXT;
 598     }
 599 
 600     /*
 601      * Computes the first sentence break, a simple dot-space algorithm.
 602      */
 603     private int defaultSentenceBreak(String s) {
 604         // scan for period followed by whitespace
 605         int period = -1;
 606         for (int i = 0; i < s.length(); i++) {
 607             switch (s.charAt(i)) {
 608                 case '.':
 609                     period = i;
 610                     break;
 611 
 612                 case ' ':
 613                 case '\f':
 614                 case '\n':
 615                 case '\r':
 616                 case '\t':
 617                     if (period >= 0) {
 618                         return i;
 619                     }
 620                     break;
 621 
 622                 default:
 623                     period = -1;
 624                     break;
 625             }
 626         }
 627         return -1;
 628     }
 629 
 630     /*
 631      * Computes the first sentence, if using a default breaker,
 632      * the break is returned, if not then a -1, indicating that
 633      * more doctree elements are required to be examined.
 634      *
 635      * BreakIterator.next points to the the start of the following sentence,
 636      * and does not provide an easy way to disambiguate between "sentence break",
 637      * "possible sentence break" and "not a sentence break" at the end of the input.
 638      * For example, BreakIterator.next returns the index for the end
 639      * of the string for all of these examples,
 640      * using vertical bars to delimit the bounds of the example text
 641      * |Abc|        (not a valid end of sentence break, if followed by more text)
 642      * |Abc.|       (maybe a valid end of sentence break, depending on the following text)
 643      * |Abc. |      (maybe a valid end of sentence break, depending on the following text)
 644      * |"Abc." |    (maybe a valid end of sentence break, depending on the following text)
 645      * |Abc.  |     (definitely a valid end of sentence break)
 646      * |"Abc."  |   (definitely a valid end of sentence break)
 647      * Therefore, we have to probe further to determine whether
 648      * there really is a sentence break or not at the end of this run of text.
 649      */
 650     private int getSentenceBreak(String s, DocTree dt) {
 651         BreakIterator breakIterator = trees.getBreakIterator();
 652         if (breakIterator == null) {
 653             return defaultSentenceBreak(s);
 654         }
 655         breakIterator.setText(s);
 656         final int sbrk = breakIterator.next();
 657         // This is the last doctree, found the droid we are looking for
 658         if (dt == null) {
 659             return sbrk;
 660         }
 661 
 662         // If the break is well within the span of the string ie. not
 663         // at EOL, then we have a clear break.
 664         if (sbrk < s.length() - 1) {
 665             return sbrk;
 666         }
 667 
 668         if (isTextTree(dt)) {
 669             // Two adjacent text trees, a corner case, perhaps
 670             // produced by a tool synthesizing a doctree. In
 671             // this case, does the break lie within the first span,
 672             // then we have the droid, otherwise allow the callers
 673             // logic to handle the break in the adjacent doctree.
 674             TextTree ttnext = (TextTree) dt;
 675             String combined = s + ttnext.getBody();
 676             breakIterator.setText(combined);
 677             int sbrk2 = breakIterator.next();
 678             if (sbrk < sbrk2) {
 679                 return sbrk;
 680             }
 681         }
 682 
 683         // Is the adjacent tree a sentence breaker ?
 684         if (isSentenceBreak(dt, false)) {
 685             return sbrk;
 686         }
 687 
 688         // At this point the adjacent tree is either a javadoc tag ({@..),
 689         // html tag (<..) or an entity (&..). Perform a litmus test, by
 690         // concatenating a sentence, to validate the break earlier identified.
 691         String combined = s + "Dummy Sentence.";
 692         breakIterator.setText(combined);
 693         int sbrk2 = breakIterator.next();
 694         if (sbrk2 <= sbrk) {
 695             return sbrk2;
 696         }
 697         return -1; // indeterminate at this time
 698     }
 699 
 700     private boolean isSentenceBreak(javax.lang.model.element.Name tagName) {
 701         return sentenceBreakTags.contains(get(tagName));
 702     }
 703 
 704     private boolean isSentenceBreak(DocTree dt, boolean isFirstDocTree) {
 705         switch (dt.getKind()) {
 706             case START_ELEMENT:
 707                     StartElementTree set = (StartElementTree)dt;
 708                     return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(set.getName());
 709             case END_ELEMENT:
 710                     EndElementTree eet = (EndElementTree)dt;
 711                     return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(eet.getName());
 712             default:
 713                 return false;
 714         }
 715     }
 716 
 717     /*
 718      * Returns the position of the the first non-white space
 719      */
 720     private int skipWhiteSpace(String s, int start) {
 721         for (int i = start; i < s.length(); i++) {
 722             char c = s.charAt(i);
 723             if (!Character.isWhitespace(c)) {
 724                 return i;
 725             }
 726         }
 727         return -1;
 728     }
 729 
 730     private String removeTrailingWhitespace(String s) {
 731         for (int i = s.length() - 1 ; i >= 0 ; i--) {
 732             char ch = s.charAt(i);
 733             if (!Character.isWhitespace(ch)) {
 734                 return s.substring(0, i + 1);
 735             }
 736         }
 737         return s;
 738     }
 739 
 740     @SuppressWarnings("unchecked")
 741     private List<DCTree> cast(List<? extends DocTree> list) {
 742         return (List<DCTree>) list;
 743     }
 744 }