クラスの動的再ロード処理

seasarとかでHotdeployというのをやっているのでseasarのコードをSVNから落としてきて勉強中。

下のコードとかからコードを追いつつ、自分でも適当にコードを作ってみて試していたわけですが

http://svn.seasar.org/browse/trunk/seasar2/s2-framework/src/main/java/org/seasar/framework/container/hotdeploy/HotdeployClassLoader.java?root=s2container&view=markup

すでにわかりやすい解説がWebにあったのね(そりゃそうか)

http://www.nminoru.jp/~nminoru/java/class_unloading.html

これぐらいの解説を何回かに分けてやろうかなぁと思ってたわけですがWebバンザイ


知り合いから参考になるリンク先を貰った
http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html

jaxenのXPathパーザーのバグ

XPath評価エンジンjaxenに長らく見つかっていなかった XPath 1.0 仕様書に則っていなかったバグが見つかったようで。
http://jira.codehaus.org/browse/JAXEN-193

jaxenはDOMやJDOM,XOM,dom4jXPath 1.0 式を使用して結果を取ってくるライブラリ(XPath 2.0 サポートはする気が全くないみたい)で、http://jaxen.org から取得できます。
また、maven1やmaven2リポジトリにも登録されているはずです。
ちなみにjaxen.orgは説明やリンク先が間違っている、ソースzipあると書いてあるのに実際には無いなんてことがしょっちゅうあるので注意。落ちてることも。(いい加減もうCVS無いのだからCVSへのリンク削除したらいいのに・・・)

問題のバグですがこれは 文字"*"の構文解析XPath 1.0 の 3.7章「Lexical Structure」の下記の内容に従っていなかったと見られる物です。
原文:http://www.w3.org/TR/xpath#exprlex

