coberturaに日本語パッチをあてる

以前の日記でコードカバレッジで、djUnitを使用するように書きましたが、
Antから実行するのがどうにも厄介そう*1なので、急遽jcoverageの後継であるcoberturaを使用するように変更しました。

しかし、このツールも他のツールと同様、お米の国の方が作成したツール同様マルチバイト文字に関する扱いはぞんざいで、UTF-8以外の文字コードを使用したコードの場合は見事に文字化けさせてくれちゃいます。*2

最初は、まぁいいやとも思ってたのですが、若干客先対応で時間が空いたのでパッチをあててみました。
ベースとなるバージョンは現時点で最新の1.9を利用します。

なお、パッチ適用済みのcobertura.jarはこちらから入手可能です。
coberturaは、GPLライセンスでの提供ですので、公開したソースに対しても当然動作保障はしませんのであしからず。
ただし、質問は受け付けますので、本日記にコメントでも入れてくださいな。

*1:内部で使用しているjcoverageがオープンソースではなくなったっぽい

*2:それでも、AntのJUnitレポートよりはまし、なんせXSLTにもろ「US-ASCII」って書いてあるし^^;

続きを読む

PPPoEの混在

最近、いろいろなお客さんから自社のメールに対して
SPAM嫌疑をかけられるようになってきました。

理由は、グローバルIPを1つしか契約してないため、
逆引きすると、プロバイダのドメイン名が返るからだと思ってます。

かっこ悪いので、このたび、グローバルIP8個の契約を別途して、
ドメインの移管を行うことにしました。

今は逆引きもちゃんとできるので、これでSPAM嫌疑は晴れる・・・はずです。



と言うお話が経緯としてあって、ドメインを移管することになりまして、
一時的に、下図のような形でとりあえず、各PCは、
CTUにPPPoE接続をさせてインターネットにアクセスし、
グローバルIP8個の方はBBRから、PPPoEを行うようにしてました。
CTUからだと、unnumberedのPPPoEはどうにもうまく設定できそうもなかったし、
staticルーティング機能もかなり怪しそうだったからです。
IP Phoneも使っていたため、多分頑張っても無理なはずです。

但し、この構成だと、何故かCTU側のインターネット回線から、
BBR側のインターネット回線には直接はつながらないと言う事が発覚です。
他のネットワーク経由では繋がります。

謎です。
設定が完了するまでえらい遠回りしました。


一応書いておくと、
プロバイダA=プロバイダB:ぷ○○
回線契約:NTT西日本 フレッツ光プレミアマンションタイプ
BBR:FedraCore3



蛇足:
unnumberedは、PPPoEのバージョンによって設定の仕方が違って
激しく悩みました。
OSが古すぎるのが一番悪いんでしょうけど。

どっちにしろ、メンテナンス中にネットも止まるのは困り者なので、
近いうちに設けてルーターとサーバーは分けたいです。

cglibを使用しているプログラムでdjUnitを使うための適当パッチ

Hibernateは内部でcglibを用いています。
SpringFrameworkもクラスに対してAOPを適用しようとするとcglibを使おうとします。
cglibは、クラス生成を行うためにASMの1.5.3を内部的に使用しています。
1.5.3は二世代前のバージョンなのですがこれまではさしたる不備も感じなかったので無視していました。


今度のお仕事はdjUnitを使うことになりました。
djUnitは、テスト実行時にコードカバレッジも一緒にとってくれる、JUnit使いにとってはういヤツなのです。
djUnitは、cglibなんてのは使わずにASMを直接触ってます。
バージョンは最新の3.xです。


バージョンがあってないですね。2世代も。。。
つまり、Hibernateとか、SpringAOPを使ってるとdjUnitは使えないんですね。
こいつはやばいぜ!

という訳で、超強引な方法ですが、cglibにパッチを当てることにします。
動作確認をした際の主要なライブラリのバージョンはこんな感じ

Eclipse 3.2.2
djUnit 0.8.2
hibernate 3.1.3
SpringFramework 1.2.6
cglib(ベース) 2.1.3(Hibernateにくっついてるヤツで可)
asm 3.0

以下、怒涛のようにパッチをさらします。ちなみに、asm-3.0.jarではなく、asm-all-3.0.jarを利用しないとコンパイルが通らないのでご用心くださいませ。
また、ソースコードの行数は、2.1.3の行数をベースにしています。

net.sf.cglib.core.AbstractClassGenerator

