001/* 002 * Copyright (c) 1997, 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.formats.html; 027 028import java.io.IOException; 029import java.nio.file.DirectoryStream; 030import java.nio.file.Files; 031import java.nio.file.InvalidPathException; 032import java.nio.file.Path; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Locale; 038import java.util.Map; 039import java.util.Set; 040import java.util.SortedSet; 041import java.util.function.Function; 042 043import javax.lang.model.SourceVersion; 044import javax.lang.model.element.ModuleElement; 045import javax.lang.model.element.PackageElement; 046import javax.lang.model.element.TypeElement; 047 048import org.jdrupes.mdoclet.internal.doclets.toolkit.AbstractDoclet; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.builders.AbstractBuilder; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.builders.BuilderFactory; 053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.ClassTree; 054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DeprecatedAPIListBuilder; 055import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 056import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 057import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 058import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 059import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexBuilder; 060import org.jdrupes.mdoclet.internal.doclets.toolkit.util.NewAPIBuilder; 061import org.jdrupes.mdoclet.internal.doclets.toolkit.util.PreviewAPIListBuilder; 062 063import jdk.javadoc.doclet.Doclet; 064import jdk.javadoc.doclet.DocletEnvironment; 065import jdk.javadoc.doclet.Reporter; 066 067/** 068 * The class with "start" method, calls individual Writers. 069 */ 070public class HtmlDoclet extends AbstractDoclet { 071 072 /** 073 * Creates a doclet to generate HTML documentation, 074 * specifying the "initiating doclet" to be used when 075 * initializing any taglets for this doclet. 076 * An initiating doclet is one that delegates to 077 * this doclet. 078 * 079 * @param initiatingDoclet the initiating doclet 080 */ 081 public HtmlDoclet(Doclet initiatingDoclet) { 082 this.initiatingDoclet = initiatingDoclet; 083 } 084 085 @Override // defined by Doclet 086 public String getName() { 087 return "Html"; 088 } 089 090 /** 091 * The initiating doclet, to be specified when creating 092 * the configuration. 093 */ 094 private final Doclet initiatingDoclet; 095 096 /** 097 * The global configuration information for this run. 098 * Initialized in {@link #init(Locale, Reporter)}. 099 */ 100 private HtmlConfiguration configuration; 101 102 /** 103 * Object for generating messages and diagnostics. 104 */ 105 private Messages messages; 106 107 /** 108 * Base path for resources for this doclet. 109 */ 110 private static final DocPath DOCLET_RESOURCES = DocPath 111 .create("/jdk/javadoc/internal/doclets/formats/html/resources"); 112 113 @Override // defined by Doclet 114 public void init(Locale locale, Reporter reporter) { 115 configuration 116 = new HtmlConfiguration(initiatingDoclet, locale, reporter); 117 messages = configuration.getMessages(); 118 } 119 120 /** 121 * Create the configuration instance. 122 * Override this method to use a different 123 * configuration. 124 * 125 * @return the configuration 126 */ 127 @Override // defined by AbstractDoclet 128 public HtmlConfiguration getConfiguration() { 129 return configuration; 130 } 131 132 @Override 133 protected Function<String, String> 134 getResourceKeyMapper(DocletEnvironment docEnv) { 135 SourceVersion sv = docEnv.getSourceVersion(); 136 Map<String, String> map = new HashMap<>(); 137 String[][] pairs = { 138 // in standard.properties 139 { "doclet.Enum_Hierarchy", "doclet.Enum_Class_Hierarchy" }, 140 { "doclet.Annotation_Type_Hierarchy", 141 "doclet.Annotation_Interface_Hierarchy" }, 142 { "doclet.Href_Annotation_Title", 143 "doclet.Href_Annotation_Interface_Title" }, 144 { "doclet.Href_Enum_Title", "doclet.Href_Enum_Class_Title" }, 145 { "doclet.Annotation_Types", "doclet.Annotation_Interfaces" }, 146 { "doclet.Annotation_Type_Members", 147 "doclet.Annotation_Interface_Members" }, 148 { "doclet.annotation_types", "doclet.annotation_interfaces" }, 149 { "doclet.annotation_type_members", 150 "doclet.annotation_interface_members" }, 151 { "doclet.help.enum.intro", "doclet.help.enum.class.intro" }, 152 { "doclet.help.annotation_type.intro", 153 "doclet.help.annotation_interface.intro" }, 154 { "doclet.help.annotation_type.declaration", 155 "doclet.help.annotation_interface.declaration" }, 156 { "doclet.help.annotation_type.description", 157 "doclet.help.annotation_interface.description" }, 158 159 // in doclets.properties 160 { "doclet.Enums", "doclet.EnumClasses" }, 161 { "doclet.AnnotationType", "doclet.AnnotationInterface" }, 162 { "doclet.AnnotationTypes", "doclet.AnnotationInterfaces" }, 163 { "doclet.annotationtype", "doclet.annotationinterface" }, 164 { "doclet.annotationtypes", "doclet.annotationinterfaces" }, 165 { "doclet.Enum", "doclet.EnumClass" }, 166 { "doclet.enum", "doclet.enumclass" }, 167 { "doclet.enums", "doclet.enumclasses" }, 168 { "doclet.Annotation_Type_Member", 169 "doclet.Annotation_Interface_Member" }, 170 { "doclet.enum_values_doc.fullbody", 171 "doclet.enum_class_values_doc.fullbody" }, 172 { "doclet.enum_values_doc.return", 173 "doclet.enum_class_values_doc.return" }, 174 { "doclet.enum_valueof_doc.fullbody", 175 "doclet.enum_class_valueof_doc.fullbody" }, 176 { "doclet.enum_valueof_doc.throws_ila", 177 "doclet.enum_class_valueof_doc.throws_ila" }, 178 { "doclet.search.types", "doclet.search.classes_and_interfaces" } 179 }; 180 for (String[] pair : pairs) { 181 if (sv.compareTo(SourceVersion.RELEASE_16) >= 0) { 182 map.put(pair[0], pair[1]); 183 } else { 184 map.put(pair[1], pair[0]); 185 } 186 } 187 return (k) -> map.getOrDefault(k, k); 188 } 189 190 @Override // defined by AbstractDoclet 191 public void generateClassFiles(ClassTree classTree) throws DocletException { 192 List<String> since = configuration.getOptions().since(); 193 if (!(configuration.getOptions().noDeprecated() 194 || configuration.getOptions().noDeprecatedList())) { 195 DeprecatedAPIListBuilder deprecatedBuilder 196 = new DeprecatedAPIListBuilder(configuration, since); 197 if (!deprecatedBuilder.isEmpty()) { 198 configuration.deprecatedAPIListBuilder = deprecatedBuilder; 199 configuration.conditionalPages 200 .add(HtmlConfiguration.ConditionalPage.DEPRECATED); 201 } 202 } 203 if (!since.isEmpty()) { 204 NewAPIBuilder newAPIBuilder 205 = new NewAPIBuilder(configuration, since); 206 if (!newAPIBuilder.isEmpty()) { 207 configuration.newAPIPageBuilder = newAPIBuilder; 208 configuration.conditionalPages 209 .add(HtmlConfiguration.ConditionalPage.NEW); 210 } 211 } 212 PreviewAPIListBuilder previewBuilder 213 = new PreviewAPIListBuilder(configuration); 214 if (!previewBuilder.isEmpty()) { 215 configuration.previewAPIListBuilder = previewBuilder; 216 configuration.conditionalPages 217 .add(HtmlConfiguration.ConditionalPage.PREVIEW); 218 } 219 220 super.generateClassFiles(classTree); 221 } 222 223 /** 224 * Start the generation of files. Call generate methods in the individual 225 * writers, which will in turn generate the documentation files. Call the 226 * TreeWriter generation first to ensure the Class Hierarchy is built 227 * first and then can be used in the later generation. 228 * 229 * For new format. 230 * 231 * @throws DocletException if there is a problem while writing the other files 232 */ 233 @Override // defined by AbstractDoclet 234 protected void generateOtherFiles(ClassTree classTree) 235 throws DocletException { 236 super.generateOtherFiles(classTree); 237 HtmlOptions options = configuration.getOptions(); 238 if (options.linkSource()) { 239 SourceToHTMLConverter.convertRoot(configuration, 240 DocPaths.SOURCE_OUTPUT); 241 } 242 // Modules with no documented classes may be specified on the 243 // command line to specify a service provider, allow these. 244 if (configuration.getSpecifiedModuleElements().isEmpty() && 245 configuration.topFile.isEmpty()) { 246 messages.error("doclet.No_Non_Deprecated_Classes_To_Document"); 247 return; 248 } 249 boolean nodeprecated = options.noDeprecated(); 250 performCopy(options.helpFile(), DocPath.empty); 251 performCopy(options.stylesheetFile(), DocPath.empty); 252 for (String stylesheet : options.additionalStylesheets()) { 253 performCopy(stylesheet, DocPath.empty); 254 } 255 for (String script : options.additionalScripts()) { 256 performCopy(script, DocPaths.SCRIPT_DIR); 257 } 258 // do early to reduce memory footprint 259 if (options.classUse()) { 260 ClassUseWriter.generate(configuration, classTree); 261 } 262 263 if (options.createTree()) { 264 TreeWriter.generate(configuration, classTree); 265 } 266 267 if (configuration.conditionalPages 268 .contains((HtmlConfiguration.ConditionalPage.DEPRECATED))) { 269 DeprecatedListWriter.generate(configuration); 270 } 271 272 if (configuration.conditionalPages 273 .contains((HtmlConfiguration.ConditionalPage.PREVIEW))) { 274 PreviewListWriter.generate(configuration); 275 } 276 277 if (configuration.conditionalPages 278 .contains((HtmlConfiguration.ConditionalPage.NEW))) { 279 NewAPIListWriter.generate(configuration); 280 } 281 282 if (options.createOverview()) { 283 if (configuration.showModules) { 284 ModuleIndexWriter.generate(configuration); 285 } else { 286 PackageIndexWriter.generate(configuration); 287 } 288 } 289 290 if (options.createIndex()) { 291 if (!options.noExternalSpecsPage()) { 292 ExternalSpecsWriter.generate(configuration); 293 } 294 SystemPropertiesWriter.generate(configuration); 295 configuration.mainIndex.addElements(); 296 IndexBuilder allClassesIndex 297 = new IndexBuilder(configuration, nodeprecated, true); 298 allClassesIndex.addElements(); 299 AllClassesIndexWriter.generate(configuration, allClassesIndex); 300 if (!configuration.packages.isEmpty()) { 301 AllPackagesIndexWriter.generate(configuration); 302 } 303 configuration.mainIndex.createSearchIndexFiles(); 304 IndexWriter.generate(configuration); 305 SearchWriter.generate(configuration); 306 } 307 308 if (options.createOverview()) { 309 IndexRedirectWriter.generate(configuration, 310 DocPaths.OVERVIEW_SUMMARY, DocPaths.INDEX); 311 } else { 312 IndexRedirectWriter.generate(configuration); 313 } 314 315 if (options.helpFile().isEmpty() && !options.noHelp()) { 316 HelpWriter.generate(configuration); 317 } 318 // If a stylesheet file is not specified, copy the default stylesheet 319 // and replace newline with platform-specific newline. 320 DocFile f; 321 if (options.stylesheetFile().length() == 0) { 322 f = DocFile.createFileForOutput(configuration, DocPaths.STYLESHEET); 323 f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.STYLESHEET), 324 true, true); 325 } 326 f = DocFile.createFileForOutput(configuration, DocPaths.JAVASCRIPT); 327 f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.JAVASCRIPT), true, 328 true); 329 f = DocFile.createFileForOutput(configuration, DocPaths.CLIPBOARD_SVG); 330 f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.CLIPBOARD_SVG), true, 331 true); 332 f = DocFile.createFileForOutput(configuration, DocPaths.LINK_SVG); 333 f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.LINK_SVG), true, 334 true); 335 if (options.createIndex()) { 336 f = DocFile.createFileForOutput(configuration, DocPaths.SEARCH_JS); 337 f.copyResource( 338 DOCLET_RESOURCES.resolve(DocPaths.SEARCH_JS_TEMPLATE), 339 configuration.docResources); 340 341 f = DocFile.createFileForOutput(configuration, 342 DocPaths.SEARCH_PAGE_JS); 343 f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.SEARCH_PAGE_JS), 344 configuration.docResources); 345 346 f = DocFile.createFileForOutput(configuration, 347 DocPaths.RESOURCES.resolve(DocPaths.GLASS_IMG)); 348 f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.GLASS_IMG), true, 349 false); 350 351 f = DocFile.createFileForOutput(configuration, 352 DocPaths.RESOURCES.resolve(DocPaths.X_IMG)); 353 f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.X_IMG), true, 354 false); 355 copyJqueryFiles(); 356 } 357 358 copyLegalFiles(options.createIndex()); 359 } 360 361 private void copyJqueryFiles() throws DocletException { 362 List<String> files = Arrays.asList( 363 DocPaths.JQUERY_JS.getPath(), 364 DocPaths.JQUERY_UI_JS.getPath(), 365 DocPaths.JQUERY_UI_CSS.getPath()); 366 DocFile f; 367 for (String file : files) { 368 DocPath filePath = DocPaths.SCRIPT_DIR.resolve(file); 369 f = DocFile.createFileForOutput(configuration, filePath); 370 f.copyResource(DOCLET_RESOURCES.resolve(filePath), true, false); 371 } 372 } 373 374 private void copyLegalFiles(boolean includeJQuery) throws DocletException { 375 Path legalNoticesDir; 376 String legalNotices = configuration.getOptions().legalNotices(); 377 switch (legalNotices) { 378 case "", "default" -> { 379 if (getClass().getModule().getName() == null) { 380 return; 381 } 382 Path javaHome = Path.of(System.getProperty("java.home")); 383 legalNoticesDir = javaHome.resolve("legal") 384 .resolve(getClass().getModule().getName()); 385 } 386 387 case "none" -> { 388 return; 389 } 390 391 default -> { 392 try { 393 legalNoticesDir = Path.of(legalNotices); 394 } catch (InvalidPathException e) { 395 messages.error("doclet.Error_invalid_path_for_legal_notices", 396 legalNotices, e.getMessage()); 397 return; 398 } 399 } 400 } 401 402 if (Files.exists(legalNoticesDir)) { 403 try (DirectoryStream<Path> ds 404 = Files.newDirectoryStream(legalNoticesDir)) { 405 for (Path entry : ds) { 406 if (!Files.isRegularFile(entry)) { 407 continue; 408 } 409 if (entry.getFileName().toString().startsWith("jquery") 410 && !includeJQuery) { 411 continue; 412 } 413 DocPath filePath = DocPaths.LEGAL 414 .resolve(entry.getFileName().toString()); 415 DocFile df 416 = DocFile.createFileForOutput(configuration, filePath); 417 df.copyFile( 418 DocFile.createFileForInput(configuration, entry)); 419 } 420 } catch (IOException e) { 421 messages.error("doclet.Error_copying_legal_notices", e); 422 } 423 } 424 } 425 426 @Override // defined by AbstractDoclet 427 protected void generateClassFiles(SortedSet<TypeElement> typeElems, 428 ClassTree classTree) 429 throws DocletException { 430 BuilderFactory f = configuration.getBuilderFactory(); 431 for (TypeElement te : typeElems) { 432 if (utils.hasHiddenTag(te) || 433 !(configuration.isGeneratedDoc(te) && utils.isIncluded(te))) { 434 continue; 435 } 436 f.getClassBuilder(te, classTree).build(); 437 } 438 } 439 440 @Override // defined by AbstractDoclet 441 protected void generateModuleFiles() throws DocletException { 442 if (configuration.showModules) { 443 List<ModuleElement> mdles 444 = new ArrayList<>(configuration.modulePackages.keySet()); 445 for (ModuleElement mdle : mdles) { 446 AbstractBuilder moduleSummaryBuilder = configuration 447 .getBuilderFactory().getModuleSummaryBuilder(mdle); 448 moduleSummaryBuilder.build(); 449 } 450 } 451 } 452 453 @Override // defined by AbstractDoclet 454 protected void generatePackageFiles(ClassTree classTree) 455 throws DocletException { 456 HtmlOptions options = configuration.getOptions(); 457 Set<PackageElement> packages = configuration.packages; 458 List<PackageElement> pList = new ArrayList<>(packages); 459 for (PackageElement pkg : pList) { 460 // if -nodeprecated option is set and the package is marked as 461 // deprecated, do not generate the package-summary.html, 462 // package-frame.html 463 // and package-tree.html pages for that package. 464 if (!(options.noDeprecated() && utils.isDeprecated(pkg))) { 465 AbstractBuilder packageSummaryBuilder = configuration 466 .getBuilderFactory().getPackageSummaryBuilder(pkg); 467 packageSummaryBuilder.build(); 468 if (options.createTree()) { 469 PackageTreeWriter.generate(configuration, pkg, 470 options.noDeprecated()); 471 } 472 } 473 } 474 } 475 476 @Override // defined by Doclet 477 public Set<? extends Option> getSupportedOptions() { 478 return configuration.getOptions().getSupportedOptions(); 479 } 480 481 private void performCopy(String filename, DocPath targetPath) 482 throws DocFileIOException { 483 if (filename.isEmpty()) { 484 return; 485 } 486 487 DocFile fromfile = DocFile.createFileForInput(configuration, filename); 488 DocPath path = targetPath.resolve(fromfile.getName()); 489 DocFile toFile = DocFile.createFileForOutput(configuration, path); 490 if (toFile.isSameFile(fromfile)) { 491 return; 492 } 493 494 messages.notice("doclet.Copying_File_0_To_File_1", 495 fromfile.getPath(), path.getPath()); 496 toFile.copyFile(fromfile); 497 } 498}