1 /* 2 * Copyright (c) 1998, 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.formats.html; 27 28 import java.net.*; 29 import java.util.*; 30 import java.util.stream.Collectors; 31 32 import javax.lang.model.element.Element; 33 import javax.lang.model.element.PackageElement; 34 import javax.lang.model.element.TypeElement; 35 import javax.tools.JavaFileManager; 36 import javax.tools.JavaFileObject; 37 import javax.tools.StandardJavaFileManager; 38 39 import com.sun.source.util.DocTreePath; 40 import com.sun.tools.doclint.DocLint; 41 42 import jdk.javadoc.doclet.Doclet; 43 import jdk.javadoc.doclet.DocletEnvironment; 44 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; 45 import jdk.javadoc.internal.doclets.toolkit.DocletException; 46 import jdk.javadoc.internal.doclets.toolkit.Messages; 47 import jdk.javadoc.internal.doclets.toolkit.Resources; 48 import jdk.javadoc.internal.doclets.toolkit.WriterFactory; 49 import jdk.javadoc.internal.doclets.toolkit.util.DocFile; 50 import jdk.javadoc.internal.doclets.toolkit.util.DocPath; 51 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; 52 53 import static javax.tools.Diagnostic.Kind.*; 54 55 /** 56 * Configure the output based on the command line options. 57 * <p> 58 * Also determine the length of the command line option. For example, 59 * for a option "-header" there will be a string argument associated, then the 60 * the length of option "-header" is two. But for option "-nohelp" no argument 61 * is needed so it's length is 1. 62 * </p> 63 * <p> 64 * Also do the error checking on the options used. For example it is illegal to 65 * use "-helpfile" option when already "-nohelp" option is used. 66 * </p> 67 * 68 * <p><b>This is NOT part of any supported API. 69 * If you write code that depends on this, you do so at your own risk. 70 * This code and its internal interfaces are subject to change or 71 * deletion without notice.</b> 72 */ 73 public class HtmlConfiguration extends BaseConfiguration { 74 75 /** 76 * Default charset for HTML. 77 */ 78 public static final String HTML_DEFAULT_CHARSET = "utf-8"; 79 80 /** 81 * Argument for command line option "-header". 82 */ 83 public String header = ""; 84 85 /** 86 * Argument for command line option "-packagesheader". 87 */ 88 public String packagesheader = ""; 89 90 /** 91 * Argument for command line option "-footer". 92 */ 93 public String footer = ""; 94 95 /** 96 * Argument for command line option "-doctitle". 97 */ 98 public String doctitle = ""; 99 100 /** 101 * Argument for command line option "-windowtitle". 102 */ 103 public String windowtitle = ""; 104 105 /** 106 * Argument for command line option "-top". 107 */ 108 public String top = ""; 109 110 /** 111 * Argument for command line option "-bottom". 112 */ 113 public String bottom = ""; 114 115 /** 116 * Argument for command line option "-helpfile". 117 */ 118 public String helpfile = ""; 119 120 /** 121 * Argument for command line option "-stylesheetfile". 122 */ 123 public String stylesheetfile = ""; 124 125 /** 126 * Argument for command line option "--add-stylesheet". 127 */ 128 public List<String> additionalStylesheets = new ArrayList<>(); 129 130 /** 131 * Argument for command line option "-Xdocrootparent". 132 */ 133 public String docrootparent = ""; 134 135 /** 136 * True if command line option "-nohelp" is used. Default value is false. 137 */ 138 public boolean nohelp = false; 139 140 /** 141 * True if command line option "-splitindex" is used. Default value is 142 * false. 143 */ 144 public boolean splitindex = false; 145 146 /** 147 * False if command line option "-noindex" is used. Default value is true. 148 */ 149 public boolean createindex = true; 150 151 /** 152 * True if command line option "-use" is used. Default value is false. 153 */ 154 public boolean classuse = false; 155 156 /** 157 * False if command line option "-notree" is used. Default value is true. 158 */ 159 public boolean createtree = true; 160 161 /** 162 * The META charset tag used for cross-platform viewing. 163 */ 164 public String charset = null; 165 166 /** 167 * True if command line option "-nodeprecated" is used. Default value is 168 * false. 169 */ 170 public boolean nodeprecatedlist = false; 171 172 /** 173 * True if command line option "-nonavbar" is used. Default value is false. 174 */ 175 public boolean nonavbar = false; 176 177 /** 178 * True if command line option "-nooverview" is used. Default value is 179 * false 180 */ 181 private boolean nooverview = false; 182 183 /** 184 * The overview path specified with "-overview" flag. 185 */ 186 public String overviewpath = null; 187 188 /** 189 * This is true if option "-overview" is used or option "-overview" is not 190 * used and number of packages is more than one. 191 */ 192 public boolean createoverview = false; 193 194 /** 195 * Collected set of doclint options 196 */ 197 public Map<Doclet.Option, String> doclintOpts = new LinkedHashMap<>(); 198 199 public final Resources resources; 200 201 /** 202 * First file to appear in the right-hand frame in the generated 203 * documentation. 204 */ 205 public DocPath topFile = DocPath.empty; 206 207 /** 208 * The TypeElement for the class file getting generated. 209 */ 210 public TypeElement currentTypeElement = null; // Set this TypeElement in the ClassWriter. 211 212 protected SortedSet<SearchIndexItem> memberSearchIndex; 213 214 protected SortedSet<SearchIndexItem> moduleSearchIndex; 215 216 protected SortedSet<SearchIndexItem> packageSearchIndex; 217 218 protected SortedSet<SearchIndexItem> tagSearchIndex; 219 220 protected SortedSet<SearchIndexItem> typeSearchIndex; 221 222 protected Map<Character,List<SearchIndexItem>> tagSearchIndexMap = new HashMap<>(); 223 224 protected Set<Character> tagSearchIndexKeys; 225 226 public final Contents contents; 227 228 protected final Messages messages; 229 230 public DocPaths docPaths; 231 232 public Map<Element, List<DocPath>> localStylesheetMap = new HashMap<>(); 233 234 /** 235 * Creates an object to hold the configuration for a doclet. 236 * 237 * @param doclet the doclet 238 */ 239 public HtmlConfiguration(Doclet doclet) { 240 super(doclet); 241 resources = new Resources(this, 242 BaseConfiguration.sharedResourceBundleName, 243 "jdk.javadoc.internal.doclets.formats.html.resources.standard"); 244 245 messages = new Messages(this); 246 contents = new Contents(this); 247 248 String v; 249 try { 250 ResourceBundle rb = ResourceBundle.getBundle(versionBundleName, getLocale()); 251 try { 252 v = rb.getString("release"); 253 } catch (MissingResourceException e) { 254 v = defaultDocletVersion; 255 } 256 } catch (MissingResourceException e) { 257 v = defaultDocletVersion; 258 } 259 docletVersion = v; 260 } 261 262 private static final String versionBundleName = "jdk.javadoc.internal.tool.resources.version"; 263 private static final String defaultDocletVersion = System.getProperty("java.version"); 264 public final String docletVersion; 265 public final Date startTime = new Date(); 266 267 @Override 268 public String getDocletVersion() { 269 return docletVersion; 270 } 271 272 @Override 273 public Resources getResources() { 274 return resources; 275 } 276 277 public Contents getContents() { 278 return contents; 279 } 280 281 @Override 282 public Messages getMessages() { 283 return messages; 284 } 285 286 protected boolean validateOptions() { 287 // check shared options 288 if (!generalValidOptions()) { 289 return false; 290 } 291 292 // check if helpfile exists 293 if (!helpfile.isEmpty()) { 294 DocFile help = DocFile.createFileForInput(this, helpfile); 295 if (!help.exists()) { 296 reporter.print(ERROR, resources.getText("doclet.File_not_found", helpfile)); 297 return false; 298 } 299 } 300 // check if stylesheetfile exists 301 if (!stylesheetfile.isEmpty()) { 302 DocFile stylesheet = DocFile.createFileForInput(this, stylesheetfile); 303 if (!stylesheet.exists()) { 304 reporter.print(ERROR, resources.getText("doclet.File_not_found", stylesheetfile)); 305 return false; 306 } 307 } 308 // check if additional stylesheets exists 309 for (String ssheet : additionalStylesheets) { 310 DocFile ssfile = DocFile.createFileForInput(this, ssheet); 311 if (!ssfile.exists()) { 312 reporter.print(ERROR, resources.getText("doclet.File_not_found", ssheet)); 313 return false; 314 } 315 } 316 317 // In a more object-oriented world, this would be done by methods on the Option objects. 318 // Note that -windowtitle silently removes any and all HTML elements, and so does not need 319 // to be handled here. 320 utils.checkJavaScriptInOption("-header", header); 321 utils.checkJavaScriptInOption("-footer", footer); 322 utils.checkJavaScriptInOption("-top", top); 323 utils.checkJavaScriptInOption("-bottom", bottom); 324 utils.checkJavaScriptInOption("-doctitle", doctitle); 325 utils.checkJavaScriptInOption("-packagesheader", packagesheader); 326 327 return true; 328 } 329 330 331 @Override 332 public boolean finishOptionSettings() { 333 if (!validateOptions()) { 334 return false; 335 } 336 if (!getSpecifiedTypeElements().isEmpty()) { 337 Map<String, PackageElement> map = new HashMap<>(); 338 PackageElement pkg; 339 for (TypeElement aClass : getIncludedTypeElements()) { 340 pkg = utils.containingPackage(aClass); 341 if (!map.containsKey(utils.getPackageName(pkg))) { 342 map.put(utils.getPackageName(pkg), pkg); 343 } 344 } 345 } 346 docPaths = new DocPaths(utils); 347 setCreateOverview(); 348 setTopFile(docEnv); 349 workArounds.initDocLint(doclintOpts.values(), tagletManager.getAllTagletNames()); 350 return true; 351 } 352 353 /** 354 * Decide the page which will appear first in the right-hand frame. It will 355 * be "overview-summary.html" if "-overview" option is used or no 356 * "-overview" but the number of packages is more than one. It will be 357 * "package-summary.html" of the respective package if there is only one 358 * package to document. It will be a class page(first in the sorted order), 359 * if only classes are provided on the command line. 360 * 361 * @param docEnv the doclet environment 362 */ 363 protected void setTopFile(DocletEnvironment docEnv) { 364 if (!checkForDeprecation(docEnv)) { 365 return; 366 } 367 if (createoverview) { 368 topFile = DocPaths.INDEX; 369 } else { 370 if (showModules) { 371 topFile = DocPath.empty.resolve(docPaths.moduleSummary(modules.first())); 372 } else if (packages.size() == 1 && packages.first().isUnnamed()) { 373 List<TypeElement> classes = new ArrayList<>(getIncludedTypeElements()); 374 if (!classes.isEmpty()) { 375 TypeElement te = getValidClass(classes); 376 topFile = docPaths.forClass(te); 377 } 378 } else if (!packages.isEmpty()) { 379 topFile = docPaths.forPackage(packages.first()).resolve(DocPaths.PACKAGE_SUMMARY); 380 } 381 } 382 } 383 384 protected TypeElement getValidClass(List<TypeElement> classes) { 385 if (!nodeprecated) { 386 return classes.get(0); 387 } 388 for (TypeElement te : classes) { 389 if (!utils.isDeprecated(te)) { 390 return te; 391 } 392 } 393 return null; 394 } 395 396 protected boolean checkForDeprecation(DocletEnvironment docEnv) { 397 for (TypeElement te : getIncludedTypeElements()) { 398 if (isGeneratedDoc(te)) { 399 return true; 400 } 401 } 402 return false; 403 } 404 405 /** 406 * Generate "overview.html" page if option "-overview" is used or number of 407 * packages is more than one. Sets {@link #createoverview} field to true. 408 */ 409 protected void setCreateOverview() { 410 if (!nooverview) { 411 if (overviewpath != null 412 || modules.size() > 1 413 || (modules.isEmpty() && packages.size() > 1)) { 414 createoverview = true; 415 } 416 } 417 } 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override 423 public WriterFactory getWriterFactory() { 424 return new WriterFactoryImpl(this); 425 } 426 427 /** 428 * {@inheritDoc} 429 */ 430 @Override 431 public Locale getLocale() { 432 if (locale == null) 433 return Locale.getDefault(); 434 return locale; 435 } 436 437 /** 438 * Return the path of the overview file or null if it does not exist. 439 * 440 * @return the path of the overview file or null if it does not exist. 441 */ 442 @Override 443 public JavaFileObject getOverviewPath() { 444 if (overviewpath != null && getFileManager() instanceof StandardJavaFileManager) { 445 StandardJavaFileManager fm = (StandardJavaFileManager) getFileManager(); 446 return fm.getJavaFileObjects(overviewpath).iterator().next(); 447 } 448 return null; 449 } 450 451 public DocPath getMainStylesheet() { 452 if(!stylesheetfile.isEmpty()){ 453 DocFile docFile = DocFile.createFileForInput(this, stylesheetfile); 454 return DocPath.create(docFile.getName()); 455 } 456 return null; 457 } 458 459 public List<DocPath> getAdditionalStylesheets() { 460 return additionalStylesheets.stream() 461 .map(ssf -> DocFile.createFileForInput(this, ssf)).map(file -> DocPath.create(file.getName())) 462 .collect(Collectors.toList()); 463 } 464 465 /** 466 * {@inheritDoc} 467 */ 468 @Override 469 public JavaFileManager getFileManager() { 470 return docEnv.getJavaFileManager(); 471 } 472 473 @Override 474 public boolean showMessage(DocTreePath path, String key) { 475 return (path == null || workArounds.haveDocLint()); 476 } 477 478 @Override 479 public boolean showMessage(Element e, String key) { 480 return (e == null || workArounds.haveDocLint()); 481 } 482 483 protected void buildSearchTagIndex() { 484 for (SearchIndexItem sii : tagSearchIndex) { 485 String tagLabel = sii.getLabel(); 486 Character unicode = (tagLabel.length() == 0) 487 ? '*' 488 : Character.toUpperCase(tagLabel.charAt(0)); 489 List<SearchIndexItem> list = tagSearchIndexMap.get(unicode); 490 if (list == null) { 491 list = new ArrayList<>(); 492 tagSearchIndexMap.put(unicode, list); 493 } 494 list.add(sii); 495 } 496 tagSearchIndexKeys = tagSearchIndexMap.keySet(); 497 } 498 499 @Override 500 public Set<Doclet.Option> getSupportedOptions() { 501 Resources resources = getResources(); 502 Doclet.Option[] options = { 503 new Option(resources, "--add-stylesheet", 1) { 504 @Override 505 public boolean process(String opt, List<String> args) { 506 additionalStylesheets.add(args.get(0)); 507 return true; 508 } 509 }, 510 new Option(resources, "-bottom", 1) { 511 @Override 512 public boolean process(String opt, List<String> args) { 513 bottom = args.get(0); 514 return true; 515 } 516 }, 517 new Option(resources, "-charset", 1) { 518 @Override 519 public boolean process(String opt, List<String> args) { 520 charset = args.get(0); 521 return true; 522 } 523 }, 524 new Option(resources, "-doctitle", 1) { 525 @Override 526 public boolean process(String opt, List<String> args) { 527 doctitle = args.get(0); 528 return true; 529 } 530 }, 531 new Option(resources, "-footer", 1) { 532 @Override 533 public boolean process(String opt, List<String> args) { 534 footer = args.get(0); 535 return true; 536 } 537 }, 538 new Option(resources, "-header", 1) { 539 @Override 540 public boolean process(String opt, List<String> args) { 541 header = args.get(0); 542 return true; 543 } 544 }, 545 new Option(resources, "-helpfile", 1) { 546 @Override 547 public boolean process(String opt, List<String> args) { 548 if (nohelp == true) { 549 reporter.print(ERROR, resources.getText("doclet.Option_conflict", 550 "-helpfile", "-nohelp")); 551 return false; 552 } 553 if (!helpfile.isEmpty()) { 554 reporter.print(ERROR, resources.getText("doclet.Option_reuse", 555 "-helpfile")); 556 return false; 557 } 558 helpfile = args.get(0); 559 return true; 560 } 561 }, 562 new Option(resources, "-html5") { 563 @Override 564 public boolean process(String opt, List<String> args) { 565 return true; 566 } 567 }, 568 new Option(resources, "-nohelp") { 569 @Override 570 public boolean process(String opt, List<String> args) { 571 nohelp = true; 572 if (!helpfile.isEmpty()) { 573 reporter.print(ERROR, resources.getText("doclet.Option_conflict", 574 "-nohelp", "-helpfile")); 575 return false; 576 } 577 return true; 578 } 579 }, 580 new Option(resources, "-nodeprecatedlist") { 581 @Override 582 public boolean process(String opt, List<String> args) { 583 nodeprecatedlist = true; 584 return true; 585 } 586 }, 587 new Option(resources, "-noindex") { 588 @Override 589 public boolean process(String opt, List<String> args) { 590 createindex = false; 591 if (splitindex == true) { 592 reporter.print(ERROR, resources.getText("doclet.Option_conflict", 593 "-noindex", "-splitindex")); 594 return false; 595 } 596 return true; 597 } 598 }, 599 new Option(resources, "-nonavbar") { 600 @Override 601 public boolean process(String opt, List<String> args) { 602 nonavbar = true; 603 return true; 604 } 605 }, 606 new Hidden(resources, "-nooverview") { 607 @Override 608 public boolean process(String opt, List<String> args) { 609 nooverview = true; 610 if (overviewpath != null) { 611 reporter.print(ERROR, resources.getText("doclet.Option_conflict", 612 "-nooverview", "-overview")); 613 return false; 614 } 615 return true; 616 } 617 }, 618 new Option(resources, "-notree") { 619 @Override 620 public boolean process(String opt, List<String> args) { 621 createtree = false; 622 return true; 623 } 624 }, 625 new Option(resources, "-overview", 1) { 626 @Override 627 public boolean process(String opt, List<String> args) { 628 overviewpath = args.get(0); 629 if (nooverview == true) { 630 reporter.print(ERROR, resources.getText("doclet.Option_conflict", 631 "-overview", "-nooverview")); 632 return false; 633 } 634 return true; 635 } 636 }, 637 new Hidden(resources, "-packagesheader", 1) { 638 @Override 639 public boolean process(String opt, List<String> args) { 640 packagesheader = args.get(0); 641 return true; 642 } 643 }, 644 new Option(resources, "-splitindex") { 645 @Override 646 public boolean process(String opt, List<String> args) { 647 splitindex = true; 648 if (createindex == false) { 649 reporter.print(ERROR, resources.getText("doclet.Option_conflict", 650 "-splitindex", "-noindex")); 651 return false; 652 } 653 return true; 654 } 655 }, 656 new Option(resources, "--main-stylesheet -stylesheetfile", 1) { 657 @Override 658 public boolean process(String opt, List<String> args) { 659 stylesheetfile = args.get(0); 660 return true; 661 } 662 }, 663 new Option(resources, "-top", 1) { 664 @Override 665 public boolean process(String opt, List<String> args) { 666 top = args.get(0); 667 return true; 668 } 669 }, 670 new Option(resources, "-use") { 671 @Override 672 public boolean process(String opt, List<String> args) { 673 classuse = true; 674 return true; 675 } 676 }, 677 new Option(resources, "-windowtitle", 1) { 678 @Override 679 public boolean process(String opt, List<String> args) { 680 windowtitle = args.get(0).replaceAll("\\<.*?>", ""); 681 return true; 682 } 683 }, 684 new XOption(resources, "-Xdoclint") { 685 @Override 686 public boolean process(String opt, List<String> args) { 687 doclintOpts.put(this, DocLint.XMSGS_OPTION); 688 return true; 689 } 690 }, 691 new XOption(resources, "-Xdocrootparent", 1) { 692 @Override 693 public boolean process(String opt, List<String> args) { 694 docrootparent = args.get(0); 695 try { 696 URL ignored = new URL(docrootparent); 697 } catch (MalformedURLException e) { 698 reporter.print(ERROR, resources.getText("doclet.MalformedURL", docrootparent)); 699 return false; 700 } 701 return true; 702 } 703 }, 704 new XOption(resources, "doclet.usage.xdoclint-extended", "-Xdoclint:", 0) { 705 @Override 706 public boolean process(String opt, List<String> args) { 707 String dopt = opt.replace("-Xdoclint:", DocLint.XMSGS_CUSTOM_PREFIX); 708 doclintOpts.put(this, dopt); 709 if (dopt.contains("/")) { 710 reporter.print(ERROR, resources.getText("doclet.Option_doclint_no_qualifiers")); 711 return false; 712 } 713 if (!DocLint.isValidOption(dopt)) { 714 reporter.print(ERROR, resources.getText("doclet.Option_doclint_invalid_arg")); 715 return false; 716 } 717 return true; 718 } 719 }, 720 new XOption(resources, "doclet.usage.xdoclint-package", "-Xdoclint/package:", 0) { 721 @Override 722 public boolean process(String opt, List<String> args) { 723 String dopt = opt.replace("-Xdoclint/package:", DocLint.XCHECK_PACKAGE); 724 doclintOpts.put(this, dopt); 725 if (!DocLint.isValidOption(dopt)) { 726 reporter.print(ERROR, resources.getText("doclet.Option_doclint_package_invalid_arg")); 727 return false; 728 } 729 return true; 730 } 731 }, 732 new XOption(resources, "--no-frames") { 733 @Override 734 public boolean process(String opt, List<String> args) { 735 reporter.print(WARNING, resources.getText("doclet.NoFrames_specified")); 736 return true; 737 } 738 } 739 }; 740 Set<Doclet.Option> oset = new TreeSet<>(); 741 oset.addAll(Arrays.asList(options)); 742 oset.addAll(super.getSupportedOptions()); 743 return oset; 744 } 745 746 @Override 747 protected boolean finishOptionSettings0() throws DocletException { 748 if (docencoding == null) { 749 if (charset == null) { 750 docencoding = charset = (encoding == null) ? HTML_DEFAULT_CHARSET : encoding; 751 } else { 752 docencoding = charset; 753 } 754 } else { 755 if (charset == null) { 756 charset = docencoding; 757 } else if (!charset.equals(docencoding)) { 758 reporter.print(ERROR, resources.getText("doclet.Option_conflict", "-charset", "-docencoding")); 759 return false; 760 } 761 } 762 return super.finishOptionSettings0(); 763 } 764 765 @Override 766 protected void initConfiguration(DocletEnvironment docEnv) { 767 super.initConfiguration(docEnv); 768 memberSearchIndex = new TreeSet<>(utils.makeGenericSearchIndexComparator()); 769 moduleSearchIndex = new TreeSet<>(utils.makeGenericSearchIndexComparator()); 770 packageSearchIndex = new TreeSet<>(utils.makeGenericSearchIndexComparator()); 771 tagSearchIndex = new TreeSet<>(utils.makeGenericSearchIndexComparator()); 772 typeSearchIndex = new TreeSet<>(utils.makeTypeSearchIndexComparator()); 773 } 774 }