001/* 002 * Copyright (c) 1998, 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.time.ZonedDateTime; 029import java.util.ArrayList; 030import java.util.EnumSet; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Locale; 034import java.util.Map; 035import java.util.Objects; 036import java.util.Set; 037import java.util.function.Function; 038import java.util.stream.Collectors; 039import javax.lang.model.element.Element; 040import javax.lang.model.element.PackageElement; 041import javax.lang.model.element.TypeElement; 042import javax.tools.JavaFileManager; 043import javax.tools.JavaFileObject; 044import javax.tools.StandardJavaFileManager; 045 046import org.jdrupes.mdoclet.internal.Versions; 047import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 048import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseOptions; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.WriterFactory; 053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DeprecatedAPIListBuilder; 054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 055import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 056import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 057import org.jdrupes.mdoclet.internal.doclets.toolkit.util.NewAPIBuilder; 058import org.jdrupes.mdoclet.internal.doclets.toolkit.util.PreviewAPIListBuilder; 059 060import jdk.javadoc.doclet.Doclet; 061import jdk.javadoc.doclet.DocletEnvironment; 062import jdk.javadoc.doclet.Reporter; 063import jdk.javadoc.doclet.StandardDoclet; 064import jdk.javadoc.doclet.Taglet; 065 066/** 067 * Configure the output based on the command-line options. 068 * <p> 069 * Also determine the length of the command-line option. For example, 070 * for a option "-header" there will be a string argument associated, then the 071 * the length of option "-header" is two. But for option "-nohelp" no argument 072 * is needed so it's length is 1. 073 * </p> 074 * <p> 075 * Also do the error checking on the options used. For example it is illegal to 076 * use "-helpfile" option when already "-nohelp" option is used. 077 * </p> 078 */ 079public class HtmlConfiguration extends BaseConfiguration { 080 081 /** 082 * Default charset for HTML. 083 */ 084 public static final String HTML_DEFAULT_CHARSET = "utf-8"; 085 086 public final Resources docResources; 087 088 /** 089 * First file to appear in the right-hand frame in the generated 090 * documentation. 091 */ 092 public DocPath topFile = DocPath.empty; 093 094 /** 095 * The TypeElement for the class file getting generated. 096 */ 097 public TypeElement currentTypeElement = null; // Set this TypeElement in 098 // the ClassWriter. 099 100 /** 101 * The collections of items for the main index. 102 * This field is only initialized if {@code options.createIndex()} 103 * is {@code true}. 104 * This index is populated somewhat lazily: 105 * 1. items found in doc comments are found while generating declaration pages 106 * 2. items for elements are added in bulk before generating the index files 107 * 3. additional items are added as needed 108 */ 109 protected HtmlIndexBuilder mainIndex; 110 111 /** 112 * The collection of deprecated items, if any, to be displayed on the deprecated-list page, 113 * or null if the page should not be generated. 114 * The page will not be generated if {@link BaseOptions#noDeprecated() no deprecated items} 115 * are to be included in the documentation, 116 * or if the page is {@link HtmlOptions#noDeprecatedList() not wanted}, 117 * or if there are no deprecated elements being documented. 118 */ 119 protected DeprecatedAPIListBuilder deprecatedAPIListBuilder; 120 121 /** 122 * The collection of preview items, if any, to be displayed on the preview-list page, 123 * or null if the page should not be generated. 124 * The page will not be generated if there are no preview elements being documented. 125 */ 126 protected PreviewAPIListBuilder previewAPIListBuilder; 127 128 /** 129 * The collection of new API items, if any, to be displayed on the new-list page, 130 * or null if the page should not be generated. 131 * The page is only generated if the {@code --since} option is used with release 132 * names matching {@code @since} tags in the documented code. 133 */ 134 protected NewAPIBuilder newAPIPageBuilder; 135 136 public Contents contents; 137 138 protected final Messages messages; 139 140 public DocPaths docPaths; 141 142 public HtmlIds htmlIds; 143 144 public Map<Element, List<DocPath>> localStylesheetMap = new HashMap<>(); 145 146 private final HtmlOptions options; 147 148 /** 149 * Kinds of conditional pages. 150 */ 151 // Note: this should (eventually) be merged with Navigation.PageMode, 152 // which performs a somewhat similar role 153 public enum ConditionalPage { 154 CONSTANT_VALUES, DEPRECATED, EXTERNAL_SPECS, PREVIEW, SERIALIZED_FORM, 155 SYSTEM_PROPERTIES, NEW 156 } 157 158 /** 159 * A set of values indicating which conditional pages should be generated. 160 * 161 * The set is computed lazily, although values must (obviously) be set before 162 * they are required, such as when deciding whether to generate links 163 * to these files in the navigation bar, on each page, the help file, and so on. 164 * 165 * The value for any page may depend on both command-line options to enable or 166 * disable a page, and on content being found for the page, such as deprecated 167 * items to appear in the summary page of deprecated items. 168 */ 169 public final Set<ConditionalPage> conditionalPages; 170 171 /** 172 * The build date, to be recorded in generated files. 173 */ 174 private ZonedDateTime buildDate; 175 176 /** 177 * Constructs the full configuration needed by the doclet, including 178 * the format-specific part, defined in this class, and the format-independent 179 * part, defined in the supertype. 180 * 181 * @apiNote The {@code doclet} parameter is used when 182 * {@link Taglet#init(DocletEnvironment, Doclet) initializing tags}. 183 * Some doclets (such as the {@link StandardDoclet}), may delegate to another 184 * (such as the {@link HtmlDoclet}). In such cases, the primary doclet (i.e 185 * {@code StandardDoclet}) should be provided here, and not any internal 186 * class like {@code HtmlDoclet}. 187 * 188 * @param doclet the doclet for this run of javadoc 189 * @param locale the locale for the generated documentation 190 * @param reporter the reporter to use for console messages 191 */ 192 public HtmlConfiguration(Doclet doclet, Locale locale, Reporter reporter) { 193 super(doclet, locale, reporter); 194 195 // Use the default locale for console messages. 196 Resources msgResources = new Resources(Locale.getDefault(), 197 BaseConfiguration.sharedResourceBundleName, 198 "org.jdrupes.mdoclet.internal.doclets.formats.html.resources.standard"); 199 200 // Use the provided locale for generated docs 201 // Ideally, the doc resources would be in different resource files than 202 // the 203 // message resources, so that we do not have different copies of the 204 // same resources. 205 if (locale.equals(Locale.getDefault())) { 206 docResources = msgResources; 207 } else { 208 docResources = new Resources(locale, 209 BaseConfiguration.sharedResourceBundleName, 210 "org.jdrupes.mdoclet.internal.doclets.formats.html.resources.standard"); 211 } 212 213 messages = new Messages(this, msgResources); 214 options = new HtmlOptions(this); 215 216 Runtime.Version v; 217 try { 218 v = Versions.javadocVersion(); 219 } catch (RuntimeException e) { 220 assert false : e; 221 v = Runtime.version(); // arguably, the only sensible default 222 } 223 docletVersion = v; 224 225 conditionalPages = EnumSet.noneOf(ConditionalPage.class); 226 } 227 228 protected void initConfiguration(DocletEnvironment docEnv, 229 Function<String, String> resourceKeyMapper) { 230 super.initConfiguration(docEnv, resourceKeyMapper); 231 contents = new Contents(this); 232 htmlIds = new HtmlIds(this); 233 } 234 235 private final Runtime.Version docletVersion; 236 237 @Override 238 public Runtime.Version getDocletVersion() { 239 return docletVersion; 240 } 241 242 @Override 243 public Resources getDocResources() { 244 return docResources; 245 } 246 247 /** 248 * Returns a utility object providing commonly used fragments of content. 249 * 250 * @return a utility object providing commonly used fragments of content 251 */ 252 public Contents getContents() { 253 return Objects.requireNonNull(contents); 254 } 255 256 @Override 257 public Messages getMessages() { 258 return messages; 259 } 260 261 @Override 262 public HtmlOptions getOptions() { 263 return options; 264 } 265 266 @Override 267 public boolean finishOptionSettings() { 268 if (!options.validateOptions()) { 269 return false; 270 } 271 272 ZonedDateTime zdt = options.date(); 273 buildDate = zdt != null ? zdt : ZonedDateTime.now(); 274 275 if (!getSpecifiedTypeElements().isEmpty()) { 276 Map<String, PackageElement> map = new HashMap<>(); 277 PackageElement pkg; 278 for (TypeElement aClass : getIncludedTypeElements()) { 279 pkg = utils.containingPackage(aClass); 280 if (!map.containsKey(utils.getPackageName(pkg))) { 281 map.put(utils.getPackageName(pkg), pkg); 282 } 283 } 284 } 285 if (options.createIndex()) { 286 mainIndex = new HtmlIndexBuilder(this); 287 } 288 docPaths = new DocPaths(utils); 289 setCreateOverview(); 290 setTopFile(); 291 initDocLint(options.doclintOpts(), tagletManager.getAllTagletNames()); 292 return true; 293 } 294 295 /** 296 * {@return the date to be recorded in generated files} 297 */ 298 public ZonedDateTime getBuildDate() { 299 return buildDate; 300 } 301 302 /** 303 * Decide the page which will appear first in the right-hand frame. It will 304 * be "overview-summary.html" if "-overview" option is used or no 305 * "-overview" but the number of packages is more than one. It will be 306 * "package-summary.html" of the respective package if there is only one 307 * package to document. It will be a class page(first in the sorted order), 308 * if only classes are provided on the command line. 309 */ 310 protected void setTopFile() { 311 if (!checkForDeprecation()) { 312 return; 313 } 314 if (options.createOverview()) { 315 topFile = DocPaths.INDEX; 316 } else { 317 if (showModules) { 318 topFile = DocPath.empty 319 .resolve(docPaths.moduleSummary(modules.first())); 320 } else if (!packages.isEmpty()) { 321 topFile = docPaths.forPackage(packages.first()) 322 .resolve(DocPaths.PACKAGE_SUMMARY); 323 } 324 } 325 } 326 327 protected TypeElement getValidClass(List<TypeElement> classes) { 328 if (!options.noDeprecated()) { 329 return classes.get(0); 330 } 331 for (TypeElement te : classes) { 332 if (!utils.isDeprecated(te)) { 333 return te; 334 } 335 } 336 return null; 337 } 338 339 protected boolean checkForDeprecation() { 340 for (TypeElement te : getIncludedTypeElements()) { 341 if (isGeneratedDoc(te)) { 342 return true; 343 } 344 } 345 return false; 346 } 347 348 /** 349 * Generate "overview.html" page if option "-overview" is used or number of 350 * packages is more than one. Sets {@code HtmlOptions.createOverview} field to true. 351 */ 352 protected void setCreateOverview() { 353 if (!options.noOverview()) { 354 if (options.overviewPath() != null 355 || modules.size() > 1 356 || (modules.isEmpty() && packages.size() > 1)) { 357 options.setCreateOverview(true); 358 } 359 } 360 } 361 362 @Override 363 public WriterFactory getWriterFactory() { 364 return new WriterFactoryImpl(this); 365 } 366 367 @Override 368 public Locale getLocale() { 369 if (locale == null) 370 return Locale.getDefault(); 371 return locale; 372 } 373 374 /** 375 * Return the path of the overview file or null if it does not exist. 376 * 377 * @return the path of the overview file or null if it does not exist. 378 */ 379 @Override 380 public JavaFileObject getOverviewPath() { 381 String overviewpath = options.overviewPath(); 382 if (overviewpath != null 383 && getFileManager() instanceof StandardJavaFileManager fm) { 384 return fm.getJavaFileObjects(overviewpath).iterator().next(); 385 } 386 return null; 387 } 388 389 public DocPath getMainStylesheet() { 390 String stylesheetfile = options.stylesheetFile(); 391 if (!stylesheetfile.isEmpty()) { 392 DocFile docFile = DocFile.createFileForInput(this, stylesheetfile); 393 return DocPath.create(docFile.getName()); 394 } 395 return null; 396 } 397 398 public List<DocPath> getAdditionalStylesheets() { 399 return options.additionalStylesheets().stream() 400 .map(ssf -> DocFile.createFileForInput(this, ssf)) 401 .map(file -> DocPath.create(file.getName())) 402 .collect(Collectors.toCollection(ArrayList::new)); 403 } 404 405 public List<DocPath> getAdditionalScripts() { 406 return options.additionalScripts().stream() 407 .map(sf -> DocFile.createFileForInput(this, sf)) 408 .map(file -> DocPath.create(file.getName())) 409 .collect(Collectors.toCollection(ArrayList::new)); 410 } 411 412 @Override 413 public JavaFileManager getFileManager() { 414 return docEnv.getJavaFileManager(); 415 } 416 417 @Override 418 protected boolean finishOptionSettings0() throws DocletException { 419 if (options.docEncoding() == null) { 420 if (options.charset() == null) { 421 String charset 422 = (options.encoding() == null) ? HTML_DEFAULT_CHARSET 423 : options.encoding(); 424 options.setCharset(charset); 425 options.setDocEncoding((options.charset())); 426 } else { 427 options.setDocEncoding(options.charset()); 428 } 429 } else { 430 if (options.charset() == null) { 431 options.setCharset(options.docEncoding()); 432 } else if (!options.charset().equals(options.docEncoding())) { 433 messages.error("doclet.Option_conflict", "-charset", 434 "-docencoding"); 435 return false; 436 } 437 } 438 return super.finishOptionSettings0(); 439 } 440}