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