1 /*
   2  * Copyright (c) 2001, 2018, 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.taglets;
  27 
  28 import java.io.*;
  29 import java.util.*;
  30 
  31 import javax.lang.model.element.Element;
  32 import javax.lang.model.element.ExecutableElement;
  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.element.VariableElement;
  37 import javax.lang.model.util.SimpleElementVisitor9;
  38 import javax.tools.JavaFileManager;
  39 import javax.tools.StandardJavaFileManager;
  40 
  41 import com.sun.source.doctree.DocTree;
  42 import jdk.javadoc.doclet.Doclet;
  43 import jdk.javadoc.doclet.DocletEnvironment;
  44 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
  45 import jdk.javadoc.internal.doclets.toolkit.DocletElement;
  46 import jdk.javadoc.internal.doclets.toolkit.Messages;
  47 import jdk.javadoc.internal.doclets.toolkit.Resources;
  48 
  49 import jdk.javadoc.internal.doclets.toolkit.taglets.BaseTaglet.Site;
  50 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
  51 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
  52 
  53 import static javax.tools.DocumentationTool.Location.*;
  54 
  55 import static com.sun.source.doctree.DocTree.Kind.*;
  56 
  57 /**
  58  * Manages the {@code Taglet}s used by doclets.
  59  *
  60  *  <p><b>This is NOT part of any supported API.
  61  *  If you write code that depends on this, you do so at your own risk.
  62  *  This code and its internal interfaces are subject to change or
  63  *  deletion without notice.</b>
  64  *
  65  * @author Jamie Ho
  66  */
  67 
  68 public class TagletManager {
  69 
  70     /**
  71      * The default separator for the simple tag option.
  72      */
  73     public static final char SIMPLE_TAGLET_OPT_SEPARATOR = ':';
  74 
  75     /**
  76      * The map of all taglets.
  77      */
  78     private final LinkedHashMap<String,Taglet> allTaglets;
  79 
  80     /**
  81      * Block (non-line) taglets, grouped by Site
  82      */
  83     private Map<Site, List<Taglet>> blockTagletsBySite;
  84 
  85     /**
  86      * The taglets that can appear inline in descriptive text.
  87      */
  88     private List<Taglet> inlineTags;
  89 
  90     /**
  91      * The taglets that can appear in the serialized form.
  92      */
  93     private List<Taglet> serializedFormTags;
  94 
  95     private final DocletEnvironment docEnv;
  96     private final Doclet doclet;
  97 
  98     private final Utils utils;
  99     private final Messages messages;
 100     private final Resources resources;
 101 
 102     /**
 103      * Keep track of standard tags.
 104      */
 105     private final Set<String> standardTags;
 106 
 107     /**
 108      * Keep track of standard tags in lowercase to compare for better
 109      * error messages when a tag like @docRoot is mistakenly spelled
 110      * lowercase @docroot.
 111      */
 112     private final Set<String> standardTagsLowercase;
 113 
 114     /**
 115      * Keep track of overriden standard tags.
 116      */
 117     private final Set<String> overridenStandardTags;
 118 
 119     /**
 120      * Keep track of the tags that may conflict
 121      * with standard tags in the future (any custom tag without
 122      * a period in its name).
 123      */
 124     private final Set<String> potentiallyConflictingTags;
 125 
 126     /**
 127      * The set of unseen custom tags.
 128      */
 129     private final Set<String> unseenCustomTags;
 130 
 131     /**
 132      * True if we do not want to use @since tags.
 133      */
 134     private final boolean nosince;
 135 
 136     /**
 137      * True if we want to use @version tags.
 138      */
 139     private final boolean showversion;
 140 
 141     /**
 142      * True if we want to use @author tags.
 143      */
 144     private final boolean showauthor;
 145 
 146     /**
 147      * True if we want to use JavaFX-related tags (@defaultValue, @treatAsPrivate).
 148      */
 149     private final boolean javafx;
 150 
 151     /**
 152      * Show the taglets table when it has been initialized.
 153      */
 154     private final boolean showTaglets;
 155 
 156     /**
 157      * Construct a new {@code TagletManager}.
 158      * @param nosince true if we do not want to use @since tags.
 159      * @param showversion true if we want to use @version tags.
 160      * @param showauthor true if we want to use @author tags.
 161      * @param javafx indicates whether javafx is active.
 162      * @param configuration the configuration for this taglet manager
 163      */
 164     public TagletManager(boolean nosince, boolean showversion,
 165                          boolean showauthor, boolean javafx,
 166                          BaseConfiguration configuration) {
 167         overridenStandardTags = new HashSet<>();
 168         potentiallyConflictingTags = new HashSet<>();
 169         standardTags = new HashSet<>();
 170         standardTagsLowercase = new HashSet<>();
 171         unseenCustomTags = new HashSet<>();
 172         allTaglets = new LinkedHashMap<>();
 173         this.nosince = nosince;
 174         this.showversion = showversion;
 175         this.showauthor = showauthor;
 176         this.javafx = javafx;
 177         this.docEnv = configuration.docEnv;
 178         this.doclet = configuration.doclet;
 179         this.messages = configuration.getMessages();
 180         this.resources = configuration.getResources();
 181         this.showTaglets = configuration.showTaglets;
 182         this.utils = configuration.utils;
 183         initStandardTaglets();
 184     }
 185 
 186     /**
 187      * Add a new {@code Taglet}.  This is used to add a Taglet from within
 188      * a Doclet.  No message is printed to indicate that the Taglet is properly
 189      * registered because these Taglets are typically added for every execution of the
 190      * Doclet.  We don't want to see this type of error message every time.
 191      * @param customTag the new {@code Taglet} to add.
 192      */
 193     public void addCustomTag(Taglet customTag) {
 194         if (customTag != null) {
 195             String name = customTag.getName();
 196             allTaglets.remove(name);
 197             allTaglets.put(name, customTag);
 198             checkTagName(name);
 199         }
 200     }
 201 
 202     public Set<String> getAllTagletNames() {
 203         return allTaglets.keySet();
 204     }
 205 
 206     /**
 207      * Initializes the location TAGLET_PATH which is used to locate the custom taglets.
 208      * @param fileManager the filemanager to load classes and resources.
 209      * @param tagletPath the path to the custom taglet.
 210      * @throws IOException if an error occurs while setting the location.
 211      */
 212     public void initTagletPath(JavaFileManager fileManager, String tagletPath) throws IOException {
 213         if (fileManager instanceof StandardJavaFileManager) {
 214             StandardJavaFileManager sfm = (StandardJavaFileManager)fileManager;
 215             if (tagletPath != null) {
 216                 List<File> paths = new ArrayList<>();
 217                 for (String pathname : tagletPath.split(File.pathSeparator)) {
 218                     paths.add(new File(pathname));
 219                 }
 220                 sfm.setLocation(TAGLET_PATH, paths);
 221             } else if (!sfm.hasLocation(TAGLET_PATH)) {
 222                 sfm.setLocation(TAGLET_PATH, Collections.emptyList());
 223             }
 224         } else if (tagletPath != null) {
 225             messages.error("doclet.not_standard_file_manager");
 226         }
 227     }
 228 
 229     /**
 230      * Adds a new {@code Taglet}.  Print a message to indicate whether or not
 231      * the Taglet was registered properly.
 232      * @param classname  the name of the class representing the custom tag.
 233      * @param fileManager the filemanager to load classes and resources.
 234      */
 235     public void addCustomTag(String classname, JavaFileManager fileManager) {
 236         try {
 237             ClassLoader tagClassLoader;
 238             tagClassLoader = fileManager.getClassLoader(TAGLET_PATH);
 239             Class<? extends jdk.javadoc.doclet.Taglet> customTagClass =
 240                     tagClassLoader.loadClass(classname).asSubclass(jdk.javadoc.doclet.Taglet.class);
 241             jdk.javadoc.doclet.Taglet instance = customTagClass.getConstructor().newInstance();
 242             registerTaglet(instance);
 243         } catch (ReflectiveOperationException exc) {
 244             messages.error("doclet.Error_taglet_not_registered", exc.getClass().getName(),
 245                     classname);
 246         }
 247     }
 248 
 249     /**
 250      * Loads taglets from a taglet path using service loader.
 251      * @param fileManager the filemanager to load the taglets.
 252      * @throws IOException if an error occurs while getting the service loader.
 253      */
 254     public void loadTaglets(JavaFileManager fileManager) throws IOException {
 255         Iterable<? extends File> location = ((StandardJavaFileManager)fileManager).getLocation(TAGLET_PATH);
 256         if (location != null && location.iterator().hasNext()) {
 257             ServiceLoader<jdk.javadoc.doclet.Taglet> serviceLoader =
 258                     fileManager.getServiceLoader(TAGLET_PATH, jdk.javadoc.doclet.Taglet.class);
 259             Iterator<jdk.javadoc.doclet.Taglet> iterator = serviceLoader.iterator();
 260             while (iterator.hasNext()) {
 261                 jdk.javadoc.doclet.Taglet taglet = iterator.next();
 262                 registerTaglet(taglet);
 263             }
 264         }
 265     }
 266 
 267     /**
 268      * Registers the {@code Taglet}. Prints a message if a {@code Taglet} got registered properly.
 269      * @param instance the {@code Taglet} instance.
 270      */
 271     private void registerTaglet(jdk.javadoc.doclet.Taglet instance) {
 272         instance.init(docEnv, doclet);
 273         Taglet newLegacy = new UserTaglet(instance);
 274         allTaglets.put(newLegacy.getName(), newLegacy);
 275         messages.notice("doclet.Notice_taglet_registered", instance.getClass().getName());
 276     }
 277 
 278     /**
 279      * Add a new {@code SimpleTaglet}.  If this tag already exists
 280      * and the header passed as an argument is null, move tag to the back of the
 281      * list. If this tag already exists and the header passed as an argument is
 282      * not null, overwrite previous tag with new one.  Otherwise, add new
 283      * SimpleTaglet to list.
 284      * @param tagName the name of this tag
 285      * @param header the header to output.
 286      * @param locations the possible locations that this tag
 287      * can appear in.
 288      */
 289     public void addNewSimpleCustomTag(String tagName, String header, String locations) {
 290         if (tagName == null || locations == null) {
 291             return;
 292         }
 293         Taglet tag = allTaglets.get(tagName);
 294         if (tag == null || header != null) {
 295             allTaglets.remove(tagName);
 296             allTaglets.put(tagName, new SimpleTaglet(tagName, header, locations));
 297             if (Utils.toLowerCase(locations).indexOf('x') == -1) {
 298                 checkTagName(tagName);
 299             }
 300         } else {
 301             //Move to back
 302             allTaglets.remove(tagName);
 303             allTaglets.put(tagName, tag);
 304         }
 305     }
 306 
 307     /**
 308      * Given a tag name, add it to the set of tags it belongs to.
 309      */
 310     private void checkTagName(String name) {
 311         if (standardTags.contains(name)) {
 312             overridenStandardTags.add(name);
 313         } else {
 314             if (name.indexOf('.') == -1) {
 315                 potentiallyConflictingTags.add(name);
 316             }
 317             unseenCustomTags.add(name);
 318         }
 319     }
 320 
 321     /**
 322      * Given a name of a seen custom tag, remove it from the set of unseen
 323      * custom tags.
 324      * @param name the name of the seen custom tag.
 325      */
 326     void seenCustomTag(String name) {
 327         unseenCustomTags.remove(name);
 328     }
 329 
 330     /**
 331      * Given a series of {@code DocTree}s, check for spelling mistakes.
 332      * @param element the tags holder
 333      * @param trees the trees containing the comments
 334      * @param areInlineTags true if the array of tags are inline and false otherwise.
 335      */
 336     public void checkTags(Element element, Iterable<? extends DocTree> trees, boolean areInlineTags) {
 337         if (trees == null) {
 338             return;
 339         }
 340         CommentHelper ch = utils.getCommentHelper(element);
 341         for (DocTree tag : trees) {
 342             String name = tag.getKind().tagName;
 343             if (name == null) {
 344                 continue;
 345             }
 346             if (name.length() > 0 && name.charAt(0) == '@') {
 347                 name = name.substring(1, name.length());
 348             }
 349             if (! (standardTags.contains(name) || allTaglets.containsKey(name))) {
 350                 if (standardTagsLowercase.contains(Utils.toLowerCase(name))) {
 351                     messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTagLowercase", ch.getTagName(tag));
 352                     continue;
 353                 } else {
 354                     messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTag", ch.getTagName(tag));
 355                     continue;
 356                 }
 357             }
 358             final Taglet taglet = allTaglets.get(name);
 359             // Check and verify tag usage
 360             if (taglet != null) {
 361                 if (areInlineTags && !taglet.isInlineTag()) {
 362                     printTagMisuseWarn(ch, taglet, tag, "inline");
 363                 }
 364                 // nothing more to do
 365                 if (element == null) {
 366                     return;
 367                 }
 368                 new SimpleElementVisitor9<Void, Void>() {
 369                     @Override
 370                     public Void visitModule(ModuleElement e, Void p) {
 371                         if (!taglet.inModule()) {
 372                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "module");
 373                         }
 374                         return null;
 375                     }
 376 
 377                     @Override
 378                     public Void visitPackage(PackageElement e, Void p) {
 379                         if (!taglet.inPackage()) {
 380                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "package");
 381                         }
 382                         return null;
 383                     }
 384 
 385                     @Override
 386                     public Void visitType(TypeElement e, Void p) {
 387                         if (!taglet.inType()) {
 388                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "class");
 389                         }
 390                         return null;
 391                     }
 392 
 393                     @Override
 394                     public Void visitExecutable(ExecutableElement e, Void p) {
 395                         if (utils.isConstructor(e) && !taglet.inConstructor()) {
 396                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "constructor");
 397                         } else if (!taglet.inMethod()) {
 398                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "method");
 399                         }
 400                         return null;
 401                     }
 402 
 403                     @Override
 404                     public Void visitVariable(VariableElement e, Void p) {
 405                         if (utils.isField(e) && !taglet.inField()) {
 406                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "field");
 407                         }
 408                         return null;
 409                     }
 410 
 411                     @Override
 412                     public Void visitUnknown(Element e, Void p) {
 413                         if (utils.isOverviewElement(e) && !taglet.inOverview()) {
 414                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "overview");
 415                         }
 416                         return null;
 417                     }
 418 
 419                     @Override
 420                     protected Void defaultAction(Element e, Void p) {
 421                         return null;
 422                     }
 423                 }.visit(element);
 424             }
 425         }
 426     }
 427 
 428     /**
 429      * Given the taglet, the tag and the type of documentation that the tag
 430      * was found in, print a tag misuse warning.
 431      * @param taglet the taglet representing the misused tag.
 432      * @param tag the misused tag.
 433      * @param holderType the type of documentation that the misused tag was found in.
 434      */
 435     private void printTagMisuseWarn(CommentHelper ch, Taglet taglet, DocTree tag, String holderType) {
 436         Set<String> locationsSet = new LinkedHashSet<>();
 437         // The following names should be localized
 438         if (taglet.inOverview()) {
 439             locationsSet.add("overview");
 440         }
 441         if (taglet.inModule()) {
 442             locationsSet.add("module");
 443         }
 444         if (taglet.inPackage()) {
 445             locationsSet.add("package");
 446         }
 447         if (taglet.inType()) {
 448             locationsSet.add("class/interface");
 449         }
 450         if (taglet.inConstructor())  {
 451             locationsSet.add("constructor");
 452         }
 453         if (taglet.inField()) {
 454             locationsSet.add("field");
 455         }
 456         if (taglet.inMethod()) {
 457             locationsSet.add("method");
 458         }
 459         if (taglet.isInlineTag()) {
 460             locationsSet.add("inline text");
 461         }
 462         if (locationsSet.isEmpty()) {
 463             //This known tag is excluded.
 464             return;
 465         }
 466         StringBuilder combined_locations = new StringBuilder();
 467         for (String location: locationsSet) {
 468             if (combined_locations.length() > 0) {
 469                 combined_locations.append(", ");
 470             }
 471             combined_locations.append(location);
 472         }
 473         messages.warning(ch.getDocTreePath(tag), "doclet.tag_misuse",
 474             "@" + taglet.getName(), holderType, combined_locations.toString());
 475     }
 476 
 477     /**
 478      * Returns the taglets that can appear inline, in descriptive text.
 479      * @return the taglets that can appear inline
 480      */
 481     List<Taglet> getInlineTaglets() {
 482         if (inlineTags == null) {
 483             initBlockTaglets();
 484         }
 485         return inlineTags;
 486     }
 487 
 488     /**
 489      * Returns the taglets that can appear in the serialized form.
 490      * @return the taglet that can appear in the serialized form
 491      */
 492     public List<Taglet> getSerializedFormTaglets() {
 493         if (serializedFormTags == null) {
 494             initBlockTaglets();
 495         }
 496         return serializedFormTags;
 497     }
 498 
 499     /**
 500      * Returns the custom tags for a given element.
 501      *
 502      * @param e the element to get custom tags for
 503      * @return the array of {@code Taglet}s that can
 504      * appear in the given element.
 505      */
 506     @SuppressWarnings("fallthrough")
 507     public List<Taglet> getBlockTaglets(Element e) {
 508         if (blockTagletsBySite == null) {
 509             initBlockTaglets();
 510         }
 511 
 512         switch (e.getKind()) {
 513             case CONSTRUCTOR:
 514                 return blockTagletsBySite.get(Site.CONSTRUCTOR);
 515             case METHOD:
 516                 return blockTagletsBySite.get(Site.METHOD);
 517             case ENUM_CONSTANT:
 518             case FIELD:
 519                 return blockTagletsBySite.get(Site.FIELD);
 520             case ANNOTATION_TYPE:
 521             case INTERFACE:
 522             case CLASS:
 523             case ENUM:
 524                 return blockTagletsBySite.get(Site.TYPE);
 525             case MODULE:
 526                 return blockTagletsBySite.get(Site.MODULE);
 527             case PACKAGE:
 528                 return blockTagletsBySite.get(Site.PACKAGE);
 529             case OTHER:
 530                 if (e instanceof DocletElement) {
 531                     DocletElement de = (DocletElement)e;
 532                     switch (de.getSubKind()) {
 533                         case DOCFILE:
 534                             return blockTagletsBySite.get(Site.PACKAGE);
 535                         case OVERVIEW:
 536                             return blockTagletsBySite.get(Site.OVERVIEW);
 537                         default:
 538                             // fall through
 539                     }
 540                 }
 541                 // fall through
 542             default:
 543                 throw new AssertionError("unknown element: " + e + " ,kind: " + e.getKind());
 544         }
 545     }
 546 
 547     /**
 548      * Initialize the custom tag Lists.
 549      */
 550     private void initBlockTaglets() {
 551 
 552         blockTagletsBySite = new EnumMap<>(Site.class);
 553         for (Site site : Site.values()) {
 554             blockTagletsBySite.put(site, new ArrayList<>());
 555         }
 556 
 557         inlineTags = new ArrayList<>();
 558 
 559         for (Taglet current : allTaglets.values()) {
 560             if (current.isInlineTag()) {
 561                 inlineTags.add(current);
 562             } else {
 563                 if (current.inOverview()) {
 564                     blockTagletsBySite.get(Site.OVERVIEW).add(current);
 565                 }
 566                 if (current.inModule()) {
 567                     blockTagletsBySite.get(Site.MODULE).add(current);
 568                 }
 569                 if (current.inPackage()) {
 570                     blockTagletsBySite.get(Site.PACKAGE).add(current);
 571                 }
 572                 if (current.inType()) {
 573                     blockTagletsBySite.get(Site.TYPE).add(current);
 574                 }
 575                 if (current.inConstructor()) {
 576                     blockTagletsBySite.get(Site.CONSTRUCTOR).add(current);
 577                 }
 578                 if (current.inMethod()) {
 579                     blockTagletsBySite.get(Site.METHOD).add(current);
 580                 }
 581                 if (current.inField()) {
 582                     blockTagletsBySite.get(Site.FIELD).add(current);
 583                 }
 584             }
 585         }
 586 
 587         //Init the serialized form tags
 588         serializedFormTags = new ArrayList<>();
 589         serializedFormTags.add(allTaglets.get(SERIAL_DATA.tagName));
 590         serializedFormTags.add(allTaglets.get(THROWS.tagName));
 591         if (!nosince)
 592             serializedFormTags.add(allTaglets.get(SINCE.tagName));
 593         serializedFormTags.add(allTaglets.get(SEE.tagName));
 594 
 595         if (showTaglets) {
 596             showTaglets(System.out);
 597         }
 598     }
 599 
 600     /**
 601      * Initialize standard Javadoc tags for ordering purposes.
 602      */
 603     private void initStandardTaglets() {
 604         if (javafx) {
 605             initJavaFXTaglets();
 606         }
 607 
 608         addStandardTaglet(new ParamTaglet());
 609         addStandardTaglet(new ReturnTaglet());
 610         addStandardTaglet(new ThrowsTaglet());
 611         addStandardTaglet(
 612                 new SimpleTaglet(EXCEPTION.tagName, null,
 613                     EnumSet.of(Site.METHOD, Site.CONSTRUCTOR)));
 614         addStandardTaglet(
 615                 new SimpleTaglet(SINCE.tagName, resources.getText("doclet.Since"),
 616                     EnumSet.allOf(Site.class), !nosince));
 617         addStandardTaglet(
 618                 new SimpleTaglet(VERSION.tagName, resources.getText("doclet.Version"),
 619                     EnumSet.of(Site.OVERVIEW, Site.MODULE, Site.PACKAGE, Site.TYPE), showversion));
 620         addStandardTaglet(
 621                 new SimpleTaglet(AUTHOR.tagName, resources.getText("doclet.Author"),
 622                     EnumSet.of(Site.OVERVIEW, Site.MODULE, Site.PACKAGE, Site.TYPE), showauthor));
 623         addStandardTaglet(
 624                 new SimpleTaglet(SERIAL_DATA.tagName, resources.getText("doclet.SerialData"),
 625                     EnumSet.noneOf(Site.class)));
 626         addStandardTaglet(
 627                 new SimpleTaglet(HIDDEN.tagName, null,
 628                     EnumSet.of(Site.TYPE, Site.METHOD, Site.FIELD)));
 629 
 630         // This appears to be a default custom (non-standard) taglet
 631         Taglet factoryTaglet = new SimpleTaglet("factory", resources.getText("doclet.Factory"),
 632                 EnumSet.of(Site.METHOD));
 633         allTaglets.put(factoryTaglet.getName(), factoryTaglet);
 634 
 635         addStandardTaglet(new SeeTaglet());
 636 
 637         // Standard inline tags
 638         addStandardTaglet(new DocRootTaglet());
 639         addStandardTaglet(new InheritDocTaglet());
 640         addStandardTaglet(new ValueTaglet());
 641         addStandardTaglet(new LiteralTaglet());
 642         addStandardTaglet(new CodeTaglet());
 643         addStandardTaglet(new IndexTaglet());
 644         addStandardTaglet(new SummaryTaglet());
 645         addStandardTaglet(new AccessorTaglet(GETTER));
 646         addStandardTaglet(new AccessorTaglet(SETTER));
 647         addStandardTaglet(new SystemPropertyTaglet());
 648 
 649         // Keep track of the names of standard tags for error checking purposes.
 650         // The following are not handled above.
 651         addStandardTaglet(new DeprecatedTaglet());
 652         addStandardTaglet(new BaseTaglet(LINK.tagName, true, EnumSet.allOf(Site.class)));
 653         addStandardTaglet(new BaseTaglet(LINK_PLAIN.tagName, true, EnumSet.allOf(Site.class)));
 654         addStandardTaglet(new BaseTaglet(USES.tagName, false, EnumSet.of(Site.MODULE)));
 655         addStandardTaglet(new BaseTaglet(PROVIDES.tagName, false, EnumSet.of(Site.MODULE)));
 656         addStandardTaglet(
 657                 new SimpleTaglet(SERIAL.tagName, null,
 658                     EnumSet.of(Site.PACKAGE, Site.TYPE, Site.FIELD)));
 659         addStandardTaglet(
 660                 new SimpleTaglet(SERIAL_FIELD.tagName, null, EnumSet.of(Site.FIELD)));
 661     }
 662 
 663     /**
 664      * Initialize JavaFX-related tags.
 665      */
 666     private void initJavaFXTaglets() {
 667         addStandardTaglet(new PropertyGetterTaglet());
 668         addStandardTaglet(new PropertySetterTaglet());
 669         addStandardTaglet(new SimpleTaglet("propertyDescription",
 670                 resources.getText("doclet.PropertyDescription"),
 671                 EnumSet.of(Site.METHOD, Site.FIELD)));
 672         addStandardTaglet(new SimpleTaglet("defaultValue", resources.getText("doclet.DefaultValue"),
 673                 EnumSet.of(Site.METHOD, Site.FIELD)));
 674         addStandardTaglet(new SimpleTaglet("treatAsPrivate", null,
 675                 EnumSet.of(Site.TYPE, Site.METHOD, Site.FIELD)));
 676     }
 677 
 678     private void addStandardTaglet(Taglet taglet) {
 679         String name = taglet.getName();
 680         allTaglets.put(name, taglet);
 681         standardTags.add(name);
 682         standardTagsLowercase.add(Utils.toLowerCase(name));
 683     }
 684 
 685     public boolean isKnownCustomTag(String tagName) {
 686         return allTaglets.containsKey(tagName);
 687     }
 688 
 689     /**
 690      * Print a list of {@link Taglet}s that might conflict with
 691      * standard tags in the future and a list of standard tags
 692      * that have been overriden.
 693      */
 694     public void printReport() {
 695         printReportHelper("doclet.Notice_taglet_conflict_warn", potentiallyConflictingTags);
 696         printReportHelper("doclet.Notice_taglet_overriden", overridenStandardTags);
 697         printReportHelper("doclet.Notice_taglet_unseen", unseenCustomTags);
 698     }
 699 
 700     private void printReportHelper(String noticeKey, Set<String> names) {
 701         if (names.size() > 0) {
 702             StringBuilder result = new StringBuilder();
 703             for (String name : names) {
 704                 result.append(result.length() == 0 ? " " : ", ");
 705                 result.append("@").append(name);
 706             }
 707             messages.notice(noticeKey, result);
 708         }
 709     }
 710 
 711     /**
 712      * Given the name of a tag, return the corresponding taglet.
 713      * Return null if the tag is unknown.
 714      *
 715      * @param name the name of the taglet to retrieve.
 716      * @return return the corresponding taglet. Return null if the tag is
 717      *         unknown.
 718      */
 719     Taglet getTaglet(String name) {
 720         if (name.indexOf("@") == 0) {
 721             return allTaglets.get(name.substring(1));
 722         } else {
 723             return allTaglets.get(name);
 724         }
 725 
 726     }
 727 
 728     /*
 729      * The output of this method is the basis for a table at the end of the
 730      * doc comment specification, so any changes in the output may indicate
 731      * a need for a corresponding update to the spec.
 732      */
 733     private void showTaglets(PrintStream out) {
 734         Set<Taglet> taglets = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));
 735         taglets.addAll(allTaglets.values());
 736 
 737         for (Taglet t : taglets) {
 738             String name = t.isInlineTag() ? "{@" + t.getName() + "}" : "@" + t.getName();
 739             out.println(String.format("%20s", name) + ": "
 740                     + format(t.inOverview(), "overview") + " "
 741                     + format(t.inModule(), "module") + " "
 742                     + format(t.inPackage(), "package") + " "
 743                     + format(t.inType(), "type") + " "
 744                     + format(t.inConstructor(),"constructor") + " "
 745                     + format(t.inMethod(), "method") + " "
 746                     + format(t.inField(), "field") + " "
 747                     + format(t.isInlineTag(), "inline")+ " "
 748                     + format((t instanceof SimpleTaglet) && !((SimpleTaglet)t).enabled, "disabled"));
 749         }
 750     }
 751 
 752     private String format(boolean b, String s) {
 753         return b ? s : s.replaceAll(".", "."); // replace all with "."
 754     }
 755 }