Последнее изменение: 1 сентября 2007г.

Создание собственных тегов javadoc

Как я уже говорил в одной из статей, Java-разработчикам сделали большой подарок – возможность генерировать документацию по комментариям в исходном коде. Документация будет структурирована в едином стиле, что существенно облегчает восприятие. Было также определено большое количество тэгов для разметки информации, причем список их непрерывно пополняется.

Однако на всех не угодишь, что неудивительно. Всегда найдется человек, которому по объективным причинам понадобится вставить в документацию именно то, о чем не подумали создатели Java. Можно, конечно, вставлять такую информацию непосредственно в основной блок комментария, но тогда придется заботиться о сохранении единообразного форматирования, что, во-первых, неудобно, во-вторых, в больших проектах просто малореально, ибо... персональный стиль. И вот в такой ситуации чрезвычайно полезным может стать введение собственного тэга javadoc, благо такая возможность есть.

Итак, рассмотрим следующую ситуацию. Необходимо держать в коде лог его изменений – кто менял, когда, что именно. Причем делать это надо на уровне методов и полей. Решением может служить ввод нового тэга javadoc – @lastChanges. Рассмотрим этот процесс по шагам.

Шаг 1. Создание класса обработчика тэга

Обработчик тэга должен реализовать интерфейс com.sun.tools.doclets.Taglet. Этот интерфейс содержит набор методов вида inXXX, определяющих, может ли данный тэг встречаться в описании конструктора (inConstructor()), метода (inMethod()), поля (inField()) и т.п. Также этот интерфейс содержит метод isInlineTag(), определяющий, является ли этот тэг inline, т.е., может ли он встречаться внутри описания другого тэга.

Интерфейс com.sun.tools.doclets.Taglet содержит еще два метода, которые отвечают непосредственно за генерацию необходимого HTML-кода – toString(com.sun.javadoc.Tag) и toString(com.sun.javadoc.Tag[]). Первый из них отвечает за генерацию в случае если тэг встретился в блоке комментария один раз, второй – если тэг встретился несколько раз.

Еще один метод, не упомянутый в интерфейсе com.sun.tools.doclets.Taglet (ибо статический), но тем не менее обязательный к реализации – public static void register(Map tagletMap). Этот метод необходим для регистрации обработчика тэга.

Итак, создаем класс ru.skipy.taglet.LastChangesTaglet. Тэг будет блочный, разрешен в любом месте комментария. Отображаться тэг будет в виде таблицы, содержащей изменения, с заголовком синего цвета 'Last changes:'. Полный код класса находится тут: lastChangesTaglet.zip. Фрагмент, содержащий основной код, приведен ниже.

package ru.skipy.taglet;

import com.sun.tools.doclets.Taglet;    // Taglet API
import com.sun.javadoc.*;               // Doclet API
import java.util.Map;

public class LastChangesTaglet implements Taglet{

    /**
     * Tag name
     */
    private static final String NAME = "lastChanges";

    /**
     * Tag header in documentation
     */
    private static final String HEADER = "Last changes:";

    /**
     * @lastChanges (2004Jul01) Eugene Matyushkin
     * @param tag tag
     * @return string representation for single tag
     */

    public String toString( Tag tag ) {
        return "<DT><B><font color=\"blue\">" + HEADER + "</font></B><DD>"
               + "<table cellpadding=\"2\" cellspacing=\"0\"><tr><td>"
               + "<code>" + tag.text() + "</code>"
               + "</td></tr></table></DD>\n";
    }

    /**
     * @lastChanges (2004Jun30) Eugene Matyushkin: table format changed
     * @lastChanges (2004Jul01) Eugene Matyushkin: bug fixed
     *
     * @param tags tags array
     * @return string representation for multiple tags
     */
    public String toString( Tag[] tags ) {
        if (tags.length == 0) {
            return null;
        }
        String result = "\n<DT><B><font color=\"blue\">" + HEADER + "</font></B><DD>";
        result += "<table cellpadding=\"2\" cellspacing=\"0\"><tr><td>";
        for (int i = 0; i < tags.length; i++) {
            if (i > 0) {
                result += "</td></tr><tr><td>";
            }
            result += "<code>"+tags[i].text()+"</code>";
        }
        return result + "</td></tr></table></DD>\n";
    }

