Monday, October 7, 2013

リソースバンドルについて

久しぶりにブログを書いてみたいと思います.

国際化のためのリソースを取り扱うということで, ResourceBundle クラスについて少し勉強しています. いままで, せいぜい Properties クラスにロケールが追加された程度のものかなと勝手に思い込んでいましたが, それだけでもないことをいまさらながら知りました.

順番に検索してくれる

たとえば, ./tt4cs/resource/ProgressiveRock.properties に以下のように記述し,

Asia = Geoff Downes, Steve Howe, Carl Palmer, John Wetton

./tt4cs/resource/ProgressiveRock_en.properties に以下のように記述し,

ELP = Keith Emerson, Greg Lake, Carl Palmer
Yes = Jon Anderson, Tony Kaye, Trevor Rabin, Chris Squire, Alan White

./tt4cs/resource/ProgressiveRock_en_UK.properties に以下のように記述し,

Yes = Jon Anderson, Bill Bruford, Steve Howe, Chris Squire, Rick Wakeman

そのうえで, 次のようなコードを書いてみたとします.

        ResourceBundle progressiveRock = ResourceBundle.getBundle(
                "tt4cs.resource.ProgressiveRock", new Locale("en", "UK"));
        String yes = progressiveRock.getString("Yes");
        String elp = progressiveRock.getString("ELP");
        String asia = progressiveRock.getString("Asia");
        System.out.println("[Yes] " + yes);
        System.out.println("[ELP] " + elp);
        System.out.println("[Asia] " + asia);

これを実行しますと, 次のような結果を得ることができます.

[Yes] Jon Anderson, Bill Bruford, Steve Howe, Chris Squire, Rick Wakeman
[ELP] Keith Emerson, Greg Lake, Carl Palmer
[Asia] Geoff Downes, Steve Howe, Carl Palmer, John Wetton

まず, getBundle メソッドで得た ResourceBundle インスタンスは, 単に en_UK ロケールのリソースをバンドルするのみでなく, en ロケールのリソースや, ロケール指定のないリソースもバンドルしてくれるのだということがわかります. そうであってほしいとは思っていましたが, getBundle メソッドの第 2 引数に en_UK ロケールを指定した場合, ひょっとしたら en_UK ロケールのリソースしか検索してくれなかったりしないだろうかと不安にも思っていたので, 安心しました.

また, キー "Yes" に該当する値として, en ロケールのリソースから得られるもの (いわゆる 「90125 イエス」 のメンバーリスト) ではなく, en_UK ロケールのリソースから得られるもの (全盛期 「こわれものイエス」 のメンバーリスト) が採用されていることから, 検索の順番や優先順位がわかります. すなわち, en_UK ロケールのリソースを検索し, 見つかればそこで終わりますし, 見つからなければ次は en ロケールを検索する, ということです. 理にかなっているというか当然そうであってほしいとは思っていましたが, そのとおりで安心しました.

プロパティファイルだけでなくクラスもある

ResourceBundle クラスがバンドルしてくれるリソースは, プロパティファイルだけではありません. ということを, はじめて知りました. どういうことかというと, 先ほどの例の続きで, たとえば tt4cs.resource パッケージに ProgressiveRock_en という名前のクラスがあって, かつそれが ListResourceBundle クラスを継承している場合, en ロケールのリソースとしては ProgressiveRock_en.properties ファイルではなく ProgressiveRock_en クラスのほうが優先されるということです.

ここで, そんなクラスを, たとえば次のように書いてみます.

package tt4cs.resource;

import java.util.ListResourceBundle;

public class ProgressiveRock_en extends ListResourceBundle {
    
    @Override
    protected Object[][] getContents() {
        return new Object[][] {
            new String[] { "King Crimson",
                "Robert Fripp, Adrian Belew, Tony Levin, Bill Bruford"
            }
        };
    }
    
}

ListResourceBundle は抽象クラスでありまして, 継承する側としては getContents() メソッドの実装が必要です. Object の 2 次元配列を返す必要がある点が, なんというか, ちょっと見慣れないものを見てしまった気分ですが, まあいいでしょう. そのうえで, 以下のようなコードを書いて, 実行してみます.

        String kc = progressiveRock.getString("King Crimson");
        System.out.println("[King Crimson] " + kc);

その結果は, 次のとおりです.

[King Crimson] Robert Fripp, Adrian Belew, Tony Levin, Bill Bruford

ただし, 気をつけなければいけない点があります. つい先ほど, 「en ロケールのリソースとしては ProgressiveRock_en.properties ファイルではなく ProgressiveRock_en クラスのほうが優先される」 と書きましたが, 本当は, 「ProgressiveRock_en.properties ファイルは参照されず ProgressiveRock_en クラスのみが参照される」 と言うほうが, 実態に近いです. ですので, 前節に掲げたコードのうち, 次の部分は MissingResourceException をスローすることになってしまいます.

        String elp = progressiveRock.getString("ELP");

そういえば, Properties クラスの場合ですと, キーに該当する値がなければ単に null が返って来ますが, ResourceBundle の場合は MissingResourceException という実行時例外がスローされる点も, 注意すべきだと思います.

文字列だけではない

プロパティファイルを利用したリソースバンドルでは, キーに対応する値として利用することができるのは単一の文字列のみでした. ListResourceBundle クラスを継承したリソースクラスなら, それ以外の使い方もできます.

tt4cs.resource.ProgressiveRock_en を次のように書き換えてみます.

package tt4cs.resource;

import java.util.ListResourceBundle;

public class ProgressiveRock_en extends ListResourceBundle {
    
    @Override
    protected Object[][] getContents() {
        return new Object[][] {
            new Object[] { "ELP", new String[] {
                "Keith Emerson", "Greg Lake", "Carl Palmer" } }
        };
    }
    
}

この場合, キー "ELP" に対応する値は, 単一の文字列 (String) ではなく, 文字列の配列 (String[]) となります. クライアント側のコードとしては, たとえば次のようになりましょうか.

        Object o = progressiveRock.getObject("ELP");
        if (o instanceof String[])
            System.out.println(Arrays.asList((String[])o));

これを実行しますと, 標準出力には次のように表示されます.

[Keith Emerson, Greg Lake, Carl Palmer]

ふつうのプロパティファイルですと, たとえばコンマ区切りの文字列を文字列の配列に分割 (split) したりとか, あるいは Commons Collection の ExtendedProperties を代用したりとか, そんな手もありそうです. しかし, もし文字列ではなく, たとえば日付や時刻だとかを値として利用したいような場合, プロパティファイルですとどうしても文字列からパースしてあげなければいけませんし, それなら ListResourceBundle を利用するほうが楽かもしれないという気もします.

まあ, でも, 通常は国際化のためのリソースをバンドルするために利用するのが ResourceBundle だよなあという気もしますし, そうなると, どうなんでしょう, ListResourceBundle はおもしろそうなんだけど使う場面が現実にありうるのかどうか, ちょっと自信がありません.

まとめ

ResourceBundle に関しては, ほかに ResourceBundle.Control の話題もあって, そちらも勉強してみるともっといろいろなことができそうな予感がしています. またの機会にやってみたいと思います.

久しぶりにブログを書くというのは, けっこう疲れますね.

No comments:

Post a Comment