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 }