1 /* 2 * Copyright (c) 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 jdk.javadoc.internal.doclets.toolkit.util; 27 28 import java.util.Objects; 29 import javax.lang.model.element.Element; 30 import javax.lang.model.element.ExecutableElement; 31 import javax.lang.model.element.ModuleElement; 32 import javax.lang.model.element.PackageElement; 33 import javax.lang.model.element.TypeElement; 34 import javax.lang.model.element.VariableElement; 35 import javax.lang.model.util.SimpleElementVisitor14; 36 37 import com.sun.source.doctree.DocTree; 38 39 /** 40 * An item to be included in the index pages and in interactive search. 41 * 42 * <p> 43 * Items are primarily defined by their position in the documentation, 44 * which is one of: 45 * 46 * <ul> 47 * <li>An element (module, package, type or member) 48 * <li>One of a small set of tags in the doc comment for an element: 49 * {@code {@index ...}}, {@code {@systemProperty ...}}, etc 50 * <li>One of a small set of outliers, corresponding to summary pages: 51 * "All Classes", "All Packages", etc 52 * </ul> 53 * 54 * <p> 55 * All items have a "label", which is the presentation string used 56 * to display the item in the list of matching choices. The 57 * label is specified when the item is created. Items also 58 * have a "url" and a "description", which are provided by 59 * the specific doclet. 60 * 61 * <p> 62 * Each item provides details to be included in the search index files 63 * read and processed by JavaScript. 64 * Items have a "category", which is normally derived from the element 65 * kind or doc tree kind; it corresponds to the JavaScript file 66 * in which this item will be written. 67 * 68 * <p> 69 * Items for an element may have one or more of the following: 70 * "containing module", "containing package", "containing type". 71 * 72 * <p> 73 * Items for a node in a doc tree have a "holder", which is a 74 * text form of the enclosing element or page. 75 * They will typically also have a "description" derived from 76 * content in the doc tree node. 77 * 78 * 79 * <p><b>This is NOT part of any supported API. 80 * If you write code that depends on this, you do so at your own risk. 81 * This code and its internal interfaces are subject to change or 82 * deletion without notice.</b> 83 */ 84 public class IndexItem { 85 86 /** 87 * The "category" used to group items for the interactive search index. 88 * Categories correspond directly to the JavaScript files that will be generated. 89 */ 90 public enum Category { 91 MODULES, 92 PACKAGES, 93 TYPES, 94 MEMBERS, 95 TAGS 96 } 97 98 /** 99 * The presentation string for the item. It must be non-empty. 100 */ 101 private final String label; 102 103 /** 104 * The element for the item. It is only null for items for summary pages that are not 105 * associated with any specific element. 106 * 107 */ 108 private final Element element; 109 110 /** 111 * The URL pointing to the element, doc tree or page being indexed. 112 * It may be empty if the information can be determined from other fields. 113 */ 114 private String url = ""; 115 116 /** 117 * The containing module, if any, for the item. 118 * It will be empty if the element is not in a package, and may be omitted if the 119 * name of the package is unique. 120 */ 121 private String containingModule = ""; 122 123 /** 124 * The containing package, if any, for the item. 125 */ 126 private String containingPackage = ""; 127 128 /** 129 * The containing class, if any, for the item. 130 */ 131 private String containingClass = ""; 132 133 /** 134 * Creates an index item for a module element. 135 * 136 * @param moduleElement the element 137 * @param utils the common utilities class 138 * 139 * @return the item 140 */ of(ModuleElement moduleElement, Utils utils)141 public static IndexItem of(ModuleElement moduleElement, Utils utils) { 142 return new IndexItem(moduleElement, utils.getFullyQualifiedName(moduleElement)); 143 } 144 145 /** 146 * Creates an index item for a package element. 147 * 148 * @param packageElement the element 149 * @param utils the common utilities class 150 * 151 * @return the item 152 */ of(PackageElement packageElement, Utils utils)153 public static IndexItem of(PackageElement packageElement, Utils utils) { 154 return new IndexItem(packageElement, utils.getPackageName(packageElement)); 155 } 156 157 /** 158 * Creates an index item for a type element. 159 * Note: use {@code getElement()} to access this value, not {@code getTypeElement}. 160 * 161 * @param typeElement the element 162 * @param utils the common utilities class 163 * 164 * @return the item 165 */ of(TypeElement typeElement, Utils utils)166 public static IndexItem of(TypeElement typeElement, Utils utils) { 167 return new IndexItem(typeElement, utils.getSimpleName(typeElement)); 168 } 169 170 /** 171 * Creates an index item for a member element. 172 * Note: the given type element may not be the same as the enclosing element of the member 173 * in cases where the enclosing element is not visible in the documentation. 174 * 175 * @param typeElement the element that contains the member 176 * @param member the member 177 * @param utils the common utilities class 178 * 179 * @return the item 180 * 181 * @see #getContainingTypeElement() 182 */ of(TypeElement typeElement, Element member, Utils utils)183 public static IndexItem of(TypeElement typeElement, Element member, Utils utils) { 184 String name = utils.getSimpleName(member); 185 if (utils.isExecutableElement(member)) { 186 ExecutableElement ee = (ExecutableElement)member; 187 name += utils.flatSignature(ee, typeElement); 188 } 189 return new IndexItem(member, name) { 190 @Override 191 public TypeElement getContainingTypeElement() { 192 return typeElement; 193 } 194 }; 195 } 196 197 /** 198 * Creates an index item for a node in the doc comment for an element. 199 * The node should only be one that gives rise to an entry in the index. 200 * 201 * @param element the element 202 * @param docTree the node in the doc comment 203 * @param label the label 204 * @param holder the holder for the comment 205 * @param description the description of the item 206 * @param link the root-relative link to the item in the generated docs 207 * 208 * @return the item 209 */ 210 public static IndexItem of(Element element, DocTree docTree, String label, 211 String holder, String description, DocLink link) { 212 Objects.requireNonNull(element); 213 Objects.requireNonNull(holder); 214 Objects.requireNonNull(description); 215 Objects.requireNonNull(link); 216 217 switch (docTree.getKind()) { 218 case INDEX, SYSTEM_PROPERTY -> { } 219 default -> throw new IllegalArgumentException(docTree.getKind().toString()); 220 } 221 222 return new IndexItem(element, label, link.toString()) { 223 @Override 224 public DocTree getDocTree() { 225 return docTree; 226 } 227 @Override 228 public Category getCategory() { 229 return getCategory(docTree); 230 } 231 @Override 232 public String getHolder() { 233 return holder; 234 } 235 @Override 236 public String getDescription() { 237 return description; 238 } 239 }; 240 } 241 242 /** 243 * Creates an index item for a summary page, that is not associated with any element or 244 * node in a doc comment. 245 * 246 * @param category the category for the item 247 * @param label the label for the item 248 * @param path the path for the page 249 * 250 * @return the item 251 */ 252 public static IndexItem of(Category category, String label, DocPath path) { 253 Objects.requireNonNull(category); 254 return new IndexItem(null, label, path.getPath()) { 255 @Override 256 public DocTree getDocTree() { 257 return null; 258 } 259 @Override 260 public Category getCategory() { 261 return category; 262 } 263 @Override 264 public String getHolder() { 265 return ""; 266 } 267 @Override 268 public String getDescription() { 269 return ""; 270 } 271 }; 272 } 273 274 private IndexItem(Element element, String label) { 275 if (label.isEmpty()) { 276 throw new IllegalArgumentException(); 277 } 278 279 this.element = element; 280 this.label = label; 281 } 282 283 private IndexItem(Element element, String label, String url) { 284 this(element, label); 285 setUrl(url); 286 } 287 288 /** 289 * Returns the label of the item. 290 * 291 * @return the label 292 */ 293 public String getLabel() { 294 return label; 295 } 296 297 /** 298 * Returns the part of the label after the last dot, or the whole label if there are no dots. 299 * 300 * @return the simple name 301 */ 302 public String getSimpleName() { 303 return label.substring(label.lastIndexOf('.') + 1); 304 } 305 306 /** 307 * Returns the label with a fully-qualified type name. 308 * (Used to determine if labels are unique or need to be qualified.) 309 * 310 * @param utils the common utilities class 311 * 312 * @return the fully qualified name 313 */ 314 public String getFullyQualifiedLabel(Utils utils) { 315 TypeElement typeElement = getContainingTypeElement(); 316 if (typeElement != null) { 317 return utils.getFullyQualifiedName(typeElement) + "." + label; 318 } else if (isElementItem()) { 319 return utils.getFullyQualifiedName(element); 320 } else { 321 return label; 322 } 323 } 324 325 /** 326 * Returns the element associate with this item, or {@code null}. 327 * 328 * @return the element 329 */ 330 public Element getElement() { 331 return element; 332 } 333 334 /** 335 * Returns the category for this item, that indicates the JavaScript file 336 * in which this item should be written. 337 * 338 * @return the category 339 */ 340 public Category getCategory() { 341 return getCategory(element); 342 } 343 344 protected Category getCategory(DocTree docTree) { 345 return switch (docTree.getKind()) { 346 case INDEX, SYSTEM_PROPERTY -> Category.TAGS; 347 default -> throw new IllegalArgumentException(docTree.getKind().toString()); 348 }; 349 } 350 351 @SuppressWarnings("preview") 352 protected Category getCategory(Element element) { 353 return new SimpleElementVisitor14<Category, Void>() { 354 @Override 355 public Category visitModule(ModuleElement t, Void v) { 356 return Category.MODULES; 357 } 358 359 @Override 360 public Category visitPackage(PackageElement e, Void v) { 361 return Category.PACKAGES; 362 } 363 364 @Override 365 public Category visitType(TypeElement e, Void v) { 366 return Category.TYPES; 367 } 368 369 @Override 370 public Category visitVariable(VariableElement e, Void v) { 371 return Category.MEMBERS; 372 } 373 374 @Override 375 public Category visitExecutable(ExecutableElement e, Void v) { 376 return Category.MEMBERS; 377 } 378 379 @Override 380 public Category defaultAction(Element e, Void v) { 381 throw new IllegalArgumentException(e.toString()); 382 } 383 }.visit(element); 384 } 385 386 /** 387 * Returns the type element that is documented as containing a member element, 388 * or {@code null} if this item does not represent a member element. 389 * 390 * @return the type element 391 */ 392 public TypeElement getContainingTypeElement() { 393 return null; 394 } 395 396 /** 397 * Returns the documentation tree node for this item, of {@code null} if this item 398 * does not represent a documentation tree node. 399 * 400 * @return the documentation tree node 401 */ 402 public DocTree getDocTree() { 403 return null; 404 } 405 406 /** 407 * Returns {@code true} if this index is for an element. 408 * 409 * @return {@code true} if this index is for an element 410 */ 411 public boolean isElementItem() { 412 return element != null && getDocTree() == null; 413 } 414 415 /** 416 * Returns {@code true} if this index is for a tag in a doc comment. 417 * 418 * @return {@code true} if this index is for a tag in a doc comment 419 */ 420 public boolean isTagItem() { 421 return getDocTree() != null; 422 } 423 424 /** 425 * Returns {@code true} if this index is for a specific kind of tag in a doc comment. 426 * 427 * @return {@code true} if this index is for a specific kind of tag in a doc comment 428 */ 429 public boolean isKind(DocTree.Kind kind) { 430 DocTree dt = getDocTree(); 431 return dt != null && dt.getKind() == kind; 432 } 433 434 /** 435 * Sets the URL for the item, when it cannot otherwise be inferred from other fields. 436 * 437 * @param u the url 438 * 439 * @return this item 440 */ 441 public IndexItem setUrl(String u) { 442 url = Objects.requireNonNull(u); 443 return this; 444 } 445 446 /** 447 * Returns the URL for this item, or an empty string if no value has been set. 448 * 449 * @return the URL for this item, or an empty string if no value has been set 450 */ 451 public String getUrl() { 452 return url; 453 } 454 455 /** 456 * Sets the name of the containing module for this item. 457 * 458 * @param m the module 459 * 460 * @return this item 461 */ 462 public IndexItem setContainingModule(String m) { 463 containingModule = Objects.requireNonNull(m); 464 return this; 465 } 466 467 /** 468 * Sets the name of the containing package for this item. 469 * 470 * @param p the package 471 * 472 * @return this item 473 */ 474 public IndexItem setContainingPackage(String p) { 475 containingPackage = Objects.requireNonNull(p); 476 return this; 477 } 478 479 /** 480 * Sets the name of the containing class for this item. 481 * 482 * @param c the class 483 * 484 * @return this item 485 */ 486 public IndexItem setContainingClass(String c) { 487 containingClass = Objects.requireNonNull(c); 488 return this; 489 } 490 491 /** 492 * Returns a description of the element owning the documentation comment for this item, 493 * or {@code null} if this is not a item for a tag for an item in a documentation tag. 494 * 495 * @return the description of the element that owns this item 496 */ 497 public String getHolder() { 498 return null; 499 } 500 501 /** 502 * Returns a description of the tag for this item or {@code null} if this is not a item 503 * for a tag for an item in a documentation tag. 504 * 505 * @return the description of the tag 506 */ 507 public String getDescription() { 508 return null; 509 } 510 511 /** 512 * Returns a string representing this item in JSON notation. 513 * 514 * @return a string representing this item in JSON notation 515 */ 516 public String toJSON() { 517 // TODO: Additional processing is required, see JDK-8238495 518 StringBuilder item = new StringBuilder(); 519 Category category = getCategory(); 520 switch (category) { 521 case MODULES: 522 item.append("{") 523 .append("\"l\":\"").append(label).append("\"") 524 .append("}"); 525 break; 526 527 case PACKAGES: 528 item.append("{"); 529 if (!containingModule.isEmpty()) { 530 item.append("\"m\":\"").append(containingModule).append("\","); 531 } 532 item.append("\"l\":\"").append(label).append("\""); 533 if (!url.isEmpty()) { 534 item.append(",\"u\":\"").append(url).append("\""); 535 } 536 item.append("}"); 537 break; 538 539 case TYPES: 540 item.append("{"); 541 if (!containingPackage.isEmpty()) { 542 item.append("\"p\":\"").append(containingPackage).append("\","); 543 } 544 if (!containingModule.isEmpty()) { 545 item.append("\"m\":\"").append(containingModule).append("\","); 546 } 547 item.append("\"l\":\"").append(label).append("\""); 548 if (!url.isEmpty()) { 549 item.append(",\"u\":\"").append(url).append("\""); 550 } 551 item.append("}"); 552 break; 553 554 case MEMBERS: 555 item.append("{"); 556 if (!containingModule.isEmpty()) { 557 item.append("\"m\":\"").append(containingModule).append("\","); 558 } 559 item.append("\"p\":\"").append(containingPackage).append("\",") 560 .append("\"c\":\"").append(containingClass).append("\",") 561 .append("\"l\":\"").append(label).append("\""); 562 if (!url.isEmpty()) { 563 item.append(",\"u\":\"").append(url).append("\""); 564 } 565 item.append("}"); 566 break; 567 568 case TAGS: 569 String holder = getHolder(); 570 String description = getDescription(); 571 item.append("{") 572 .append("\"l\":\"").append(label).append("\",") 573 .append("\"h\":\"").append(holder).append("\","); 574 if (!description.isEmpty()) { 575 item.append("\"d\":\"").append(description).append("\","); 576 } 577 item.append("\"u\":\"").append(url).append("\"") 578 .append("}"); 579 break; 580 581 default: 582 throw new AssertionError("Unexpected category: " + category); 583 } 584 return item.toString(); 585 } 586 } 587