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 }