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