    /**
     * This method is used to register tag in the taglet map
     *
     * @param tagletMap map, in which this tag should be placed (with tag name as key)
     */
    public static void register(Map tagletMap) {
       LastChangesTaglet tag = new LastChangesTaglet();
       Taglet t = (Taglet) tagletMap.get(tag.getName());
       if (t != null) {
           tagletMap.remove(tag.getName());
       }
       tagletMap.put(tag.getName(), tag);
    }

    // some code is skipped!

}

Всё. Обработчик создан. Теперь...

Шаг 2. Компиляция и создание библиотеки

Собственно, этот шаг тривиален. Единственный момент, стоящий упоминания – в classpath необходимо включить библитеку, содержащую Doclet API и Taglet API. Этот файл называется tools.jar и лежит он в директории <J2SDK_INSTALLATION_DIR>/lib. Больше никаких дополнительных действий не требуется. (При использовании ant, однако, есть один неочевидный момент.) Создание библиотеки тоже не должно вызвать никаких трудностей.

javac -classpath ./classes;%JAVA_HOME%/lib/tools.jar
      -d ./classes src/ru/skipy/taglet/LastChangesTaglet.java
jar cvf lib/lastChangesTaglet.jar -C classes .

Теперь, когда класс скомпилирован и библиотека создана – на очереди...

Шаг 3. Генерация документации с использованием нового тега.

В качестве примера возьмем тот же самый класс ru.skipy.taglet.LastChangesTaglet. В документации к методам toString(com.sun.javadoc.Tag) и toString(com.sun.javadoc.Tag[]) используется новый тег.

Для использования нового тега надо добавить его в параметры javadoc следующим образом: -taglet ru.skipy.taglet.LastChangesTaglet -tagletpath lib/lastChangesTaglet.jar. Полная команда приведена ниже:

javadoc -classpath ./classes;%JAVA_HOME%/lib/tools.jar -sourcepath src -d ./doc
        -taglet ru.skipy.taglet.LastChangesTaglet
        -tagletpath lib/lastChangesTaglet.jar ru.skipy.taglet

При использовании ant достаточно добавить параметр taglet задаче javadoc:

<taglet name="ru.skipy.taglet.LastChangesTaglet" path="lib/lastChangesTaglet.jar"/>

Хочу обратить внимание на то, что в любом случае в classpath команды javadoc должна быть включена библиотека tools.jar

Всё. После выполнения команды javadoc будет создана документация, в которой тег @lastChanges будет корректно обработан.

Как видите, в процедуре создания нового тега javadoc нет ничего сложного. Эта процедура вызывает проблемы в основном потому, что ее описание упрятано достаточно глубоко в документации.

Полный код примера вместе с build-файлом для ant находится тут – lastChangesTaglet.zip. Для проведения всех действий вплоть до генерации документации достаточно выполнить команду ant javadoc.


* * *

Возможно, кому-то этот пример покажется надуманным. В самом деле, информация об изменениях интересна в основном разработчикам, а для них существуют системы контроля версий и т.п. Так оно и есть. Но это – всего лишь пример. В реальности могут быть совсем другие ситуации. Представьте себе библиотеку – реализацию машины состояний. И для каждого класса, описывающего определенное состояние, необходимо знать, в какие состояния произойдет переход при вызове каких методов, и каким образом может произойти переход в данное состояние. Отражение этой информации в документации в виде дополнительных тегов с использованием перекрестных ссылок может оказаться очень полезным и существенно облегчит использование этой библиотеки. И таких примеров – очень и очень много. Не случайно в javadoc заложили возможность использования тегов, определенных пользователем.

На этом всё. Спасибо за внимание!

P.S. Кстати, посмотрите ради интереса исходный код Java. В нем фигурирует довольно много нестандартных тегов.


Замечание по поводу использования ant. В документации к ant неоднократно встречается путь вида ${java.home}/jre/..., из чего можно сделать вывод, что системное свойство java.home указывает на директорию J2SDK (<J2SDK_INSTALLATION_DIR>). Однако этот вывод неверен. ant сам не выставляет это свойство, а виртуальная машина выставляет это свойство в <J2SDK_INSTALLATION_DIR>/jre. И потому путь к библиотеке надо определять следующим образом:

<property name="tools.jar" value="${java.home}/../lib/tools.jar"/>