1 /* 2 * Copyright (c) 1997, 2017, 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.SimpleElementVisitor9; 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.internal.doclets.toolkit.builders.BuilderFactory; 47 import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; 48 import jdk.javadoc.internal.doclets.toolkit.util.DocFile; 49 import jdk.javadoc.internal.doclets.toolkit.util.DocFileFactory; 50 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException; 51 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; 52 import jdk.javadoc.internal.doclets.toolkit.util.Extern; 53 import jdk.javadoc.internal.doclets.toolkit.util.Group; 54 import jdk.javadoc.internal.doclets.toolkit.util.MetaKeywords; 55 import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException; 56 import jdk.javadoc.internal.doclets.toolkit.util.TypeElementCatalog; 57 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 58 import jdk.javadoc.internal.doclets.toolkit.util.Utils.Pair; 59 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; 60 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.GetterSetter; 61 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.Kind; 62 63 import static javax.tools.Diagnostic.Kind.*; 64 65 /** 66 * Configure the output based on the options. Doclets should sub-class 67 * BaseConfiguration, to configure and add their own options. This class contains 68 * all user options which are supported by the 1.1 doclet and the standard 69 * 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 final Extern extern = new Extern(this); 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 overrides a super-type's method 287 * with no changes to the API contract, should be summarized in the 288 * foot note 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 299 public boolean dumpOnError = false; 300 301 private List<Pair<String, String>> groupPairs; 302 303 private final Map<TypeElement, EnumMap<Kind, Reference<VisibleMemberMap>>> typeElementMemberCache; 304 305 public abstract Messages getMessages(); 306 307 public abstract Resources getResources(); 308 309 /** 310 * Return the build date for the doclet. 311 * 312 * @return the build date 313 */ 314 public abstract String getDocletSpecificBuildDate(); 315 316 /** 317 * This method should be defined in all those doclets (configurations), 318 * which want to derive themselves from this BaseConfiguration. This method 319 * can be used to finish up the options setup. 320 * 321 * @return true if successful and false otherwise 322 */ 323 324 public abstract boolean finishOptionSettings(); 325 326 public CommentUtils cmtUtils; 327 328 /** 329 * A sorted set of included packages. 330 */ 331 public SortedSet<PackageElement> packages = null; 332 333 public OverviewElement overviewElement; 334 335 // The following three fields provide caches for use by all instances of VisibleMemberMap. 336 public final Map<TypeElement, List<Element>> propertiesCache = new HashMap<>(); 337 public final Map<Element, Element> classPropertiesMap = new HashMap<>(); 338 public final Map<Element, GetterSetter> getterSetterMap = new HashMap<>(); 339 340 public DocFileFactory docFileFactory; 341 342 /** 343 * A sorted map, giving the (specified|included|other) packages for each module. 344 */ 345 public SortedMap<ModuleElement, Set<PackageElement>> modulePackages; 346 347 /** 348 * The list of known modules, that should be documented. 349 */ 350 public SortedSet<ModuleElement> modules; 351 352 protected static final String sharedResourceBundleName = 353 "jdk.javadoc.internal.doclets.toolkit.resources.doclets"; 354 355 /** 356 * Constructs the configurations needed by the doclet. 357 * 358 * @param doclet the doclet that created this configuration 359 */ 360 public BaseConfiguration(Doclet doclet) { 361 this.doclet = doclet; 362 excludedDocFileDirs = new HashSet<>(); 363 excludedQualifiers = new HashSet<>(); 364 setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH); 365 metakeywords = new MetaKeywords(this); 366 groupPairs = new ArrayList<>(0); 367 typeElementMemberCache = new HashMap<>(); 368 } 369 370 private boolean initialized = false; 371 372 protected void initConfiguration(DocletEnvironment docEnv) { 373 if (initialized) { 374 throw new IllegalStateException("configuration previously initialized"); 375 } 376 initialized = true; 377 this.docEnv = docEnv; 378 overviewElement = new OverviewElement(docEnv); 379 Splitter specifiedSplitter = new Splitter(docEnv, false); 380 specifiedModuleElements = Collections.unmodifiableSet(specifiedSplitter.mset); 381 specifiedPackageElements = Collections.unmodifiableSet(specifiedSplitter.pset); 382 specifiedTypeElements = Collections.unmodifiableSet(specifiedSplitter.tset); 383 384 Splitter includedSplitter = new Splitter(docEnv, true); 385 includedModuleElements = Collections.unmodifiableSet(includedSplitter.mset); 386 includedPackageElements = Collections.unmodifiableSet(includedSplitter.pset); 387 includedTypeElements = Collections.unmodifiableSet(includedSplitter.tset); 388 } 389 390 /** 391 * Return the builder factory for this doclet. 392 * 393 * @return the builder factory for this doclet. 394 */ 395 public BuilderFactory getBuilderFactory() { 396 if (builderFactory == null) { 397 builderFactory = new BuilderFactory(this); 398 } 399 return builderFactory; 400 } 401 402 public Reporter getReporter() { 403 return this.reporter; 404 } 405 406 private Set<ModuleElement> specifiedModuleElements; 407 408 public Set<ModuleElement> getSpecifiedModuleElements() { 409 return specifiedModuleElements; 410 } 411 412 private Set<PackageElement> specifiedPackageElements; 413 414 public Set<PackageElement> getSpecifiedPackageElements() { 415 return specifiedPackageElements; 416 } 417 418 private Set<TypeElement> specifiedTypeElements; 419 420 public Set<TypeElement> getSpecifiedTypeElements() { 421 return specifiedTypeElements; 422 } 423 424 private Set<ModuleElement> includedModuleElements; 425 426 public Set<ModuleElement> getIncludedModuleElements() { 427 return includedModuleElements; 428 } 429 430 private Set<PackageElement> includedPackageElements; 431 432 public Set<PackageElement> getIncludedPackageElements() { 433 return includedPackageElements; 434 } 435 436 private Set<TypeElement> includedTypeElements; 437 438 public Set<TypeElement> getIncludedTypeElements() { 439 return includedTypeElements; 440 } 441 442 private void initModules() { 443 // Build the modules structure used by the doclet 444 modules = new TreeSet<>(utils.makeModuleComparator()); 445 modules.addAll(getSpecifiedModuleElements()); 446 447 modulePackages = new TreeMap<>(utils.makeModuleComparator()); 448 for (PackageElement p : packages) { 449 ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); 450 if (mdle != null && !mdle.isUnnamed()) { 451 Set<PackageElement> s = modulePackages 452 .computeIfAbsent(mdle, m -> new TreeSet<>(utils.makePackageComparator())); 453 s.add(p); 454 } 455 } 456 457 for (PackageElement p : getIncludedPackageElements()) { 458 ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); 459 if (mdle != null && !mdle.isUnnamed()) { 460 Set<PackageElement> s = modulePackages 461 .computeIfAbsent(mdle, m -> new TreeSet<>(utils.makePackageComparator())); 462 s.add(p); 463 } 464 } 465 466 // add entries for modules which may not have exported packages 467 modules.forEach((ModuleElement mdle) -> { 468 modulePackages.computeIfAbsent(mdle, m -> Collections.emptySet()); 469 }); 470 471 modules.addAll(modulePackages.keySet()); 472 showModules = !modules.isEmpty(); 473 for (Set<PackageElement> pkgs : modulePackages.values()) { 474 packages.addAll(pkgs); 475 } 476 } 477 478 private void initPackages() { 479 packages = new TreeSet<>(utils.makePackageComparator()); 480 // add all the included packages 481 packages.addAll(includedPackageElements); 482 } 483 484 public Set<Doclet.Option> getSupportedOptions() { 485 Resources resources = getResources(); 486 Doclet.Option[] options = { 487 new Option(resources, "-author") { 488 @Override 489 public boolean process(String opt, List<String> args) { 490 showauthor = true; 491 return true; 492 } 493 }, 494 new Option(resources, "-d", 1) { 495 @Override 496 public boolean process(String opt, List<String> args) { 497 destDirName = addTrailingFileSep(args.get(0)); 498 return true; 499 } 500 }, 501 new Option(resources, "-docencoding", 1) { 502 @Override 503 public boolean process(String opt, List<String> args) { 504 docencoding = args.get(0); 505 return true; 506 } 507 }, 508 new Option(resources, "-docfilessubdirs") { 509 @Override 510 public boolean process(String opt, List<String> args) { 511 copydocfilesubdirs = true; 512 return true; 513 } 514 }, 515 new Hidden(resources, "-encoding", 1) { 516 @Override 517 public boolean process(String opt, List<String> args) { 518 encoding = args.get(0); 519 return true; 520 } 521 }, 522 new Option(resources, "-excludedocfilessubdir", 1) { 523 @Override 524 public boolean process(String opt, List<String> args) { 525 addToSet(excludedDocFileDirs, args.get(0)); 526 return true; 527 } 528 }, 529 new Option(resources, "-group", 2) { 530 @Override 531 public boolean process(String opt, List<String> args) { 532 groupPairs.add(new Pair<>(args.get(0), args.get(1))); 533 return true; 534 } 535 }, 536 new Option(resources, "--javafx -javafx") { 537 @Override 538 public boolean process(String opt, List<String> args) { 539 javafx = true; 540 return true; 541 } 542 }, 543 new Option(resources, "-keywords") { 544 @Override 545 public boolean process(String opt, List<String> args) { 546 keywords = true; 547 return true; 548 } 549 }, 550 new Option(resources, "-link", 1) { 551 @Override 552 public boolean process(String opt, List<String> args) { 553 linkList.add(args.get(0)); 554 return true; 555 } 556 }, 557 new Option(resources, "-linksource") { 558 @Override 559 public boolean process(String opt, List<String> args) { 560 linksource = true; 561 return true; 562 } 563 }, 564 new Option(resources, "-linkoffline", 2) { 565 @Override 566 public boolean process(String opt, List<String> args) { 567 linkOfflineList.add(new Pair<String, String>(args.get(0), args.get(1))); 568 return true; 569 } 570 }, 571 new Option(resources, "-nocomment") { 572 @Override 573 public boolean process(String opt, List<String> args) { 574 nocomment = true; 575 return true; 576 } 577 }, 578 new Option(resources, "-nodeprecated") { 579 @Override 580 public boolean process(String opt, List<String> args) { 581 nodeprecated = true; 582 return true; 583 } 584 }, 585 new Option(resources, "-nosince") { 586 @Override 587 public boolean process(String opt, List<String> args) { 588 nosince = true; 589 return true; 590 } 591 }, 592 new Option(resources, "-notimestamp") { 593 @Override 594 public boolean process(String opt, List<String> args) { 595 notimestamp = true; 596 return true; 597 } 598 }, 599 new Option(resources, "-noqualifier", 1) { 600 @Override 601 public boolean process(String opt, List<String> args) { 602 addToSet(excludedQualifiers, args.get(0)); 603 return true; 604 } 605 }, 606 new Option(resources, "--override-methods", 1) { 607 @Override 608 public boolean process(String opt, List<String> args) { 609 summarizeOverriddenMethods = args.get(0).equals("summary"); 610 return true; 611 } 612 }, 613 new Hidden(resources, "-quiet") { 614 @Override 615 public boolean process(String opt, List<String> args) { 616 quiet = true; 617 return true; 618 } 619 }, 620 new Option(resources, "-serialwarn") { 621 @Override 622 public boolean process(String opt, List<String> args) { 623 serialwarn = true; 624 return true; 625 } 626 }, 627 new Option(resources, "-sourcetab", 1) { 628 @Override 629 public boolean process(String opt, List<String> args) { 630 linksource = true; 631 try { 632 setTabWidth(Integer.parseInt(args.get(0))); 633 } catch (NumberFormatException e) { 634 //Set to -1 so that warning will be printed 635 //to indicate what is valid argument. 636 sourcetab = -1; 637 } 638 if (sourcetab <= 0) { 639 getMessages().warning("doclet.sourcetab_warning"); 640 setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH); 641 } 642 return true; 643 } 644 }, 645 new Option(resources, "-tag", 1) { 646 @Override 647 public boolean process(String opt, List<String> args) { 648 ArrayList<String> list = new ArrayList<>(); 649 list.add(opt); 650 list.add(args.get(0)); 651 customTagStrs.add(list); 652 return true; 653 } 654 }, 655 new Option(resources, "-taglet", 1) { 656 @Override 657 public boolean process(String opt, List<String> args) { 658 ArrayList<String> list = new ArrayList<>(); 659 list.add(opt); 660 list.add(args.get(0)); 661 customTagStrs.add(list); 662 return true; 663 } 664 }, 665 new Option(resources, "-tagletpath", 1) { 666 @Override 667 public boolean process(String opt, List<String> args) { 668 tagletpath = args.get(0); 669 return true; 670 } 671 }, 672 new Option(resources, "-version") { 673 @Override 674 public boolean process(String opt, List<String> args) { 675 showversion = true; 676 return true; 677 } 678 }, 679 new Hidden(resources, "--dump-on-error") { 680 @Override 681 public boolean process(String opt, List<String> args) { 682 dumpOnError = true; 683 return true; 684 } 685 }, 686 new Option(resources, "--allow-script-in-comments") { 687 @Override 688 public boolean process(String opt, List<String> args) { 689 allowScriptInComments = true; 690 return true; 691 } 692 } 693 }; 694 Set<Doclet.Option> set = new TreeSet<>(); 695 set.addAll(Arrays.asList(options)); 696 return set; 697 } 698 699 final LinkedHashSet<List<String>> customTagStrs = new LinkedHashSet<>(); 700 701 /* 702 * when this is called all the option have been set, this method, 703 * initializes certain components before anything else is started. 704 */ 705 protected boolean finishOptionSettings0() throws DocletException { 706 initDestDirectory(); 707 for (String link : linkList) { 708 extern.link(link, reporter); 709 } 710 for (Pair<String, String> linkOfflinePair : linkOfflineList) { 711 extern.link(linkOfflinePair.first, linkOfflinePair.second, reporter); 712 } 713 typeElementCatalog = new TypeElementCatalog(includedTypeElements, this); 714 initTagletManager(customTagStrs); 715 groupPairs.stream().forEach((grp) -> { 716 if (showModules) { 717 group.checkModuleGroups(grp.first, grp.second); 718 } else { 719 group.checkPackageGroups(grp.first, grp.second); 720 } 721 }); 722 return true; 723 } 724 725 /** 726 * Set the command line options supported by this configuration. 727 * 728 * @return true if the options are set successfully 729 * @throws DocletException if there is a problem while setting the options 730 */ 731 public boolean setOptions() throws DocletException { 732 initPackages(); 733 initModules(); 734 if (!finishOptionSettings0() || !finishOptionSettings()) 735 return false; 736 737 return true; 738 } 739 740 private void initDestDirectory() throws DocletException { 741 if (!destDirName.isEmpty()) { 742 DocFile destDir = DocFile.createFileForDirectory(this, destDirName); 743 if (!destDir.exists()) { 744 //Create the output directory (in case it doesn't exist yet) 745 reporter.print(NOTE, getText("doclet.dest_dir_create", destDirName)); 746 destDir.mkdirs(); 747 } else if (!destDir.isDirectory()) { 748 throw new SimpleDocletException(getText( 749 "doclet.destination_directory_not_directory_0", 750 destDir.getPath())); 751 } else if (!destDir.canWrite()) { 752 throw new SimpleDocletException(getText( 753 "doclet.destination_directory_not_writable_0", 754 destDir.getPath())); 755 } 756 } 757 DocFileFactory.getFactory(this).setDestDir(destDirName); 758 } 759 760 /** 761 * Initialize the taglet manager. The strings to initialize the simple custom tags should 762 * be in the following format: "[tag name]:[location str]:[heading]". 763 * 764 * @param customTagStrs the set two dimensional arrays of strings. These arrays contain 765 * either -tag or -taglet arguments. 766 */ 767 private void initTagletManager(Set<List<String>> customTagStrs) { 768 tagletManager = tagletManager == null ? 769 new TagletManager(nosince, showversion, showauthor, javafx, this) : 770 tagletManager; 771 for (List<String> args : customTagStrs) { 772 if (args.get(0).equals("-taglet")) { 773 tagletManager.addCustomTag(args.get(1), getFileManager(), tagletpath); 774 continue; 775 } 776 List<String> tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3); 777 if (tokens.size() == 1) { 778 String tagName = args.get(1); 779 if (tagletManager.isKnownCustomTag(tagName)) { 780 //reorder a standard tag 781 tagletManager.addNewSimpleCustomTag(tagName, null, ""); 782 } else { 783 //Create a simple tag with the heading that has the same name as the tag. 784 StringBuilder heading = new StringBuilder(tagName + ":"); 785 heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0))); 786 tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a"); 787 } 788 } else if (tokens.size() == 2) { 789 //Add simple taglet without heading, probably to excluding it in the output. 790 tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), ""); 791 } else if (tokens.size() >= 3) { 792 tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1)); 793 } else { 794 Messages messages = getMessages(); 795 messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1)); 796 } 797 } 798 } 799 800 /** 801 * Given a string, return an array of tokens. The separator can be escaped 802 * with the '\' character. The '\' character may also be escaped by the 803 * '\' character. 804 * 805 * @param s the string to tokenize. 806 * @param separator the separator char. 807 * @param maxTokens the maximum number of tokens returned. If the 808 * max is reached, the remaining part of s is appended 809 * to the end of the last token. 810 * @return an array of tokens. 811 */ 812 private List<String> tokenize(String s, char separator, int maxTokens) { 813 List<String> tokens = new ArrayList<>(); 814 StringBuilder token = new StringBuilder(); 815 boolean prevIsEscapeChar = false; 816 for (int i = 0; i < s.length(); i += Character.charCount(i)) { 817 int currentChar = s.codePointAt(i); 818 if (prevIsEscapeChar) { 819 // Case 1: escaped character 820 token.appendCodePoint(currentChar); 821 prevIsEscapeChar = false; 822 } else if (currentChar == separator && tokens.size() < maxTokens - 1) { 823 // Case 2: separator 824 tokens.add(token.toString()); 825 token = new StringBuilder(); 826 } else if (currentChar == '\\') { 827 // Case 3: escape character 828 prevIsEscapeChar = true; 829 } else { 830 // Case 4: regular character 831 token.appendCodePoint(currentChar); 832 } 833 } 834 if (token.length() > 0) { 835 tokens.add(token.toString()); 836 } 837 return tokens; 838 } 839 840 private void addToSet(Set<String> s, String str) { 841 StringTokenizer st = new StringTokenizer(str, ":"); 842 String current; 843 while (st.hasMoreTokens()) { 844 current = st.nextToken(); 845 s.add(current); 846 } 847 } 848 849 /** 850 * Add a trailing file separator, if not found. Remove superfluous 851 * file separators if any. Preserve the front double file separator for 852 * UNC paths. 853 * 854 * @param path Path under consideration. 855 * @return String Properly constructed path string. 856 */ 857 public static String addTrailingFileSep(String path) { 858 String fs = System.getProperty("file.separator"); 859 String dblfs = fs + fs; 860 int indexDblfs; 861 while ((indexDblfs = path.indexOf(dblfs, 1)) >= 0) { 862 path = path.substring(0, indexDblfs) + 863 path.substring(indexDblfs + fs.length()); 864 } 865 if (!path.endsWith(fs)) 866 path += fs; 867 return path; 868 } 869 870 /** 871 * This checks for the validity of the options used by the user. 872 * As of this writing, this checks only docencoding. 873 * 874 * @return true if all the options are valid. 875 */ 876 public boolean generalValidOptions() { 877 if (docencoding != null) { 878 if (!checkOutputFileEncoding(docencoding)) { 879 return false; 880 } 881 } 882 if (docencoding == null && (encoding != null && !encoding.isEmpty())) { 883 if (!checkOutputFileEncoding(encoding)) { 884 return false; 885 } 886 } 887 return true; 888 } 889 890 /** 891 * Check the validity of the given Source or Output File encoding on this 892 * platform. 893 * 894 * @param docencoding output file encoding. 895 * @param reporter used to report errors. 896 */ 897 private boolean checkOutputFileEncoding(String docencoding) { 898 OutputStream ost = new ByteArrayOutputStream(); 899 OutputStreamWriter osw = null; 900 try { 901 osw = new OutputStreamWriter(ost, docencoding); 902 } catch (UnsupportedEncodingException exc) { 903 reporter.print(ERROR, getText("doclet.Encoding_not_supported", docencoding)); 904 return false; 905 } finally { 906 try { 907 if (osw != null) { 908 osw.close(); 909 } 910 } catch (IOException exc) { 911 } 912 } 913 return true; 914 } 915 916 /** 917 * Return true if the given doc-file subdirectory should be excluded and 918 * false otherwise. 919 * 920 * @param docfilesubdir the doc-files subdirectory to check. 921 * @return true if the directory is excluded. 922 */ 923 public boolean shouldExcludeDocFileDir(String docfilesubdir) { 924 return excludedDocFileDirs.contains(docfilesubdir); 925 } 926 927 /** 928 * Return true if the given qualifier should be excluded and false otherwise. 929 * 930 * @param qualifier the qualifier to check. 931 * @return true if the qualifier should be excluded 932 */ 933 public boolean shouldExcludeQualifier(String qualifier) { 934 if (excludedQualifiers.contains("all") || 935 excludedQualifiers.contains(qualifier) || 936 excludedQualifiers.contains(qualifier + ".*")) { 937 return true; 938 } else { 939 int index = -1; 940 while ((index = qualifier.indexOf(".", index + 1)) != -1) { 941 if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) { 942 return true; 943 } 944 } 945 return false; 946 } 947 } 948 949 /** 950 * Return the qualified name of the Element if its qualifier is not excluded. 951 * Otherwise return the unqualified Element name. 952 * 953 * @param te the TypeElement to check. 954 * @return the class name 955 */ 956 public String getClassName(TypeElement te) { 957 PackageElement pkg = utils.containingPackage(te); 958 return shouldExcludeQualifier(utils.getPackageName(pkg)) 959 ? utils.getSimpleName(te) 960 : utils.getFullyQualifiedName(te); 961 } 962 963 /** 964 * Convenience method to obtain a resource from the doclet's 965 * {@link Resources resources}. 966 * Equivalent to <code>getResources.getText(key);</code>. 967 * 968 * @param key the key for the desired string 969 * @return the string for the given key 970 * @throws MissingResourceException if the key is not found in either 971 * bundle. 972 */ 973 public abstract String getText(String key); 974 975 /** 976 * Convenience method to obtain a resource from the doclet's 977 * {@link Resources resources}. 978 * Equivalent to <code>getResources.getText(key, args);</code>. 979 * 980 * @param key the key for the desired string 981 * @param args values to be substituted into the resulting string 982 * @return the string for the given key 983 * @throws MissingResourceException if the key is not found in either 984 * bundle. 985 */ 986 public abstract String getText(String key, String... args); 987 988 /** 989 * Convenience method to obtain a resource from the doclet's 990 * {@link Resources resources} as a {@code Content} object. 991 * 992 * @param key the key for the desired string 993 * @return a content tree for the text 994 */ 995 public abstract Content getContent(String key); 996 997 /** 998 * Convenience method to obtain a resource from the doclet's 999 * {@link Resources resources} as a {@code Content} object. 1000 * 1001 * @param key the key for the desired string 1002 * @param o string or content argument added to configuration text 1003 * @return a content tree for the text 1004 */ 1005 public abstract Content getContent(String key, Object o); 1006 1007 /** 1008 * Convenience method to obtain a resource from the doclet's 1009 * {@link Resources resources} as a {@code Content} object. 1010 * 1011 * @param key the key for the desired string 1012 * @param o1 resource argument 1013 * @param o2 resource argument 1014 * @return a content tree for the text 1015 */ 1016 public abstract Content getContent(String key, Object o1, Object o2); 1017 1018 /** 1019 * Get the configuration string as a content. 1020 * 1021 * @param key the key for the desired string 1022 * @param o0 string or content argument added to configuration text 1023 * @param o1 string or content argument added to configuration text 1024 * @param o2 string or content argument added to configuration text 1025 * @return a content tree for the text 1026 */ 1027 public abstract Content getContent(String key, Object o0, Object o1, Object o2); 1028 1029 /** 1030 * Return true if the TypeElement element is getting documented, depending upon 1031 * -nodeprecated option and the deprecation information. Return true if 1032 * -nodeprecated is not used. Return false if -nodeprecated is used and if 1033 * either TypeElement element is deprecated or the containing package is deprecated. 1034 * 1035 * @param te the TypeElement for which the page generation is checked 1036 * @return true if it is a generated doc. 1037 */ 1038 public boolean isGeneratedDoc(TypeElement te) { 1039 if (!nodeprecated) { 1040 return true; 1041 } 1042 return !(utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te))); 1043 } 1044 1045 /** 1046 * Return the doclet specific instance of a writer factory. 1047 * 1048 * @return the {@link WriterFactory} for the doclet. 1049 */ 1050 public abstract WriterFactory getWriterFactory(); 1051 1052 /** 1053 * Return the input stream to the builder XML. 1054 * 1055 * @return the input steam to the builder XML. 1056 * @throws DocFileIOException when the given XML file cannot be found or opened. 1057 */ 1058 public InputStream getBuilderXML() throws DocFileIOException { 1059 return builderXMLPath == null ? 1060 BaseConfiguration.class.getResourceAsStream(DEFAULT_BUILDER_XML) : 1061 DocFile.createFileForInput(this, builderXMLPath).openInputStream(); 1062 } 1063 1064 /** 1065 * Return the Locale for this document. 1066 * 1067 * @return the current locale 1068 */ 1069 public abstract Locale getLocale(); 1070 1071 /** 1072 * Return the path of the overview file and null if it does not exist. 1073 * 1074 * @return the path of the overview file. 1075 */ 1076 public abstract JavaFileObject getOverviewPath(); 1077 1078 /** 1079 * Return the current file manager. 1080 * 1081 * @return JavaFileManager 1082 */ 1083 public abstract JavaFileManager getFileManager(); 1084 1085 private void setTabWidth(int n) { 1086 sourcetab = n; 1087 tabSpaces = String.format("%" + n + "s", ""); 1088 } 1089 1090 public abstract boolean showMessage(DocTreePath path, String key); 1091 1092 public abstract boolean showMessage(Element e, String key); 1093 1094 public static abstract class Option implements Doclet.Option, Comparable<Option> { 1095 private final String[] names; 1096 private final String parameters; 1097 private final String description; 1098 private final int argCount; 1099 1100 protected Option(Resources resources, String name, int argCount) { 1101 this(resources, null, name, argCount); 1102 } 1103 1104 protected Option(Resources resources, String keyBase, String name, int argCount) { 1105 this.names = name.trim().split("\\s+"); 1106 if (keyBase == null) { 1107 keyBase = "doclet.usage." + names[0].toLowerCase().replaceAll("^-+", ""); 1108 } 1109 String desc = getOptionsMessage(resources, keyBase + ".description"); 1110 if (desc.isEmpty()) { 1111 this.description = "<MISSING KEY>"; 1112 this.parameters = "<MISSING KEY>"; 1113 } else { 1114 this.description = desc; 1115 this.parameters = getOptionsMessage(resources, keyBase + ".parameters"); 1116 } 1117 this.argCount = argCount; 1118 } 1119 1120 protected Option(Resources resources, String name) { 1121 this(resources, name, 0); 1122 } 1123 1124 private String getOptionsMessage(Resources resources, String key) { 1125 try { 1126 return resources.getText(key); 1127 } catch (MissingResourceException ignore) { 1128 return ""; 1129 } 1130 } 1131 1132 @Override 1133 public String getDescription() { 1134 return description; 1135 } 1136 1137 @Override 1138 public Option.Kind getKind() { 1139 return Doclet.Option.Kind.STANDARD; 1140 } 1141 1142 @Override 1143 public List<String> getNames() { 1144 return Arrays.asList(names); 1145 } 1146 1147 @Override 1148 public String getParameters() { 1149 return parameters; 1150 } 1151 1152 @Override 1153 public String toString() { 1154 return Arrays.toString(names); 1155 } 1156 1157 @Override 1158 public int getArgumentCount() { 1159 return argCount; 1160 } 1161 1162 public boolean matches(String option) { 1163 for (String name : names) { 1164 boolean matchCase = name.startsWith("--"); 1165 if (option.startsWith("--") && option.contains("=")) { 1166 return name.equals(option.substring(option.indexOf("=") + 1)); 1167 } else if (matchCase) { 1168 return name.equals(option); 1169 } 1170 return name.toLowerCase().equals(option.toLowerCase()); 1171 } 1172 return false; 1173 } 1174 1175 @Override 1176 public int compareTo(Option that) { 1177 return this.getNames().get(0).compareTo(that.getNames().get(0)); 1178 } 1179 } 1180 1181 public abstract class XOption extends Option { 1182 1183 public XOption(Resources resources, String prefix, String name, int argCount) { 1184 super(resources, prefix, name, argCount); 1185 } 1186 1187 public XOption(Resources resources, String name, int argCount) { 1188 super(resources, name, argCount); 1189 } 1190 1191 public XOption(Resources resources, String name) { 1192 this(resources, name, 0); 1193 } 1194 1195 @Override 1196 public Option.Kind getKind() { 1197 return Doclet.Option.Kind.EXTENDED; 1198 } 1199 } 1200 1201 public abstract class Hidden extends Option { 1202 1203 public Hidden(Resources resources, String name, int argCount) { 1204 super(resources, name, argCount); 1205 } 1206 1207 public Hidden(Resources resources, String name) { 1208 this(resources, name, 0); 1209 } 1210 1211 @Override 1212 public Option.Kind getKind() { 1213 return Doclet.Option.Kind.OTHER; 1214 } 1215 } 1216 1217 /* 1218 * Splits the elements in a collection to its individual 1219 * collection. 1220 */ 1221 static private class Splitter { 1222 1223 final Set<ModuleElement> mset = new LinkedHashSet<>(); 1224 final Set<PackageElement> pset = new LinkedHashSet<>(); 1225 final Set<TypeElement> tset = new LinkedHashSet<>(); 1226 1227 Splitter(DocletEnvironment docEnv, boolean included) { 1228 1229 Set<? extends Element> inset = included 1230 ? docEnv.getIncludedElements() 1231 : docEnv.getSpecifiedElements(); 1232 1233 for (Element e : inset) { 1234 new SimpleElementVisitor9<Void, Void>() { 1235 @Override 1236 @DefinedBy(Api.LANGUAGE_MODEL) 1237 public Void visitModule(ModuleElement e, Void p) { 1238 mset.add(e); 1239 return null; 1240 } 1241 1242 @Override 1243 @DefinedBy(Api.LANGUAGE_MODEL) 1244 public Void visitPackage(PackageElement e, Void p) { 1245 pset.add(e); 1246 return null; 1247 } 1248 1249 @Override 1250 @DefinedBy(Api.LANGUAGE_MODEL) 1251 public Void visitType(TypeElement e, Void p) { 1252 tset.add(e); 1253 return null; 1254 } 1255 1256 @Override 1257 @DefinedBy(Api.LANGUAGE_MODEL) 1258 protected Void defaultAction(Element e, Void p) { 1259 throw new AssertionError("unexpected element: " + e); 1260 } 1261 1262 }.visit(e); 1263 } 1264 } 1265 } 1266 1267 /** 1268 * Returns whether or not to allow JavaScript in comments. 1269 * Default is off; can be set true from a command line option. 1270 * 1271 * @return the allowScriptInComments 1272 */ 1273 public boolean isAllowScriptInComments() { 1274 return allowScriptInComments; 1275 } 1276 1277 public VisibleMemberMap getVisibleMemberMap(TypeElement te, VisibleMemberMap.Kind kind) { 1278 EnumMap<Kind, Reference<VisibleMemberMap>> cacheMap = typeElementMemberCache 1279 .computeIfAbsent(te, k -> new EnumMap<>(VisibleMemberMap.Kind.class)); 1280 1281 Reference<VisibleMemberMap> vmapRef = cacheMap.get(kind); 1282 // recompute, if referent has been garbage collected 1283 VisibleMemberMap vMap = vmapRef == null ? null : vmapRef.get(); 1284 if (vMap == null) { 1285 vMap = new VisibleMemberMap(te, kind, this); 1286 cacheMap.put(kind, new SoftReference<>(vMap)); 1287 } 1288 return vMap; 1289 } 1290 }