1 /* 2 * Copyright (c) 1997, 2019, 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.toolkit; 27 28 import java.io.*; 29 import java.lang.ref.*; 30 import java.util.*; 31 32 import javax.lang.model.element.Element; 33 import javax.lang.model.element.ModuleElement; 34 import javax.lang.model.element.PackageElement; 35 import javax.lang.model.element.TypeElement; 36 import javax.lang.model.util.SimpleElementVisitor14; 37 import javax.tools.JavaFileManager; 38 import javax.tools.JavaFileObject; 39 40 import com.sun.source.util.DocTreePath; 41 import com.sun.tools.javac.util.DefinedBy; 42 import com.sun.tools.javac.util.DefinedBy.Api; 43 import jdk.javadoc.doclet.Doclet; 44 import jdk.javadoc.doclet.DocletEnvironment; 45 import jdk.javadoc.doclet.Reporter; 46 import jdk.javadoc.doclet.StandardDoclet; 47 import jdk.javadoc.doclet.Taglet; 48 import jdk.javadoc.internal.doclets.formats.html.HtmlDoclet; 49 import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory; 50 import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; 51 import jdk.javadoc.internal.doclets.toolkit.util.DocFile; 52 import jdk.javadoc.internal.doclets.toolkit.util.DocFileFactory; 53 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException; 54 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; 55 import jdk.javadoc.internal.doclets.toolkit.util.Extern; 56 import jdk.javadoc.internal.doclets.toolkit.util.Group; 57 import jdk.javadoc.internal.doclets.toolkit.util.MetaKeywords; 58 import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException; 59 import jdk.javadoc.internal.doclets.toolkit.util.TypeElementCatalog; 60 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 61 import jdk.javadoc.internal.doclets.toolkit.util.Utils.Pair; 62 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberCache; 63 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; 64 65 import static javax.tools.Diagnostic.Kind.*; 66 67 /** 68 * Configure the output based on the options. Doclets should sub-class 69 * BaseConfiguration, to configure and add their own options. This class contains 70 * all user options which are supported by the standard doclet. 71 * <p> 72 * <p><b>This is NOT part of any supported API. 73 * If you write code that depends on this, you do so at your own risk. 74 * This code and its internal interfaces are subject to change or 75 * deletion without notice.</b> 76 */ 77 public abstract class BaseConfiguration { 78 /** 79 * The doclet that created this configuration. 80 */ 81 public final Doclet doclet; 82 83 /** 84 * The factory for builders. 85 */ 86 protected BuilderFactory builderFactory; 87 88 /** 89 * The taglet manager. 90 */ 91 public TagletManager tagletManager; 92 93 /** 94 * The path to the builder XML input file. 95 */ 96 public String builderXMLPath; 97 98 /** 99 * The default path to the builder XML. 100 */ 101 public static final String DEFAULT_BUILDER_XML = "resources/doclet.xml"; 102 103 /** 104 * The path to Taglets 105 */ 106 public String tagletpath = null; 107 108 /** 109 * This is true if option "-serialwarn" is used. Default value is false to 110 * suppress excessive warnings about serial tag. 111 */ 112 public boolean serialwarn = false; 113 114 /** 115 * The specified amount of space between tab stops. 116 */ 117 public int sourcetab; 118 119 public String tabSpaces; 120 121 /** 122 * True if we should generate browsable sources. 123 */ 124 public boolean linksource = false; 125 126 /** 127 * True if command line option "-nosince" is used. Default value is 128 * false. 129 */ 130 public boolean nosince = false; 131 132 /** 133 * True if we should recursively copy the doc-file subdirectories 134 */ 135 public boolean copydocfilesubdirs = false; 136 137 /** 138 * Maintain backward compatibility with previous javadoc version 139 */ 140 public boolean backwardCompatibility = true; 141 142 /** 143 * True if user wants to add member names as meta keywords. 144 * Set to false because meta keywords are ignored in general 145 * by most Internet search engines. 146 */ 147 public boolean keywords = false; 148 149 /** 150 * The meta tag keywords instance. 151 */ 152 public final MetaKeywords metakeywords; 153 154 /** 155 * The set of doc-file subdirectories to exclude 156 */ 157 protected Set<String> excludedDocFileDirs; 158 159 /** 160 * The set of qualifiers to exclude 161 */ 162 protected Set<String> excludedQualifiers; 163 164 /** 165 * The doclet environment. 166 */ 167 public DocletEnvironment docEnv; 168 169 /** 170 * An utility class for commonly used helpers 171 */ 172 public Utils utils; 173 174 /** 175 * All the temporary accessors to javac internals. 176 */ 177 public WorkArounds workArounds; 178 179 /** 180 * Destination directory name, in which doclet will generate the entire 181 * documentation. Default is current directory. 182 */ 183 public String destDirName = ""; 184 185 /** 186 * Destination directory name, in which doclet will copy the doc-files to. 187 */ 188 public String docFileDestDirName = ""; 189 190 /** 191 * Encoding for this document. Default is default encoding for this 192 * platform. 193 */ 194 public String docencoding = null; 195 196 /** 197 * True if user wants to suppress descriptions and tags. 198 */ 199 public boolean nocomment = false; 200 201 /** 202 * Encoding for this document. Default is default encoding for this 203 * platform. 204 */ 205 public String encoding = null; 206 207 /** 208 * Generate author specific information for all the classes if @author 209 * tag is used in the doc comment and if -author option is used. 210 * <code>showauthor</code> is set to true if -author option is used. 211 * Default is don't show author information. 212 */ 213 public boolean showauthor = false; 214 215 /** 216 * Generate documentation for JavaFX getters and setters automatically 217 * by copying it from the appropriate property definition. 218 */ 219 public boolean javafx = false; 220 221 /** 222 * Generate version specific information for the all the classes 223 * if @version tag is used in the doc comment and if -version option is 224 * used. <code>showversion</code> is set to true if -version option is 225 * used.Default is don't show version information. 226 */ 227 public boolean showversion = false; 228 229 /** 230 * Allow JavaScript in doc comments. 231 */ 232 private boolean allowScriptInComments = false; 233 234 /** 235 * Sourcepath from where to read the source files. Default is classpath. 236 */ 237 public String sourcepath = ""; 238 239 /** 240 * Generate modules documentation if more than one module is present. 241 */ 242 public boolean showModules = false; 243 244 /** 245 * Don't generate deprecated API information at all, if -nodeprecated 246 * option is used. <code>nodeprecated</code> is set to true if 247 * -nodeprecated option is used. Default is generate deprecated API 248 * information. 249 */ 250 public boolean nodeprecated = false; 251 252 /** 253 * The catalog of classes specified on the command-line 254 */ 255 public TypeElementCatalog typeElementCatalog; 256 257 /** 258 * True if user wants to suppress time stamp in output. 259 * Default is false. 260 */ 261 public boolean notimestamp = false; 262 263 /** 264 * The package grouping instance. 265 */ 266 public final Group group = new Group(this); 267 268 /** 269 * The tracker of external package links. 270 */ 271 public Extern extern; 272 273 public Reporter reporter; 274 275 public Locale locale; 276 277 /** 278 * Suppress all messages 279 */ 280 public boolean quiet = false; 281 282 /** 283 * Specifies whether those methods that override a super-type's method 284 * with no changes to the API contract should be summarized in the 285 * footnote section. 286 */ 287 public boolean summarizeOverriddenMethods = false; 288 289 // A list containing urls 290 private final List<String> linkList = new ArrayList<>(); 291 292 // A list of pairs containing urls and package list 293 private final List<Pair<String, String>> linkOfflineList = new ArrayList<>(); 294 295 public boolean dumpOnError = false; 296 297 private List<Pair<String, String>> groupPairs; 298 299 public abstract Messages getMessages(); 300 301 public abstract Resources getResources(); 302 303 /** 304 * Returns a string identifying the version of the doclet. 305 * 306 * @return a version string 307 */ 308 public abstract String getDocletVersion(); 309 310 /** 311 * This method should be defined in all those doclets (configurations), 312 * which want to derive themselves from this BaseConfiguration. This method 313 * can be used to finish up the options setup. 314 * 315 * @return true if successful and false otherwise 316 */ 317 318 public abstract boolean finishOptionSettings(); 319 320 public CommentUtils cmtUtils; 321 322 /** 323 * A sorted set of included packages. 324 */ 325 public SortedSet<PackageElement> packages = null; 326 327 public OverviewElement overviewElement; 328 329 public DocFileFactory docFileFactory; 330 331 /** 332 * A sorted map, giving the (specified|included|other) packages for each module. 333 */ 334 public SortedMap<ModuleElement, Set<PackageElement>> modulePackages; 335 336 /** 337 * The list of known modules, that should be documented. 338 */ 339 public SortedSet<ModuleElement> modules; 340 341 protected static final String sharedResourceBundleName = 342 "jdk.javadoc.internal.doclets.toolkit.resources.doclets"; 343 344 /** 345 * Primarily used to disable strict checks in the regression 346 * tests allowing those tests to be executed successfully, for 347 * instance, with OpenJDK builds which may not contain FX libraries. 348 */ 349 public boolean disableJavaFxStrictChecks = false; 350 351 /** 352 * Show taglets (internal debug switch) 353 */ 354 public boolean showTaglets = false; 355 356 VisibleMemberCache visibleMemberCache = null; 357 358 public PropertyUtils propertyUtils = null; 359 360 /** 361 * Constructs the configurations needed by the doclet. 362 * 363 * @apiNote 364 * The {@code doclet} parameter is used when {@link Taglet#init(DocletEnvironment, Doclet) 365 * initializing tags}. 366 * Some doclets (such as the {@link StandardDoclet), may delegate to another 367 * (such as the {@link HtmlDoclet}). In such cases, the primary doclet (i.e 368 * {@code StandardDoclet}) should be provided here, and not any internal 369 * class like {@code HtmlDoclet}. 370 * 371 * @param doclet the doclet for this run of javadoc 372 */ 373 public BaseConfiguration(Doclet doclet) { 374 this.doclet = doclet; 375 excludedDocFileDirs = new HashSet<>(); 376 excludedQualifiers = new HashSet<>(); 377 setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH); 378 metakeywords = new MetaKeywords(this); 379 groupPairs = new ArrayList<>(0); 380 } 381 382 private boolean initialized = false; 383 384 protected void initConfiguration(DocletEnvironment docEnv) { 385 if (initialized) { 386 throw new IllegalStateException("configuration previously initialized"); 387 } 388 initialized = true; 389 this.docEnv = docEnv; 390 // Utils needs docEnv, safe to init now. 391 utils = new Utils(this); 392 393 if (!javafx) { 394 javafx = isJavaFXMode(); 395 } 396 397 // Once docEnv and Utils have been initialized, others should be safe. 398 cmtUtils = new CommentUtils(this); 399 workArounds = new WorkArounds(this); 400 visibleMemberCache = new VisibleMemberCache(this); 401 propertyUtils = new PropertyUtils(this); 402 403 Splitter specifiedSplitter = new Splitter(docEnv, false); 404 specifiedModuleElements = Collections.unmodifiableSet(specifiedSplitter.mset); 405 specifiedPackageElements = Collections.unmodifiableSet(specifiedSplitter.pset); 406 specifiedTypeElements = Collections.unmodifiableSet(specifiedSplitter.tset); 407 408 Splitter includedSplitter = new Splitter(docEnv, true); 409 includedModuleElements = Collections.unmodifiableSet(includedSplitter.mset); 410 includedPackageElements = Collections.unmodifiableSet(includedSplitter.pset); 411 includedTypeElements = Collections.unmodifiableSet(includedSplitter.tset); 412 } 413 414 /** 415 * Return the builder factory for this doclet. 416 * 417 * @return the builder factory for this doclet. 418 */ 419 public BuilderFactory getBuilderFactory() { 420 if (builderFactory == null) { 421 builderFactory = new BuilderFactory(this); 422 } 423 return builderFactory; 424 } 425 426 public Reporter getReporter() { 427 return this.reporter; 428 } 429 430 private Set<ModuleElement> specifiedModuleElements; 431 432 public Set<ModuleElement> getSpecifiedModuleElements() { 433 return specifiedModuleElements; 434 } 435 436 private Set<PackageElement> specifiedPackageElements; 437 438 public Set<PackageElement> getSpecifiedPackageElements() { 439 return specifiedPackageElements; 440 } 441 442 private Set<TypeElement> specifiedTypeElements; 443 444 public Set<TypeElement> getSpecifiedTypeElements() { 445 return specifiedTypeElements; 446 } 447 448 private Set<ModuleElement> includedModuleElements; 449 450 public Set<ModuleElement> getIncludedModuleElements() { 451 return includedModuleElements; 452 } 453 454 private Set<PackageElement> includedPackageElements; 455 456 public Set<PackageElement> getIncludedPackageElements() { 457 return includedPackageElements; 458 } 459 460 private Set<TypeElement> includedTypeElements; 461 462 public Set<TypeElement> getIncludedTypeElements() { 463 return includedTypeElements; 464 } 465 466 private void initModules() { 467 // Build the modules structure used by the doclet 468 modules = new TreeSet<>(utils.makeModuleComparator()); 469 modules.addAll(getSpecifiedModuleElements()); 470 471 modulePackages = new TreeMap<>(utils.makeModuleComparator()); 472 for (PackageElement p : packages) { 473 ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); 474 if (mdle != null && !mdle.isUnnamed()) { 475 Set<PackageElement> s = modulePackages 476 .computeIfAbsent(mdle, m -> new TreeSet<>(utils.makePackageComparator())); 477 s.add(p); 478 } 479 } 480 481 for (PackageElement p : getIncludedPackageElements()) { 482 ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); 483 if (mdle != null && !mdle.isUnnamed()) { 484 Set<PackageElement> s = modulePackages 485 .computeIfAbsent(mdle, m -> new TreeSet<>(utils.makePackageComparator())); 486 s.add(p); 487 } 488 } 489 490 // add entries for modules which may not have exported packages 491 modules.forEach((ModuleElement mdle) -> { 492 modulePackages.computeIfAbsent(mdle, m -> Collections.emptySet()); 493 }); 494 495 modules.addAll(modulePackages.keySet()); 496 showModules = !modules.isEmpty(); 497 for (Set<PackageElement> pkgs : modulePackages.values()) { 498 packages.addAll(pkgs); 499 } 500 } 501 502 private void initPackages() { 503 packages = new TreeSet<>(utils.makePackageComparator()); 504 // add all the included packages 505 packages.addAll(includedPackageElements); 506 } 507 508 public Set<Doclet.Option> getSupportedOptions() { 509 Resources resources = getResources(); 510 Doclet.Option[] options = { 511 new Option(resources, "-author") { 512 @Override 513 public boolean process(String opt, List<String> args) { 514 showauthor = true; 515 return true; 516 } 517 }, 518 new Option(resources, "-d", 1) { 519 @Override 520 public boolean process(String opt, List<String> args) { 521 destDirName = addTrailingFileSep(args.get(0)); 522 return true; 523 } 524 }, 525 new Option(resources, "-docencoding", 1) { 526 @Override 527 public boolean process(String opt, List<String> args) { 528 docencoding = args.get(0); 529 return true; 530 } 531 }, 532 new Option(resources, "-docfilessubdirs") { 533 @Override 534 public boolean process(String opt, List<String> args) { 535 copydocfilesubdirs = true; 536 return true; 537 } 538 }, 539 new Hidden(resources, "-encoding", 1) { 540 @Override 541 public boolean process(String opt, List<String> args) { 542 encoding = args.get(0); 543 return true; 544 } 545 }, 546 new Option(resources, "-excludedocfilessubdir", 1) { 547 @Override 548 public boolean process(String opt, List<String> args) { 549 addToSet(excludedDocFileDirs, args.get(0)); 550 return true; 551 } 552 }, 553 new Option(resources, "-group", 2) { 554 @Override 555 public boolean process(String opt, List<String> args) { 556 groupPairs.add(new Pair<>(args.get(0), args.get(1))); 557 return true; 558 } 559 }, 560 new Option(resources, "--javafx -javafx") { 561 @Override 562 public boolean process(String opt, List<String> args) { 563 javafx = true; 564 return true; 565 } 566 }, 567 new Option(resources, "-keywords") { 568 @Override 569 public boolean process(String opt, List<String> args) { 570 keywords = true; 571 return true; 572 } 573 }, 574 new Option(resources, "-link", 1) { 575 @Override 576 public boolean process(String opt, List<String> args) { 577 linkList.add(args.get(0)); 578 return true; 579 } 580 }, 581 new Option(resources, "-linksource") { 582 @Override 583 public boolean process(String opt, List<String> args) { 584 linksource = true; 585 return true; 586 } 587 }, 588 new Option(resources, "-linkoffline", 2) { 589 @Override 590 public boolean process(String opt, List<String> args) { 591 linkOfflineList.add(new Pair<>(args.get(0), args.get(1))); 592 return true; 593 } 594 }, 595 new Option(resources, "-nocomment") { 596 @Override 597 public boolean process(String opt, List<String> args) { 598 nocomment = true; 599 return true; 600 } 601 }, 602 new Option(resources, "-nodeprecated") { 603 @Override 604 public boolean process(String opt, List<String> args) { 605 nodeprecated = true; 606 return true; 607 } 608 }, 609 new Option(resources, "-nosince") { 610 @Override 611 public boolean process(String opt, List<String> args) { 612 nosince = true; 613 return true; 614 } 615 }, 616 new Option(resources, "-notimestamp") { 617 @Override 618 public boolean process(String opt, List<String> args) { 619 notimestamp = true; 620 return true; 621 } 622 }, 623 new Option(resources, "-noqualifier", 1) { 624 @Override 625 public boolean process(String opt, List<String> args) { 626 addToSet(excludedQualifiers, args.get(0)); 627 return true; 628 } 629 }, 630 new Option(resources, "--override-methods", 1) { 631 @Override 632 public boolean process(String opt, List<String> args) { 633 String o = args.get(0); 634 switch (o) { 635 case "summary": 636 summarizeOverriddenMethods = true; 637 break; 638 case "detail": 639 summarizeOverriddenMethods = false; 640 break; 641 default: 642 reporter.print(ERROR, 643 getResources().getText("doclet.Option_invalid",o, "--override-methods")); 644 return false; 645 } 646 return true; 647 } 648 }, 649 new Hidden(resources, "-quiet") { 650 @Override 651 public boolean process(String opt, List<String> args) { 652 quiet = true; 653 return true; 654 } 655 }, 656 new Option(resources, "-serialwarn") { 657 @Override 658 public boolean process(String opt, List<String> args) { 659 serialwarn = true; 660 return true; 661 } 662 }, 663 new Option(resources, "-sourcetab", 1) { 664 @Override 665 public boolean process(String opt, List<String> args) { 666 linksource = true; 667 try { 668 setTabWidth(Integer.parseInt(args.get(0))); 669 } catch (NumberFormatException e) { 670 //Set to -1 so that warning will be printed 671 //to indicate what is valid argument. 672 sourcetab = -1; 673 } 674 if (sourcetab <= 0) { 675 getMessages().warning("doclet.sourcetab_warning"); 676 setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH); 677 } 678 return true; 679 } 680 }, 681 new Option(resources, "-tag", 1) { 682 @Override 683 public boolean process(String opt, List<String> args) { 684 ArrayList<String> list = new ArrayList<>(); 685 list.add(opt); 686 list.add(args.get(0)); 687 customTagStrs.add(list); 688 return true; 689 } 690 }, 691 new Option(resources, "-taglet", 1) { 692 @Override 693 public boolean process(String opt, List<String> args) { 694 ArrayList<String> list = new ArrayList<>(); 695 list.add(opt); 696 list.add(args.get(0)); 697 customTagStrs.add(list); 698 return true; 699 } 700 }, 701 new Option(resources, "-tagletpath", 1) { 702 @Override 703 public boolean process(String opt, List<String> args) { 704 tagletpath = args.get(0); 705 return true; 706 } 707 }, 708 new Option(resources, "-version") { 709 @Override 710 public boolean process(String opt, List<String> args) { 711 showversion = true; 712 return true; 713 } 714 }, 715 new Hidden(resources, "--dump-on-error") { 716 @Override 717 public boolean process(String opt, List<String> args) { 718 dumpOnError = true; 719 return true; 720 } 721 }, 722 new Option(resources, "--allow-script-in-comments") { 723 @Override 724 public boolean process(String opt, List<String> args) { 725 allowScriptInComments = true; 726 return true; 727 } 728 }, 729 new Hidden(resources, "--disable-javafx-strict-checks") { 730 @Override 731 public boolean process(String opt, List<String> args) { 732 disableJavaFxStrictChecks = true; 733 return true; 734 } 735 }, 736 new Hidden(resources, "--show-taglets") { 737 @Override 738 public boolean process(String opt, List<String> args) { 739 showTaglets = true; 740 return true; 741 } 742 } 743 }; 744 Set<Doclet.Option> set = new TreeSet<>(); 745 set.addAll(Arrays.asList(options)); 746 return set; 747 } 748 749 final LinkedHashSet<List<String>> customTagStrs = new LinkedHashSet<>(); 750 751 /* 752 * when this is called all the option have been set, this method, 753 * initializes certain components before anything else is started. 754 */ 755 protected boolean finishOptionSettings0() throws DocletException { 756 extern = new Extern(this); 757 initDestDirectory(); 758 for (String link : linkList) { 759 extern.link(link, reporter); 760 } 761 for (Pair<String, String> linkOfflinePair : linkOfflineList) { 762 extern.link(linkOfflinePair.first, linkOfflinePair.second, reporter); 763 } 764 typeElementCatalog = new TypeElementCatalog(includedTypeElements, this); 765 initTagletManager(customTagStrs); 766 groupPairs.stream().forEach((grp) -> { 767 if (showModules) { 768 group.checkModuleGroups(grp.first, grp.second); 769 } else { 770 group.checkPackageGroups(grp.first, grp.second); 771 } 772 }); 773 overviewElement = new OverviewElement(workArounds.getUnnamedPackage(), getOverviewPath()); 774 return true; 775 } 776 777 /** 778 * Set the command line options supported by this configuration. 779 * 780 * @return true if the options are set successfully 781 * @throws DocletException if there is a problem while setting the options 782 */ 783 public boolean setOptions() throws DocletException { 784 initPackages(); 785 initModules(); 786 if (!finishOptionSettings0() || !finishOptionSettings()) 787 return false; 788 789 return true; 790 } 791 792 private void initDestDirectory() throws DocletException { 793 if (!destDirName.isEmpty()) { 794 Resources resources = getResources(); 795 DocFile destDir = DocFile.createFileForDirectory(this, destDirName); 796 if (!destDir.exists()) { 797 //Create the output directory (in case it doesn't exist yet) 798 reporter.print(NOTE, resources.getText("doclet.dest_dir_create", destDirName)); 799 destDir.mkdirs(); 800 } else if (!destDir.isDirectory()) { 801 throw new SimpleDocletException(resources.getText( 802 "doclet.destination_directory_not_directory_0", 803 destDir.getPath())); 804 } else if (!destDir.canWrite()) { 805 throw new SimpleDocletException(resources.getText( 806 "doclet.destination_directory_not_writable_0", 807 destDir.getPath())); 808 } 809 } 810 DocFileFactory.getFactory(this).setDestDir(destDirName); 811 } 812 813 /** 814 * Initialize the taglet manager. The strings to initialize the simple custom tags should 815 * be in the following format: "[tag name]:[location str]:[heading]". 816 * 817 * @param customTagStrs the set two dimensional arrays of strings. These arrays contain 818 * either -tag or -taglet arguments. 819 */ 820 private void initTagletManager(Set<List<String>> customTagStrs) { 821 tagletManager = tagletManager == null ? 822 new TagletManager(nosince, showversion, showauthor, javafx, this) : 823 tagletManager; 824 JavaFileManager fileManager = getFileManager(); 825 Messages messages = getMessages(); 826 try { 827 tagletManager.initTagletPath(fileManager, tagletpath); 828 tagletManager.loadTaglets(fileManager); 829 830 for (List<String> args : customTagStrs) { 831 if (args.get(0).equals("-taglet")) { 832 tagletManager.addCustomTag(args.get(1), fileManager); 833 continue; 834 } 835 List<String> tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3); 836 switch (tokens.size()) { 837 case 1: 838 String tagName = args.get(1); 839 if (tagletManager.isKnownCustomTag(tagName)) { 840 //reorder a standard tag 841 tagletManager.addNewSimpleCustomTag(tagName, null, ""); 842 } else { 843 //Create a simple tag with the heading that has the same name as the tag. 844 StringBuilder heading = new StringBuilder(tagName + ":"); 845 heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0))); 846 tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a"); 847 } 848 break; 849 850 case 2: 851 //Add simple taglet without heading, probably to excluding it in the output. 852 tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), ""); 853 break; 854 855 case 3: 856 tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1)); 857 break; 858 859 default: 860 messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1)); 861 } 862 } 863 } catch (IOException e) { 864 messages.error("doclet.taglet_could_not_set_location", e.toString()); 865 } 866 } 867 868 /** 869 * Given a string, return an array of tokens. The separator can be escaped 870 * with the '\' character. The '\' character may also be escaped by the 871 * '\' character. 872 * 873 * @param s the string to tokenize. 874 * @param separator the separator char. 875 * @param maxTokens the maximum number of tokens returned. If the 876 * max is reached, the remaining part of s is appended 877 * to the end of the last token. 878 * @return an array of tokens. 879 */ 880 private List<String> tokenize(String s, char separator, int maxTokens) { 881 List<String> tokens = new ArrayList<>(); 882 StringBuilder token = new StringBuilder(); 883 boolean prevIsEscapeChar = false; 884 for (int i = 0; i < s.length(); i += Character.charCount(i)) { 885 int currentChar = s.codePointAt(i); 886 if (prevIsEscapeChar) { 887 // Case 1: escaped character 888 token.appendCodePoint(currentChar); 889 prevIsEscapeChar = false; 890 } else if (currentChar == separator && tokens.size() < maxTokens - 1) { 891 // Case 2: separator 892 tokens.add(token.toString()); 893 token = new StringBuilder(); 894 } else if (currentChar == '\\') { 895 // Case 3: escape character 896 prevIsEscapeChar = true; 897 } else { 898 // Case 4: regular character 899 token.appendCodePoint(currentChar); 900 } 901 } 902 if (token.length() > 0) { 903 tokens.add(token.toString()); 904 } 905 return tokens; 906 } 907 908 private void addToSet(Set<String> s, String str) { 909 StringTokenizer st = new StringTokenizer(str, ":"); 910 String current; 911 while (st.hasMoreTokens()) { 912 current = st.nextToken(); 913 s.add(current); 914 } 915 } 916 917 /** 918 * Add a trailing file separator, if not found. Remove superfluous 919 * file separators if any. Preserve the front double file separator for 920 * UNC paths. 921 * 922 * @param path Path under consideration. 923 * @return String Properly constructed path string. 924 */ 925 public static String addTrailingFileSep(String path) { 926 String fs = System.getProperty("file.separator"); 927 String dblfs = fs + fs; 928 int indexDblfs; 929 while ((indexDblfs = path.indexOf(dblfs, 1)) >= 0) { 930 path = path.substring(0, indexDblfs) + 931 path.substring(indexDblfs + fs.length()); 932 } 933 if (!path.endsWith(fs)) 934 path += fs; 935 return path; 936 } 937 938 /** 939 * This checks for the validity of the options used by the user. 940 * As of this writing, this checks only docencoding. 941 * 942 * @return true if all the options are valid. 943 */ 944 public boolean generalValidOptions() { 945 if (docencoding != null) { 946 if (!checkOutputFileEncoding(docencoding)) { 947 return false; 948 } 949 } 950 if (docencoding == null && (encoding != null && !encoding.isEmpty())) { 951 if (!checkOutputFileEncoding(encoding)) { 952 return false; 953 } 954 } 955 return true; 956 } 957 958 /** 959 * Check the validity of the given Source or Output File encoding on this 960 * platform. 961 * 962 * @param docencoding output file encoding. 963 */ 964 private boolean checkOutputFileEncoding(String docencoding) { 965 OutputStream ost = new ByteArrayOutputStream(); 966 OutputStreamWriter osw = null; 967 try { 968 osw = new OutputStreamWriter(ost, docencoding); 969 } catch (UnsupportedEncodingException exc) { 970 reporter.print(ERROR, getResources().getText("doclet.Encoding_not_supported", docencoding)); 971 return false; 972 } finally { 973 try { 974 if (osw != null) { 975 osw.close(); 976 } 977 } catch (IOException exc) { 978 } 979 } 980 return true; 981 } 982 983 /** 984 * Return true if the given doc-file subdirectory should be excluded and 985 * false otherwise. 986 * 987 * @param docfilesubdir the doc-files subdirectory to check. 988 * @return true if the directory is excluded. 989 */ 990 public boolean shouldExcludeDocFileDir(String docfilesubdir) { 991 return excludedDocFileDirs.contains(docfilesubdir); 992 } 993 994 /** 995 * Return true if the given qualifier should be excluded and false otherwise. 996 * 997 * @param qualifier the qualifier to check. 998 * @return true if the qualifier should be excluded 999 */ 1000 public boolean shouldExcludeQualifier(String qualifier) { 1001 if (excludedQualifiers.contains("all") || 1002 excludedQualifiers.contains(qualifier) || 1003 excludedQualifiers.contains(qualifier + ".*")) { 1004 return true; 1005 } else { 1006 int index = -1; 1007 while ((index = qualifier.indexOf(".", index + 1)) != -1) { 1008 if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) { 1009 return true; 1010 } 1011 } 1012 return false; 1013 } 1014 } 1015 1016 /** 1017 * Return the qualified name of the Element if its qualifier is not excluded. 1018 * Otherwise return the unqualified Element name. 1019 * 1020 * @param te the TypeElement to check. 1021 * @return the class name 1022 */ 1023 public String getClassName(TypeElement te) { 1024 PackageElement pkg = utils.containingPackage(te); 1025 return shouldExcludeQualifier(utils.getPackageName(pkg)) 1026 ? utils.getSimpleName(te) 1027 : utils.getFullyQualifiedName(te); 1028 } 1029 1030 /** 1031 * Return true if the TypeElement element is getting documented, depending upon 1032 * -nodeprecated option and the deprecation information. Return true if 1033 * -nodeprecated is not used. Return false if -nodeprecated is used and if 1034 * either TypeElement element is deprecated or the containing package is deprecated. 1035 * 1036 * @param te the TypeElement for which the page generation is checked 1037 * @return true if it is a generated doc. 1038 */ 1039 public boolean isGeneratedDoc(TypeElement te) { 1040 if (!nodeprecated) { 1041 return true; 1042 } 1043 return !(utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te))); 1044 } 1045 1046 /** 1047 * Return the doclet specific instance of a writer factory. 1048 * 1049 * @return the {@link WriterFactory} for the doclet. 1050 */ 1051 public abstract WriterFactory getWriterFactory(); 1052 1053 /** 1054 * Return the input stream to the builder XML. 1055 * 1056 * @return the input steam to the builder XML. 1057 * @throws DocFileIOException when the given XML file cannot be found or opened. 1058 */ 1059 public InputStream getBuilderXML() throws DocFileIOException { 1060 return builderXMLPath == null ? 1061 BaseConfiguration.class.getResourceAsStream(DEFAULT_BUILDER_XML) : 1062 DocFile.createFileForInput(this, builderXMLPath).openInputStream(); 1063 } 1064 1065 /** 1066 * Return the Locale for this document. 1067 * 1068 * @return the current locale 1069 */ 1070 public abstract Locale getLocale(); 1071 1072 /** 1073 * Return the path of the overview file and null if it does not exist. 1074 * 1075 * @return the path of the overview file. 1076 */ 1077 public abstract JavaFileObject getOverviewPath(); 1078 1079 /** 1080 * Return the current file manager. 1081 * 1082 * @return JavaFileManager 1083 */ 1084 public abstract JavaFileManager getFileManager(); 1085 1086 private void setTabWidth(int n) { 1087 sourcetab = n; 1088 tabSpaces = String.format("%" + n + "s", ""); 1089 } 1090 1091 public abstract boolean showMessage(DocTreePath path, String key); 1092 1093 public abstract boolean showMessage(Element e, String key); 1094 1095 public abstract static class Option implements Doclet.Option, Comparable<Option> { 1096 private final String[] names; 1097 private final String parameters; 1098 private final String description; 1099 private final int argCount; 1100 1101 protected Option(Resources resources, String name, int argCount) { 1102 this(resources, null, name, argCount); 1103 } 1104 1105 protected Option(Resources resources, String keyBase, String name, int argCount) { 1106 this.names = name.trim().split("\\s+"); 1107 if (keyBase == null) { 1108 keyBase = "doclet.usage." + names[0].toLowerCase().replaceAll("^-+", ""); 1109 } 1110 String desc = getOptionsMessage(resources, keyBase + ".description"); 1111 if (desc.isEmpty()) { 1112 this.description = "<MISSING KEY>"; 1113 this.parameters = "<MISSING KEY>"; 1114 } else { 1115 this.description = desc; 1116 this.parameters = getOptionsMessage(resources, keyBase + ".parameters"); 1117 } 1118 this.argCount = argCount; 1119 } 1120 1121 protected Option(Resources resources, String name) { 1122 this(resources, name, 0); 1123 } 1124 1125 private String getOptionsMessage(Resources resources, String key) { 1126 try { 1127 return resources.getText(key); 1128 } catch (MissingResourceException ignore) { 1129 return ""; 1130 } 1131 } 1132 1133 @Override 1134 public String getDescription() { 1135 return description; 1136 } 1137 1138 @Override 1139 public Option.Kind getKind() { 1140 return Doclet.Option.Kind.STANDARD; 1141 } 1142 1143 @Override 1144 public List<String> getNames() { 1145 return Arrays.asList(names); 1146 } 1147 1148 @Override 1149 public String getParameters() { 1150 return parameters; 1151 } 1152 1153 @Override 1154 public String toString() { 1155 return Arrays.toString(names); 1156 } 1157 1158 @Override 1159 public int getArgumentCount() { 1160 return argCount; 1161 } 1162 1163 public boolean matches(String option) { 1164 for (String name : names) { 1165 boolean matchCase = name.startsWith("--"); 1166 if (option.startsWith("--") && option.contains("=")) { 1167 return name.equals(option.substring(option.indexOf("=") + 1)); 1168 } else if (matchCase) { 1169 return name.equals(option); 1170 } 1171 return name.toLowerCase().equals(option.toLowerCase()); 1172 } 1173 return false; 1174 } 1175 1176 @Override 1177 public int compareTo(Option that) { 1178 return this.getNames().get(0).compareTo(that.getNames().get(0)); 1179 } 1180 } 1181 1182 public abstract class XOption extends Option { 1183 1184 public XOption(Resources resources, String prefix, String name, int argCount) { 1185 super(resources, prefix, name, argCount); 1186 } 1187 1188 public XOption(Resources resources, String name, int argCount) { 1189 super(resources, name, argCount); 1190 } 1191 1192 public XOption(Resources resources, String name) { 1193 this(resources, name, 0); 1194 } 1195 1196 @Override 1197 public Option.Kind getKind() { 1198 return Doclet.Option.Kind.EXTENDED; 1199 } 1200 } 1201 1202 public abstract class Hidden extends Option { 1203 1204 public Hidden(Resources resources, String name, int argCount) { 1205 super(resources, name, argCount); 1206 } 1207 1208 public Hidden(Resources resources, String name) { 1209 this(resources, name, 0); 1210 } 1211 1212 @Override 1213 public Option.Kind getKind() { 1214 return Doclet.Option.Kind.OTHER; 1215 } 1216 } 1217 1218 /* 1219 * Splits the elements in a collection to its individual 1220 * collection. 1221 */ 1222 @SuppressWarnings("preview") 1223 private static class Splitter { 1224 1225 final Set<ModuleElement> mset = new LinkedHashSet<>(); 1226 final Set<PackageElement> pset = new LinkedHashSet<>(); 1227 final Set<TypeElement> tset = new LinkedHashSet<>(); 1228 1229 Splitter(DocletEnvironment docEnv, boolean included) { 1230 1231 Set<? extends Element> inset = included 1232 ? docEnv.getIncludedElements() 1233 : docEnv.getSpecifiedElements(); 1234 1235 for (Element e : inset) { 1236 new SimpleElementVisitor14<Void, Void>() { 1237 @Override 1238 @DefinedBy(Api.LANGUAGE_MODEL) 1239 public Void visitModule(ModuleElement e, Void p) { 1240 mset.add(e); 1241 return null; 1242 } 1243 1244 @Override 1245 @DefinedBy(Api.LANGUAGE_MODEL) 1246 public Void visitPackage(PackageElement e, Void p) { 1247 pset.add(e); 1248 return null; 1249 } 1250 1251 @Override 1252 @DefinedBy(Api.LANGUAGE_MODEL) 1253 public Void visitType(TypeElement e, Void p) { 1254 tset.add(e); 1255 return null; 1256 } 1257 1258 @Override 1259 @DefinedBy(Api.LANGUAGE_MODEL) 1260 protected Void defaultAction(Element e, Void p) { 1261 throw new AssertionError("unexpected element: " + e); 1262 } 1263 1264 }.visit(e); 1265 } 1266 } 1267 } 1268 1269 /** 1270 * Returns whether or not to allow JavaScript in comments. 1271 * Default is off; can be set true from a command line option. 1272 * 1273 * @return the allowScriptInComments 1274 */ 1275 public boolean isAllowScriptInComments() { 1276 return allowScriptInComments; 1277 } 1278 1279 public synchronized VisibleMemberTable getVisibleMemberTable(TypeElement te) { 1280 return visibleMemberCache.getVisibleMemberTable(te); 1281 } 1282 1283 /** 1284 * Determines if JavaFX is available in the compilation environment. 1285 * @return true if JavaFX is available 1286 */ 1287 public boolean isJavaFXMode() { 1288 TypeElement observable = utils.elementUtils.getTypeElement("javafx.beans.Observable"); 1289 if (observable != null) { 1290 ModuleElement javafxModule = utils.elementUtils.getModuleOf(observable); 1291 if (javafxModule == null || javafxModule.isUnnamed() || javafxModule.getQualifiedName().contentEquals("javafx.base")) { 1292 return true; 1293 } 1294 } 1295 return false; 1296 } 1297 }