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