:
032:abstract public class AbstractClassGenerator
:
186:    protected Object create(Object key) {
:
209:                            try {
210:                                gen = loader.loadClass(getClassName());
211:                            } catch (ClassNotFoundException e) {
212:                                // ignore
213:                            } catch(Error e) {  //1
214:                            	//ignore
215:                            }
:
  1. djUnitで用意されているClassLoaderは、loadClassに失敗した際にClassNotFoundExceptionではなく、Errorを送出するので無視しないと動作しないのであえてキャッチ

net.sf.cglib.core.ClassEmitter

:
025:public class ClassEmitter extends ClassAdapter {
:
030:    private MethodVisitor rawStaticInit;  //1
:
061:    public void begin_class(int version, final int access, String className, final Type superType, final Type interfaces, String sourceFile) {
:
077:        cv.visit(version,
078:                 access,
079:                 classInfo.getType().getInternalName(),
080:                 null,
081:                 classInfo.getSuperType().getInternalName(),
082:                 TypeUtils.toInternalNames(interfaces)); //2
:
134:    public CodeEmitter begin_method(int access, Signature sig, Type exceptions, Attribute attrs) {
135:        if (classInfo == null)
136:            throw new IllegalStateException("classInfo is null! " + this);
137:        MethodVisitor v = cv.visitMethod(access,
138:                                       sig.getName(),
139:                                       sig.getDescriptor(),
140:                                       null, 
141:                                       TypeUtils.toInternalNames(exceptions));  //3
:
144:            MethodVisitor wrapped = new MethodAdapter(v) {  //4
:
177:    public FieldVisitor declare_field(int access, String name, Type type, Object value, Attribute attrs) {
:
186:            cv.visitField(access, name, type.getDescriptor(), null, value);  //5
:
243:    public void visit(int version, int access, String name, String signature, String superName, String interfaces) {
244:        begin_class(version,
245:                    access,
246:                    name.replace('/', '.'),
247:                    TypeUtils.fromInternalName(superName),
248:                    TypeUtils.fromInternalNames(interfaces),
249:                    null);
250:    }  //6
:
262:    public MethodVisitor visitMethod(int access, String name, String desc, String exceptions, Attribute attrs) {  //7
:
  1. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているのでタイプを変更
  2. org.objectweb.asm.ClassVisitor#visitメソッドの引数変更に対応。signatureはよーわからんのでとりあえずnull
  3. org.objectweb.asm.ClassVisitor#visitMethodの引数変更に対応。signatureはよーわからんのでnull。逆にattrsを渡せていないのが気色悪いのですが、とりあえず全部のテストがパスしているのでまずは気にしないことにする
  4. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているのでタイプを変更。あわせてorg.objectweb.asm.CodeAdapterも廃止されているので、org.objectweb.asm.MethodAdapterに置き換え
  5. org.objectweb.asm.ClassVisitor#visitFieldの引数変更に対応。signatureはよーわからんのでnull。逆にattrsを渡せていないのが気色悪いのですが、とりあえず全部のテストがパスしているのでまずは気にしないことにする
  6. org.objectweb.asm.ClassVisitor#visitメソッドの引数変更に対応。sourceFileがなくなったのでbegin_classにはnullを渡す。本当は、begin_classから、sourceFlileを取り除くほうがよいのでしょうが影響範囲が広がるのでまずは放置
  7. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているので戻り値のタイプを変更

あと、visitFieldメソッドは本当は、FieldVisitorを返却するように変更されているので厳密には引数もあわせて修正しなければいけないと思われるのですが、現時点でテストは全部パスしているので、かなぁり気持ち悪いですが無視します

net.sf.cglib.core.ClassNameReader

:
023:public class ClassNameReader {
:
036:    public static String getClassInfo(ClassReader r) {
:
039:            r.accept(new ClassAdapter(null) {
040:                public void visit(int version,
041:                                  int access,
042:                                  String name,
043:                                  String signature,
044:                                  String superName,
045:                                  String interfaces) {  /1
:
  1. org.objectweb.asm.ClassAdapter#visitメソッドの引数変更に対応

net.sf.cglib.core.CodeEmitter

:
025:public class CodeEmitter extends RemappingCodeVisitor {
:
105:    CodeEmitter(ClassEmitter ce, MethodVisitor cv, int access, Signature sig, Type[] exceptionTypes) {  //1
:
  1. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているので引数タイプを変更

net.sf.cglib.core.Constants

:
024:public interface Constants extends org.objectweb.asm.Opcodes {  //1
:
  1. org.objectweb.asm.Constantsは廃止されて、org.objectweb.asm.Opcodesに置き換えられているので実装インタフェースタイプを変更

net.sf.cglib.core.DebuggingClassWriter

:
024:public class DebuggingClassWriter extends ClassWriter {
:
046:    public DebuggingClassWriter(boolean computeMaxs) {
047:        super((computeMaxs) ? 1 : 0);  //1
048:    }
:
053:    public DebuggingClassWriter(boolean computeMaxs, int major, int minor) {
054:        super((computeMaxs) ? 1 : 0);  //2
055:    }
056:
057:    public void visit(int version, int access, String name, String signature, String superName, String interfaces) {  //3
058:        className = name.replace('/', '.');
059:        this.superName = superName.replace('/', '.');
060:        super.visit(version, access, name, signature, superName, interfaces);  //4
061:    }
:
071:	public byte toByteArray() {
:
099:                                cr.accept(tcv, 0);  //5
:
  1. org.objectweb.asm.ClassWriterのコンストラクタの引数が変更になっているので変更
  2. org.objectweb.asm.ClassWriterのコンストラクタの引数が変更になっているので変更
  3. org.objectweb.asm.ClassWriter#visitメソッドの引数が変更になっているので変更
  4. org.objectweb.asm.ClassWriter#visitメソッドの引数が変更になっているので変更
  5. org.objectweb.asm.ClassReader#acceptメソッドの引数が変更になっているので変更

net.sf.cglib.core.RemappingCodeVisitor

:
021:public class RemappingCodeVisitor extends MethodAdapter {  //1
022:    protected MethodVisitor cv;  //2
:
049:    public RemappingCodeVisitor(MethodVisitor v, int access, Type[] args) {  //3
050:        super(v);
051:        state = new State(access, args);
052:        cv = v;  //4
053:    }
054:
055:    public RemappingCodeVisitor(RemappingCodeVisitor wrap) {
056:        super(wrap.cv);
057:        this.state = wrap.state;
058:        cv = wrap.cv;  //5
059:    }
:
099:    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
100:        cv.visitLocalVariable(name, desc, signature, start, end, remap(index, 0));  //6
101:    }
:
  1. org.objectweb.asm.CodeAdapterは廃止されているので、継承元クラスをorg.objectweb.asm.MethodAdapterに置き換え
  2. org.objectweb.asm.MethodAdapterでは、protectedメンバcvが削除されていたので、影響範囲を減らすためにcvを(冗長だけど)追加
  3. org.objectweb.asm.CodeVisitorは廃止されているので、引数タイプをorg.objectweb.asm.MethodVisitorに置き換え
  4. メンバ変数cvにMethodVisitorオブジェクトをセットする
  5. メンバ変数cvにMethodVisitorオブジェクトをセットする
  6. org.objectweb.asm.MethodAdapter#visitLocalVariableメソッドの引数変更に対応

以上、これだけ修正すればとりあえず、Hibernateとか、SpringAOPとかはなんとか動いてくれそうです。
駄目な場合はコメントくだせぇ。

『{6}マイルストーン別全チケット』のレポートが動かない


個人的にはまぁいいかと思ったんですが、一緒のプロジェクトのメンバーに
突っ込まれたのでSQLを直してみました。

OS Fedora Core3
PostgreSQL 8.2.4
Trac 0.10.4-ja-1
SELECT p.value AS __color__,
   t.milestone AS __group__,
   (CASE status 
      WHEN 'closed' THEN 'color: #777; background: #ddd; border-color: #ccc;'
      ELSE 
        (CASE owner WHEN $USER THEN 'font-weight: bold' END)
    END) AS __style__,
   id AS ticket, summary, component, status, 
   resolution,version, t.type AS type, priority, owner,
   changetime AS modified,
   time AS _time,reporter AS _reporter
  FROM ticket t
  LEFT JOIN enum p ON p.name = t.priority AND p.type = 'priority'
  ORDER BY (milestone IS NULL), milestone DESC, (status = 'closed'), 
        (CASE status WHEN 'closed' THEN modified ELSE (-1)*p.value END) DESC

SELECT p.value AS __color__,
   t.milestone AS __group__,
   (CASE status 
      WHEN 'closed' THEN 'color: #777; background: #ddd; border-color: #ccc;'
      ELSE 
        (CASE owner WHEN $USER THEN 'font-weight: bold' END)
    END) AS __style__,
   id AS ticket, summary, component, status, 
   resolution,version, t.type AS type, priority, owner,
   changetime AS modified,
   time AS _time,reporter AS _reporter
  FROM ticket t
  LEFT JOIN enum p ON p.name = t.priority AND p.type = 'priority'
  ORDER BY (milestone IS NULL), milestone DESC, (status = 'closed'), 
        (CASE status WHEN 'closed' THEN changetime 
         ELSE (-1)*cast(p.value as int8) END) DESC

こんな感じに。とりあえず、性能とかは無視無視。


1つ前の日記、訳もわからず「Add Star」クリックしまくってしまいました。
他の文章と比較して、『すげぇ』って訳ではありません。

cflow系ポイントカットとifポイントカットの相性

この件は、言語仕様として明には書かれていない(と思われる)ため、
違うバージョンでは違う振る舞いをするかもしれません。


ちなみに、実験したバージョンはこんな感じです。

Eclipse 3.3.0(Europa)
AJDT 1.5.0
AspectJ 1.5.4.200705211336
JDK 1.6.0_02


例えば、以下のようなコードがあったとします。

01:public class AspectSample {
02:    public static void main(String[] args) {
03:        foo();
04:        bar();
05:    }
06:    private static void bar() {
07:        foo();
08:    }
09:    private static void foo() {
10:        System.out.println("Call foo.");
11:    }
12:}

そこに、以下のようなアスペクトを定義したとします。

01:public aspect SampleAspect {
02:    public pointcut pc() : 
03:        call(void AspectSample.foo())            //(i)
04:        && cflow(call(void AspectSample.bar()))  //(ii)
05:        && if(isHook())                          //(iii)
06:        ;
07:    static boolean isHook() {
08:        System.out.println("calling isHook");
09:        return true;
10:    }
11:    before() : pc() {
12:        System.out.println("calling foo...");
13:    }
14:}

この状態で、AspectSampleを実行すると標準出力にはいかのように出力されます。

calling isHook
Call foo.
calling isHook
calling foo...
Call foo.

何が嫌って、「calling isHook」が2回表示されてるじゃないですか!


論理積は、論理式を構成する1要素でも偽なら式全体も偽になります。
前方評価であれ、後方評価であれ、真偽値として偽が現れれば以降の
評価は中断したほうが性能はいいのですがAspectJでは、全ての単項目を評価してしまいます。
(i)、(ii)、(iii)の表記順を変えても「calling isHook」はやはり2回表示されてしまいます。
javaのif文では前方評価なので、「if(func1() && func2())」という記述をした場合、
func1がfalseを返すのであれば、func2は呼び出しません。
ifポイントカット内のメソッドでDBにアクセスでもしようものならその性能差は無視できないものになるでしょう。


動的に変化するcflow系とifポイントカットは性能的には一緒くたに使用しないほうがよいみたいです。
cflow系を用いたポイントカットでかつifポイントカットが必要になるような場合は、
アドバイスの中で素直にif文で処理の振り分けをしてあげるほうが、速度的にはGoodと言えるでしょう。


ただ、この問題の対処は実が難しい気がしています。
実装的にと言うよりも、数学的にです。
なぜならば、ポイントカットとは、論理値ではなくジョインポイント集合だからです。
だから、論理積って上で書いたのは適切ではなく、正確には積集合と書くべきなのです。
集合だと考えると、1つ1つの集合がありきであって、集合同士をかけたり足したりは、
その次だと言われればそのような気がしなくもないのです。


とは言え、どっちにしろおいらは素人さんなのでAと言う集合とBと言う集合を積集合した結果、
特定のジョインポイントが対象外ならばCの集合はみなくてもいいじゃんとも思うんですけど、
正味なところどうなんでしょうねぇ。。。

今日の備忘録

PostgreSQLをインストールする場合、サービス起動のアカウントに、
ローカルログオンを許可して無いとdataディレクトリの作成に失敗するみたい。

でも、一度インストールしてしまえばローカルログオンの権限がなくても動作する。
不思議だ。。。

もしかしたら、こっちじゃなくて、インストーラー(.msi)の制限を解除したのが
影響したのかもしれない。
#でも、前にインストールしたときはそんなことなかったんだけどな。

あっ、再インストールしたのは、ローケルを「C」にしないと、
日本語の前方後方一致検索でエラーがでたからなんだな。
うん、これも忘れちゃだめだな。

プロジェクトのインポートに失敗する

個人的な備忘録。

Subversionでバージョン管理をしているEclipseのプロジェクトをインポートすると、
Eclipseが異常終了するざます。
再現性はありまくりですので要注意。

OS Windows Vista Business
Eclipse 3.2.x、3.3.0(Europa)
Subversion 1.4.4
Subclipse 1.2.3

回避方法は、環境変数PATHから
[Subversionインストールディレクトリ]\bin
を外せばOKです。

多分、いずれかのDLLが悪さしてるんでしょう。

Vistaのトラブルはまだまだ情報が少なくてきついです。(T_T)