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