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