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