JavaDoc 编程,书写自定义的 Taglet 支持 @unmi 等

javadoc 可为我们的 Java 项目生成 API 文档,别人的应该是看得多了,自己的可能不好意思晾出来看。那 Java 源代码里的 @author, @see, @param 等应该是司空见惯了吧。除此之外我们还可以自定义自己的 tag,并让它们的内容按照我们需要的格式生成到 javadoc 文档中,或作他用。还记得没有 Maven 的时代我们是怎样用 XDoclet 生映射文件的吗?现在的 Taglet 定制想要做的事情大抵如此。


执行一下 javadoc 命令看看,一堆的参数可以指定,又有学问在里头,且看:

-tag <name>:<locations>:<header>  Specify single argument custom tags
-taglet                           The fully qualified name of Taglet to register
-tagletpath                       The path to Taglets



-doclet <class>           Generate output via alternate doclet
-docletpath <path>        Specify where to find doclet class files

关于 doclet 部份这儿暂且不说,单讲 tag 部分的东西。

对于自定义 tag,简单的时候,用参数 -tag  都可以不写自己的 taglet 类,例如有这样一个代码:
 1public class TestJavaDocTag {
 2
 3    /**
 4     * @param args input command arguments
 5     * @document help
 6     * yourself
 7     */
 8    public static void main(String[] args) {
 9
10    }
11}

上面使用了 @document 自定义 tag,要为它生成文档,可以用命令:

javadoc -tag document:a:Document: *.java

于是生成的 javadoc 文档中有了:


-tag 参数的 name, header 部分一对照就知道了,中间那个 location 参数代表修饰谁的注释要被解析,取值有:

X (disable tag)
a (all)
o (overview)
p (packages)
t (types, that is classes and interfaces)
c (constructors)
m (methods)
f (fields)

上面的 -tag 为 @document 生成的 HTML  是:
1<b>Document:</b></dt>
2  <dd>help
3 yourself</dd>

如果我们要为前面的 @document 生成更具表现力说明,或是另有企图 -- 如提取 @document 后的内容生成自己的外部文件中,那现在就得让 Taglet 登场了。从 DocumentTaglet 代码开始,它需要实现 com.sun.tools.doclets.Taglet 接口,要实现它所有的方法。里面有一片的 inField(),inMethod() 等方法,若返回 true 则表示这个标签可作用于这个位置上。

那什么是 isInlineTag() 呢,下面这样写的注释就是 Inline Tag: 用大括号括起来的就是 Inline tag,这时取的 tag.text() 就只是 "help"   了。你自己决定 isInlineTag() 方法返回 true 还是 false 吧。
1/**
2 * Inline: {@document help} yourself
3 */
4public static void main(String[] args) {
5
6}

还有那个注册方法,需 Taglet 接口中没有,但却是一定要写的,方法原型是:

public static void register(Map<String, Taglet> tagletMap),用来注册要用到的所有 Taglet, 也就是可在此一个个指定什么 tag 由哪一个类来处理。其实这里的 DocumentTaglet 可不实现 Taglet, javadoc -taglet 指定为这个类后所要做的工作就是执行该类的 register(Map<String, Taglet> tagletMap) 方法,至于后面碰到了某个 tag 时才真正去调用对应 Taglet 类的 toString 方法。

也就是说在这个 register 方法中可以同时注册多个  Taglet 类实例。这种情况下把现在的 DocumentTaglet 更名为 CustomTags 意义就更准确些。

关键是那两个 toString() 方法,它的返回值就是要交给 javadoc 显示出来的内容,文章要在这儿做。
 1package cc.unmi.taglet;
 2
 3import java.util.Map;
 4import com.sun.javadoc.Tag;
 5import com.sun.tools.doclets.Taglet;
 6
 7public class DocumentTaglet implements Taglet {
 8
 9    private static final String NAME = "document";
10    private static final String HEADER = "Document:";
11
12    @Override
13    public String getName() {
14        return NAME;
15    }
16
17    @Override
18    public String toString(Tag[] tags) {
19        StringBuilder result = new StringBuilder();
20        for (Tag tag : tags) {
21            result.append("\n&lt;dt&gt;&lt;b&gt;" + HEADER + "&lt;/b&gt;");
22            result.append("&lt;dd style='color:red'&gt;" + tag.text() + "&lt;/dd&gt;");
23        }
24        //or do anything here ......
25        return result.toString();
26    }
27
28    @Override
29    public String toString(Tag tag) {
30        return toString(new Tag[] { tag });
31    }
32
33    public static void register(Map&lt;String, Taglet&gt; tagletMap) {
34        DocumentTaglet tag = new DocumentTaglet();
35        Taglet t = (Taglet) tagletMap.get(tag.getName());
36        if (t != null) {
37            tagletMap.remove(tag.getName());
38        }
39        tagletMap.put(tag.getName(), tag);
40    }
41
42    @Override
43    public boolean inConstructor() {
44        return false;
45    }
46
47    @Override
48    public boolean inField() {
49        return false;
50    }
51
52    @Override
53    public boolean inMethod() {
54        return true;
55    }
56
57    @Override
58    public boolean inOverview() {
59        return false;
60    }
61
62    @Override
63    public boolean inPackage() {
64        return false;
65    }
66
67    @Override
68    public boolean inType() {
69        return false;
70    }
71
72    @Override
73    public boolean isInlineTag() {
74        return true;
75    }
76}

把重要的代码前移了,因为用到了 tools.jar 包,所以编译时需把 JDK 目录中的 tools.jar 加到 classpath 上去。实际测试中不管为一个方法写一个还是多个 @document 注释,javadoc 都是直接命中 toString(Tag[] tags) 方法。

要是想在 toString(Tag[] tags) 方法中得到被注释的元素的信息,就从 Tag 类型出发,tag.holder() 得到一个 Doc,对应注释的位置,它可能是一个 MethodDoc, ClassDoc, FieldDoc, PackageDoc 等,所以当前被注释元素的信息便可由此而得。

在假设编译后的 DocumentTaglet.class 文件在 c:/javadoc_taglet/bin/cc/unmi/taglet 目录下,你可以用下面的命令为前面那个 TestJavaDocTag.java 生成 javadoc 文档:

javadoc -taglet cc.unmi.taglet.DocumentTaglet -tagletpath c:/javadoc_taglet/bin

生成的 javadoc 的效果就是, @document 的值提取出来,并渲染为红色字体显示:


我们在生成 javadoc 时,会得到提示:

Note: Custom tags that could override future standard tags:  @document. To avoid
 potential overrides, use at least one period character (.) in custom tag names.

那是 javadoc 怕 @document 会与别人重复,希望你能加个命名空间,如写成 @unmi.document 这样子。

tools.jar 中还有另外一个 Taglet(com.sun.tools.doclets.internal.toolkit.taglets.Taglet) 接口,尚不知作用何的,Doclet 吗?接着有空也看看 -doclet 参数的应用定制。

参考:1. javadoc - The Java API Documentation Generator
          2. Taglet Overview
          3. Javadoc Programming
          4. Taglets Collection 永久链接 https://yanbin.blog/javadoc-programming-customize-taglet/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。