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