If there is a preceding token and the preceding token is not one of @, ::, (, [, , or an Operator, then a * must be recognized as a MultiplyOperator and an NCName must be recognized as an OperatorName.

If the character following an NCName (possibly after intervening ExprWhitespace) is (, then the token must be recognized as a NodeType or a FunctionName.

If the two characters following an NCName (possibly after intervening ExprWhitespace) are ::, then the token must be recognized as an AxisName.

Otherwise, the token must not be recognized as a MultiplyOperator, an OperatorName, a NodeType, a FunctionName, or an AxisName.

日本語訳:http://www.infoteria.com/jp/contents/xml-data/REC-xpath-19991116-jpn.htm#exprlex

前にトークンがあり、そのトークンが @、::、(、 [、, または Operatorではない場合には、* を MultiplyOperator として認識しなければならい (must)。また NCName を OperatorName として認識しなければならない (must)。

NCName の後に続く文字 (ExprWhitespace を間に挟む場合もある) が ( の場合は、そのトークンを NodeType または FunctionName として認識しなければならない (must)。

NCName の後に続く2つの文字 (ExprWhitespace を間に挟む場合もある) が :: の場合は、そのトークンを AxisName として認識しなければならない (must)。

上記以外の場合はトークンを MultiplyOperator または OperatorName、NodeType、FunctionName、AxisName として認識してはならない (must)。

上記の"*"に関するところだけを抜き出すと、@*や、::*、(*、[* や Operator(andやorなど)*の場合は"*"をMultiplyOperator(算数の掛け算記号と考えて貰うと良いです)として認識してはならないとのことです。この場合、"*"は何に該当するかというと3.7章に書いてあるBNF式を追っていくと、NameTestに該当します。まぁ確かに@*などはNameTestなので掛け算と思われたらたまらんわけですが。

これを踏まえ、jaxen-193によるとjaxenで下記のXPath式を処理しようとするとSAXPathExceptionが発生します。
(SAXPathはjaxenの内部で使用されるXPath構文解析エンジンの名前です)

/*[ * or processing-instruction() ]
/*[ processing-instruction() or * ]

修正に結構手間取っていてだれか妙案はない?とMLにて話が出ており(http://archive.jaxen.codehaus.org/dev/14904761.1192805692397.JavaMail.haus-jira%40codehaus01.managed.contegix.com)、一応パッチらしき物は出ていますがまだコミットはされていない模様。(SVNのログ見る限り。http://fisheye.codehaus.org/changelog/jaxen/trunk/jaxen?todate=1209261370459)

実際のコードを見ると分かるのですが"*"は単にどのような場合でもSTARというトークンに分類されており、この辺りが問題ではないかと思われます。いつ直るのかなぁ・・・

surefire 2.4.3 リリース

というわけで surefire 2.4.3 がリリースされました。
http://maven.apache.org/plugins/maven-surefire-plugin/

リリース文によると additionalClassPath についての文書が追加されたようなので以下訳文。

      • -

元:http://maven.apache.org/plugins/maven-surefire-plugin/examples/additional-classpath.html

Additional Classpath Elements

もし Surefire を実行する際にクラスパスに対して何か(リソースや特別なjarファイルなど)追加したいなら、一般にはdependencyとしてクラスパスに追加することを勧めます。共有するJarファイルをあなたの組織のプライベートなリモートレポジトリにデプロイすることを考えてください。

しかし、どうしてもクラスパスにカスタムしたリソースやJarファイルを追加したいなら additionalClasspathElements 要素を使用できます。

Additional Classpath Elements

If you need to put more stuff in your classpath when Surefire executes (e.g some funky resources or a container special jar), we normally recommend you add it to your classpath as a dependency. Consider deploying shared jars to a private remote repository for your organization.

But, if you must, you can use the additionalClasspathElements element to add custom resources/jars to your classpath.

<project>
  [...]
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <additionalClasspathElements>
                <additionalClasspathElement>path/to/additional/resources</additionalClasspathElement>
                <additionalClasspathElement>path/to/additional/jar</additionalClasspathElement>
          </additionalClasspathElements>
        </configuration>
      </plugin>
    </plugins>
  </build>
  [...]
</project>

あまりたいしたことは書かれていないような。

むしろ歓迎

川口さんの「これだから世の中にはiso-8859-1しかないと思ってる奴らは...」
d:id:masanobuimai:20080426:1209208285より。

むしろ標準がプラットフォームエンコーディングからISO-8859-1になってくれた方がまだマシです。

WindowsからLinuxに持って行ったらとたんに動かなくなり、何がおかしいのか考えるより、「皆さんちゃんとencoding指定をしましょう」というポリシーの方が良いと思います。

まぁデフォルトがISO-8859-1よりかUTF-8のほうが100倍マシですが。

ナビゲーションが面どい

所用があって日記の関連記事の前回・次回ナビゲーションを各日記に一つ一つ追加していったのだが作業がめんどい。どーにかならんのか。

IDリンクの場合リンクのエイリアス文字列も設定できないっぽいし。ううーむ

maven ant の落とし穴

maven2 ではなく maven1 です

maven1 では、 maven ant で project.xml から ant の build.xml を作成してくれる便利なプラグインがありますがこいつがどうにもこうにも信用できません。

■駄目なところ1
maven1 にはプロパティとして

  • ~/.maven/build.properties
  • 各プロジェクトの build.properties
  • 各プロジェクトの project.properties

の3種類のファイル中のプロパティをロードする仕組みがあるのですが、作成した build.xml はproject.propertiesを読んでくれません。

■駄目なところ2
maven1 は、 maven.compile.target など、maven.compile系でjavacを実行するときの各種設定ができます。
なのに作成されたbuild.xmlはこの値を全く参照しません

■駄目なところ3
同様に maven.proxy 系も全くみません。しかも、元々参照していたのを最新コードでは参照しないように変更されているという・・・

などなどそこら中に落とし穴が空きまくっている状態なので maven ant を行う場合はご注意を。
まだ maven1 の ant プラグインを落としてきてローカルで各種プロパティを見るように修正した方が使い勝手がよいかも・・・

maven-surefire-plugin についての覚え書き

とりあえず maven-surefire-plugin のバージョンが 4.5-SNAPSHOT で、かつ fork=once と、junitを使用している場合だけ調査

maven surefire プラグインのソース構造

このエントリで必要なもののみを記述

surefire
├mavne-surefire-plugin
│└SurefirePlugin
├maven-surefire-booter
│└SurefireBooter
├maven-surefire-api
│└Surefiremaven-providers
 ├surefire-junit
 │├JUnitDirectoryTestDuite.java
 │└JUnitTestSet
 ├surefire-junit4
 │├JUnit4DirectoryTestDuite.java
 │└JUnit4TestSet
 └surefire-testng

maven surefire プラグインの動作順番

フォークする前処理
  1. SurefirePlugin でpom中の各種設定値を取得
  2. SurefireBooter の各種フィールドに上記の値を設定
  3. テンポラリフォルダに surefirebooter*.jar というファイルを作成する。この中身はMANIFEST.MFのみ。
    • MANIFEST.MFの中身は クラスパス設定と、SurefireBooterをMain-Classとすることが書いてある
    • クラスパス設定は下記の順で追加される
      1. (※${project.testClasspathElements}が使用される。read only指定のため差し替え不可?)
      2. 指定があった場合:${project.build.outputDirectory}の替わりに
      3. 指定があった場合:${project.build.testClassesDirectory}の替わりに
    • このため、dependenciesとaddClasspathElements中に同じクラスが存在すると、dependenciesの方が勝ちます
  4. テンポラリフォルダに surefire*tmp というファイルを作成する。この中身は各種プロパティが設定されている。プロパティはフォーク前の各種OSやJavaのプロパティ値など
フォーク
  1. コマンドラインから javaコマンドを使用して先ほどのsurefirebooter*.jarを実行
  2. 各種プロパティをsurefire*tmpからロード
  3. SurefireBooterからSurefire#runを実行
Surefireでのテストクラスのロード処理
  1. ベースディレクトリとしてtestClassesDirectoryを用い、そこからディレクトリを辿っての対象となっているクラスをリストにする
    • ※ディレクトリ探索クラスはdependenciesの記述により下記のように変更されます
      • testNGもしくはtestNGArtifactNameがある場合:TestNGDirectoryTestSuite
      • maven-junit-plugin のバージョンが4から始まる場合:JUnit4DirectoryTestSuite
      • その他の場合:JUnitDirectoryTestSuite
  2. この段階でテストクラスファイルが見つからない場合処理終了。
  3. クラスファイルが見つかった場合、リストからテストメソッドを集める
    1. junit及びjunit4の場合、テストメソッドのかき集め方は下記の通り
      • リスト中のクラスがTest派生の場合は通常通り
      • リスト中のクラスがTest派生では無い場合、そのクラス中のstaticではなく、returnTypeがvoidで、かつメソッド名の最初4文字が"test"のメソッドがテストメソッドとしてロードされる。
    2. suite() の扱い
      • suite() を含むがTestクラス派生でない場合maven-junit-pluginのを4から始めておけば集めてくれる。3の場合は駄目
      • suite() を含むクラスがTest派生の場合はバージョンが4以降でなくてもsuiteメソッドが使用される。
テスト実行
  1. JUnitRunnerを使用して先ほど集めたテスト群をテスト実行