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