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