1 /*
2  * Copyright (c) 2001, 2013, 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 com.sun.tools.doclets.internal.toolkit.taglets;
27 
28 import java.io.*;
29 import java.lang.reflect.*;
30 import java.net.*;
31 import java.util.*;
32 
33 import javax.tools.DocumentationTool;
34 import javax.tools.JavaFileManager;
35 
36 import com.sun.javadoc.*;
37 import com.sun.tools.doclets.internal.toolkit.util.*;
38 import com.sun.tools.javac.util.StringUtils;
39 
40 /**
41  * Manages the<code>Taglet</code>s used by doclets.
42  *
43  *  <p><b>This is NOT part of any supported API.
44  *  If you write code that depends on this, you do so at your own risk.
45  *  This code and its internal interfaces are subject to change or
46  *  deletion without notice.</b>
47  *
48  * @author Jamie Ho
49  * @since 1.4
50  */
51 
52 public class TagletManager {
53 
54     /**
55      * The default separator for the simple tag option.
56      */
57     public static final char SIMPLE_TAGLET_OPT_SEPARATOR = ':';
58 
59     /**
60      * The alternate separator for simple tag options.  Use this
61      * when you want the default separator to be in the name of the
62      * custom tag.
63      */
64     public static final String ALT_SIMPLE_TAGLET_OPT_SEPARATOR = "-";
65 
66     /**
67      * The map of custom tags.
68      */
69     private LinkedHashMap<String,Taglet> customTags;
70 
71     /**
72      * The array of custom tags that can appear in packages.
73      */
74     private Taglet[] packageTags;
75 
76     /**
77      * The array of custom tags that can appear in classes or interfaces.
78      */
79     private Taglet[] typeTags;
80 
81     /**
82      * The array of custom tags that can appear in fields.
83      */
84     private Taglet[] fieldTags;
85 
86     /**
87      * The array of custom tags that can appear in constructors.
88      */
89     private Taglet[] constructorTags;
90 
91     /**
92      * The array of custom tags that can appear in methods.
93      */
94     private Taglet[] methodTags;
95 
96     /**
97      * The array of custom tags that can appear in the overview.
98      */
99     private Taglet[] overviewTags;
100 
101     /**
102      * The array of custom tags that can appear in comments.
103      */
104     private Taglet[] inlineTags;
105 
106     /**
107      * The array of custom tags that can appear in the serialized form.
108      */
109     private Taglet[] serializedFormTags;
110 
111     /**
112      * The message retriever that will be used to print error messages.
113      */
114     private MessageRetriever message;
115 
116     /**
117      * Keep track of standard tags.
118      */
119     private Set<String> standardTags;
120 
121     /**
122      * Keep track of standard tags in lowercase to compare for better
123      * error messages when a tag like @docRoot is mistakenly spelled
124      * lowercase @docroot.
125      */
126     private Set<String> standardTagsLowercase;
127 
128     /**
129      * Keep track of overriden standard tags.
130      */
131     private Set<String> overridenStandardTags;
132 
133     /**
134      * Keep track of the tags that may conflict
135      * with standard tags in the future (any custom tag without
136      * a period in its name).
137      */
138     private Set<String> potentiallyConflictingTags;
139 
140     /**
141      * The set of unseen custom tags.
142      */
143     private Set<String> unseenCustomTags;
144 
145     /**
146      * True if we do not want to use @since tags.
147      */
148     private boolean nosince;
149 
150     /**
151      * True if we want to use @version tags.
152      */
153     private boolean showversion;
154 
155     /**
156      * True if we want to use @author tags.
157      */
158     private boolean showauthor;
159 
160     /**
161      * True if we want to use JavaFX-related tags (@propertyGetter,
162      * @propertySetter, @propertyDescription, @defaultValue, @treatAsPrivate).
163      */
164     private boolean javafx;
165 
166     /**
167      * Construct a new <code>TagletManager</code>.
168      * @param nosince true if we do not want to use @since tags.
169      * @param showversion true if we want to use @version tags.
170      * @param showauthor true if we want to use @author tags.
171      * @param message the message retriever to print warnings.
172      */
TagletManager(boolean nosince, boolean showversion, boolean showauthor, boolean javafx, MessageRetriever message)173     public TagletManager(boolean nosince, boolean showversion,
174                          boolean showauthor, boolean javafx,
175                          MessageRetriever message) {
176         overridenStandardTags = new HashSet<String>();
177         potentiallyConflictingTags = new HashSet<String>();
178         standardTags = new HashSet<String>();
179         standardTagsLowercase = new HashSet<String>();
180         unseenCustomTags = new HashSet<String>();
181         customTags = new LinkedHashMap<String,Taglet>();
182         this.nosince = nosince;
183         this.showversion = showversion;
184         this.showauthor = showauthor;
185         this.javafx = javafx;
186         this.message = message;
187         initStandardTaglets();
188         initStandardTagsLowercase();
189     }
190 
191     /**
192      * Add a new <code>CustomTag</code>.  This is used to add a Taglet from within
193      * a Doclet.  No message is printed to indicate that the Taglet is properly
194      * registered because these Taglets are typically added for every execution of the
195      * Doclet.  We don't want to see this type of error message every time.
196      * @param customTag the new <code>CustomTag</code> to add.
197      */
addCustomTag(Taglet customTag)198     public void addCustomTag(Taglet customTag) {
199         if (customTag != null) {
200             String name = customTag.getName();
201             if (customTags.containsKey(name)) {
202                 customTags.remove(name);
203             }
204             customTags.put(name, customTag);
205             checkTagName(name);
206         }
207     }
208 
getCustomTagNames()209     public Set<String> getCustomTagNames() {
210         return customTags.keySet();
211     }
212 
213     /**
214      * Add a new <code>Taglet</code>.  Print a message to indicate whether or not
215      * the Taglet was registered properly.
216      * @param classname  the name of the class representing the custom tag.
217      * @param tagletPath  the path to the class representing the custom tag.
218      */
addCustomTag(String classname, JavaFileManager fileManager, String tagletPath)219     public void addCustomTag(String classname, JavaFileManager fileManager, String tagletPath) {
220         try {
221             Class<?> customTagClass = null;
222             // construct class loader
223             String cpString = null;   // make sure env.class.path defaults to dot
224 
225             ClassLoader tagClassLoader;
226             if (fileManager != null && fileManager.hasLocation(DocumentationTool.Location.TAGLET_PATH)) {
227                 tagClassLoader = fileManager.getClassLoader(DocumentationTool.Location.TAGLET_PATH);
228             } else {
229                 // do prepends to get correct ordering
230                 cpString = appendPath(System.getProperty("env.class.path"), cpString);
231                 cpString = appendPath(System.getProperty("java.class.path"), cpString);
232                 cpString = appendPath(tagletPath, cpString);
233                 tagClassLoader = new URLClassLoader(pathToURLs(cpString));
234             }
235 
236             customTagClass = tagClassLoader.loadClass(classname);
237             Method meth = customTagClass.getMethod("register",
238                                                    new Class<?>[] {java.util.Map.class});
239             Object[] list = customTags.values().toArray();
240             Taglet lastTag = (list != null && list.length > 0)
241                 ? (Taglet) list[list.length-1] : null;
242             meth.invoke(null, new Object[] {customTags});
243             list = customTags.values().toArray();
244             Object newLastTag = (list != null&& list.length > 0)
245                 ? list[list.length-1] : null;
246             if (lastTag != newLastTag) {
247                 //New taglets must always be added to the end of the LinkedHashMap.
248                 //If the current and previous last taglet are not equal, that
249                 //means a new Taglet has been added.
250                 message.notice("doclet.Notice_taglet_registered", classname);
251                 if (newLastTag != null) {
252                     checkTaglet(newLastTag);
253                 }
254             }
255         } catch (Exception exc) {
256             message.error("doclet.Error_taglet_not_registered", exc.getClass().getName(), classname);
257         }
258 
259     }
260 
appendPath(String path1, String path2)261     private String appendPath(String path1, String path2) {
262         if (path1 == null || path1.length() == 0) {
263             return path2 == null ? "." : path2;
264         } else if (path2 == null || path2.length() == 0) {
265             return path1;
266         } else {
267             return path1  + File.pathSeparator + path2;
268         }
269     }
270 
271     /**
272      * Utility method for converting a search path string to an array
273      * of directory and JAR file URLs.
274      *
275      * @param path the search path string
276      * @return the resulting array of directory and JAR file URLs
277      */
pathToURLs(String path)278     private URL[] pathToURLs(String path) {
279         Set<URL> urls = new LinkedHashSet<URL>();
280         for (String s: path.split(File.pathSeparator)) {
281             if (s.isEmpty()) continue;
282             try {
283                 urls.add(new File(s).getAbsoluteFile().toURI().toURL());
284             } catch (MalformedURLException e) {
285                 message.error("doclet.MalformedURL", s);
286             }
287         }
288         return urls.toArray(new URL[urls.size()]);
289     }
290 
291 
292     /**
293      * Add a new <code>SimpleTaglet</code>.  If this tag already exists
294      * and the header passed as an argument is null, move tag to the back of the
295      * list. If this tag already exists and the header passed as an argument is
296      * not null, overwrite previous tag with new one.  Otherwise, add new
297      * SimpleTaglet to list.
298      * @param tagName the name of this tag
299      * @param header the header to output.
300      * @param locations the possible locations that this tag
301      * can appear in.
302      */
addNewSimpleCustomTag(String tagName, String header, String locations)303     public void addNewSimpleCustomTag(String tagName, String header, String locations) {
304         if (tagName == null || locations == null) {
305             return;
306         }
307         Taglet tag = customTags.get(tagName);
308         locations = StringUtils.toLowerCase(locations);
309         if (tag == null || header != null) {
310             customTags.remove(tagName);
311             customTags.put(tagName, new SimpleTaglet(tagName, header, locations));
312             if (locations != null && locations.indexOf('x') == -1) {
313                 checkTagName(tagName);
314             }
315         } else {
316             //Move to back
317             customTags.remove(tagName);
318             customTags.put(tagName, tag);
319         }
320     }
321 
322     /**
323      * Given a tag name, add it to the set of tags it belongs to.
324      */
checkTagName(String name)325     private void checkTagName(String name) {
326         if (standardTags.contains(name)) {
327             overridenStandardTags.add(name);
328         } else {
329             if (name.indexOf('.') == -1) {
330                 potentiallyConflictingTags.add(name);
331             }
332             unseenCustomTags.add(name);
333         }
334     }
335 
336     /**
337      * Check the taglet to see if it is a legacy taglet.  Also
338      * check its name for errors.
339      */
checkTaglet(Object taglet)340     private void checkTaglet(Object taglet) {
341         if (taglet instanceof Taglet) {
342             checkTagName(((Taglet) taglet).getName());
343         } else if (taglet instanceof com.sun.tools.doclets.Taglet) {
344             com.sun.tools.doclets.Taglet legacyTaglet = (com.sun.tools.doclets.Taglet) taglet;
345             customTags.remove(legacyTaglet.getName());
346             customTags.put(legacyTaglet.getName(), new LegacyTaglet(legacyTaglet));
347             checkTagName(legacyTaglet.getName());
348         } else {
349             throw new IllegalArgumentException("Given object is not a taglet.");
350         }
351     }
352 
353     /**
354      * Given a name of a seen custom tag, remove it from the set of unseen
355      * custom tags.
356      * @param name the name of the seen custom tag.
357      */
seenCustomTag(String name)358     public void seenCustomTag(String name) {
359         unseenCustomTags.remove(name);
360     }
361 
362     /**
363      * Given an array of <code>Tag</code>s, check for spelling mistakes.
364      * @param doc the Doc object that holds the tags.
365      * @param tags the list of <code>Tag</code>s to check.
366      * @param areInlineTags true if the array of tags are inline and false otherwise.
367      */
checkTags(Doc doc, Tag[] tags, boolean areInlineTags)368     public void checkTags(Doc doc, Tag[] tags, boolean areInlineTags) {
369         if (tags == null) {
370             return;
371         }
372         Taglet taglet;
373         for (int i = 0; i < tags.length; i++) {
374             String name = tags[i].name();
375             if (name.length() > 0 && name.charAt(0) == '@') {
376                 name = name.substring(1, name.length());
377             }
378             if (! (standardTags.contains(name) || customTags.containsKey(name))) {
379                 if (standardTagsLowercase.contains(StringUtils.toLowerCase(name))) {
380                     message.warning(tags[i].position(), "doclet.UnknownTagLowercase", tags[i].name());
381                     continue;
382                 } else {
383                     message.warning(tags[i].position(), "doclet.UnknownTag", tags[i].name());
384                     continue;
385                 }
386             }
387             //Check if this tag is being used in the wrong location.
388             if ((taglet = customTags.get(name)) != null) {
389                 if (areInlineTags && ! taglet.isInlineTag()) {
390                     printTagMisuseWarn(taglet, tags[i], "inline");
391                 }
392                 if ((doc instanceof RootDoc) && ! taglet.inOverview()) {
393                     printTagMisuseWarn(taglet, tags[i], "overview");
394                 } else if ((doc instanceof PackageDoc) && ! taglet.inPackage()) {
395                     printTagMisuseWarn(taglet, tags[i], "package");
396                 } else if ((doc instanceof ClassDoc) && ! taglet.inType()) {
397                     printTagMisuseWarn(taglet, tags[i], "class");
398                 } else if ((doc instanceof ConstructorDoc) && ! taglet.inConstructor()) {
399                     printTagMisuseWarn(taglet, tags[i], "constructor");
400                 } else if ((doc instanceof FieldDoc) && ! taglet.inField()) {
401                     printTagMisuseWarn(taglet, tags[i], "field");
402                 } else if ((doc instanceof MethodDoc) && ! taglet.inMethod()) {
403                     printTagMisuseWarn(taglet, tags[i], "method");
404                 }
405             }
406         }
407     }
408 
409     /**
410      * Given the taglet, the tag and the type of documentation that the tag
411      * was found in, print a tag misuse warning.
412      * @param taglet the taglet representing the misused tag.
413      * @param tag the misused tag.
414      * @param holderType the type of documentation that the misused tag was found in.
415      */
printTagMisuseWarn(Taglet taglet, Tag tag, String holderType)416     private void printTagMisuseWarn(Taglet taglet, Tag tag, String holderType) {
417         Set<String> locationsSet = new LinkedHashSet<String>();
418         if (taglet.inOverview()) {
419             locationsSet.add("overview");
420         }
421         if (taglet.inPackage()) {
422             locationsSet.add("package");
423         }
424         if (taglet.inType()) {
425             locationsSet.add("class/interface");
426         }
427         if (taglet.inConstructor())  {
428             locationsSet.add("constructor");
429         }
430         if (taglet.inField()) {
431             locationsSet.add("field");
432         }
433         if (taglet.inMethod()) {
434             locationsSet.add("method");
435         }
436         if (taglet.isInlineTag()) {
437             locationsSet.add("inline text");
438         }
439         String[] locations = locationsSet.toArray(new String[]{});
440         if (locations == null || locations.length == 0) {
441             //This known tag is excluded.
442             return;
443         }
444         StringBuilder combined_locations = new StringBuilder();
445         for (int i = 0; i < locations.length; i++) {
446             if (i > 0) {
447                 combined_locations.append(", ");
448             }
449             combined_locations.append(locations[i]);
450         }
451         message.warning(tag.position(), "doclet.tag_misuse",
452             "@" + taglet.getName(), holderType, combined_locations.toString());
453     }
454 
455     /**
456      * Return the array of <code>Taglet</code>s that can
457      * appear in packages.
458      * @return the array of <code>Taglet</code>s that can
459      * appear in packages.
460      */
getPackageCustomTaglets()461     public Taglet[] getPackageCustomTaglets() {
462         if (packageTags == null) {
463             initCustomTagletArrays();
464         }
465         return packageTags;
466     }
467 
468     /**
469      * Return the array of <code>Taglet</code>s that can
470      * appear in classes or interfaces.
471      * @return the array of <code>Taglet</code>s that can
472      * appear in classes or interfaces.
473      */
getTypeCustomTaglets()474     public Taglet[] getTypeCustomTaglets() {
475         if (typeTags == null) {
476             initCustomTagletArrays();
477         }
478         return typeTags;
479     }
480 
481     /**
482      * Return the array of inline <code>Taglet</code>s that can
483      * appear in comments.
484      * @return the array of <code>Taglet</code>s that can
485      * appear in comments.
486      */
getInlineCustomTaglets()487     public Taglet[] getInlineCustomTaglets() {
488         if (inlineTags == null) {
489             initCustomTagletArrays();
490         }
491         return inlineTags;
492     }
493 
494     /**
495      * Return the array of <code>Taglet</code>s that can
496      * appear in fields.
497      * @return the array of <code>Taglet</code>s that can
498      * appear in field.
499      */
getFieldCustomTaglets()500     public Taglet[] getFieldCustomTaglets() {
501         if (fieldTags == null) {
502             initCustomTagletArrays();
503         }
504         return fieldTags;
505     }
506 
507     /**
508      * Return the array of <code>Taglet</code>s that can
509      * appear in the serialized form.
510      * @return the array of <code>Taglet</code>s that can
511      * appear in the serialized form.
512      */
getSerializedFormTaglets()513     public Taglet[] getSerializedFormTaglets() {
514         if (serializedFormTags == null) {
515             initCustomTagletArrays();
516         }
517         return serializedFormTags;
518     }
519 
520     /**
521      * @return the array of <code>Taglet</code>s that can
522      * appear in the given Doc.
523      */
getCustomTaglets(Doc doc)524     public Taglet[] getCustomTaglets(Doc doc) {
525         if (doc instanceof ConstructorDoc) {
526             return getConstructorCustomTaglets();
527         } else if (doc instanceof MethodDoc) {
528             return getMethodCustomTaglets();
529         } else if (doc instanceof FieldDoc) {
530             return getFieldCustomTaglets();
531         } else if (doc instanceof ClassDoc) {
532             return getTypeCustomTaglets();
533         } else if (doc instanceof PackageDoc) {
534             return getPackageCustomTaglets();
535         } else if (doc instanceof RootDoc) {
536             return getOverviewCustomTaglets();
537         }
538         return null;
539     }
540 
541     /**
542      * Return the array of <code>Taglet</code>s that can
543      * appear in constructors.
544      * @return the array of <code>Taglet</code>s that can
545      * appear in constructors.
546      */
getConstructorCustomTaglets()547     public Taglet[] getConstructorCustomTaglets() {
548         if (constructorTags == null) {
549             initCustomTagletArrays();
550         }
551         return constructorTags;
552     }
553 
554     /**
555      * Return the array of <code>Taglet</code>s that can
556      * appear in methods.
557      * @return the array of <code>Taglet</code>s that can
558      * appear in methods.
559      */
getMethodCustomTaglets()560     public Taglet[] getMethodCustomTaglets() {
561         if (methodTags == null) {
562             initCustomTagletArrays();
563         }
564         return methodTags;
565     }
566 
567     /**
568      * Return the array of <code>Taglet</code>s that can
569      * appear in an overview.
570      * @return the array of <code>Taglet</code>s that can
571      * appear in overview.
572      */
getOverviewCustomTaglets()573     public Taglet[] getOverviewCustomTaglets() {
574         if (overviewTags == null) {
575             initCustomTagletArrays();
576         }
577         return overviewTags;
578     }
579 
580     /**
581      * Initialize the custom tag arrays.
582      */
initCustomTagletArrays()583     private void initCustomTagletArrays() {
584         Iterator<Taglet> it = customTags.values().iterator();
585         ArrayList<Taglet> pTags = new ArrayList<Taglet>(customTags.size());
586         ArrayList<Taglet> tTags = new ArrayList<Taglet>(customTags.size());
587         ArrayList<Taglet> fTags = new ArrayList<Taglet>(customTags.size());
588         ArrayList<Taglet> cTags = new ArrayList<Taglet>(customTags.size());
589         ArrayList<Taglet> mTags = new ArrayList<Taglet>(customTags.size());
590         ArrayList<Taglet> iTags = new ArrayList<Taglet>(customTags.size());
591         ArrayList<Taglet> oTags = new ArrayList<Taglet>(customTags.size());
592         ArrayList<Taglet> sTags = new ArrayList<Taglet>();
593         Taglet current;
594         while (it.hasNext()) {
595             current = it.next();
596             if (current.inPackage() && !current.isInlineTag()) {
597                 pTags.add(current);
598             }
599             if (current.inType() && !current.isInlineTag()) {
600                 tTags.add(current);
601             }
602             if (current.inField() && !current.isInlineTag()) {
603                 fTags.add(current);
604             }
605             if (current.inConstructor() && !current.isInlineTag()) {
606                 cTags.add(current);
607             }
608             if (current.inMethod() && !current.isInlineTag()) {
609                 mTags.add(current);
610             }
611             if (current.isInlineTag()) {
612                 iTags.add(current);
613             }
614             if (current.inOverview() && !current.isInlineTag()) {
615                 oTags.add(current);
616             }
617         }
618         packageTags = pTags.toArray(new Taglet[] {});
619         typeTags = tTags.toArray(new Taglet[] {});
620         fieldTags = fTags.toArray(new Taglet[] {});
621         constructorTags = cTags.toArray(new Taglet[] {});
622         methodTags = mTags.toArray(new Taglet[] {});
623         overviewTags = oTags.toArray(new Taglet[] {});
624         inlineTags = iTags.toArray(new Taglet[] {});
625 
626         //Init the serialized form tags
627         sTags.add(customTags.get("serialData"));
628         sTags.add(customTags.get("throws"));
629         if (!nosince)
630             sTags.add(customTags.get("since"));
631         sTags.add(customTags.get("see"));
632         serializedFormTags = sTags.toArray(new Taglet[] {});
633     }
634 
635     /**
636      * Initialize standard Javadoc tags for ordering purposes.
637      */
initStandardTaglets()638     private void initStandardTaglets() {
639         if (javafx) {
640             initJavaFXTaglets();
641         }
642 
643         Taglet temp;
644         addStandardTaglet(new ParamTaglet());
645         addStandardTaglet(new ReturnTaglet());
646         addStandardTaglet(new ThrowsTaglet());
647         addStandardTaglet(new SimpleTaglet("exception", null,
648                 SimpleTaglet.METHOD + SimpleTaglet.CONSTRUCTOR));
649         addStandardTaglet(!nosince, new SimpleTaglet("since", message.getText("doclet.Since"),
650                SimpleTaglet.ALL));
651         addStandardTaglet(showversion, new SimpleTaglet("version", message.getText("doclet.Version"),
652                 SimpleTaglet.PACKAGE + SimpleTaglet.TYPE + SimpleTaglet.OVERVIEW));
653         addStandardTaglet(showauthor, new SimpleTaglet("author", message.getText("doclet.Author"),
654                 SimpleTaglet.PACKAGE + SimpleTaglet.TYPE + SimpleTaglet.OVERVIEW));
655         addStandardTaglet(new SimpleTaglet("serialData", message.getText("doclet.SerialData"),
656             SimpleTaglet.EXCLUDED));
657         customTags.put((temp = new SimpleTaglet("factory", message.getText("doclet.Factory"),
658             SimpleTaglet.METHOD)).getName(), temp);
659         addStandardTaglet(new SeeTaglet());
660         //Standard inline tags
661         addStandardTaglet(new DocRootTaglet());
662         addStandardTaglet(new InheritDocTaglet());
663         addStandardTaglet(new ValueTaglet());
664         addStandardTaglet(new LiteralTaglet());
665         addStandardTaglet(new CodeTaglet());
666 
667         // Keep track of the names of standard tags for error
668         // checking purposes. The following are not handled above.
669         // See, for example, com.sun.tools.javadoc.Comment
670         standardTags.add("deprecated");
671         standardTags.add("link");
672         standardTags.add("linkplain");
673         standardTags.add("serial");
674         standardTags.add("serialField");
675         standardTags.add("Text");
676     }
677 
678     /**
679      * Initialize JavaFX-related tags.
680      */
initJavaFXTaglets()681     private void initJavaFXTaglets() {
682         addStandardTaglet(new PropertyGetterTaglet());
683         addStandardTaglet(new PropertySetterTaglet());
684         addStandardTaglet(new SimpleTaglet("propertyDescription",
685                 message.getText("doclet.PropertyDescription"),
686                 SimpleTaglet.FIELD + SimpleTaglet.METHOD));
687         addStandardTaglet(new SimpleTaglet("defaultValue", message.getText("doclet.DefaultValue"),
688             SimpleTaglet.FIELD + SimpleTaglet.METHOD));
689         addStandardTaglet(new SimpleTaglet("treatAsPrivate", null,
690                 SimpleTaglet.FIELD + SimpleTaglet.METHOD + SimpleTaglet.TYPE));
691     }
692 
addStandardTaglet(Taglet taglet)693     void addStandardTaglet(Taglet taglet) {
694         String name = taglet.getName();
695         customTags.put(name, taglet);
696         standardTags.add(name);
697     }
698 
addStandardTaglet(boolean enable, Taglet taglet)699     void addStandardTaglet(boolean enable, Taglet taglet) {
700         String name = taglet.getName();
701         if (enable)
702             customTags.put(name, taglet);
703         standardTags.add(name);
704     }
705 
706     /**
707      * Initialize lowercase version of standard Javadoc tags.
708      */
initStandardTagsLowercase()709     private void initStandardTagsLowercase() {
710         Iterator<String> it = standardTags.iterator();
711         while (it.hasNext()) {
712             standardTagsLowercase.add(StringUtils.toLowerCase(it.next()));
713         }
714     }
715 
isKnownCustomTag(String tagName)716     public boolean isKnownCustomTag(String tagName) {
717         return customTags.containsKey(tagName);
718     }
719 
720     /**
721      * Print a list of {@link Taglet}s that might conflict with
722      * standard tags in the future and a list of standard tags
723      * that have been overriden.
724      */
printReport()725     public void printReport() {
726         printReportHelper("doclet.Notice_taglet_conflict_warn", potentiallyConflictingTags);
727         printReportHelper("doclet.Notice_taglet_overriden", overridenStandardTags);
728         printReportHelper("doclet.Notice_taglet_unseen", unseenCustomTags);
729     }
730 
printReportHelper(String noticeKey, Set<String> names)731     private void printReportHelper(String noticeKey, Set<String> names) {
732         if (names.size() > 0) {
733             String[] namesArray = names.toArray(new String[] {});
734             String result = " ";
735             for (int i = 0; i < namesArray.length; i++) {
736                 result += "@" + namesArray[i];
737                 if (i + 1 < namesArray.length) {
738                     result += ", ";
739                 }
740             }
741             message.notice(noticeKey, result);
742         }
743     }
744 
745     /**
746      * Given the name of a tag, return the corresponding taglet.
747      * Return null if the tag is unknown.
748      *
749      * @param name the name of the taglet to retrieve.
750      * @return return the corresponding taglet. Return null if the tag is
751      *         unknown.
752      */
getTaglet(String name)753     public Taglet getTaglet(String name) {
754         if (name.indexOf("@") == 0) {
755             return customTags.get(name.substring(1));
756         } else {
757             return customTags.get(name);
758         }
759 
760     }
761 }
762