1 /*
2  * Copyright (c) 1997, 2020, 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 
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.SortedMap;
41 import java.util.SortedSet;
42 import java.util.TreeMap;
43 import java.util.TreeSet;
44 import java.util.function.Function;
45 
46 import javax.lang.model.SourceVersion;
47 import javax.lang.model.element.Element;
48 import javax.lang.model.element.ModuleElement;
49 import javax.lang.model.element.PackageElement;
50 import javax.lang.model.element.TypeElement;
51 import javax.lang.model.util.Elements;
52 import javax.lang.model.util.SimpleElementVisitor14;
53 import javax.tools.JavaFileManager;
54 import javax.tools.JavaFileObject;
55 
56 import com.sun.source.tree.CompilationUnitTree;
57 import com.sun.source.util.DocTreePath;
58 import com.sun.source.util.TreePath;
59 import com.sun.tools.javac.util.DefinedBy;
60 import com.sun.tools.javac.util.DefinedBy.Api;
61 import jdk.javadoc.doclet.Doclet;
62 import jdk.javadoc.doclet.DocletEnvironment;
63 import jdk.javadoc.doclet.Reporter;
64 import jdk.javadoc.doclet.StandardDoclet;
65 import jdk.javadoc.doclet.Taglet;
66 import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory;
67 import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager;
68 import jdk.javadoc.internal.doclets.toolkit.util.Comparators;
69 import jdk.javadoc.internal.doclets.toolkit.util.DocFile;
70 import jdk.javadoc.internal.doclets.toolkit.util.DocFileFactory;
71 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
72 import jdk.javadoc.internal.doclets.toolkit.util.Extern;
73 import jdk.javadoc.internal.doclets.toolkit.util.Group;
74 import jdk.javadoc.internal.doclets.toolkit.util.MetaKeywords;
75 import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException;
76 import jdk.javadoc.internal.doclets.toolkit.util.TypeElementCatalog;
77 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
78 import jdk.javadoc.internal.doclets.toolkit.util.Utils.Pair;
79 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberCache;
80 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable;
81 import jdk.javadoc.internal.doclint.DocLint;
82 
83 /**
84  * Configure the output based on the options. Doclets should sub-class
85  * BaseConfiguration, to configure and add their own options. This class contains
86  * all user options which are supported by the standard doclet.
87  *
88  * <p><b>This is NOT part of any supported API.
89  * If you write code that depends on this, you do so at your own risk.
90  * This code and its internal interfaces are subject to change or
91  * deletion without notice.</b>
92  */
93 public abstract class BaseConfiguration {
94     /**
95      * The doclet that created this configuration.
96      */
97     public final Doclet doclet;
98 
99     /**
100      * The factory for builders.
101      */
102     protected BuilderFactory builderFactory;
103 
104     /**
105      * The taglet manager.
106      */
107     public TagletManager tagletManager;
108 
109     /**
110      * The meta tag keywords instance.
111      */
112     public MetaKeywords metakeywords;
113 
114     /**
115      * The doclet environment.
116      */
117     public DocletEnvironment docEnv;
118 
119     /**
120      * An utility class for commonly used helpers
121      */
122     public Utils utils;
123 
124     /**
125      * All the temporary accessors to javac internals.
126      */
127     public WorkArounds workArounds;
128 
129     /**
130      * Sourcepath from where to read the source files. Default is classpath.
131      */
132     public String sourcepath = "";
133 
134     /**
135      * Generate modules documentation if more than one module is present.
136      */
137     public boolean showModules = false;
138 
139     /**
140      * The catalog of classes specified on the command-line
141      */
142     public TypeElementCatalog typeElementCatalog;
143 
144     /**
145      * The package grouping instance.
146      */
147     public final Group group = new Group(this);
148 
149     /**
150      * The tracker of external package links.
151      */
152     public Extern extern;
153 
154     public final Reporter reporter;
155 
156     public final Locale locale;
157 
getMessages()158     public abstract Messages getMessages();
159 
getDocResources()160     public abstract Resources getDocResources();
161 
162     /**
163      * Returns the version of the {@link #doclet doclet}.
164      *
165      * @return the version
166      */
getDocletVersion()167     public abstract Runtime.Version getDocletVersion();
168 
169     /**
170      * This method should be defined in all those doclets (configurations),
171      * which want to derive themselves from this BaseConfiguration. This method
172      * can be used to finish up the options setup.
173      *
174      * @return true if successful and false otherwise
175      */
176 
finishOptionSettings()177     public abstract boolean finishOptionSettings();
178 
179     public CommentUtils cmtUtils;
180 
181     /**
182      * A sorted set of included packages.
183      */
184     public SortedSet<PackageElement> packages = null;
185 
186     public OverviewElement overviewElement;
187 
188     public DocFileFactory docFileFactory;
189 
190     /**
191      * A sorted map, giving the (specified|included|other) packages for each module.
192      */
193     public SortedMap<ModuleElement, Set<PackageElement>> modulePackages;
194 
195     /**
196      * The list of known modules, that should be documented.
197      */
198     public SortedSet<ModuleElement> modules;
199 
200     protected static final String sharedResourceBundleName =
201             "jdk.javadoc.internal.doclets.toolkit.resources.doclets";
202 
203     VisibleMemberCache visibleMemberCache = null;
204 
205     public PropertyUtils propertyUtils = null;
206 
207     /**
208      * Constructs the format-independent configuration needed by the doclet.
209      *
210      * @apiNote The {@code doclet} parameter is used when
211      * {@link Taglet#init(DocletEnvironment, Doclet) initializing tags}.
212      * Some doclets (such as the {@link StandardDoclet}), may delegate to another
213      * (such as the {@code HtmlDoclet}).  In such cases, the primary doclet (i.e
214      * {@code StandardDoclet}) should be provided here, and not any internal
215      * class like {@code HtmlDoclet}.
216      *
217      * @param doclet   the doclet for this run of javadoc
218      * @param locale   the locale for the generated documentation
219      * @param reporter the reporter to use for console messages
220      */
BaseConfiguration(Doclet doclet, Locale locale, Reporter reporter)221     public BaseConfiguration(Doclet doclet, Locale locale, Reporter reporter) {
222         this.doclet = doclet;
223         this.locale = locale;
224         this.reporter = reporter;
225     }
226 
getOptions()227     public abstract BaseOptions getOptions();
228 
229     private boolean initialized = false;
230 
initConfiguration(DocletEnvironment docEnv, Function<String, String> resourceKeyMapper)231     protected void initConfiguration(DocletEnvironment docEnv,
232                                      Function<String, String> resourceKeyMapper) {
233         if (initialized) {
234             throw new IllegalStateException("configuration previously initialized");
235         }
236         initialized = true;
237         this.docEnv = docEnv;
238         // Utils needs docEnv, safe to init now.
239         utils = new Utils(this);
240 
241         BaseOptions options = getOptions();
242         if (!options.javafx()) {
243             options.setJavaFX(isJavaFXMode());
244         }
245 
246         getDocResources().setKeyMapper(resourceKeyMapper);
247 
248         // Once docEnv and Utils have been initialized, others should be safe.
249         metakeywords = new MetaKeywords(this);
250         cmtUtils = new CommentUtils(this);
251         workArounds = new WorkArounds(this);
252         visibleMemberCache = new VisibleMemberCache(this);
253         propertyUtils = new PropertyUtils(this);
254 
255         Splitter specifiedSplitter = new Splitter(docEnv, false);
256         specifiedModuleElements = Collections.unmodifiableSet(specifiedSplitter.mset);
257         specifiedPackageElements = Collections.unmodifiableSet(specifiedSplitter.pset);
258         specifiedTypeElements = Collections.unmodifiableSet(specifiedSplitter.tset);
259 
260         Splitter includedSplitter = new Splitter(docEnv, true);
261         includedModuleElements = Collections.unmodifiableSet(includedSplitter.mset);
262         includedPackageElements = Collections.unmodifiableSet(includedSplitter.pset);
263         includedTypeElements = Collections.unmodifiableSet(includedSplitter.tset);
264     }
265 
266     /**
267      * Return the builder factory for this doclet.
268      *
269      * @return the builder factory for this doclet.
270      */
getBuilderFactory()271     public BuilderFactory getBuilderFactory() {
272         if (builderFactory == null) {
273             builderFactory = new BuilderFactory(this);
274         }
275         return builderFactory;
276     }
277 
getReporter()278     public Reporter getReporter() {
279         return this.reporter;
280     }
281 
282     private Set<ModuleElement> specifiedModuleElements;
283 
getSpecifiedModuleElements()284     public Set<ModuleElement> getSpecifiedModuleElements() {
285         return specifiedModuleElements;
286     }
287 
288     private Set<PackageElement> specifiedPackageElements;
289 
getSpecifiedPackageElements()290     public Set<PackageElement> getSpecifiedPackageElements() {
291         return specifiedPackageElements;
292     }
293 
294     private Set<TypeElement> specifiedTypeElements;
295 
getSpecifiedTypeElements()296     public Set<TypeElement> getSpecifiedTypeElements() {
297         return specifiedTypeElements;
298     }
299 
300     private Set<ModuleElement> includedModuleElements;
301 
getIncludedModuleElements()302     public Set<ModuleElement> getIncludedModuleElements() {
303         return includedModuleElements;
304     }
305 
306     private Set<PackageElement> includedPackageElements;
307 
getIncludedPackageElements()308     public Set<PackageElement> getIncludedPackageElements() {
309         return includedPackageElements;
310     }
311 
312     private Set<TypeElement> includedTypeElements;
313 
getIncludedTypeElements()314     public Set<TypeElement> getIncludedTypeElements() {
315         return includedTypeElements;
316     }
317 
initModules()318     private void initModules() {
319         Comparators comparators = utils.comparators;
320         // Build the modules structure used by the doclet
321         modules = new TreeSet<>(comparators.makeModuleComparator());
322         modules.addAll(getSpecifiedModuleElements());
323 
324         modulePackages = new TreeMap<>(comparators.makeModuleComparator());
325         for (PackageElement p : packages) {
326             ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p);
327             if (mdle != null && !mdle.isUnnamed()) {
328                 Set<PackageElement> s = modulePackages
329                         .computeIfAbsent(mdle, m -> new TreeSet<>(comparators.makePackageComparator()));
330                 s.add(p);
331             }
332         }
333 
334         for (PackageElement p : getIncludedPackageElements()) {
335             ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p);
336             if (mdle != null && !mdle.isUnnamed()) {
337                 Set<PackageElement> s = modulePackages
338                         .computeIfAbsent(mdle, m -> new TreeSet<>(comparators.makePackageComparator()));
339                 s.add(p);
340             }
341         }
342 
343         // add entries for modules which may not have exported packages
344         modules.forEach(mdle -> modulePackages.computeIfAbsent(mdle, m -> Collections.emptySet()));
345 
346         modules.addAll(modulePackages.keySet());
347         showModules = !modules.isEmpty();
348         for (Set<PackageElement> pkgs : modulePackages.values()) {
349             packages.addAll(pkgs);
350         }
351     }
352 
initPackages()353     private void initPackages() {
354         packages = new TreeSet<>(utils.comparators.makePackageComparator());
355         // add all the included packages
356         packages.addAll(includedPackageElements);
357     }
358 
359     /*
360      * when this is called all the option have been set, this method,
361      * initializes certain components before anything else is started.
362      */
finishOptionSettings0()363     protected boolean finishOptionSettings0() throws DocletException {
364         BaseOptions options = getOptions();
365         extern = new Extern(this);
366         initDestDirectory();
367         for (String link : options.linkList()) {
368             extern.link(link, reporter);
369         }
370         for (Pair<String, String> linkOfflinePair : options.linkOfflineList()) {
371             extern.link(linkOfflinePair.first, linkOfflinePair.second, reporter);
372         }
373         if (!options.noPlatformLinks()) {
374             extern.checkPlatformLinks(options.linkPlatformProperties(), reporter);
375         }
376         typeElementCatalog = new TypeElementCatalog(includedTypeElements, this);
377         initTagletManager(options.customTagStrs());
378         options.groupPairs().forEach(grp -> {
379             if (showModules) {
380                 group.checkModuleGroups(grp.first, grp.second);
381             } else {
382                 group.checkPackageGroups(grp.first, grp.second);
383             }
384         });
385 
386         PackageElement unnamedPackage;
387         Elements elementUtils = utils.elementUtils;
388         if (docEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_9) >= 0) {
389             ModuleElement unnamedModule = elementUtils.getModuleElement("");
390             unnamedPackage = elementUtils.getPackageElement(unnamedModule, "");
391         } else {
392             unnamedPackage = elementUtils.getPackageElement("");
393         }
394         overviewElement = new OverviewElement(unnamedPackage, getOverviewPath());
395         return true;
396     }
397 
398     /**
399      * Set the command-line options supported by this configuration.
400      *
401      * @return true if the options are set successfully
402      * @throws DocletException if there is a problem while setting the options
403      */
setOptions()404     public boolean setOptions() throws DocletException {
405         initPackages();
406         initModules();
407         return finishOptionSettings0()
408                 && finishOptionSettings();
409     }
410 
initDestDirectory()411     private void initDestDirectory() throws DocletException {
412         String destDirName = getOptions().destDirName();
413         if (!destDirName.isEmpty()) {
414             Messages messages = getMessages();
415             DocFile destDir = DocFile.createFileForDirectory(this, destDirName);
416             if (!destDir.exists()) {
417                 //Create the output directory (in case it doesn't exist yet)
418                 messages.notice("doclet.dest_dir_create", destDirName);
419                 destDir.mkdirs();
420             } else if (!destDir.isDirectory()) {
421                 throw new SimpleDocletException(messages.getResources().getText(
422                         "doclet.destination_directory_not_directory_0",
423                         destDir.getPath()));
424             } else if (!destDir.canWrite()) {
425                 throw new SimpleDocletException(messages.getResources().getText(
426                         "doclet.destination_directory_not_writable_0",
427                         destDir.getPath()));
428             }
429         }
430         DocFileFactory.getFactory(this).setDestDir(destDirName);
431     }
432 
433     /**
434      * Initialize the taglet manager.  The strings to initialize the simple custom tags should
435      * be in the following format:  "[tag name]:[location str]:[heading]".
436      *
437      * @param customTagStrs the set two dimensional arrays of strings.  These arrays contain
438      *                      either -tag or -taglet arguments.
439      */
initTagletManager(Set<List<String>> customTagStrs)440     private void initTagletManager(Set<List<String>> customTagStrs) {
441         tagletManager = tagletManager != null ? tagletManager : new TagletManager(this);
442         JavaFileManager fileManager = getFileManager();
443         Messages messages = getMessages();
444         try {
445             tagletManager.initTagletPath(fileManager);
446             tagletManager.loadTaglets(fileManager);
447 
448             for (List<String> args : customTagStrs) {
449                 if (args.get(0).equals("-taglet")) {
450                     tagletManager.addCustomTag(args.get(1), fileManager);
451                     continue;
452                 }
453                 List<String> tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3);
454                 switch (tokens.size()) {
455                     case 1:
456                         String tagName = args.get(1);
457                         if (tagletManager.isKnownCustomTag(tagName)) {
458                             //reorder a standard tag
459                             tagletManager.addNewSimpleCustomTag(tagName, null, "");
460                         } else {
461                             //Create a simple tag with the heading that has the same name as the tag.
462                             StringBuilder heading = new StringBuilder(tagName + ":");
463                             heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0)));
464                             tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a");
465                         }
466                         break;
467 
468                     case 2:
469                         //Add simple taglet without heading, probably to excluding it in the output.
470                         tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), "");
471                         break;
472 
473                     case 3:
474                         tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1));
475                         break;
476 
477                     default:
478                         messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1));
479                 }
480             }
481         } catch (IOException e) {
482             messages.error("doclet.taglet_could_not_set_location", e.toString());
483         }
484     }
485 
486     /**
487      * Given a string, return an array of tokens.  The separator can be escaped
488      * with the '\' character.  The '\' character may also be escaped by the
489      * '\' character.
490      *
491      * @param s         the string to tokenize.
492      * @param separator the separator char.
493      * @param maxTokens the maximum number of tokens returned.  If the
494      *                  max is reached, the remaining part of s is appended
495      *                  to the end of the last token.
496      * @return an array of tokens.
497      */
tokenize(String s, char separator, int maxTokens)498     private List<String> tokenize(String s, char separator, int maxTokens) {
499         List<String> tokens = new ArrayList<>();
500         StringBuilder token = new StringBuilder();
501         boolean prevIsEscapeChar = false;
502         for (int i = 0; i < s.length(); i += Character.charCount(i)) {
503             int currentChar = s.codePointAt(i);
504             if (prevIsEscapeChar) {
505                 // Case 1:  escaped character
506                 token.appendCodePoint(currentChar);
507                 prevIsEscapeChar = false;
508             } else if (currentChar == separator && tokens.size() < maxTokens - 1) {
509                 // Case 2:  separator
510                 tokens.add(token.toString());
511                 token = new StringBuilder();
512             } else if (currentChar == '\\') {
513                 // Case 3:  escape character
514                 prevIsEscapeChar = true;
515             } else {
516                 // Case 4:  regular character
517                 token.appendCodePoint(currentChar);
518             }
519         }
520         if (token.length() > 0) {
521             tokens.add(token.toString());
522         }
523         return tokens;
524     }
525 
526     /**
527      * Return true if the given doc-file subdirectory should be excluded and
528      * false otherwise.
529      *
530      * @param docfilesubdir the doc-files subdirectory to check.
531      * @return true if the directory is excluded.
532      */
shouldExcludeDocFileDir(String docfilesubdir)533     public boolean shouldExcludeDocFileDir(String docfilesubdir) {
534         Set<String> excludedDocFileDirs = getOptions().excludedDocFileDirs();
535         return excludedDocFileDirs.contains(docfilesubdir);
536     }
537 
538     /**
539      * Return true if the given qualifier should be excluded and false otherwise.
540      *
541      * @param qualifier the qualifier to check.
542      * @return true if the qualifier should be excluded
543      */
shouldExcludeQualifier(String qualifier)544     public boolean shouldExcludeQualifier(String qualifier) {
545         Set<String> excludedQualifiers = getOptions().excludedQualifiers();
546         if (excludedQualifiers.contains("all") ||
547                 excludedQualifiers.contains(qualifier) ||
548                 excludedQualifiers.contains(qualifier + ".*")) {
549             return true;
550         } else {
551             int index = -1;
552             while ((index = qualifier.indexOf(".", index + 1)) != -1) {
553                 if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) {
554                     return true;
555                 }
556             }
557             return false;
558         }
559     }
560 
561     /**
562      * Return the qualified name of the Element if its qualifier is not excluded.
563      * Otherwise return the unqualified Element name.
564      *
565      * @param te the TypeElement to check.
566      * @return the class name
567      */
getClassName(TypeElement te)568     public String getClassName(TypeElement te) {
569         PackageElement pkg = utils.containingPackage(te);
570         return shouldExcludeQualifier(utils.getPackageName(pkg))
571                 ? utils.getSimpleName(te)
572                 : utils.getFullyQualifiedName(te);
573     }
574 
575     /**
576      * Return true if the TypeElement element is getting documented, depending upon
577      * -nodeprecated option and the deprecation information. Return true if
578      * -nodeprecated is not used. Return false if -nodeprecated is used and if
579      * either TypeElement element is deprecated or the containing package is deprecated.
580      *
581      * @param te the TypeElement for which the page generation is checked
582      * @return true if it is a generated doc.
583      */
isGeneratedDoc(TypeElement te)584     public boolean isGeneratedDoc(TypeElement te) {
585         boolean nodeprecated = getOptions().noDeprecated();
586         if (!nodeprecated) {
587             return true;
588         }
589         return !(utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te)));
590     }
591 
592     /**
593      * Return the doclet specific instance of a writer factory.
594      *
595      * @return the {@link WriterFactory} for the doclet.
596      */
getWriterFactory()597     public abstract WriterFactory getWriterFactory();
598 
599     /**
600      * Return the Locale for this document.
601      *
602      * @return the current locale
603      */
getLocale()604     public abstract Locale getLocale();
605 
606     /**
607      * Return the path of the overview file and null if it does not exist.
608      *
609      * @return the path of the overview file.
610      */
getOverviewPath()611     public abstract JavaFileObject getOverviewPath();
612 
613     /**
614      * Return the current file manager.
615      *
616      * @return JavaFileManager
617      */
getFileManager()618     public abstract JavaFileManager getFileManager();
619 
showMessage(DocTreePath path, String key)620     public abstract boolean showMessage(DocTreePath path, String key);
621 
showMessage(Element e, String key)622     public abstract boolean showMessage(Element e, String key);
623 
624     /*
625      * Splits the elements in a collection to its individual
626      * collection.
627      */
628     private static class Splitter {
629 
630         final Set<ModuleElement> mset = new LinkedHashSet<>();
631         final Set<PackageElement> pset = new LinkedHashSet<>();
632         final Set<TypeElement> tset = new LinkedHashSet<>();
633 
Splitter(DocletEnvironment docEnv, boolean included)634         Splitter(DocletEnvironment docEnv, boolean included) {
635 
636             Set<? extends Element> inset = included
637                     ? docEnv.getIncludedElements()
638                     : docEnv.getSpecifiedElements();
639 
640             for (Element e : inset) {
641                 new SimpleElementVisitor14<Void, Void>() {
642                     @Override
643                     @DefinedBy(Api.LANGUAGE_MODEL)
644                     public Void visitModule(ModuleElement e, Void p) {
645                         mset.add(e);
646                         return null;
647                     }
648 
649                     @Override
650                     @DefinedBy(Api.LANGUAGE_MODEL)
651                     public Void visitPackage(PackageElement e, Void p) {
652                         pset.add(e);
653                         return null;
654                     }
655 
656                     @Override
657                     @DefinedBy(Api.LANGUAGE_MODEL)
658                     public Void visitType(TypeElement e, Void p) {
659                         tset.add(e);
660                         return null;
661                     }
662 
663                     @Override
664                     @DefinedBy(Api.LANGUAGE_MODEL)
665                     protected Void defaultAction(Element e, Void p) {
666                         throw new AssertionError("unexpected element: " + e);
667                     }
668 
669                 }.visit(e);
670             }
671         }
672     }
673 
674     /**
675      * Returns whether or not to allow JavaScript in comments.
676      * Default is off; can be set true from a command-line option.
677      *
678      * @return the allowScriptInComments
679      */
isAllowScriptInComments()680     public boolean isAllowScriptInComments() {
681         return getOptions().allowScriptInComments();
682     }
683 
getVisibleMemberTable(TypeElement te)684     public synchronized VisibleMemberTable getVisibleMemberTable(TypeElement te) {
685         return visibleMemberCache.getVisibleMemberTable(te);
686     }
687 
688     /**
689      * Determines if JavaFX is available in the compilation environment.
690      * @return true if JavaFX is available
691      */
isJavaFXMode()692     public boolean isJavaFXMode() {
693         TypeElement observable = utils.elementUtils.getTypeElement("javafx.beans.Observable");
694         if (observable == null) {
695             return false;
696         }
697         ModuleElement javafxModule = utils.elementUtils.getModuleOf(observable);
698         return javafxModule == null
699                 || javafxModule.isUnnamed()
700                 || javafxModule.getQualifiedName().contentEquals("javafx.base");
701     }
702 
703 
704     //<editor-fold desc="DocLint support">
705 
706     private DocLint doclint;
707 
708     Map<CompilationUnitTree, Boolean> shouldCheck = new HashMap<>();
709 
runDocLint(TreePath path)710     public void runDocLint(TreePath path) {
711         CompilationUnitTree unit = path.getCompilationUnit();
712         if (doclint != null && shouldCheck.computeIfAbsent(unit, doclint::shouldCheck)) {
713             doclint.scan(path);
714         }
715     }
716 
717     /**
718      * Initializes DocLint, if appropriate, depending on options derived
719      * from the doclet command-line options, and the set of custom tags
720      * that should be ignored by DocLint.
721      *
722      * DocLint is not enabled if the option {@code -Xmsgs:none} is given,
723      * and it is not followed by any options to enable any groups.
724      * Note that arguments for {@code -Xmsgs:} can be given individually
725      * in separate {@code -Xmsgs:} options, or in a comma-separated list
726      * for a single option. For example, the following are equivalent:
727      * <ul>
728      *     <li>{@code -Xmsgs:all} {@code -Xmsgs:-html}
729      *     <li>{@code -Xmsgs:all,-html}
730      * </ul>
731      *
732      * @param opts  options for DocLint, derived from the corresponding doclet
733      *              command-line options
734      * @param customTagNames the names of custom tags, to be ignored by doclint
735      */
initDocLint(List<String> opts, Set<String> customTagNames)736     public void initDocLint(List<String> opts, Set<String> customTagNames) {
737         List<String> doclintOpts = new ArrayList<>();
738 
739         // basic analysis of -Xmsgs and -Xmsgs: options to see if doclint is enabled
740         Set<String> groups = new HashSet<>();
741         boolean seenXmsgs = false;
742         for (String opt : opts) {
743             if (opt.equals(DocLint.XMSGS_OPTION)) {
744                 groups.add("all");
745                 seenXmsgs = true;
746             } else if (opt.startsWith(DocLint.XMSGS_CUSTOM_PREFIX)) {
747                 String[] args = opt.substring(DocLint.XMSGS_CUSTOM_PREFIX.length())
748                         .split(DocLint.SEPARATOR);
749                 for (String a : args) {
750                     if (a.equals("none")) {
751                         groups.clear();
752                     } else if (a.startsWith("-")) {
753                         groups.remove(a.substring(1));
754                     } else {
755                         groups.add(a);
756                     }
757                 }
758                 seenXmsgs = true;
759             }
760             doclintOpts.add(opt);
761         }
762 
763         if (seenXmsgs) {
764             if (groups.isEmpty()) {
765                 // no groups enabled; do not init doclint
766                 return;
767             }
768         } else {
769             // no -Xmsgs options of any kind, use default
770             doclintOpts.add(DocLint.XMSGS_OPTION);
771         }
772 
773         if (!customTagNames.isEmpty()) {
774             String customTags = String.join(DocLint.SEPARATOR, customTagNames);
775             doclintOpts.add(DocLint.XCUSTOM_TAGS_PREFIX + customTags);
776         }
777 
778         doclint = new DocLint();
779         doclint.init(docEnv.getDocTrees(), docEnv.getElementUtils(), docEnv.getTypeUtils(),
780                 doclintOpts.toArray(new String[0]));
781     }
782 
haveDocLint()783     public boolean haveDocLint() {
784         return (doclint != null);
785     }
786     //</editor-fold>
787 }
788