001/* 002 * Copyright (c) 2021, 2022, 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.formats.html; 027 028import java.util.List; 029import java.util.Locale; 030import java.util.Map; 031import java.util.Set; 032import javax.lang.model.element.Element; 033import javax.lang.model.element.ExecutableElement; 034import javax.lang.model.element.PackageElement; 035import javax.lang.model.element.TypeElement; 036import javax.lang.model.element.VariableElement; 037import javax.lang.model.type.ArrayType; 038import javax.lang.model.type.DeclaredType; 039import javax.lang.model.type.TypeMirror; 040import javax.lang.model.type.TypeVariable; 041import javax.lang.model.util.SimpleTypeVisitor9; 042 043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlId; 044import org.jdrupes.mdoclet.internal.doclets.toolkit.util.SummaryAPIListBuilder; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable; 047 048/** 049 * Centralized constants and factory methods for HTML ids. 050 * 051 * <p>To ensure consistency, these constants and methods should be used 052 * both when declaring ids (for example, {@code HtmlTree.setId}) 053 * and creating references (for example, {@code Links.createLink}). 054 * 055 * <p>Most ids are mostly for internal use within the pages of a documentation 056 * bundle. However, the ids for member declarations may be referred to 057 * from other documentation using {@code {@link}}, and should not be 058 * changed without due consideration for the compatibility impact. 059 * 060 * <p>The use of punctuating characters is inconsistent and could be improved. 061 * 062 * <p>Constants and methods are {@code static} where possible. 063 * However, some methods require access to {@code utils} and are 064 * better provided as instance methods. 065 */ 066public class HtmlIds { 067 private final HtmlConfiguration configuration; 068 private final Utils utils; 069 070 static final HtmlId ALL_CLASSES_TABLE = HtmlId.of("all-classes-table"); 071 static final HtmlId ALL_MODULES_TABLE = HtmlId.of("all-modules-table"); 072 static final HtmlId ALL_PACKAGES_TABLE = HtmlId.of("all-packages-table"); 073 static final HtmlId ANNOTATION_TYPE_ELEMENT_DETAIL 074 = HtmlId.of("annotation-interface-element-detail"); 075 static final HtmlId ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY 076 = HtmlId.of("annotation-interface-optional-element-summary"); 077 static final HtmlId ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY 078 = HtmlId.of("annotation-interface-required-element-summary"); 079 static final HtmlId CLASS_DESCRIPTION = HtmlId.of("class-description"); 080 static final HtmlId CLASS_SUMMARY = HtmlId.of("class-summary"); 081 static final HtmlId CONSTRUCTOR_DETAIL = HtmlId.of("constructor-detail"); 082 static final HtmlId CONSTRUCTOR_SUMMARY = HtmlId.of("constructor-summary"); 083 static final HtmlId ENUM_CONSTANT_DETAIL 084 = HtmlId.of("enum-constant-detail"); 085 static final HtmlId ENUM_CONSTANT_SUMMARY 086 = HtmlId.of("enum-constant-summary"); 087 static final HtmlId EXTERNAL_SPECS = HtmlId.of("external-specs"); 088 static final HtmlId FIELD_DETAIL = HtmlId.of("field-detail"); 089 static final HtmlId FIELD_SUMMARY = HtmlId.of("field-summary"); 090 static final HtmlId FOR_REMOVAL = HtmlId.of("for-removal"); 091 static final HtmlId HELP_NAVIGATION = HtmlId.of("help-navigation"); 092 static final HtmlId HELP_PAGES = HtmlId.of("help-pages"); 093 static final HtmlId METHOD_DETAIL = HtmlId.of("method-detail"); 094 static final HtmlId METHOD_SUMMARY = HtmlId.of("method-summary"); 095 static final HtmlId METHOD_SUMMARY_TABLE 096 = HtmlId.of("method-summary-table"); 097 static final HtmlId MODULES = HtmlId.of("modules-summary"); 098 static final HtmlId MODULE_DESCRIPTION = HtmlId.of("module-description"); 099 static final HtmlId NAVBAR_SUB_LIST = HtmlId.of("navbar-sub-list"); 100 static final HtmlId NAVBAR_TOGGLE_BUTTON 101 = HtmlId.of("navbar-toggle-button"); 102 static final HtmlId NAVBAR_TOP = HtmlId.of("navbar-top"); 103 static final HtmlId NAVBAR_TOP_FIRSTROW = HtmlId.of("navbar-top-firstrow"); 104 static final HtmlId NESTED_CLASS_SUMMARY 105 = HtmlId.of("nested-class-summary"); 106 static final HtmlId PACKAGES = HtmlId.of("packages-summary"); 107 static final HtmlId PACKAGE_DESCRIPTION = HtmlId.of("package-description"); 108 static final HtmlId PACKAGE_SUMMARY_TABLE 109 = HtmlId.of("package-summary-table"); 110 static final HtmlId PROPERTY_DETAIL = HtmlId.of("property-detail"); 111 static final HtmlId PROPERTY_SUMMARY = HtmlId.of("property-summary"); 112 static final HtmlId RELATED_PACKAGE_SUMMARY 113 = HtmlId.of("related-package-summary"); 114 static final HtmlId RESET_BUTTON = HtmlId.of("reset-button"); 115 static final HtmlId SEARCH_INPUT = HtmlId.of("search-input"); 116 static final HtmlId SERVICES = HtmlId.of("services-summary"); 117 static final HtmlId SKIP_NAVBAR_TOP = HtmlId.of("skip-navbar-top"); 118 static final HtmlId UNNAMED_PACKAGE_ANCHOR = HtmlId.of("unnamed-package"); 119 120 private static final String ENUM_CONSTANTS_INHERITANCE 121 = "enum-constants-inherited-from-class-"; 122 private static final String FIELDS_INHERITANCE 123 = "fields-inherited-from-class-"; 124 private static final String METHODS_INHERITANCE 125 = "methods-inherited-from-class-"; 126 private static final String NESTED_CLASSES_INHERITANCE 127 = "nested-classes-inherited-from-class-"; 128 private static final String PROPERTIES_INHERITANCE 129 = "properties-inherited-from-class-"; 130 131 /** 132 * Creates a factory for element-specific ids. 133 * 134 * @param configuration the configuration 135 */ 136 HtmlIds(HtmlConfiguration configuration) { 137 this.configuration = configuration; 138 this.utils = configuration.utils; 139 } 140 141 /** 142 * Returns an id for a package. 143 * 144 * @param element the package 145 * 146 * @return the id 147 */ 148 HtmlId forPackage(PackageElement element) { 149 return element == null || element.isUnnamed() 150 ? UNNAMED_PACKAGE_ANCHOR 151 : HtmlId.of(element.getQualifiedName().toString()); 152 } 153 154 /** 155 * Returns an id for a package name. 156 * 157 * @param pkgName the package name 158 * 159 * @return the id 160 */ 161 HtmlId forPackageName(String pkgName) { 162 return pkgName.isEmpty() 163 ? UNNAMED_PACKAGE_ANCHOR 164 : HtmlId.of(pkgName); 165 } 166 167 /** 168 * Returns an id for a class or interface. 169 * 170 * @param element the class or interface 171 * 172 * @return the id 173 */ 174 HtmlId forClass(TypeElement element) { 175 return HtmlId.of(utils.getFullyQualifiedName(element)); 176 } 177 178 /** 179 * Returns an id for an executable element, suitable for use when the 180 * simple name and argument list will be unique within the page, such as 181 * in the page for the declaration of the enclosing class or interface. 182 * 183 * @param element the element 184 * 185 * @return the id 186 */ 187 HtmlId forMember(ExecutableElement element) { 188 String a = element.getSimpleName() 189 + utils.makeSignature(element, null, true, true); 190 // utils.makeSignature includes spaces 191 return HtmlId.of(a.replaceAll("\\s", "")); 192 } 193 194 /** 195 * Returns an id for an executable element, including the context 196 * of its documented enclosing class or interface. 197 * 198 * @param typeElement the enclosing class or interface 199 * @param member the element 200 * 201 * @return the id 202 */ 203 HtmlId forMember(TypeElement typeElement, ExecutableElement member) { 204 return HtmlId.of( 205 utils.getSimpleName(member) + utils.signature(member, typeElement)); 206 } 207 208 /** 209 * Returns an id for a field, suitable for use when the simple name 210 * will be unique within the page, such as in the page for the 211 * declaration of the enclosing class or interface. 212 * 213 * <p>Warning: the name may not be unique if a property with the same 214 * name is also being documented in the same class. 215 * 216 * @param element the element 217 * 218 * @return the id 219 * 220 * @see #forProperty(ExecutableElement) 221 */ 222 HtmlId forMember(VariableElement element) { 223 return HtmlId.of(element.getSimpleName().toString()); 224 } 225 226 /** 227 * Returns an id for a field, including the context 228 * of its documented enclosing class or interface. 229 * 230 * @param typeElement the enclosing class or interface 231 * @param member the element 232 * 233 * @return the id 234 */ 235 HtmlId forMember(TypeElement typeElement, VariableElement member) { 236 return HtmlId 237 .of(typeElement.getQualifiedName() + "." + member.getSimpleName()); 238 } 239 240 /** 241 * Returns an id for the erasure of an executable element, 242 * or {@code null} if there are no type variables in the signature. 243 * 244 * For backward compatibility, include an anchor using the erasures of the 245 * parameters. NOTE: We won't need this method anymore after we fix 246 * {@code @see} tags so that they use the type instead of the erasure. 247 * 248 * @param executableElement the element to anchor to 249 * @return the 1.4.x style anchor for the executable element 250 */ 251 protected HtmlId forErasure(ExecutableElement executableElement) { 252 final StringBuilder buf 253 = new StringBuilder(executableElement.getSimpleName().toString()); 254 buf.append("("); 255 List<? extends VariableElement> parameters 256 = executableElement.getParameters(); 257 boolean foundTypeVariable = false; 258 for (int i = 0; i < parameters.size(); i++) { 259 if (i > 0) { 260 buf.append(","); 261 } 262 TypeMirror t = parameters.get(i).asType(); 263 SimpleTypeVisitor9<Boolean, Void> stv = new SimpleTypeVisitor9<>() { 264 boolean foundTypeVariable = false; 265 266 @Override 267 public Boolean visitArray(ArrayType t, Void p) { 268 visit(t.getComponentType()); 269 buf.append(utils.getDimension(t)); 270 return foundTypeVariable; 271 } 272 273 @Override 274 public Boolean visitTypeVariable(TypeVariable t, Void p) { 275 buf.append( 276 utils.asTypeElement(t).getQualifiedName().toString()); 277 foundTypeVariable = true; 278 return foundTypeVariable; 279 } 280 281 @Override 282 public Boolean visitDeclared(DeclaredType t, Void p) { 283 buf.append(utils.getQualifiedTypeName(t)); 284 return foundTypeVariable; 285 } 286 287 @Override 288 protected Boolean defaultAction(TypeMirror e, Void p) { 289 buf.append(e); 290 return foundTypeVariable; 291 } 292 }; 293 294 boolean isTypeVariable = stv.visit(t); 295 if (!foundTypeVariable) { 296 foundTypeVariable = isTypeVariable; 297 } 298 } 299 buf.append(")"); 300 return foundTypeVariable ? HtmlId.of(buf.toString()) : null; 301 } 302 303 /** 304 * Returns an id for a property, suitable for use when the simple name 305 * will be unique within the page, such as in the page for the 306 * declaration of the enclosing class or interface. 307 * 308 * <p>Warning: the name may not be unique if a field with the same 309 * name is also being documented in the same class. 310 * 311 * @param element the element 312 * 313 * @return the id 314 * 315 * @see #forMember(VariableElement) 316 */ 317 HtmlId forProperty(ExecutableElement element) { 318 return HtmlId.of(element.getSimpleName().toString()); 319 } 320 321 /** 322 * Returns an id for the list of classes and interfaces inherited from 323 * a class or interface. 324 * 325 * <p>Note: the use of {@code utils} may not be strictly necessary. 326 * 327 * @param element the class or interface 328 * 329 * @return the id 330 */ 331 HtmlId forInheritedClasses(TypeElement element) { 332 return HtmlId.of( 333 NESTED_CLASSES_INHERITANCE + utils.getFullyQualifiedName(element)); 334 } 335 336 /** 337 * Returns an id for the list of fields inherited from a class or interface. 338 * 339 * @param element the class or interface 340 * 341 * @return the id 342 */ 343 HtmlId forInheritedFields(TypeElement element) { 344 return forInherited(FIELDS_INHERITANCE, element); 345 } 346 347 /** 348 * Returns an id for the list of enum constants inherited from a class or interface. 349 * 350 * @param element the class or interface 351 * 352 * @return the id 353 */ 354 HtmlId forInheritedEnumConstants(TypeElement element) { 355 return forInherited(ENUM_CONSTANTS_INHERITANCE, element); 356 } 357 358 /** 359 * Returns an id for the list of methods inherited from a class or interface. 360 * 361 * @param element the class or interface 362 * 363 * @return the id 364 */ 365 HtmlId forInheritedMethods(TypeElement element) { 366 return forInherited(METHODS_INHERITANCE, element); 367 } 368 369 /** 370 * Returns an id for the list of properties inherited from a class or interface. 371 * 372 * @param element the class or interface 373 * 374 * @return the id 375 */ 376 HtmlId forInheritedProperties(TypeElement element) { 377 return forInherited(PROPERTIES_INHERITANCE, element); 378 } 379 380 // Note: the use of {@code configuration} may not be strictly necessary as 381 // compared to just using the fully qualified name, but would be a change in 382 // the value. 383 private HtmlId forInherited(String prefix, TypeElement element) { 384 return HtmlId.of(prefix + configuration.getClassName(element)); 385 } 386 387 /** 388 * Returns an id for a character on the A-Z Index page. 389 * 390 * @param character the character 391 * 392 * @return the id 393 */ 394 static HtmlId forIndexChar(char character) { 395 return HtmlId.of("I:" + character); 396 } 397 398 /** 399 * Returns an id for a line in a source-code listing. 400 * 401 * @param lineNumber the line number 402 * 403 * @return the id 404 */ 405 static HtmlId forLine(int lineNumber) { 406 return HtmlId.of("line-" + lineNumber); 407 } 408 409 /** 410 * Returns an id for a parameter, such as a component of a record. 411 * 412 * <p>Warning: this may not be unique on the page if used when there are 413 * other like-named parameters. 414 * 415 * @param paramName the parameter name 416 * 417 * @return the id 418 */ 419 static HtmlId forParam(String paramName) { 420 return HtmlId.of("param-" + paramName); 421 } 422 423 /** 424 * Returns an id for a fragment of text, such as in an {@code @index} tag, 425 * using a map of counts to ensure the id is unique. 426 * 427 * @param text the text 428 * @param counts the map of counts 429 * 430 * @return the id 431 */ 432 static HtmlId forText(String text, Map<String, Integer> counts) { 433 String base = text.replaceAll("\\s+", ""); 434 int count = counts.compute(base, (k, v) -> v == null ? 0 : v + 1); 435 return HtmlId.of(count == 0 ? base : base + "-" + count); 436 } 437 438 /** 439 * Returns an id for one of the kinds of section in the pages for item group summaries. 440 * 441 * <p>Note: while the use of simple names (that are not keywords) 442 * may seem undesirable, they cannot conflict with the unqualified names 443 * of fields and properties, which should not also appear on the same page. 444 * 445 * @param kind the kind of deprecated items in the section 446 * 447 * @return the id 448 */ 449 static HtmlId 450 forSummaryKind(SummaryAPIListBuilder.SummaryElementKind kind) { 451 return HtmlId.of(switch (kind) { 452 case MODULE -> "module"; 453 case PACKAGE -> "package"; 454 case INTERFACE -> "interface"; 455 case CLASS -> "class"; 456 case ENUM -> "enum-class"; 457 case EXCEPTION_CLASS -> "exception-class"; 458 case ANNOTATION_TYPE -> "annotation-interface"; 459 case FIELD -> "field"; 460 case METHOD -> "method"; 461 case CONSTRUCTOR -> "constructor"; 462 case ENUM_CONSTANT -> "enum-constant"; 463 case ANNOTATION_TYPE_MEMBER -> "annotation-interface-member"; 464 case RECORD_CLASS -> "record-class"; 465 }); 466 } 467 468 /** 469 * Returns an id for the member summary table of the given {@code kind} in a class page. 470 * 471 * @param kind the kind of member 472 * 473 * @return the id 474 */ 475 static HtmlId forMemberSummary(VisibleMemberTable.Kind kind) { 476 return switch (kind) { 477 case NESTED_CLASSES -> NESTED_CLASS_SUMMARY; 478 case ENUM_CONSTANTS -> ENUM_CONSTANT_SUMMARY; 479 case FIELDS -> FIELD_SUMMARY; 480 case CONSTRUCTORS -> CONSTRUCTOR_SUMMARY; 481 case METHODS -> METHOD_SUMMARY; 482 // We generate separate summaries for optional and required annotation 483 // members 484 case ANNOTATION_TYPE_MEMBER -> throw new IllegalArgumentException( 485 "unsupported member kind"); 486 case ANNOTATION_TYPE_MEMBER_OPTIONAL -> ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY; 487 case ANNOTATION_TYPE_MEMBER_REQUIRED -> ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY; 488 case PROPERTIES -> PROPERTY_SUMMARY; 489 }; 490 } 491 492 /** 493 * Returns an id for a "tab" in a table. 494 * 495 * @param tableId the id for the table 496 * @param tabIndex the index of the tab 497 * 498 * @return the id 499 */ 500 public static HtmlId forTab(HtmlId tableId, int tabIndex) { 501 return HtmlId.of(tableId.name() + "-tab" + tabIndex); 502 } 503 504 /** 505 * Returns an id for the "tab panel" in a table. 506 * 507 * @param tableId the id for the table 508 * 509 * @return the id 510 */ 511 public static HtmlId forTabPanel(HtmlId tableId) { 512 return HtmlId.of(tableId.name() + ".tabpanel"); 513 } 514 515 /** 516 * Returns an id for the "preview" section for an element. 517 * 518 * @param el the element 519 * 520 * @return the id 521 */ 522 public HtmlId forPreviewSection(Element el) { 523 return HtmlId.of("preview-" + switch (el.getKind()) { 524 case CONSTRUCTOR, METHOD -> forMember((ExecutableElement) el).name(); 525 case PACKAGE -> forPackage((PackageElement) el).name(); 526 default -> utils.getFullyQualifiedName(el, false); 527 }); 528 } 529 530 /** 531 * Returns an id for the entry on the HELP page for a kind of generated page. 532 * 533 * @param page the kind of page 534 * 535 * @return the id 536 */ 537 public HtmlId forPage(Navigation.PageMode page) { 538 return HtmlId 539 .of(page.name().toLowerCase(Locale.ROOT).replace("_", "-")); 540 } 541 542 /** 543 * Returns an id for a heading in a doc comment. The id value is derived from the contents 544 * of the heading with additional checks to make it unique within its containing page. 545 * 546 * @param headingText the text contained by the heading 547 * @param headingIds the set of heading ids already generated for the current page 548 * @return a unique id value for the heading 549 */ 550 public HtmlId forHeading(CharSequence headingText, Set<String> headingIds) { 551 String idValue = headingText.toString() 552 .toLowerCase(Locale.ROOT) 553 .trim() 554 .replaceAll("[^\\w_-]+", "-"); 555 // Make id value unique 556 idValue = idValue + "-heading"; 557 if (!headingIds.add(idValue)) { 558 int counter = 1; 559 while (!headingIds.add(idValue + counter)) { 560 counter++; 561 } 562 idValue = idValue + counter; 563 } 564 return HtmlId.of(idValue); 565 } 566}