001/* 002 * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit; 027 028import java.io.File; 029import java.io.IOException; 030import java.nio.file.InvalidPathException; 031import java.nio.file.Path; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Collections; 035import java.util.HashMap; 036import java.util.HashSet; 037import java.util.LinkedHashSet; 038import java.util.List; 039import java.util.Locale; 040import java.util.Map; 041import java.util.Set; 042import java.util.SortedMap; 043import java.util.SortedSet; 044import java.util.TreeMap; 045import java.util.TreeSet; 046import java.util.function.Function; 047 048import javax.lang.model.SourceVersion; 049import javax.lang.model.element.Element; 050import javax.lang.model.element.ModuleElement; 051import javax.lang.model.element.PackageElement; 052import javax.lang.model.element.TypeElement; 053import javax.lang.model.util.Elements; 054import javax.lang.model.util.SimpleElementVisitor14; 055import javax.tools.DocumentationTool; 056import javax.tools.JavaFileManager; 057import javax.tools.JavaFileObject; 058import javax.tools.StandardJavaFileManager; 059 060import org.jdrupes.mdoclet.internal.doclets.toolkit.builders.BuilderFactory; 061import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.TagletManager; 062import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Comparators; 063import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 064import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileFactory; 065import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Extern; 066import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Group; 067import org.jdrupes.mdoclet.internal.doclets.toolkit.util.MetaKeywords; 068import org.jdrupes.mdoclet.internal.doclets.toolkit.util.SimpleDocletException; 069import org.jdrupes.mdoclet.internal.doclets.toolkit.util.TypeElementCatalog; 070import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 071import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberCache; 072import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable; 073import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils.Pair; 074import org.jdrupes.mdoclet.internal.doclint.DocLint; 075import org.jdrupes.mdoclet.internal.doclint.Env; 076 077import com.sun.source.tree.CompilationUnitTree; 078import com.sun.source.util.TreePath; 079import com.sun.tools.javac.util.DefinedBy; 080import com.sun.tools.javac.util.DefinedBy.Api; 081import jdk.javadoc.doclet.Doclet; 082import jdk.javadoc.doclet.DocletEnvironment; 083import jdk.javadoc.doclet.Reporter; 084import jdk.javadoc.doclet.StandardDoclet; 085import jdk.javadoc.doclet.Taglet; 086 087/** 088 * Configure the output based on the options. Doclets should subclass 089 * BaseConfiguration, to configure and add their own options. This class contains 090 * all user options which are supported by the standard doclet. 091 */ 092public abstract class BaseConfiguration { 093 /** 094 * The doclet that created this configuration. 095 */ 096 public final Doclet doclet; 097 098 /** 099 * The factory for builders. 100 */ 101 protected BuilderFactory builderFactory; 102 103 /** 104 * The taglet manager. 105 */ 106 public TagletManager tagletManager; 107 108 /** 109 * The meta tag keywords instance. 110 */ 111 public MetaKeywords metakeywords; 112 113 /** 114 * The doclet environment. 115 */ 116 public DocletEnvironment docEnv; 117 118 /** 119 * An utility class for commonly used helpers 120 */ 121 public Utils utils; 122 123 /** 124 * All the temporary accessors to javac internals. 125 */ 126 public WorkArounds workArounds; 127 128 /** 129 * Sourcepath from where to read the source files. Default is classpath. 130 */ 131 public String sourcepath = ""; 132 133 /** 134 * Generate modules documentation if more than one module is present. 135 */ 136 public boolean showModules = false; 137 138 /** 139 * The catalog of classes specified on the command-line 140 */ 141 public TypeElementCatalog typeElementCatalog; 142 143 /** 144 * The package grouping instance. 145 */ 146 public final Group group = new Group(this); 147 148 /** 149 * The tracker of external package links. 150 */ 151 public Extern extern; 152 153 public final Reporter reporter; 154 155 public final Locale locale; 156 157 public abstract Messages getMessages(); 158 159 public abstract Resources getDocResources(); 160 161 /** 162 * Returns the version of the {@link #doclet doclet}. 163 * 164 * @return the version 165 */ 166 public abstract Runtime.Version getDocletVersion(); 167 168 /** 169 * This method should be defined in all those doclets (configurations), 170 * which want to derive themselves from this BaseConfiguration. This method 171 * can be used to finish up the options setup. 172 * 173 * @return true if successful and false otherwise 174 */ 175 176 public abstract boolean finishOptionSettings(); 177 178 public CommentUtils cmtUtils; 179 180 /** 181 * A sorted set of included packages. 182 */ 183 public SortedSet<PackageElement> packages = null; 184 185 public OverviewElement overviewElement; 186 187 public DocFileFactory docFileFactory; 188 189 /** 190 * A sorted map, giving the (specified|included|other) packages for each module. 191 */ 192 public SortedMap<ModuleElement, Set<PackageElement>> modulePackages; 193 194 /** 195 * The list of known modules, that should be documented. 196 */ 197 public SortedSet<ModuleElement> modules; 198 199 protected static final String sharedResourceBundleName 200 = "org.jdrupes.mdoclet.internal.doclets.toolkit.resources.doclets"; 201 202 private VisibleMemberCache visibleMemberCache; 203 204 public PropertyUtils propertyUtils = null; 205 206 /** 207 * Constructs the format-independent configuration needed by the doclet. 208 * 209 * @apiNote The {@code doclet} parameter is used when 210 * {@link Taglet#init(DocletEnvironment, Doclet) initializing tags}. 211 * Some doclets (such as the {@link StandardDoclet}), may delegate to another 212 * (such as the {@code HtmlDoclet}). In such cases, the primary doclet (i.e 213 * {@code StandardDoclet}) should be provided here, and not any internal 214 * class like {@code HtmlDoclet}. 215 * 216 * @param doclet the doclet for this run of javadoc 217 * @param locale the locale for the generated documentation 218 * @param reporter the reporter to use for console messages 219 */ 220 public BaseConfiguration(Doclet doclet, Locale locale, Reporter reporter) { 221 this.doclet = doclet; 222 this.locale = locale; 223 this.reporter = reporter; 224 } 225 226 public abstract BaseOptions getOptions(); 227 228 private boolean initialized = false; 229 230 protected void initConfiguration(DocletEnvironment docEnv, 231 Function<String, String> resourceKeyMapper) { 232 if (initialized) { 233 throw new IllegalStateException( 234 "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 257 = Collections.unmodifiableSet(specifiedSplitter.mset); 258 specifiedPackageElements 259 = Collections.unmodifiableSet(specifiedSplitter.pset); 260 specifiedTypeElements 261 = Collections.unmodifiableSet(specifiedSplitter.tset); 262 263 Splitter includedSplitter = new Splitter(docEnv, true); 264 includedModuleElements 265 = Collections.unmodifiableSet(includedSplitter.mset); 266 includedPackageElements 267 = Collections.unmodifiableSet(includedSplitter.pset); 268 includedTypeElements 269 = Collections.unmodifiableSet(includedSplitter.tset); 270 } 271 272 /** 273 * Return the builder factory for this doclet. 274 * 275 * @return the builder factory for this doclet. 276 */ 277 public BuilderFactory getBuilderFactory() { 278 if (builderFactory == null) { 279 builderFactory = new BuilderFactory(this); 280 } 281 return builderFactory; 282 } 283 284 public Reporter getReporter() { 285 return this.reporter; 286 } 287 288 private Set<ModuleElement> specifiedModuleElements; 289 290 public Set<ModuleElement> getSpecifiedModuleElements() { 291 return specifiedModuleElements; 292 } 293 294 private Set<PackageElement> specifiedPackageElements; 295 296 public Set<PackageElement> getSpecifiedPackageElements() { 297 return specifiedPackageElements; 298 } 299 300 private Set<TypeElement> specifiedTypeElements; 301 302 public Set<TypeElement> getSpecifiedTypeElements() { 303 return specifiedTypeElements; 304 } 305 306 private Set<ModuleElement> includedModuleElements; 307 308 public Set<ModuleElement> getIncludedModuleElements() { 309 return includedModuleElements; 310 } 311 312 private Set<PackageElement> includedPackageElements; 313 314 public Set<PackageElement> getIncludedPackageElements() { 315 return includedPackageElements; 316 } 317 318 private Set<TypeElement> includedTypeElements; 319 320 public Set<TypeElement> getIncludedTypeElements() { 321 return includedTypeElements; 322 } 323 324 private void initModules() { 325 Comparators comparators = utils.comparators; 326 // Build the modules structure used by the doclet 327 modules = new TreeSet<>(comparators.makeModuleComparator()); 328 modules.addAll(getSpecifiedModuleElements()); 329 330 modulePackages = new TreeMap<>(comparators.makeModuleComparator()); 331 for (PackageElement p : packages) { 332 ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); 333 if (mdle != null && !mdle.isUnnamed()) { 334 Set<PackageElement> s = modulePackages 335 .computeIfAbsent(mdle, m -> new TreeSet<>( 336 comparators.makePackageComparator())); 337 s.add(p); 338 } 339 } 340 341 for (PackageElement p : getIncludedPackageElements()) { 342 ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); 343 if (mdle != null && !mdle.isUnnamed()) { 344 Set<PackageElement> s = modulePackages 345 .computeIfAbsent(mdle, m -> new TreeSet<>( 346 comparators.makePackageComparator())); 347 s.add(p); 348 } 349 } 350 351 // add entries for modules which may not have exported packages 352 modules.forEach( 353 mdle -> modulePackages.computeIfAbsent(mdle, m -> Set.of())); 354 355 modules.addAll(modulePackages.keySet()); 356 showModules = !modules.isEmpty(); 357 for (Set<PackageElement> pkgs : modulePackages.values()) { 358 packages.addAll(pkgs); 359 } 360 } 361 362 private void initPackages() { 363 packages = new TreeSet<>(utils.comparators.makePackageComparator()); 364 // add all the included packages 365 packages.addAll(includedPackageElements); 366 } 367 368 /* 369 * when this is called all the option have been set, this method, 370 * initializes certain components before anything else is started. 371 */ 372 protected boolean finishOptionSettings0() throws DocletException { 373 BaseOptions options = getOptions(); 374 extern = new Extern(this); 375 initDestDirectory(); 376 for (String link : options.linkList()) { 377 extern.link(link, reporter); 378 } 379 for (Pair<String, String> linkOfflinePair : options.linkOfflineList()) { 380 extern.link(linkOfflinePair.first, linkOfflinePair.second, 381 reporter); 382 } 383 if (!options.noPlatformLinks()) { 384 extern.checkPlatformLinks(options.linkPlatformProperties(), 385 reporter); 386 } 387 typeElementCatalog = new TypeElementCatalog(includedTypeElements, this); 388 389 String snippetPath = options.snippetPath(); 390 if (snippetPath != null) { 391 Messages messages = getMessages(); 392 JavaFileManager fm = getFileManager(); 393 if (fm instanceof StandardJavaFileManager) { 394 try { 395 List<Path> sp 396 = Arrays.stream(snippetPath.split(File.pathSeparator)) 397 .map(Path::of) 398 .toList(); 399 StandardJavaFileManager sfm = (StandardJavaFileManager) fm; 400 sfm.setLocationFromPaths( 401 DocumentationTool.Location.SNIPPET_PATH, sp); 402 } catch (IOException | InvalidPathException e) { 403 throw new SimpleDocletException( 404 messages.getResources().getText( 405 "doclet.error_setting_snippet_path", snippetPath, 406 e), 407 e); 408 } 409 } else { 410 throw new SimpleDocletException(messages.getResources().getText( 411 "doclet.cannot_use_snippet_path", snippetPath)); 412 } 413 } 414 415 initTagletManager(options.customTagStrs()); 416 options.groupPairs().forEach(grp -> { 417 if (showModules) { 418 group.checkModuleGroups(grp.first, grp.second); 419 } else { 420 group.checkPackageGroups(grp.first, grp.second); 421 } 422 }); 423 424 PackageElement unnamedPackage; 425 Elements elementUtils = utils.elementUtils; 426 if (docEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_9) >= 0) { 427 ModuleElement unnamedModule = elementUtils.getModuleElement(""); 428 unnamedPackage = elementUtils.getPackageElement(unnamedModule, ""); 429 } else { 430 unnamedPackage = elementUtils.getPackageElement(""); 431 } 432 overviewElement 433 = new OverviewElement(unnamedPackage, getOverviewPath()); 434 return true; 435 } 436 437 /** 438 * Set the command-line options supported by this configuration. 439 * 440 * @return true if the options are set successfully 441 * @throws DocletException if there is a problem while setting the options 442 */ 443 public boolean setOptions() throws DocletException { 444 initPackages(); 445 initModules(); 446 return finishOptionSettings0() 447 && finishOptionSettings(); 448 } 449 450 private void initDestDirectory() throws DocletException { 451 String destDirName = getOptions().destDirName(); 452 if (!destDirName.isEmpty()) { 453 Messages messages = getMessages(); 454 DocFile destDir = DocFile.createFileForDirectory(this, destDirName); 455 if (!destDir.exists()) { 456 // Create the output directory (in case it doesn't exist yet) 457 messages.notice("doclet.dest_dir_create", destDirName); 458 destDir.mkdirs(); 459 } else if (!destDir.isDirectory()) { 460 throw new SimpleDocletException(messages.getResources().getText( 461 "doclet.destination_directory_not_directory_0", 462 destDir.getPath())); 463 } else if (!destDir.canWrite()) { 464 throw new SimpleDocletException(messages.getResources().getText( 465 "doclet.destination_directory_not_writable_0", 466 destDir.getPath())); 467 } 468 } 469 DocFileFactory.getFactory(this).setDestDir(destDirName); 470 } 471 472 /** 473 * Initialize the taglet manager. The strings to initialize the simple custom tags should 474 * be in the following format: "[tag name]:[location str]:[heading]". 475 * 476 * @param customTagStrs the set two dimensional arrays of strings. These arrays contain 477 * either -tag or -taglet arguments. 478 */ 479 private void initTagletManager(Set<List<String>> customTagStrs) { 480 tagletManager 481 = tagletManager != null ? tagletManager : new TagletManager(this); 482 JavaFileManager fileManager = getFileManager(); 483 Messages messages = getMessages(); 484 try { 485 tagletManager.initTagletPath(fileManager); 486 tagletManager.loadTaglets(fileManager); 487 488 for (List<String> args : customTagStrs) { 489 if (args.get(0).equals("-taglet")) { 490 tagletManager.addCustomTag(args.get(1), fileManager); 491 continue; 492 } 493 /* 494 * Since there are few constraints on the characters in a tag 495 * name, 496 * and real world examples with ':' in the tag name, we cannot 497 * simply use 498 * String.split(regex); instead, we tokenize the string, 499 * allowing 500 * special characters to be escaped with '\'. 501 */ 502 List<String> tokens = tokenize(args.get(1), 3); 503 switch (tokens.size()) { 504 case 1 -> { 505 String tagName = args.get(1); 506 if (tagletManager.isKnownCustomTag(tagName)) { 507 // reorder a standard tag 508 tagletManager.addNewSimpleCustomTag(tagName, null, ""); 509 } else { 510 // Create a simple tag with the heading that has the 511 // same name as the tag. 512 StringBuilder heading 513 = new StringBuilder(tagName + ":"); 514 heading.setCharAt(0, 515 Character.toUpperCase(tagName.charAt(0))); 516 tagletManager.addNewSimpleCustomTag(tagName, 517 heading.toString(), "a"); 518 } 519 } 520 521 case 2 -> 522 // Add simple taglet without heading, probably to 523 // excluding it in the output. 524 tagletManager.addNewSimpleCustomTag(tokens.get(0), 525 tokens.get(1), ""); 526 527 case 3 -> tagletManager.addNewSimpleCustomTag(tokens.get(0), 528 tokens.get(2), tokens.get(1)); 529 530 default -> messages.error( 531 "doclet.Error_invalid_custom_tag_argument", args.get(1)); 532 } 533 } 534 } catch (IOException e) { 535 messages.error("doclet.taglet_could_not_set_location", 536 e.toString()); 537 } 538 } 539 540 /** 541 * Given a string, return an array of tokens, separated by ':'. 542 * The separator character can be escaped with the '\' character. 543 * The '\' character may also be escaped with the '\' character. 544 * 545 * @param s the string to tokenize 546 * @param maxTokens the maximum number of tokens returned. If the 547 * max is reached, the remaining part of s is appended 548 * to the end of the last token. 549 * @return an array of tokens 550 */ 551 private List<String> tokenize(String s, int maxTokens) { 552 List<String> tokens = new ArrayList<>(); 553 StringBuilder token = new StringBuilder(); 554 boolean prevIsEscapeChar = false; 555 for (int i = 0; i < s.length(); i += Character.charCount(i)) { 556 int currentChar = s.codePointAt(i); 557 if (prevIsEscapeChar) { 558 // Case 1: escaped character 559 token.appendCodePoint(currentChar); 560 prevIsEscapeChar = false; 561 } else if (currentChar == ':' && tokens.size() < maxTokens - 1) { 562 // Case 2: separator 563 tokens.add(token.toString()); 564 token = new StringBuilder(); 565 } else if (currentChar == '\\') { 566 // Case 3: escape character 567 prevIsEscapeChar = true; 568 } else { 569 // Case 4: regular character 570 token.appendCodePoint(currentChar); 571 } 572 } 573 if (token.length() > 0) { 574 tokens.add(token.toString()); 575 } 576 return tokens; 577 } 578 579 /** 580 * Return true if the given doc-file subdirectory should be excluded and 581 * false otherwise. 582 * 583 * @param docfilesubdir the doc-files subdirectory to check. 584 * @return true if the directory is excluded. 585 */ 586 public boolean shouldExcludeDocFileDir(String docfilesubdir) { 587 Set<String> excludedDocFileDirs = getOptions().excludedDocFileDirs(); 588 return excludedDocFileDirs.contains(docfilesubdir); 589 } 590 591 /** 592 * Return true if the given qualifier should be excluded and false otherwise. 593 * 594 * @param qualifier the qualifier to check. 595 * @return true if the qualifier should be excluded 596 */ 597 public boolean shouldExcludeQualifier(String qualifier) { 598 Set<String> excludedQualifiers = getOptions().excludedQualifiers(); 599 if (excludedQualifiers.contains("all") || 600 excludedQualifiers.contains(qualifier) || 601 excludedQualifiers.contains(qualifier + ".*")) { 602 return true; 603 } else { 604 int index = -1; 605 while ((index = qualifier.indexOf(".", index + 1)) != -1) { 606 if (excludedQualifiers 607 .contains(qualifier.substring(0, index + 1) + "*")) { 608 return true; 609 } 610 } 611 return false; 612 } 613 } 614 615 /** 616 * Return the qualified name of the Element if its qualifier is not excluded. 617 * Otherwise return the unqualified Element name. 618 * 619 * @param te the TypeElement to check. 620 * @return the class name 621 */ 622 public String getClassName(TypeElement te) { 623 PackageElement pkg = utils.containingPackage(te); 624 return shouldExcludeQualifier(utils.getPackageName(pkg)) 625 ? utils.getSimpleName(te) 626 : utils.getFullyQualifiedName(te); 627 } 628 629 /** 630 * Return true if the TypeElement element is getting documented, depending upon 631 * -nodeprecated option and the deprecation information. Return true if 632 * -nodeprecated is not used. Return false if -nodeprecated is used and if 633 * either TypeElement element is deprecated or the containing package is deprecated. 634 * 635 * @param te the TypeElement for which the page generation is checked 636 * @return true if it is a generated doc. 637 */ 638 public boolean isGeneratedDoc(TypeElement te) { 639 boolean nodeprecated = getOptions().noDeprecated(); 640 if (!nodeprecated) { 641 return true; 642 } 643 return !(utils.isDeprecated(te) 644 || utils.isDeprecated(utils.containingPackage(te))); 645 } 646 647 /** 648 * Return the doclet specific instance of a writer factory. 649 * 650 * @return the {@link WriterFactory} for the doclet. 651 */ 652 public abstract WriterFactory getWriterFactory(); 653 654 /** 655 * Return the Locale for this document. 656 * 657 * @return the current locale 658 */ 659 public abstract Locale getLocale(); 660 661 /** 662 * Return the path of the overview file and null if it does not exist. 663 * 664 * @return the path of the overview file. 665 */ 666 public abstract JavaFileObject getOverviewPath(); 667 668 /** 669 * Return the current file manager. 670 * 671 * @return JavaFileManager 672 */ 673 public abstract JavaFileManager getFileManager(); 674 675 /* 676 * Splits the elements in a collection to its individual 677 * collection. 678 */ 679 private static class Splitter { 680 681 final Set<ModuleElement> mset = new LinkedHashSet<>(); 682 final Set<PackageElement> pset = new LinkedHashSet<>(); 683 final Set<TypeElement> tset = new LinkedHashSet<>(); 684 685 Splitter(DocletEnvironment docEnv, boolean included) { 686 687 Set<? extends Element> inset = included 688 ? docEnv.getIncludedElements() 689 : docEnv.getSpecifiedElements(); 690 691 for (Element e : inset) { 692 new SimpleElementVisitor14<Void, Void>() { 693 @Override 694 @DefinedBy(Api.LANGUAGE_MODEL) 695 public Void visitModule(ModuleElement e, Void p) { 696 mset.add(e); 697 return null; 698 } 699 700 @Override 701 @DefinedBy(Api.LANGUAGE_MODEL) 702 public Void visitPackage(PackageElement e, Void p) { 703 pset.add(e); 704 return null; 705 } 706 707 @Override 708 @DefinedBy(Api.LANGUAGE_MODEL) 709 public Void visitType(TypeElement e, Void p) { 710 tset.add(e); 711 return null; 712 } 713 714 @Override 715 @DefinedBy(Api.LANGUAGE_MODEL) 716 protected Void defaultAction(Element e, Void p) { 717 throw new AssertionError("unexpected element: " + e); 718 } 719 720 }.visit(e); 721 } 722 } 723 } 724 725 /** 726 * Returns whether or not to allow JavaScript in comments. 727 * Default is off; can be set true from a command-line option. 728 * 729 * @return the allowScriptInComments 730 */ 731 public boolean isAllowScriptInComments() { 732 return getOptions().allowScriptInComments(); 733 } 734 735 public VisibleMemberTable getVisibleMemberTable(TypeElement te) { 736 return visibleMemberCache.getVisibleMemberTable(te); 737 } 738 739 /** 740 * Determines if JavaFX is available in the compilation environment. 741 * @return true if JavaFX is available 742 */ 743 public boolean isJavaFXMode() { 744 TypeElement observable 745 = utils.elementUtils.getTypeElement("javafx.beans.Observable"); 746 if (observable == null) { 747 return false; 748 } 749 ModuleElement javafxModule = utils.elementUtils.getModuleOf(observable); 750 return javafxModule == null 751 || javafxModule.isUnnamed() 752 || javafxModule.getQualifiedName().contentEquals("javafx.base"); 753 } 754 755 // <editor-fold desc="DocLint support"> 756 757 private DocLint doclint; 758 759 Map<CompilationUnitTree, Boolean> shouldCheck = new HashMap<>(); 760 761 public void runDocLint(TreePath path) { 762 CompilationUnitTree unit = path.getCompilationUnit(); 763 if (doclint != null 764 && shouldCheck.computeIfAbsent(unit, doclint::shouldCheck)) { 765 doclint.scan(path); 766 } 767 } 768 769 /** 770 * Initializes DocLint, if appropriate, depending on options derived 771 * from the doclet command-line options, and the set of custom tags 772 * that should be ignored by DocLint. 773 * 774 * DocLint is not enabled if the option {@code -Xmsgs:none} is given, 775 * and it is not followed by any options to enable any groups. 776 * Note that arguments for {@code -Xmsgs:} can be given individually 777 * in separate {@code -Xmsgs:} options, or in a comma-separated list 778 * for a single option. For example, the following are equivalent: 779 * <ul> 780 * <li>{@code -Xmsgs:all} {@code -Xmsgs:-html} 781 * <li>{@code -Xmsgs:all,-html} 782 * </ul> 783 * 784 * @param opts options for DocLint, derived from the corresponding doclet 785 * command-line options 786 * @param customTagNames the names of custom tags, to be ignored by doclint 787 */ 788 public void initDocLint(List<String> opts, Set<String> customTagNames) { 789 List<String> doclintOpts = new ArrayList<>(); 790 791 // basic analysis of -Xmsgs and -Xmsgs: options to see if doclint is 792 // enabled 793 Set<String> groups = new HashSet<>(); 794 boolean seenXmsgs = false; 795 for (String opt : opts) { 796 if (opt.equals(DocLint.XMSGS_OPTION)) { 797 groups.add("all"); 798 seenXmsgs = true; 799 } else if (opt.startsWith(DocLint.XMSGS_CUSTOM_PREFIX)) { 800 String[] args 801 = opt.substring(DocLint.XMSGS_CUSTOM_PREFIX.length()) 802 .split(DocLint.SEPARATOR); 803 for (String a : args) { 804 if (a.equals("none")) { 805 groups.clear(); 806 } else if (a.startsWith("-")) { 807 groups.remove(a.substring(1)); 808 } else { 809 groups.add(a); 810 } 811 } 812 seenXmsgs = true; 813 } 814 doclintOpts.add(opt); 815 } 816 817 if (seenXmsgs) { 818 if (groups.isEmpty()) { 819 // no groups enabled; do not init doclint 820 return; 821 } 822 } else { 823 // no -Xmsgs options of any kind, use default 824 doclintOpts.add(DocLint.XMSGS_OPTION); 825 } 826 827 if (!customTagNames.isEmpty()) { 828 String customTags = String.join(DocLint.SEPARATOR, customTagNames); 829 doclintOpts.add(DocLint.XCUSTOM_TAGS_PREFIX + customTags); 830 } 831 832 doclint = new DocLint(); 833 doclint.init(docEnv.getDocTrees(), docEnv.getElementUtils(), 834 docEnv.getTypeUtils(), 835 doclintOpts.toArray(new String[0])); 836 } 837 838 public boolean isDocLintReferenceGroupEnabled() { 839 return isDocLintGroupEnabled( 840 org.jdrupes.mdoclet.internal.doclint.Messages.Group.REFERENCE); 841 } 842 843 public boolean isDocLintSyntaxGroupEnabled() { 844 return isDocLintGroupEnabled( 845 org.jdrupes.mdoclet.internal.doclint.Messages.Group.SYNTAX); 846 } 847 848 private boolean isDocLintGroupEnabled( 849 org.jdrupes.mdoclet.internal.doclint.Messages.Group group) { 850 // Use AccessKind.PUBLIC as a stand-in, since it is not common to 851 // set DocLint options per access kind (as is common with javac.) 852 // A more sophisticated solution might be to derive the access kind from 853 // the 854 // element owning the comment, and its enclosing elements. 855 return doclint != null 856 && doclint.isGroupEnabled(group, Env.AccessKind.PUBLIC); 857 } 858 // </editor-fold> 859}