謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

JavaDoc を XML で吐き出すメモ

やりたい事が何かと言うと、外部プログラムにおいて、Java クラスのリスト取得と、そのドキュメントを引き抜きたいと考えた。

しかしながら、単純に javadoc のコマンドだけ実行すると、HTML ファイルが出てくる。
これは JavaDoc コマンドの仕様であり、基本動作となっていて、中間ファイルなども出てきていないようだ。

要するに javadoc コマンドの中でパースやら構築やら全部やってしまっていて、その間にあるだろう Javaクラス構成とそのドキュメント構造を引っこ抜く事がこのままだとできない。
でも当たり前だが、同じことをしたい人なんて絶対いるだろうと…むしろ居ない訳がなかろうと思ったのだ。

そして散々悩んだところ、どうも JavaDoc には Doclet なる機構が存在するらしい。
この Doclet とは、JavaDoc で読み取ったデータを出力する際のデータ整形に使われるようだ。

つまりこいつに XML 生成の Doclet 食わせてやれば、XML が吐き出されると思われた。
で、探して見たところ案の定。

github.com

そりゃそーだよなと、むしろない訳ないよなと。

って事でやってみた。

Maven3

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- 中略 -->

    <build>
        <plugins>
            <!-- 中略 -->

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.10.4</version>
                <configuration>
                    <doclet>com.github.markusbernhardt.xmldoclet.XmlDoclet</doclet>
                    <additionalparam>-d ${project.build.directory} -filename ${project.artifactId}-${project.version}-javadoc.xml</additionalparam>
                    <useStandardDocletOptions>false</useStandardDocletOptions>
                    <docletArtifact>
                        <groupId>com.github.markusbernhardt</groupId>
                        <artifactId>xml-doclet</artifactId>
                        <version>1.0.5</version>
                    </docletArtifact>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

こんな風に書いて、 mvn javadoc:test-javadoc とタイプしてみたところ、target 直下に下記のようなXMLが出てきた。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <package name="net.white_azalea.example">
        <comment>日本語パッケージネーム</comment>
        <class name="UnitTest2" qualified="net.white_azalea.example.UnitTest2" scope="public" abstract="false" error="false" exception="false" externalizable="false" included="true" serializable="false">
            <comment>日本語メッセージテスト。.

 JUnit のテストケース JavaDoc に日本語を入れた場合の動作チェックを行うものである。&lt;br&gt;
 もちろん複数行で記述され、エンコードは UTF-8 だ。

 Created by azalea on 2017/06/03.</comment>
            <class qualified="java.lang.Object"/>
            <constructor name="UnitTest2" signature="()" qualified="net.white_azalea.example.UnitTest2" scope="public" final="false" included="true" native="false" synchronized="false" static="false" varArgs="false"/>
            <method name="toHexString_returnHexString_thatArgsAsUTF8Parsed" signature="()" qualified="net.white_azalea.example.UnitTest2.toHexString_returnHexString_thatArgsAsUTF8Parsed" scope="public" abstract="false" final="false" included="true" native="false" synchronized="false" static="false" varArgs="false">
                <comment>toHexString は引数を UTF-8 文字列とみなして数値化、16 進数表現に変換する。.</comment>
                <return qualified="void"/>
                <exception qualified="java.io.UnsupportedEncodingException"/>
                <annotation name="Test" qualified="org.junit.Test"/>
            </method>
            <annotation name="RunWith" qualified="org.junit.runner.RunWith">
                <argument name="value" primitive="false" array="false">
                    <type qualified="java.lang.Class">
                        <generic qualified="?">
                            <wildcard>
<extendsBound qualified="org.junit.runner.Runner"/>
                            </wildcard>
                        </generic>
                    </type>
                    <value>org.junit.runners.JUnit4</value>
                </argument>
            </annotation>
        </class>
    </package>
    <package name="net.white_azalea">
        <comment>English package name.

 With descriptions.</comment>
        <class name="UnitTest1" qualified="net.white_azalea.UnitTest1" scope="public" abstract="false" error="false" exception="false" externalizable="false" included="true" serializable="false">
            <comment>UnitTest1 is unit test about UnitTestTarget1 class.

 Description messages example.

 Created by azalea on 2017/06/03.</comment>
            <class qualified="java.lang.Object"/>
            <constructor name="UnitTest1" signature="()" qualified="net.white_azalea.UnitTest1" scope="public" final="false" included="true" native="false" synchronized="false" static="false" varArgs="false"/>
            <method name="addHelloPrefix_returnArgWithHello" signature="()" qualified="net.white_azalea.UnitTest1.addHelloPrefix_returnArgWithHello" scope="public" abstract="false" final="false" included="true" native="false" synchronized="false" static="false" varArgs="false">
                <comment>addHelloPrefix are return "Hello," prefixed message.

 Request addHelloPrefix("Hoge") method
 should return "Hello, Hoge"</comment>
                <return qualified="void"/>
                <annotation name="Test" qualified="org.junit.Test"/>
            </method>
            <method name="errorTest" signature="()" qualified="net.white_azalea.UnitTest1.errorTest" scope="public" abstract="false" final="false" included="true" native="false" synchronized="false" static="false" varArgs="false">
                <comment>Test for error message test.</comment>
                <return qualified="void"/>
                <annotation name="Test" qualified="org.junit.Test"/>
            </method>
            <annotation name="RunWith" qualified="org.junit.runner.RunWith">
                <argument name="value" primitive="false" array="false">
                    <type qualified="java.lang.Class">
                        <generic qualified="?">
                            <wildcard>
<extendsBound qualified="org.junit.runner.Runner"/>
                            </wildcard>
                        </generic>
                    </type>
                    <value>org.junit.runners.JUnit4</value>
                </argument>
            </annotation>
        </class>
    </package>
</root>

うん、良さげな感じ。

Gradle

Gradle も 公式ドキュメント を見ると、Doclet の適用方法が乗っているので、これを元に考える。

因みに、サンプルを作るのが面倒だったので、SpringBoot のドキュメント Building a RESTful Web Service の complete ディレクトリを元にさくさくっと。

// 前略

configurations {
    xmlDoclet
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('com.jayway.jsonpath:json-path')
    xmlDoclet "com.github.markusbernhardt:xml-doclet:1.0.5"
}

task xmlJavadoc(type: Javadoc) {
    source = sourceSets.main.allJava
    destinationDir = reporting.file("rest-api-docs")
    title = null

    options.doclet = "com.github.markusbernhardt.xmldoclet.XmlDoclet"
    options.docletpath = configurations.xmlDoclet.files.asType(List)
    options.addStringOption("-filename", "export.xml")
}

気をつけないといけないのは、 title=null を設定しておかないと、こんなエラーが出てくる点。
この doclet で対応して居ないせいだろう。

:xmlJavadoc
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
javadoc: エラー - -doctitleは無効なフラグです
使用方法: javadoc [options] [packagenames] [sourcefiles] [@files]
  -overview <file>          HTMLファイルから概要ドキュメントを読み込む
  -public                   publicクラスとメンバーのみを示す
  -protected                protected/publicクラスとメンバーを示す(デフォルト)
  -package                  package/protected/publicクラスとメンバーを示す
  -private                  すべてのクラスとメンバーを示す

あとは実行するだけ。

./gradlew xmlJavadoc

Good Luck!