001/* 002 * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.util; 027 028import com.sun.source.doctree.AuthorTree; 029import com.sun.source.doctree.BlockTagTree; 030import com.sun.source.doctree.CommentTree; 031import com.sun.source.doctree.DeprecatedTree; 032import com.sun.source.doctree.DocCommentTree; 033import com.sun.source.doctree.DocTree; 034import com.sun.source.doctree.EscapeTree; 035import com.sun.source.doctree.IdentifierTree; 036import com.sun.source.doctree.InlineTagTree; 037import com.sun.source.doctree.LinkTree; 038import com.sun.source.doctree.LiteralTree; 039import com.sun.source.doctree.ParamTree; 040import com.sun.source.doctree.ProvidesTree; 041import com.sun.source.doctree.ReferenceTree; 042import com.sun.source.doctree.ReturnTree; 043import com.sun.source.doctree.SeeTree; 044import com.sun.source.doctree.SerialDataTree; 045import com.sun.source.doctree.SerialFieldTree; 046import com.sun.source.doctree.SerialTree; 047import com.sun.source.doctree.SinceTree; 048import com.sun.source.doctree.SpecTree; 049import com.sun.source.doctree.TextTree; 050import com.sun.source.doctree.ThrowsTree; 051import com.sun.source.doctree.UnknownBlockTagTree; 052import com.sun.source.doctree.UsesTree; 053import com.sun.source.doctree.ValueTree; 054import com.sun.source.doctree.VersionTree; 055import com.sun.source.util.DocTreePath; 056import com.sun.source.util.DocTrees; 057import com.sun.source.util.SimpleDocTreeVisitor; 058import com.sun.source.util.TreePath; 059 060import javax.lang.model.element.Element; 061import javax.lang.model.element.ElementKind; 062import javax.lang.model.element.ExecutableElement; 063import javax.lang.model.element.ModuleElement; 064import javax.lang.model.element.PackageElement; 065import javax.lang.model.element.TypeElement; 066import javax.lang.model.type.TypeKind; 067import javax.lang.model.type.TypeMirror; 068 069import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 070import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFinder.Result; 071 072import java.util.List; 073import java.util.Optional; 074 075import static com.sun.source.doctree.DocTree.Kind.SEE; 076import static com.sun.source.doctree.DocTree.Kind.SERIAL_FIELD; 077 078/** 079 * A utility class. 080 */ 081public class CommentHelper { 082 private final BaseConfiguration configuration; 083 public final TreePath path; 084 public final DocCommentTree dcTree; 085 public final Element element; 086 087 /** 088 * Creates a utility class to encapsulate the contextual information for a doc comment tree. 089 * 090 * @param configuration the configuration 091 * @param element the element for which this is a doc comment 092 * @param path the path for the element 093 * @param dcTree the doc comment 094 */ 095 public CommentHelper(BaseConfiguration configuration, Element element, 096 TreePath path, DocCommentTree dcTree) { 097 this.configuration = configuration; 098 this.element = element; 099 this.path = path; 100 this.dcTree = dcTree; 101 } 102 103 public String getTagName(DocTree dtree) { 104 return switch (dtree.getKind()) { 105 case AUTHOR, DEPRECATED, PARAM, PROVIDES, RETURN, SEE, SERIAL_DATA, SERIAL_FIELD, THROWS, UNKNOWN_BLOCK_TAG, USES, VERSION -> ((BlockTagTree) dtree) 106 .getTagName(); 107 case UNKNOWN_INLINE_TAG -> ((InlineTagTree) dtree).getTagName(); 108 case ERRONEOUS -> "erroneous"; 109 default -> dtree.getKind().tagName; 110 }; 111 } 112 113 public String getParameterName(ParamTree p) { 114 return p.getName().getName().toString(); 115 } 116 117 Element getElement(ReferenceTree rtree) { 118 // We need to lookup type variables and other types 119 Utils utils = configuration.utils; 120 // likely a synthesized tree 121 if (path == null) { 122 // NOTE: this code path only supports module/package/type signatures 123 // and not member signatures. For more complete support, 124 // set a suitable path and avoid this branch. 125 TypeMirror symbol = utils.getSymbol(rtree.getSignature()); 126 if (symbol == null) { 127 return null; 128 } 129 return configuration.docEnv.getTypeUtils().asElement(symbol); 130 } 131 DocTreePath docTreePath = getDocTreePath(rtree); 132 if (docTreePath == null) { 133 return null; 134 } 135 DocTrees doctrees = configuration.docEnv.getDocTrees(); 136 // Workaround for JDK-8284193 137 // DocTrees.getElement(DocTreePath) returns javac-internal Symbols 138 var e = doctrees.getElement(docTreePath); 139 return e == null || e.getKind() == ElementKind.CLASS 140 && e.asType().getKind() != TypeKind.DECLARED ? null : e; 141 } 142 143 public TypeMirror getType(ReferenceTree rtree) { 144 DocTreePath docTreePath = getDocTreePath(rtree); 145 if (docTreePath != null) { 146 DocTrees docTrees = configuration.docEnv.getDocTrees(); 147 return docTrees.getType(docTreePath); 148 } 149 return null; 150 } 151 152 public Element getException(ThrowsTree tt) { 153 return getElement(tt.getExceptionName()); 154 } 155 156 public List<? extends DocTree> getDescription(DocTree dtree) { 157 return getTags(dtree); 158 } 159 160 public TypeElement getReferencedClass(Element e) { 161 Utils utils = configuration.utils; 162 if (e == null) { 163 return null; 164 } else if (utils.isTypeElement(e)) { 165 return (TypeElement) e; 166 } else if (!utils.isPackage(e) && !utils.isModule(e)) { 167 return utils.getEnclosingTypeElement(e); 168 } 169 return null; 170 } 171 172 public String getReferencedModuleName(String signature) { 173 if (signature == null || signature.contains("#") 174 || signature.contains("(")) { 175 return null; 176 } 177 int n = signature.indexOf("/"); 178 return (n == -1) ? signature : signature.substring(0, n); 179 } 180 181 public Element getReferencedMember(DocTree dtree) { 182 Element e = getReferencedElement(dtree); 183 return getReferencedMember(e); 184 } 185 186 public Element getReferencedMember(Element e) { 187 Utils utils = configuration.utils; 188 if (e == null) { 189 return null; 190 } 191 return (utils.isExecutableElement(e) || utils.isVariableElement(e)) ? e 192 : null; 193 } 194 195 public String getReferencedFragment(String signature) { 196 if (signature == null) { 197 return null; 198 } 199 int n = signature.indexOf("#"); 200 return (n == -1) ? null : signature.substring(n + 1); 201 } 202 203 public PackageElement getReferencedPackage(Element e) { 204 if (e != null) { 205 Utils utils = configuration.utils; 206 return utils.containingPackage(e); 207 } 208 return null; 209 } 210 211 public ModuleElement getReferencedModule(Element e) { 212 if (e != null && configuration.utils.isModule(e)) { 213 return (ModuleElement) e; 214 } 215 return null; 216 } 217 218 public List<? extends DocTree> 219 getFirstSentenceTrees(List<? extends DocTree> body) { 220 return configuration.docEnv.getDocTrees().getFirstSentence(body); 221 } 222 223 public Element getReferencedElement(DocTree dtree) { 224 return new ReferenceDocTreeVisitor<Element>() { 225 @Override 226 public Element visitReference(ReferenceTree node, Void p) { 227 return getElement(node); 228 } 229 }.visit(dtree, null); 230 } 231 232 public TypeMirror getReferencedType(DocTree dtree) { 233 return new ReferenceDocTreeVisitor<TypeMirror>() { 234 @Override 235 public TypeMirror visitReference(ReferenceTree node, Void p) { 236 return getType(node); 237 } 238 }.visit(dtree, null); 239 } 240 241 public TypeElement getServiceType(DocTree dtree) { 242 Element e = getReferencedElement(dtree); 243 if (e != null) { 244 Utils utils = configuration.utils; 245 return utils.isTypeElement(e) ? (TypeElement) e : null; 246 } 247 return null; 248 } 249 250 /** 251 * {@return the normalized signature from a {@code ReferenceTree}} 252 */ 253 public String getReferencedSignature(DocTree dtree) { 254 return new ReferenceDocTreeVisitor<String>() { 255 @Override 256 public String visitReference(ReferenceTree node, Void p) { 257 return normalizeSignature(node.getSignature()); 258 } 259 }.visit(dtree, null); 260 } 261 262 @SuppressWarnings("fallthrough") 263 private static String normalizeSignature(String sig) { 264 if (sig == null 265 || (!sig.contains(" ") && !sig.contains("\n") 266 && !sig.contains("\r") && !sig.endsWith("/"))) { 267 return sig; 268 } 269 StringBuilder sb = new StringBuilder(); 270 char lastChar = 0; 271 for (int i = 0; i < sig.length(); i++) { 272 char ch = sig.charAt(i); 273 switch (ch) { 274 case '\n': 275 case '\r': 276 case '\f': 277 case '\t': 278 case ' ': 279 // Add at most one space char, or none if it isn't needed 280 switch (lastChar) { 281 case 0: 282 case '(': 283 case '<': 284 case ' ': 285 case '.': 286 break; 287 default: 288 sb.append(' '); 289 lastChar = ' '; 290 break; 291 } 292 break; 293 case ',': 294 case '>': 295 case ')': 296 case '.': 297 // Remove preceding space character 298 if (lastChar == ' ') { 299 sb.setLength(sb.length() - 1); 300 } 301 // fallthrough 302 default: 303 sb.append(ch); 304 lastChar = ch; 305 } 306 } 307 // Delete trailing slash 308 if (lastChar == '/') { 309 sb.setLength(sb.length() - 1); 310 } 311 return sb.toString(); 312 } 313 314 private static class ReferenceDocTreeVisitor<R> 315 extends SimpleDocTreeVisitor<R, Void> { 316 @Override 317 public R visitSee(SeeTree node, Void p) { 318 for (DocTree dt : node.getReference()) { 319 return visit(dt, null); 320 } 321 return null; 322 } 323 324 @Override 325 public R visitLink(LinkTree node, Void p) { 326 return visit(node.getReference(), null); 327 } 328 329 @Override 330 public R visitProvides(ProvidesTree node, Void p) { 331 return visit(node.getServiceType(), null); 332 } 333 334 @Override 335 public R visitValue(ValueTree node, Void p) { 336 return visit(node.getReference(), null); 337 } 338 339 @Override 340 public R visitSerialField(SerialFieldTree node, Void p) { 341 return visit(node.getType(), null); 342 } 343 344 @Override 345 public R visitUses(UsesTree node, Void p) { 346 return visit(node.getServiceType(), null); 347 } 348 349 @Override 350 protected R defaultAction(DocTree node, Void p) { 351 return null; 352 } 353 } 354 355 public List<? extends DocTree> getReference(DocTree dtree) { 356 return dtree.getKind() == SEE ? ((SeeTree) dtree).getReference() : null; 357 } 358 359 public IdentifierTree getName(DocTree dtree) { 360 return switch (dtree.getKind()) { 361 case PARAM -> ((ParamTree) dtree).getName(); 362 case SERIAL_FIELD -> ((SerialFieldTree) dtree).getName(); 363 default -> null; 364 }; 365 } 366 367 public List<? extends DocTree> getTags(DocTree dtree) { 368 return new SimpleDocTreeVisitor<List<? extends DocTree>, Void>() { 369 370 private List<DocTree> asList(String content) { 371 return List.of(configuration.cmtUtils.makeTextTree(content)); 372 } 373 374 @Override 375 public List<? extends DocTree> visitAuthor(AuthorTree node, 376 Void p) { 377 return node.getName(); 378 } 379 380 @Override 381 public List<? extends DocTree> visitComment(CommentTree node, 382 Void p) { 383 return asList(node.getBody()); 384 } 385 386 @Override 387 public List<? extends DocTree> visitDeprecated(DeprecatedTree node, 388 Void p) { 389 return node.getBody(); 390 } 391 392 @Override 393 public List<? extends DocTree> visitDocComment(DocCommentTree node, 394 Void p) { 395 return node.getBody(); 396 } 397 398 @Override 399 public List<? extends DocTree> visitEscape(EscapeTree node, 400 Void p) { 401 return asList(node.getBody()); 402 } 403 404 @Override 405 public List<? extends DocTree> visitLiteral(LiteralTree node, 406 Void p) { 407 return asList(node.getBody().getBody()); 408 } 409 410 @Override 411 public List<? extends DocTree> visitProvides(ProvidesTree node, 412 Void p) { 413 return node.getDescription(); 414 } 415 416 @Override 417 public List<? extends DocTree> visitSince(SinceTree node, Void p) { 418 return node.getBody(); 419 } 420 421 @Override 422 public List<? extends DocTree> visitText(TextTree node, Void p) { 423 return asList(node.getBody()); 424 } 425 426 @Override 427 public List<? extends DocTree> visitVersion(VersionTree node, 428 Void p) { 429 return node.getBody(); 430 } 431 432 @Override 433 public List<? extends DocTree> visitParam(ParamTree node, Void p) { 434 return node.getDescription(); 435 } 436 437 @Override 438 public List<? extends DocTree> visitReturn(ReturnTree node, 439 Void p) { 440 return node.getDescription(); 441 } 442 443 @Override 444 public List<? extends DocTree> visitSee(SeeTree node, Void p) { 445 return node.getReference(); 446 } 447 448 @Override 449 public List<? extends DocTree> visitSerial(SerialTree node, 450 Void p) { 451 return node.getDescription(); 452 } 453 454 @Override 455 public List<? extends DocTree> visitSerialData(SerialDataTree node, 456 Void p) { 457 return node.getDescription(); 458 } 459 460 @Override 461 public List<? extends DocTree> 462 visitSerialField(SerialFieldTree node, Void p) { 463 return node.getDescription(); 464 } 465 466 @Override 467 public List<? extends DocTree> visitSpec(SpecTree node, Void p) { 468 return node.getTitle(); 469 } 470 471 @Override 472 public List<? extends DocTree> visitThrows(ThrowsTree node, 473 Void p) { 474 return node.getDescription(); 475 } 476 477 @Override 478 public List<? extends DocTree> 479 visitUnknownBlockTag(UnknownBlockTagTree node, Void p) { 480 return node.getContent(); 481 } 482 483 @Override 484 public List<? extends DocTree> visitUses(UsesTree node, Void p) { 485 return node.getDescription(); 486 } 487 488 @Override 489 protected List<? extends DocTree> defaultAction(DocTree node, 490 Void p) { 491 return List.of(); 492 } 493 }.visit(dtree, null); 494 } 495 496 public List<? extends DocTree> getBody(DocTree dtree) { 497 return getTags(dtree); 498 } 499 500 public ReferenceTree getType(DocTree dtree) { 501 if (dtree.getKind() == SERIAL_FIELD) { 502 return ((SerialFieldTree) dtree).getType(); 503 } else { 504 return null; 505 } 506 } 507 508 public DocTreePath getDocTreePath(DocTree dtree) { 509 if (dcTree == null && element instanceof ExecutableElement ee) { 510 return getInheritedDocTreePath(dtree, ee); 511 } 512 if (path == null || dcTree == null || dtree == null) { 513 return null; 514 } 515 DocTreePath dtPath = DocTreePath.getPath(path, dcTree, dtree); 516 if (dtPath == null && element instanceof ExecutableElement ee) { 517 // The overriding element has a doc tree, but it doesn't contain 518 // what we're looking for. 519 return getInheritedDocTreePath(dtree, ee); 520 } 521 return dtPath; 522 } 523 524 private DocTreePath getInheritedDocTreePath(DocTree dtree, 525 ExecutableElement ee) { 526 Utils utils = configuration.utils; 527 var docFinder = utils.docFinder(); 528 Optional<ExecutableElement> inheritedDoc = docFinder.search(ee, 529 (m -> { 530 Optional<ExecutableElement> optional 531 = utils.getFullBody(m).isEmpty() ? Optional.empty() 532 : Optional.of(m); 533 return Result.fromOptional(optional); 534 })).toOptional(); 535 return inheritedDoc.isEmpty() || inheritedDoc.get().equals(ee) 536 ? null 537 : utils.getCommentHelper(inheritedDoc.get()).getDocTreePath(dtree); 538 } 539 540 /** 541 * For debugging purposes only. Do not rely on this for other things. 542 * @return a string representation. 543 */ 544 @Override 545 public String toString() { 546 return "CommentHelper{" 547 + "path=" + path 548 + ", dcTree=" + dcTree 549 + ", element=" + element.getEnclosingElement() + "::" + element 550 + '}'; 551 } 552}