DocTreeMaker.java revision 3162:f164d4c2d33e
1/* 2 * Copyright (c) 2011, 2015, 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 26package com.sun.tools.javac.tree; 27 28import java.text.BreakIterator; 29import java.util.ArrayList; 30import java.util.Collection; 31import java.util.EnumSet; 32import java.util.ListIterator; 33 34import com.sun.source.doctree.AttributeTree.ValueKind; 35import com.sun.source.doctree.DocTree; 36import com.sun.source.doctree.DocTree.Kind; 37import com.sun.source.doctree.EndElementTree; 38import com.sun.source.doctree.StartElementTree; 39import com.sun.source.doctree.TextTree; 40import com.sun.tools.doclint.HtmlTag; 41import com.sun.tools.javac.api.JavacTrees; 42import com.sun.tools.javac.parser.Tokens.Comment; 43import com.sun.tools.javac.tree.DCTree.DCAttribute; 44import com.sun.tools.javac.tree.DCTree.DCAuthor; 45import com.sun.tools.javac.tree.DCTree.DCComment; 46import com.sun.tools.javac.tree.DCTree.DCDeprecated; 47import com.sun.tools.javac.tree.DCTree.DCDocComment; 48import com.sun.tools.javac.tree.DCTree.DCDocRoot; 49import com.sun.tools.javac.tree.DCTree.DCEndElement; 50import com.sun.tools.javac.tree.DCTree.DCEntity; 51import com.sun.tools.javac.tree.DCTree.DCErroneous; 52import com.sun.tools.javac.tree.DCTree.DCIdentifier; 53import com.sun.tools.javac.tree.DCTree.DCIndex; 54import com.sun.tools.javac.tree.DCTree.DCInheritDoc; 55import com.sun.tools.javac.tree.DCTree.DCLink; 56import com.sun.tools.javac.tree.DCTree.DCLiteral; 57import com.sun.tools.javac.tree.DCTree.DCParam; 58import com.sun.tools.javac.tree.DCTree.DCReference; 59import com.sun.tools.javac.tree.DCTree.DCReturn; 60import com.sun.tools.javac.tree.DCTree.DCSee; 61import com.sun.tools.javac.tree.DCTree.DCSerial; 62import com.sun.tools.javac.tree.DCTree.DCSerialData; 63import com.sun.tools.javac.tree.DCTree.DCSerialField; 64import com.sun.tools.javac.tree.DCTree.DCSince; 65import com.sun.tools.javac.tree.DCTree.DCStartElement; 66import com.sun.tools.javac.tree.DCTree.DCText; 67import com.sun.tools.javac.tree.DCTree.DCThrows; 68import com.sun.tools.javac.tree.DCTree.DCUnknownBlockTag; 69import com.sun.tools.javac.tree.DCTree.DCUnknownInlineTag; 70import com.sun.tools.javac.tree.DCTree.DCValue; 71import com.sun.tools.javac.tree.DCTree.DCVersion; 72import com.sun.tools.javac.util.Context; 73import com.sun.tools.javac.util.DiagnosticSource; 74import com.sun.tools.javac.util.JCDiagnostic; 75import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; 76import com.sun.tools.javac.util.List; 77import com.sun.tools.javac.util.ListBuffer; 78import com.sun.tools.javac.util.Name; 79import com.sun.tools.javac.util.Pair; 80import com.sun.tools.javac.util.Position; 81 82import static com.sun.tools.doclint.HtmlTag.*; 83 84/** 85 * 86 * <p><b>This is NOT part of any supported API. 87 * If you write code that depends on this, you do so at your own risk. 88 * This code and its internal interfaces are subject to change or 89 * deletion without notice.</b> 90 */ 91public class DocTreeMaker { 92 93 /** The context key for the tree factory. */ 94 protected static final Context.Key<DocTreeMaker> treeMakerKey = new Context.Key<>(); 95 96 // A subset of block tags, which acts as sentence breakers, appearing 97 // anywhere but the zero'th position in the first sentence. 98 final EnumSet<HtmlTag> sentenceBreakTags; 99 100 /** Get the TreeMaker instance. */ 101 public static DocTreeMaker instance(Context context) { 102 DocTreeMaker instance = context.get(treeMakerKey); 103 if (instance == null) 104 instance = new DocTreeMaker(context); 105 return instance; 106 } 107 108 /** The position at which subsequent trees will be created. 109 */ 110 public int pos = Position.NOPOS; 111 112 /** Access to diag factory for ErroneousTrees. */ 113 private final JCDiagnostic.Factory diags; 114 115 private final JavacTrees trees; 116 117 /** Create a tree maker with NOPOS as initial position. 118 */ 119 protected DocTreeMaker(Context context) { 120 context.put(treeMakerKey, this); 121 diags = JCDiagnostic.Factory.instance(context); 122 this.pos = Position.NOPOS; 123 trees = JavacTrees.instance(context); 124 sentenceBreakTags = EnumSet.of(H1, H2, H3, H4, H5, H6, PRE, P); 125 } 126 127 /** Reassign current position. 128 */ 129 public DocTreeMaker at(int pos) { 130 this.pos = pos; 131 return this; 132 } 133 134 /** Reassign current position. 135 */ 136 public DocTreeMaker at(DiagnosticPosition pos) { 137 this.pos = (pos == null ? Position.NOPOS : pos.getStartPosition()); 138 return this; 139 } 140 141 public DCAttribute Attribute(Name name, ValueKind vkind, List<DCTree> value) { 142 DCAttribute tree = new DCAttribute(name, vkind, value); 143 tree.pos = pos; 144 return tree; 145 } 146 147 public DCAuthor Author(List<DCTree> name) { 148 DCAuthor tree = new DCAuthor(name); 149 tree.pos = pos; 150 return tree; 151 } 152 153 public DCLiteral Code(DCText text) { 154 DCLiteral tree = new DCLiteral(Kind.CODE, text); 155 tree.pos = pos; 156 return tree; 157 } 158 159 public DCComment Comment(String text) { 160 DCComment tree = new DCComment(text); 161 tree.pos = pos; 162 return tree; 163 } 164 165 public DCDeprecated Deprecated(List<DCTree> text) { 166 DCDeprecated tree = new DCDeprecated(text); 167 tree.pos = pos; 168 return tree; 169 } 170 171 public DCDocComment DocComment(Comment comment, List<DCTree> fullBody, List<DCTree> tags) { 172 Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody); 173 DCDocComment tree = new DCDocComment(comment, fullBody, pair.fst, pair.snd, tags); 174 tree.pos = pos; 175 return tree; 176 } 177 178 /* 179 * Primarily to produce a DocCommenTree when given a 180 * first sentence and a body, this is useful, in cases 181 * where the trees are being synthesized by a tool. 182 */ 183 public DCDocComment DocComment(List<DCTree> firstSentence, List<DCTree> body, List<DCTree> tags) { 184 ListBuffer<DCTree> lb = new ListBuffer<>(); 185 lb.addAll(firstSentence); 186 lb.addAll(body); 187 List<DCTree> fullBody = lb.toList(); 188 DCDocComment tree = new DCDocComment(null, fullBody, firstSentence, body, tags); 189 return tree; 190 } 191 192 public DCDocRoot DocRoot() { 193 DCDocRoot tree = new DCDocRoot(); 194 tree.pos = pos; 195 return tree; 196 } 197 198 public DCEndElement EndElement(Name name) { 199 DCEndElement tree = new DCEndElement(name); 200 tree.pos = pos; 201 return tree; 202 } 203 204 public DCEntity Entity(Name name) { 205 DCEntity tree = new DCEntity(name); 206 tree.pos = pos; 207 return tree; 208 } 209 210 public DCErroneous Erroneous(String text, DiagnosticSource diagSource, String code, Object... args) { 211 DCErroneous tree = new DCErroneous(text, diags, diagSource, code, args); 212 tree.pos = pos; 213 return tree; 214 } 215 216 public DCThrows Exception(DCReference name, List<DCTree> description) { 217 DCThrows tree = new DCThrows(Kind.EXCEPTION, name, description); 218 tree.pos = pos; 219 return tree; 220 } 221 222 public DCIdentifier Identifier(Name name) { 223 DCIdentifier tree = new DCIdentifier(name); 224 tree.pos = pos; 225 return tree; 226 } 227 228 public DCIndex Index(DCTree term, List<DCTree> description) { 229 DCIndex tree = new DCIndex(term, description); 230 tree.pos = pos; 231 return tree; 232 } 233 234 public DCInheritDoc InheritDoc() { 235 DCInheritDoc tree = new DCInheritDoc(); 236 tree.pos = pos; 237 return tree; 238 } 239 240 public DCLink Link(DCReference ref, List<DCTree> label) { 241 DCLink tree = new DCLink(Kind.LINK, ref, label); 242 tree.pos = pos; 243 return tree; 244 } 245 246 public DCLink LinkPlain(DCReference ref, List<DCTree> label) { 247 DCLink tree = new DCLink(Kind.LINK_PLAIN, ref, label); 248 tree.pos = pos; 249 return tree; 250 } 251 252 public DCLiteral Literal(DCText text) { 253 DCLiteral tree = new DCLiteral(Kind.LITERAL, text); 254 tree.pos = pos; 255 return tree; 256 } 257 258 public DCParam Param(boolean isTypeParameter, DCIdentifier name, List<DCTree> description) { 259 DCParam tree = new DCParam(isTypeParameter, name, description); 260 tree.pos = pos; 261 return tree; 262 } 263 264 public DCReference Reference(String signature, 265 JCTree qualExpr, Name member, List<JCTree> paramTypes) { 266 DCReference tree = new DCReference(signature, qualExpr, member, paramTypes); 267 tree.pos = pos; 268 return tree; 269 } 270 271 public DCReturn Return(List<DCTree> description) { 272 DCReturn tree = new DCReturn(description); 273 tree.pos = pos; 274 return tree; 275 } 276 277 public DCSee See(List<DCTree> reference) { 278 DCSee tree = new DCSee(reference); 279 tree.pos = pos; 280 return tree; 281 } 282 283 public DCSerial Serial(List<DCTree> description) { 284 DCSerial tree = new DCSerial(description); 285 tree.pos = pos; 286 return tree; 287 } 288 289 public DCSerialData SerialData(List<DCTree> description) { 290 DCSerialData tree = new DCSerialData(description); 291 tree.pos = pos; 292 return tree; 293 } 294 295 public DCSerialField SerialField(DCIdentifier name, DCReference type, List<DCTree> description) { 296 DCSerialField tree = new DCSerialField(name, type, description); 297 tree.pos = pos; 298 return tree; 299 } 300 301 public DCSince Since(List<DCTree> text) { 302 DCSince tree = new DCSince(text); 303 tree.pos = pos; 304 return tree; 305 } 306 307 public DCStartElement StartElement(Name name, List<DCTree> attrs, boolean selfClosing) { 308 DCStartElement tree = new DCStartElement(name, attrs, selfClosing); 309 tree.pos = pos; 310 return tree; 311 } 312 313 public DCText Text(String text) { 314 DCText tree = new DCText(text); 315 tree.pos = pos; 316 return tree; 317 } 318 319 public DCThrows Throws(DCReference name, List<DCTree> description) { 320 DCThrows tree = new DCThrows(Kind.THROWS, name, description); 321 tree.pos = pos; 322 return tree; 323 } 324 325 public DCUnknownBlockTag UnknownBlockTag(Name name, List<DCTree> content) { 326 DCUnknownBlockTag tree = new DCUnknownBlockTag(name, content); 327 tree.pos = pos; 328 return tree; 329 } 330 331 public DCUnknownInlineTag UnknownInlineTag(Name name, List<DCTree> content) { 332 DCUnknownInlineTag tree = new DCUnknownInlineTag(name, content); 333 tree.pos = pos; 334 return tree; 335 } 336 337 public DCValue Value(DCReference ref) { 338 DCValue tree = new DCValue(ref); 339 tree.pos = pos; 340 return tree; 341 } 342 343 public DCVersion Version(List<DCTree> text) { 344 DCVersion tree = new DCVersion(text); 345 tree.pos = pos; 346 return tree; 347 } 348 349 public java.util.List<DocTree> getFirstSentence(java.util.List<? extends DocTree> list) { 350 Pair<List<DCTree>, List<DCTree>> pair = splitBody(list); 351 return new ArrayList<>(pair.fst); 352 } 353 354 /* 355 * Breaks up the body tags into the first sentence and its successors. 356 * The first sentence is determined with the presence of a period, 357 * block tag, or a sentence break, as returned by the BreakIterator. 358 * Trailing whitespaces are trimmed. 359 */ 360 private Pair<List<DCTree>, List<DCTree>> splitBody(Collection<? extends DocTree> list) { 361 // pos is modified as we create trees, therefore 362 // we save the pos and restore it later. 363 final int savedpos = this.pos; 364 try { 365 ListBuffer<DCTree> body = new ListBuffer<>(); 366 // split body into first sentence and body 367 ListBuffer<DCTree> fs = new ListBuffer<>(); 368 if (list.isEmpty()) { 369 return new Pair<>(fs.toList(), body.toList()); 370 } 371 boolean foundFirstSentence = false; 372 ArrayList<DocTree> alist = new ArrayList<>(list); 373 ListIterator<DocTree> itr = alist.listIterator(); 374 while (itr.hasNext()) { 375 boolean isFirst = !itr.hasPrevious(); 376 DocTree dt = itr.next(); 377 int spos = ((DCTree) dt).pos; 378 if (foundFirstSentence) { 379 body.add((DCTree) dt); 380 continue; 381 } 382 switch (dt.getKind()) { 383 case TEXT: 384 DCText tt = (DCText) dt; 385 String s = tt.getBody(); 386 DocTree peekedNext = itr.hasNext() 387 ? alist.get(itr.nextIndex()) 388 : null; 389 int sbreak = getSentenceBreak(s, peekedNext); 390 if (sbreak > 0) { 391 s = removeTrailingWhitespace(s.substring(0, sbreak)); 392 DCText text = this.at(spos).Text(s); 393 fs.add(text); 394 foundFirstSentence = true; 395 int nwPos = skipWhiteSpace(tt.getBody(), sbreak); 396 if (nwPos > 0) { 397 DCText text2 = this.at(spos + nwPos).Text(tt.getBody().substring(nwPos)); 398 body.add(text2); 399 } 400 continue; 401 } else if (itr.hasNext()) { 402 // if the next doctree is a break, remove trailing spaces 403 peekedNext = alist.get(itr.nextIndex()); 404 boolean sbrk = isSentenceBreak(peekedNext, false); 405 if (sbrk) { 406 DocTree next = itr.next(); 407 s = removeTrailingWhitespace(s); 408 DCText text = this.at(spos).Text(s); 409 fs.add(text); 410 body.add((DCTree) next); 411 foundFirstSentence = true; 412 continue; 413 } 414 } 415 break; 416 default: 417 if (isSentenceBreak(dt, isFirst)) { 418 body.add((DCTree) dt); 419 foundFirstSentence = true; 420 continue; 421 } 422 break; 423 } 424 fs.add((DCTree) dt); 425 } 426 return new Pair<>(fs.toList(), body.toList()); 427 } finally { 428 this.pos = savedpos; 429 } 430 } 431 432 private boolean isTextTree(DocTree tree) { 433 return tree.getKind() == Kind.TEXT; 434 } 435 436 /* 437 * Computes the first sentence break, a simple dot-space algorithm. 438 */ 439 int defaultSentenceBreak(String s) { 440 // scan for period followed by whitespace 441 int period = -1; 442 for (int i = 0; i < s.length(); i++) { 443 switch (s.charAt(i)) { 444 case '.': 445 period = i; 446 break; 447 448 case ' ': 449 case '\f': 450 case '\n': 451 case '\r': 452 case '\t': 453 if (period >= 0) { 454 return i; 455 } 456 break; 457 458 default: 459 period = -1; 460 break; 461 } 462 } 463 return -1; 464 } 465 466 /* 467 * Computes the first sentence, if using a default breaker, 468 * the break is returned, if not then a -1, indicating that 469 * more doctree elements are required to be examined. 470 * 471 * BreakIterator.next points to the the start of the following sentence, 472 * and does not provide an easy way to disambiguate between "sentence break", 473 * "possible sentence break" and "not a sentence break" at the end of the input. 474 * For example, BreakIterator.next returns the index for the end 475 * of the string for all of these examples, 476 * using vertical bars to delimit the bounds of the example text 477 * |Abc| (not a valid end of sentence break, if followed by more text) 478 * |Abc.| (maybe a valid end of sentence break, depending on the following text) 479 * |Abc. | (maybe a valid end of sentence break, depending on the following text) 480 * |"Abc." | (maybe a valid end of sentence break, depending on the following text) 481 * |Abc. | (definitely a valid end of sentence break) 482 * |"Abc." | (definitely a valid end of sentence break) 483 * Therefore, we have to probe further to determine whether 484 * there really is a sentence break or not at the end of this run of text. 485 */ 486 int getSentenceBreak(String s, DocTree dt) { 487 BreakIterator breakIterator = trees.getBreakIterator(); 488 if (breakIterator == null) { 489 return defaultSentenceBreak(s); 490 } 491 breakIterator.setText(s); 492 final int sbrk = breakIterator.next(); 493 // This is the last doctree, found the droid we are looking for 494 if (dt == null) { 495 return sbrk; 496 } 497 498 // If the break is well within the span of the string ie. not 499 // at EOL, then we have a clear break. 500 if (sbrk < s.length() - 1) { 501 return sbrk; 502 } 503 504 if (isTextTree(dt)) { 505 // Two adjacent text trees, a corner case, perhaps 506 // produced by a tool synthesizing a doctree. In 507 // this case, does the break lie within the first span, 508 // then we have the droid, otherwise allow the callers 509 // logic to handle the break in the adjacent doctree. 510 TextTree ttnext = (TextTree) dt; 511 String combined = s + ttnext.getBody(); 512 breakIterator.setText(combined); 513 int sbrk2 = breakIterator.next(); 514 if (sbrk < sbrk2) { 515 return sbrk; 516 } 517 } 518 519 // Is the adjacent tree a sentence breaker ? 520 if (isSentenceBreak(dt, false)) { 521 return sbrk; 522 } 523 524 // At this point the adjacent tree is either a javadoc tag ({@..), 525 // html tag (<..) or an entity (&..). Perform a litmus test, by 526 // concatenating a sentence, to validate the break earlier identified. 527 String combined = s + "Dummy Sentence."; 528 breakIterator.setText(combined); 529 int sbrk2 = breakIterator.next(); 530 if (sbrk2 <= sbrk) { 531 return sbrk2; 532 } 533 return -1; // indeterminate at this time 534 } 535 536 boolean isSentenceBreak(javax.lang.model.element.Name tagName) { 537 return sentenceBreakTags.contains(get(tagName)); 538 } 539 540 boolean isSentenceBreak(DocTree dt, boolean isFirstDocTree) { 541 switch (dt.getKind()) { 542 case START_ELEMENT: 543 StartElementTree set = (StartElementTree)dt; 544 return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(set.getName()); 545 case END_ELEMENT: 546 EndElementTree eet = (EndElementTree)dt; 547 return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(eet.getName()); 548 default: 549 return false; 550 } 551 } 552 553 /* 554 * Returns the position of the the first non-white space 555 */ 556 int skipWhiteSpace(String s, int start) { 557 for (int i = start; i < s.length(); i++) { 558 char c = s.charAt(i); 559 if (!Character.isWhitespace(c)) { 560 return i; 561 } 562 } 563 return -1; 564 } 565 566 String removeTrailingWhitespace(String s) { 567 for (int i = s.length() - 1 ; i >= 0 ; i--) { 568 char ch = s.charAt(i); 569 if (!Character.isWhitespace(ch)) { 570 return s.substring(0, i + 1); 571 } 572 } 573 return s; 574 } 575} 576