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      * Add a new {@code Taglet}.  Print a message to indicate whether or not
 208      * the Taglet was registered properly.
 209      * @param classname  the name of the class representing the custom tag.
 210      * @param fileManager the filemanager to load classes and resources.
 211      * @param tagletPath  the path to the class representing the custom tag.
 212      */
 213     public void addCustomTag(String classname, JavaFileManager fileManager, String tagletPath) {
 214         try {
 215             ClassLoader tagClassLoader;
 216             if (!fileManager.hasLocation(TAGLET_PATH)) {
 217                 List<File> paths = new ArrayList<>();
 218                 if (tagletPath != null) {
 219                     for (String pathname : tagletPath.split(File.pathSeparator)) {
 220                         paths.add(new File(pathname));
 221                     }
 222                 }
 223                 if (fileManager instanceof StandardJavaFileManager) {
 224                     ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, paths);
 225                 }
 226             }
 227             tagClassLoader = fileManager.getClassLoader(TAGLET_PATH);
 228             Class<? extends jdk.javadoc.doclet.Taglet> customTagClass =
 229                     tagClassLoader.loadClass(classname).asSubclass(jdk.javadoc.doclet.Taglet.class);
 230             jdk.javadoc.doclet.Taglet instance = customTagClass.getConstructor().newInstance();
 231             instance.init(docEnv, doclet);
 232             Taglet newLegacy = new UserTaglet(instance);
 233             String tname = newLegacy.getName();
 234             Taglet t = allTaglets.get(tname);
 235             if (t != null) {
 236                 allTaglets.remove(tname);
 237             }
 238             allTaglets.put(tname, newLegacy);
 239             messages.notice("doclet.Notice_taglet_registered", classname);
 240         } catch (Exception exc) {
 241             messages.error("doclet.Error_taglet_not_registered", exc.getClass().getName(), classname);
 242         }
 243     }
 244 
 245     /**
 246      * Add a new {@code SimpleTaglet}.  If this tag already exists
 247      * and the header passed as an argument is null, move tag to the back of the
 248      * list. If this tag already exists and the header passed as an argument is
 249      * not null, overwrite previous tag with new one.  Otherwise, add new
 250      * SimpleTaglet to list.
 251      * @param tagName the name of this tag
 252      * @param header the header to output.
 253      * @param locations the possible locations that this tag
 254      * can appear in.
 255      */
 256     public void addNewSimpleCustomTag(String tagName, String header, String locations) {
 257         if (tagName == null || locations == null) {
 258             return;
 259         }
 260         Taglet tag = allTaglets.get(tagName);
 261         if (tag == null || header != null) {
 262             allTaglets.remove(tagName);
 263             allTaglets.put(tagName, new SimpleTaglet(tagName, header, locations));
 264             if (Utils.toLowerCase(locations).indexOf('x') == -1) {
 265                 checkTagName(tagName);
 266             }
 267         } else {
 268             //Move to back
 269             allTaglets.remove(tagName);
 270             allTaglets.put(tagName, tag);
 271         }
 272     }
 273 
 274     /**
 275      * Given a tag name, add it to the set of tags it belongs to.
 276      */
 277     private void checkTagName(String name) {
 278         if (standardTags.contains(name)) {
 279             overridenStandardTags.add(name);
 280         } else {
 281             if (name.indexOf('.') == -1) {
 282                 potentiallyConflictingTags.add(name);
 283             }
 284             unseenCustomTags.add(name);
 285         }
 286     }
 287 
 288     /**
 289      * Given a name of a seen custom tag, remove it from the set of unseen
 290      * custom tags.
 291      * @param name the name of the seen custom tag.
 292      */
 293     void seenCustomTag(String name) {
 294         unseenCustomTags.remove(name);
 295     }
 296 
 297     /**
 298      * Given a series of {@code DocTree}s, check for spelling mistakes.
 299      * @param element the tags holder
 300      * @param trees the trees containing the comments
 301      * @param areInlineTags true if the array of tags are inline and false otherwise.
 302      */
 303     public void checkTags(Element element, Iterable<? extends DocTree> trees, boolean areInlineTags) {
 304         if (trees == null) {
 305             return;
 306         }
 307         CommentHelper ch = utils.getCommentHelper(element);
 308         for (DocTree tag : trees) {
 309             String name = tag.getKind().tagName;
 310             if (name == null) {
 311                 continue;
 312             }
 313             if (name.length() > 0 && name.charAt(0) == '@') {
 314                 name = name.substring(1, name.length());
 315             }
 316             if (! (standardTags.contains(name) || allTaglets.containsKey(name))) {
 317                 if (standardTagsLowercase.contains(Utils.toLowerCase(name))) {
 318                     messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTagLowercase", ch.getTagName(tag));
 319                     continue;
 320                 } else {
 321                     messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTag", ch.getTagName(tag));
 322                     continue;
 323                 }
 324             }
 325             final Taglet taglet = allTaglets.get(name);
 326             // Check and verify tag usage
 327             if (taglet != null) {
 328                 if (areInlineTags && !taglet.isInlineTag()) {
 329                     printTagMisuseWarn(ch, taglet, tag, "inline");
 330                 }
 331                 // nothing more to do
 332                 if (element == null) {
 333                     return;
 334                 }
 335                 new SimpleElementVisitor9<Void, Void>() {
 336                     @Override
 337                     public Void visitModule(ModuleElement e, Void p) {
 338                         if (!taglet.inModule()) {
 339                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "module");
 340                         }
 341                         return null;
 342                     }
 343 
 344                     @Override
 345                     public Void visitPackage(PackageElement e, Void p) {
 346                         if (!taglet.inPackage()) {
 347                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "package");
 348                         }
 349                         return null;
 350                     }
 351 
 352                     @Override
 353                     public Void visitType(TypeElement e, Void p) {
 354                         if (!taglet.inType()) {
 355                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "class");
 356                         }
 357                         return null;
 358                     }
 359 
 360                     @Override
 361                     public Void visitExecutable(ExecutableElement e, Void p) {
 362                         if (utils.isConstructor(e) && !taglet.inConstructor()) {
 363                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "constructor");
 364                         } else if (!taglet.inMethod()) {
 365                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "method");
 366                         }
 367                         return null;
 368                     }
 369 
 370                     @Override
 371                     public Void visitVariable(VariableElement e, Void p) {
 372                         if (utils.isField(e) && !taglet.inField()) {
 373                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "field");
 374                         }
 375                         return null;
 376                     }
 377 
 378                     @Override
 379                     public Void visitUnknown(Element e, Void p) {
 380                         if (utils.isOverviewElement(e) && !taglet.inOverview()) {
 381                             printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "overview");
 382                         }
 383                         return null;
 384                     }
 385 
 386                     @Override
 387                     protected Void defaultAction(Element e, Void p) {
 388                         return null;
 389                     }
 390                 }.visit(element);
 391             }
 392         }
 393     }
 394 
 395     /**
 396      * Given the taglet, the tag and the type of documentation that the tag
 397      * was found in, print a tag misuse warning.
 398      * @param taglet the taglet representing the misused tag.
 399      * @param tag the misused tag.
 400      * @param holderType the type of documentation that the misused tag was found in.
 401      */
 402     private void printTagMisuseWarn(CommentHelper ch, Taglet taglet, DocTree tag, String holderType) {
 403         Set<String> locationsSet = new LinkedHashSet<>();
 404         // The following names should be localized
 405         if (taglet.inOverview()) {
 406             locationsSet.add("overview");
 407         }
 408         if (taglet.inModule()) {
 409             locationsSet.add("module");
 410         }
 411         if (taglet.inPackage()) {
 412             locationsSet.add("package");
 413         }
 414         if (taglet.inType()) {
 415             locationsSet.add("class/interface");
 416         }
 417         if (taglet.inConstructor())  {
 418             locationsSet.add("constructor");
 419         }
 420         if (taglet.inField()) {
 421             locationsSet.add("field");
 422         }
 423         if (taglet.inMethod()) {
 424             locationsSet.add("method");
 425         }
 426         if (taglet.isInlineTag()) {
 427             locationsSet.add("inline text");
 428         }
 429         if (locationsSet.isEmpty()) {
 430             //This known tag is excluded.
 431             return;
 432         }
 433         StringBuilder combined_locations = new StringBuilder();
 434         for (String location: locationsSet) {
 435             if (combined_locations.length() > 0) {
 436                 combined_locations.append(", ");
 437             }
 438             combined_locations.append(location);
 439         }
 440         messages.warning(ch.getDocTreePath(tag), "doclet.tag_misuse",
 441             "@" + taglet.getName(), holderType, combined_locations.toString());
 442     }
 443 
 444     /**
 445      * Returns the taglets that can appear inline, in descriptive text.
 446      * @return the taglets that can appear inline
 447      */
 448     List<Taglet> getInlineTaglets() {
 449         if (inlineTags == null) {
 450             initBlockTaglets();
 451         }
 452         return inlineTags;
 453     }
 454 
 455     /**
 456      * Returns the taglets that can appear in the serialized form.
 457      * @return the taglet that can appear in the serialized form
 458      */
 459     public List<Taglet> getSerializedFormTaglets() {
 460         if (serializedFormTags == null) {
 461             initBlockTaglets();
 462         }
 463         return serializedFormTags;
 464     }
 465 
 466     /**
 467      * Returns the custom tags for a given element.
 468      *
 469      * @param e the element to get custom tags for
 470      * @return the array of {@code Taglet}s that can
 471      * appear in the given element.
 472      */
 473     @SuppressWarnings("fallthrough")
 474     public List<Taglet> getBlockTaglets(Element e) {
 475         if (blockTagletsBySite == null) {
 476             initBlockTaglets();
 477         }
 478 
 479         switch (e.getKind()) {
 480             case CONSTRUCTOR:
 481                 return blockTagletsBySite.get(Site.CONSTRUCTOR);
 482             case METHOD:
 483                 return blockTagletsBySite.get(Site.METHOD);
 484             case ENUM_CONSTANT:
 485             case FIELD:
 486                 return blockTagletsBySite.get(Site.FIELD);
 487             case ANNOTATION_TYPE:
 488             case INTERFACE:
 489             case CLASS:
 490             case ENUM:
 491                 return blockTagletsBySite.get(Site.TYPE);
 492             case MODULE:
 493                 return blockTagletsBySite.get(Site.MODULE);
 494             case PACKAGE:
 495                 return blockTagletsBySite.get(Site.PACKAGE);
 496             case OTHER:
 497                 if (e instanceof DocletElement) {
 498                     DocletElement de = (DocletElement)e;
 499                     switch (de.getSubKind()) {
 500                         case DOCFILE:
 501                             return blockTagletsBySite.get(Site.PACKAGE);
 502                         case OVERVIEW:
 503                             return blockTagletsBySite.get(Site.OVERVIEW);
 504                         default:
 505                             // fall through
 506                     }
 507                 }
 508                 // fall through
 509             default:
 510                 throw new AssertionError("unknown element: " + e + " ,kind: " + e.getKind());
 511         }
 512     }
 513 
 514     /**
 515      * Initialize the custom tag Lists.
 516      */
 517     private void initBlockTaglets() {
 518 
 519         blockTagletsBySite = new EnumMap<>(Site.class);
 520         for (Site site : Site.values()) {
 521             blockTagletsBySite.put(site, new ArrayList<>());
 522         }
 523 
 524         inlineTags = new ArrayList<>();
 525 
 526         for (Taglet current : allTaglets.values()) {
 527             if (current.isInlineTag()) {
 528                 inlineTags.add(current);
 529             } else {
 530                 if (current.inOverview()) {
 531                     blockTagletsBySite.get(Site.OVERVIEW).add(current);
 532                 }
 533                 if (current.inModule()) {
 534                     blockTagletsBySite.get(Site.MODULE).add(current);
 535                 }
 536                 if (current.inPackage()) {
 537                     blockTagletsBySite.get(Site.PACKAGE).add(current);
 538                 }
 539                 if (current.inType()) {
 540                     blockTagletsBySite.get(Site.TYPE).add(current);
 541                 }
 542                 if (current.inConstructor()) {
 543                     blockTagletsBySite.get(Site.CONSTRUCTOR).add(current);
 544                 }
 545                 if (current.inMethod()) {
 546                     blockTagletsBySite.get(Site.METHOD).add(current);
 547                 }
 548                 if (current.inField()) {
 549                     blockTagletsBySite.get(Site.FIELD).add(current);
 550                 }
 551             }
 552         }
 553 
 554         //Init the serialized form tags
 555         serializedFormTags = new ArrayList<>();
 556         serializedFormTags.add(allTaglets.get(SERIAL_DATA.tagName));
 557         serializedFormTags.add(allTaglets.get(THROWS.tagName));
 558         if (!nosince)
 559             serializedFormTags.add(allTaglets.get(SINCE.tagName));
 560         serializedFormTags.add(allTaglets.get(SEE.tagName));
 561 
 562         if (showTaglets) {
 563             showTaglets(System.out);
 564         }
 565     }
 566 
 567     /**
 568      * Initialize standard Javadoc tags for ordering purposes.
 569      */
 570     private void initStandardTaglets() {
 571         if (javafx) {
 572             initJavaFXTaglets();
 573         }
 574 
 575         addStandardTaglet(new ParamTaglet());
 576         addStandardTaglet(new ReturnTaglet());
 577         addStandardTaglet(new ThrowsTaglet());
 578         addStandardTaglet(
 579                 new SimpleTaglet(EXCEPTION.tagName, null,
 580                     EnumSet.of(Site.METHOD, Site.CONSTRUCTOR)));
 581         addStandardTaglet(
 582                 new SimpleTaglet(SINCE.tagName, resources.getText("doclet.Since"),
 583                     EnumSet.allOf(Site.class), !nosince));
 584         addStandardTaglet(
 585                 new SimpleTaglet(VERSION.tagName, resources.getText("doclet.Version"),
 586                     EnumSet.of(Site.OVERVIEW, Site.MODULE, Site.PACKAGE, Site.TYPE), showversion));
 587         addStandardTaglet(
 588                 new SimpleTaglet(AUTHOR.tagName, resources.getText("doclet.Author"),
 589                     EnumSet.of(Site.OVERVIEW, Site.MODULE, Site.PACKAGE, Site.TYPE), showauthor));
 590         addStandardTaglet(
 591                 new SimpleTaglet(SERIAL_DATA.tagName, resources.getText("doclet.SerialData"),
 592                     EnumSet.noneOf(Site.class)));
 593         addStandardTaglet(
 594                 new SimpleTaglet(HIDDEN.tagName, null,
 595                     EnumSet.of(Site.TYPE, Site.METHOD, Site.FIELD)));
 596 
 597         // This appears to be a default custom (non-standard) taglet
 598         Taglet factoryTaglet = new SimpleTaglet("factory", resources.getText("doclet.Factory"),
 599                 EnumSet.of(Site.METHOD));
 600         allTaglets.put(factoryTaglet.getName(), factoryTaglet);
 601 
 602         addStandardTaglet(new SeeTaglet());
 603 
 604         // Standard inline tags
 605         addStandardTaglet(new DocRootTaglet());
 606         addStandardTaglet(new InheritDocTaglet());
 607         addStandardTaglet(new ValueTaglet());
 608         addStandardTaglet(new LiteralTaglet());
 609         addStandardTaglet(new CodeTaglet());
 610         addStandardTaglet(new IndexTaglet());
 611         addStandardTaglet(new SummaryTaglet());
 612         addStandardTaglet(new SystemPropertyTaglet());
 613 
 614         // Keep track of the names of standard tags for error checking purposes.
 615         // The following are not handled above.
 616         addStandardTaglet(new DeprecatedTaglet());
 617         addStandardTaglet(new BaseTaglet(LINK.tagName, true, EnumSet.allOf(Site.class)));
 618         addStandardTaglet(new BaseTaglet(LINK_PLAIN.tagName, true, EnumSet.allOf(Site.class)));
 619         addStandardTaglet(new BaseTaglet(USES.tagName, false, EnumSet.of(Site.MODULE)));
 620         addStandardTaglet(new BaseTaglet(PROVIDES.tagName, false, EnumSet.of(Site.MODULE)));
 621         addStandardTaglet(
 622                 new SimpleTaglet(SERIAL.tagName, null,
 623                     EnumSet.of(Site.PACKAGE, Site.TYPE, Site.FIELD)));
 624         addStandardTaglet(
 625                 new SimpleTaglet(SERIAL_FIELD.tagName, null, EnumSet.of(Site.FIELD)));
 626     }
 627 
 628     /**
 629      * Initialize JavaFX-related tags.
 630      */
 631     private void initJavaFXTaglets() {
 632         addStandardTaglet(new PropertyGetterTaglet());
 633         addStandardTaglet(new PropertySetterTaglet());
 634         addStandardTaglet(new SimpleTaglet("propertyDescription",
 635                 resources.getText("doclet.PropertyDescription"),
 636                 EnumSet.of(Site.METHOD, Site.FIELD)));
 637         addStandardTaglet(new SimpleTaglet("defaultValue", resources.getText("doclet.DefaultValue"),
 638                 EnumSet.of(Site.METHOD, Site.FIELD)));
 639         addStandardTaglet(new SimpleTaglet("treatAsPrivate", null,
 640                 EnumSet.of(Site.TYPE, Site.METHOD, Site.FIELD)));
 641     }
 642 
 643     private void addStandardTaglet(Taglet taglet) {
 644         String name = taglet.getName();
 645         allTaglets.put(name, taglet);
 646         standardTags.add(name);
 647         standardTagsLowercase.add(Utils.toLowerCase(name));
 648     }
 649 
 650     public boolean isKnownCustomTag(String tagName) {
 651         return allTaglets.containsKey(tagName);
 652     }
 653 
 654     /**
 655      * Print a list of {@link Taglet}s that might conflict with
 656      * standard tags in the future and a list of standard tags
 657      * that have been overriden.
 658      */
 659     public void printReport() {
 660         printReportHelper("doclet.Notice_taglet_conflict_warn", potentiallyConflictingTags);
 661         printReportHelper("doclet.Notice_taglet_overriden", overridenStandardTags);
 662         printReportHelper("doclet.Notice_taglet_unseen", unseenCustomTags);
 663     }
 664 
 665     private void printReportHelper(String noticeKey, Set<String> names) {
 666         if (names.size() > 0) {
 667             StringBuilder result = new StringBuilder();
 668             for (String name : names) {
 669                 result.append(result.length() == 0 ? " " : ", ");
 670                 result.append("@").append(name);
 671             }
 672             messages.notice(noticeKey, result);
 673         }
 674     }
 675 
 676     /**
 677      * Given the name of a tag, return the corresponding taglet.
 678      * Return null if the tag is unknown.
 679      *
 680      * @param name the name of the taglet to retrieve.
 681      * @return return the corresponding taglet. Return null if the tag is
 682      *         unknown.
 683      */
 684     Taglet getTaglet(String name) {
 685         if (name.indexOf("@") == 0) {
 686             return allTaglets.get(name.substring(1));
 687         } else {
 688             return allTaglets.get(name);
 689         }
 690 
 691     }
 692 
 693     /*
 694      * The output of this method is the basis for a table at the end of the
 695      * doc comment specification, so any changes in the output may indicate
 696      * a need for a corresponding update to the spec.
 697      */
 698     private void showTaglets(PrintStream out) {
 699         Set<Taglet> taglets = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));
 700         taglets.addAll(allTaglets.values());
 701 
 702         for (Taglet t : taglets) {
 703             String name = t.isInlineTag() ? "{@" + t.getName() + "}" : "@" + t.getName();
 704             out.println(String.format("%20s", name) + ": "
 705                     + format(t.inOverview(), "overview") + " "
 706                     + format(t.inModule(), "module") + " "
 707                     + format(t.inPackage(), "package") + " "
 708                     + format(t.inType(), "type") + " "
 709                     + format(t.inConstructor(),"constructor") + " "
 710                     + format(t.inMethod(), "method") + " "
 711                     + format(t.inField(), "field") + " "
 712                     + format(t.isInlineTag(), "inline")+ " "
 713                     + format((t instanceof SimpleTaglet) && !((SimpleTaglet)t).enabled, "disabled"));
 714         }
 715     }
 716 
 717     private String format(boolean b, String s) {
 718         return b ? s : s.replaceAll(".", "."); // replace all with "."
 719     }
 720 }