[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[jfriends-ml 1614] 標準 Doclet を変更してスレッドセー フタグを追加するの巻



こんばんは。武川です。

標準Docletを変更してスレッドセーフタグを追加する手順について
まとめてみました。かなり長い割には要領を得ていませんが、
時間のある方は見てみてください。

#見るより自分で作ったほうが早いと思いますが。。。

1.動機

スレッドプログラミングの読書会で
「スレッドセーフかどうかがJavaDocで出力されると便利だよねぇ」
という話題がありました。
確かに便利そうなので、作ってみることにしました。

2.Docletについて

(たしか)JDK1.2から、Docletがサポートされました。
Docletは、javadocコマンドから呼ばれるクラスで、javadocコマンドが解析した
コメント情報を受け取ることができ、これを使って、API Documentのような
プログラムのドキュメントを出力することができます。
また、通常のjavadocで出力されるHTMLドキュメントを作成するdocletは
"standard doclet"と呼ばれ、JDKのドキュメントにソースが附属しています。
今回は、この"standard doclet"を修正して、スレッドセーフ用のタグを
追加、表示できるようにします。

ちなみに、Docletについての情報は
$(JAVA_DOCUMENT)/docs/ja/tooldocs/javadoc/standard-doclet.html
に載っています。
($(JAVA_DOCUMENT)はJDKドキュメントをインストールした場所に読み変えて下さいね)

3.環境

以下の環境で作成しました。
Windows Me
JDK 1.3.0_01
Emacs 20.7 + JDE mode
Cygwin(バージョンよくわからず)

Javadocのソースは、Java2SE 1.3 の JDKドキュメントから取得しました。
javadocのソースは、JDKドキュメントのバージョンにより変更があるよう
なので、自分の変更をそのまま適応するのは難しそうです。

4.ソースの変更

本来ならば、もっとスマートな方法があるのですが、時間に追われていたため
(転職と引越と2時間通勤の3重苦はつらい)、非常にアドホックな
方法で変更しています。でも今から考えてみると、少し深く考えていれば、
もっと楽ができたかなと思います。では以下に実行の手順を書きます。

4.1 ファイルのコピー
$(JAVA_DOCUMENT)/docs/ja/tooldocs/javadoc/source/standard/配下のファイルを
全て開発用ディレクトリにコピーします。僕の場合は、
//c/project/java/javadoc/source/standard/
にファイルをコピーしました。(以下$(PROJECT)と呼びます)
また、//c/project/java/javadoc/source/MessageRetriever.javaも
開発用ディレクトリにコピーして下さい。

4.2 パッケージ名の変更

ここが非常にアホなやり方なのですが、開発用ディレクトリにコピーした
各Javaファイルから
package com.sun.tools.doclets.standard;
の文を取り除いて下さい。

実は"standard doclet"はtools.jarファイルに含まれているため、
普通にjavadocを実行した場合、tools.jarに含まれるクラスの方が実行
されてしまいます。
何も考えずにパッケージ名を残したまま、ソースを変更、実行すると、
全然変更が反映されないまま、時のみが過ぎてゆくという事態に陥ります。
僕は、3週間ばかりここで無駄な時間を楽しんでしまいました。

別解としては、-Xbootclasspath オプションを使うともっと楽に
出来たかもしれません。ただ、パッケージスコープの制約が問題で動作
しないかもしれません。同様にjreで実行してクラスパスにtools.jarを
指定するという方法もあると思います。もしこれで上手くいくようで
したら、教えてもらえるとありがたいです。

4.3 スレッドセーフタグの仕様

スレッドセーフかどうかは、以下のようなタグで記述します。
@threadsafe true
@threadsafe false
上記の記述はそれぞれ、Javadoc出力したHTMLで以下のように出力します。
スレッドセーフ: はい
スレッドセーフ: いいえ
これ以外の記述がされている場合や、記述自体がない場合は
スレッドセーフ: 不明
となります。
スレッドセーフかどうかはメソッド単位で決まるので、このタグはメソッド毎に
出力します。

4.4 ソースの変更

スレッドセーフかどうかの表示はメソッド単位で行ないます。
ということはメソッド単位の処理を行なっている部分を探せばよいということです。
クラスでそれっぽいのを探すと、MethodSubWriterなんてのがあります。
で、このクラスを見てみるとprintTagsや、printTagsFromTaggedMethod
なんていうそれらしいメソッドがあります。このメソッドを見ると
        Tag[] returns = method.tags("return");
MethodDoc#tags()は、return タグの部分を抜きだす処理です。
threadsafeタグはreturnタグと同様にメソッド単位のタグなので、
returnタグの処理を真似すればよいということで、深く考えもせず、
このreturnの部分を参考にthreadsafeタグを追加します。
        Tag[] threadsafes = method.tags("threadsafe");

returns変数を追ってゆくと
        printReturnTag(returns);
があるので、これを真似して printThreadSafesTagメソッドを作成します。 
printReturnTagメソッドの中を見ると
writer.ほげほげ 
という部分が沢山あります。
writerを調べると、どうやらHTMLを出力するためのクラスで、多言語化
のためにリソースを使用していることがわかりました。

後でリソースを追加するということで、先にメソッドを作ります。

    protected void printThreadSafesTag(Tag[] params) {
            writer.dt();
            writer.boldText("doclet.Threadsafe");
            writer.dd();
        if (params.length < 1) {
            writer.printText("doclet.ThreadsafeUnknown");
            return;
        }
        if ("true".equals(params[0].text().trim().toLowerCase())){
            writer.printText("doclet.ThreadsafeYes");
        }else if ("false".equals(params[0].text().trim().toLowerCase())){
            writer.printText("doclet.ThreadsafeNo");
        }else{
            writer.printText("doclet.ThreadsafeUnknown");
        }

    }

4.4 リソースの追加

//c/project/java/javadoc/source/standard/resources/standard_ja.properties
にthreadsafeタグ用の記述を追加します。

doclet.Threadsafe=\u30b9\u30ec\u30c3\u30c9\u30bb\u30fc\u30d5:
doclet.ThreadsafeYes=\u306f\u3044
doclet.ThreadsafeNo=\u3044\u3044\u3048
doclet.ThreadsafeUnknown=\u4e0d\u660e

実際は、sjisで書いてnative2asciiで変換しました。
英語環境ならば、
$(PROJECT)/resources/standard.properties
を書換えてください。

4.5 リソースを扱うクラスの変更

このまま、コンパイル、実行してもうまく動作しませんでした。リソースファイルの
参照先が誤っているためです。デフォルトではjavadocに含まれるリソースを見てし
まうので、これを4.4で変更したファイルを見るように変更します。

まずConfigurationStandard.javaのL158を
               new MessageRetriever("resources.Standard");
のように変更します。
本来ならば、引数のリソース名だけを変更すればよいはずなのですが、何故かうまく
いかなかったので、クラスの方もパッケージ名を削除しました。
このMessageRetrieverクラスは、パッケージ名を削除するだけで他の変更は無用です。

5 コンパイルと実行
適当なJavaファイル(ここではTest.java)にthreadsafeタグを入れ、Javadocを実行します。

$cd $(PROJECT)/
$javac *.java
$mkdir doc
$javadoc -d doc -doclet Standard -classpath . Test.java

うまくいけば、これで$(PROJECT)/docの下にスレッドセーフ属性の入った
JavaDocファイルが出来ているはずです。

6.感想

振り返ってみると、パッケージの問題で3週間も無駄にしたことを別にすれば
修正自体は、解析も含めて半日程度だったので、結構敷居は低そうです。
基本的に出力部分は自分で作る必要がある(javadoc側では解析しかやってくれない)
ので、PDFやらWordのDoc形式やらxmlやらcsvなど色々作り込める分、手間は大きいと思います。
ちなみにjavadocのドキュメントによるとPDFやWordのDoc形式を出力するDocletが開発中
とのことです。
あとは、メソッド内のコメントやメソッド自体を取得するような仕組みもあれば、
さらに面白そうなことが出来るのではないでしょうか?もしかしたら、出来るの
かもしれないのですが、まだ調べていません。

概念的にdocletのインターフェースは、DOMに似ているように感じました。
(まあ、どちらも木構造を扱うわけですから当然といえば当然でしょうか?)

dolcetの修正をする際の現時点で不都合に感じたのはデバッガが使えない
点でした。 デバッガを使うにはmainメソッドを見つけてドライバプログラム
を作るか、途中でアタッチする方法を見つける必要がありますがわかりませんでした。
結局System.out#printlnを入れるというダサダサな方法で逃げました。
AspectJを使うといいかもしれませんね。

ではでは。