matarilloの雑記

GitHubに公開したソフトウェアなどについて書きます。

dcsoupがテストに通るようになりました

というわけで本体コードもテストコードもJavaからのコンバートが片付いたので、知見をメモしておきたいと思います。

Sharpen

JavaのコードをC#に変換するのには Sharpen を使いました。 とはいえ私が実際に使ったのは(ngitのリポジトリにある)古いバージョンのやつでした。なーんだ、こっちはちゃんとメンテされてるじゃん……

というわけで以下に書くのは古いSharpenに基づくものなので、最新版では事情が違うかもしれません。違うといいな。あとで試そう。

Eclipse

Eclipse 3.7 IndigoじゃないとSharpenプラグインがうまく動きませんでした。最新のSharpenなら違うのかな……

Eclipse 3.7 の \plugins フォルダに sharpen.core_1.0.0.jar を放り込んでEclipseを起動したら認識しているはず。

eclipse

設定ファイル

プロジェクト直下に設定ファイルが必要です。run-sharpen.xml (Antタスク定義)、sharpen.properties (パスの設定)、sharpen-all-options (マッピング定義)、header.txt (C#の共通ヘッダテンプレート、Antタスクで参照されてる)に関しては、 この記事その翻訳からリンクされている、Paulさんの設定ファイルテンプレートを元にするといいかも。

ちなみに、sharpen.properties にSharpenの作業ディレクトリが書いてあるけど、そのディレクトリが見つからないとエラーになるから注意。

実行前

最初は極小のサンプルコードみたいなやつを変換して、動くか確認するといいと思います。

ant task

run-sharpen.xml にAntのアイコンが出てると思うので、右クリックして "Run as"を選べば動きます。

思ったより普通に変換してくれましたが、注意点はあります。それは、一部のタイプセーフenumが変換エラーになったことです。 いわく、「enumにはフィールドとかメソッドとか定義できないよ!」とのこと。いや、C#ではできないのはわかるけど、そこはよしなにやってほしかった。

仕方ないので、コンバート前のJavaコードを人力で書き換え。メソッドなどを持つenumをまるっと普通の抽象クラスにしました。

人力プリコンパイル前:

enum Strategy {
    Foo {
        void execute() { ... }
    },
    Bar {
        void execute() { ... }
    };
    abstract void execute();
}

人力プリコンパイル後:

abstract class Strategy {
    public static final Strategy Foo = new Strategy("Foo") {
        void execute() { ... }
    };
    public static final Strategy Bar = new Strategy("Bar") {
        void execute() { ... }
    };
    abstract void execute();

    final String name;
    protected Strategy(String name) { this.name = name; } 
    public String name() { return name; }
    public static Strategy valueOf(String name) { ... }
}

なにこれ面倒くさい。nameの処理とかすげえ面倒くさい。 こんなんだったらSharpenを使わずに手でC#にしようか……とも思ったのだけどそっちのほうがミスが多くなりそうだったから諦めて上みたいに書き換えました。

実行後

さて、変換エラーを手で回避する作業が終われば、無事C#に変換されたコードが手に入ります。 とはいえ、すぐコンパイルが通るかというとそうでもなかったり。

まず気づくのが、using Sharpen; とか Sharpen.Runtime.Substring(...) とか IList<string> list = new AList<string>(); みたいなコード。 結局ね、Javaの標準クラスライブラリを全部.NETの標準クラスライブラリに機械的に置き換えることには無理があるのですよ。なので、拡張メソッドを生やしたり、JavaのクラスをシミュレートするC# クラスを作ったりしているのです。 そのあたりのクラスのソースコード(ただし古い)はこちら。最新のものは最初に書いたリポジトリにあるんじゃないかな。

次に注意するのは命名規則とかそういうやつのあれ。Java

class Foo {
    public static class Bar {}
    public Bar bar() { return new Bar(); }
}

みたいな感じで、メンバーに Barbar() がいたとき、Sharpenにお任せすると

class Foo
{
    public class Bar {}
    public Bar Bar() { return new Bar(); }
}

と、メソッド bar()Bar() になって、そのせいでコンパイルに通らなかったりしました。 そこはレアケースだから許容するんだけど。

3番目のの注意点はジェネリクスワイルドカード

いや、変換はできるんですが

public boolean addAll(Collection<? extends Foo> col) {
    ...
}
public boolean removeAll(Collection<?> col) {
    ...
}

public virtual bool AddAll<_T0>(ICollection<_T0> col)
    where _T0:Foo
{
    ...
}
public virtual bool RemoveAll<_T0>(ICollection<_T0> col)
{
    ...
}

になって嬉しいかは自分で考えなければならないでしょう。 つまり、「その型 _T0 はほんとに必要なのか?」ってことです。

そして最後の注意点。変換後のC# コードには、おそらく virtual とか protected とか、そういう修飾子が山ほど入ってきます。これも、「その修飾子はほんとに必要なのか?」と考えたほうがいいでしょう。とはいえ、要らないからといって削除するのも面倒だったりしますが……

~~~~~~~~

と、長くなったのでこのあたりで。続く!(かも)