Bundle を少しだけ簡単に書く

# Bundle をビルダーパターンで書きたい

[前記事](http://qiita.com/kojionilk/items/138eea19dadb14997136) で Bundle が頻出する上割と使える入れものであることは述べたが、もう少し簡単に書きたい。

```java
final Bundle bundle = new Bundle();
bundle.putString("hoge", "hoge");
bundle.putInt("fuga", 3);
bundle.putBoolean("is_hage", true);
```

だいたい Java はオーバーロードがあるのだから `putString()` やら `putInt()` やらで型名をメソッドに付けるのは冗長に思える。大体 `Intent#putExtra()` で以下のように実現しているではないか:

```java
// 内部的に Bundle に入るが putExtra() はオーバーロードされたメソッド
final Intent intent = new Intent(this, MyActivity.class);
intent.putExtra("hoge", "hoge");
intent.putExtra("fuga", 3);
intent.putExtra("is_hage", true);
```

あと毎回改行しなければいけないのもつらい。メソッドチェーンで書きたい。

# Bundler を作ったのでソース

というわけで **Bundler** を作った。 **(注: 前は BundleBuilder としていたのだが若干クラス名が長いと思ったので Bundler にした。好きなクラス名でお使い頂きたい)** 定型的なコードが多いので下に大きくスクロールして欲しい:

```java
/**
 * Bundle を Builder パターンで扱うためのヘルパ.
 *
 * @author Hideyuki Kojima
 */
public final class Bundler {

    /** Bundle. */
    private final Bundle mBundle;

    /**
     * コンストラクタ.
     */
    public Bundler() {
        this(null);
    }

    /**
     * コンストラクタ. 与えられた Bundle で初期化する.
     *
     * @param bundle 与えられた Bundle
     */
    public Bundler(final Bundle bundle) {
        mBundle = bundle == null ? new Bundle() : new Bundle(bundle);  // 防御的コピー
    }

    /**
     * Bundle をビルドして返す.
     *
     * @return Bundle
     */
    public Bundle build() {
        return new Bundle(mBundle);  // 防御的コピー
    }

    /**
     * 内容をクリアする.
     *
     * @return this
     */
    public Bundler clear() {
        mBundle.clear();
        return this;
    }

    /**
     * 対象キーの項目を削除する.
     *
     * @param key 対象キー
     * @return this
     */
    public Bundler remove(final String key) {
        mBundle.remove(key);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final boolean value) {
        mBundle.putBoolean(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final boolean[] value) {
        mBundle.putBooleanArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final Bundle value) {
        mBundle.putBundle(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final byte value) {
        mBundle.putByte(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final byte[] value) {
        mBundle.putByteArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final char value) {
        mBundle.putChar(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final char[] value) {
        mBundle.putCharArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final CharSequence value) {
        mBundle.putCharSequence(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final CharSequence[] value) {
        mBundle.putCharSequenceArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final double value) {
        mBundle.putDouble(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final double[] value) {
        mBundle.putDoubleArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final float value) {
        mBundle.putFloat(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final float[] value) {
        mBundle.putFloatArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final int value) {
        mBundle.putInt(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final int[] value) {
        mBundle.putIntArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final long value) {
        mBundle.putLong(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final long[] value) {
        mBundle.putLongArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final Parcelable value) {
        mBundle.putParcelable(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final Parcelable[] value) {
        mBundle.putParcelableArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final Serializable value) {
        mBundle.putSerializable(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final short value) {
        mBundle.putShort(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final short[] value) {
        mBundle.putShortArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final SparseArray<? extends Parcelable> value) {
        mBundle.putSparseParcelableArray(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final String value) {
        mBundle.putString(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler put(final String key, final String[] value) {
        mBundle.putStringArray(key, value);
        return this;
    }

    /**
     * Bundle の内容をコピーする.
     *
     * @param bundle Bundle
     * @return this
     */
    public Bundler putAll(final Bundle bundle) {
        mBundle.putAll(bundle);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler putCharSequenceList(final String key, final ArrayList<CharSequence> value) {
        mBundle.putCharSequenceArrayList(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler putIntegerList(final String key, final ArrayList<Integer> value) {
        mBundle.putIntegerArrayList(key, value);
        return this;
    }

    /**
     * 対象キーに値を設定する.
     *
     * @param key 対象キー
     * @param value 値
     * @return this
     */
    public Bundler putStringList(final String key, final ArrayList<String> value) {
        mBundle.putStringArrayList(key, value);
        return this;
    }

    @Override
    public String toString() {
        return mBundle.toString();
    }
}
```

# 使い方

Bundler は Bundle を構築する事に特化しているので Bundle の `putXXX()` `remove()` `clear()` のみサポートする。キーを iterate したり存在チェックしたりは `build()` して Bundle にしてから行う想定。そもそも `containsKey()` とかしたら戻り値 boolean なのでチェーン出来なくて美しくないというのもある。 

新しい Bundle を作るとき以下のように書くことができる:

```java
Bundle bundle = new Bundler().put("hoge", "hoge").put("fuga", 3).put("is_hage", true).build();
```

元々ある Bundle を元に追加・削除したい場合はコンストラクタの引数に渡す。ディープコピーされるので元の Bundle インスタンスに影響はない:

```java
Bundle src = new Bundler().put("hoge", "hoge").put("fuga", 3).put("is_hage", true).build();
Bundle dest = new Bundler(src).remove("hoge").put("date", new Date()).build();
// これで dest には fuga, is_hage, date が存在する
// src は hoge, fuga, is_hage のまま
```

# 注意点

オーバーロード対象に複数合致するような場合は正しく使用できないので、明示的にキャストして使う。
例えば StringBuilder は Serializable かつ CharSequence なので `Bundler.put()` しようとしても `Ambiguous method call.` と怒られてしまう。

```java
new Bundler().put("sb", new StringBuilder()).build();  // ビルドできない
new Bundler().put("sb", (CharSequence) new StringBuilder()).build();  // OK
```

# テストコード

テストも書いたのでついでに載せておく。

* オブジェクトを new した時 Bundle を引数に入れた場合ディープコピーされること
* それぞれ期待する型として Bundle に put できていること

```java
public class BundlerTest extends TestCase {

    /**
     * コンストラクタが正しく動作することを確認する.
     */
    public void testNewInstance() {

        // 空の Bundle が正しくできることを確認
        assertTrue(new Bundler().build().isEmpty());

        // Bundle の内容が正しくコピーされているがオブジェクトは同一でないことを確認
        final Bundle src = new Bundle();
        src.putString("hoge", "hoge");
        src.putInt("fuga", 3);
        src.putBoolean("is_hage", true);
        final Bundle dest = new Bundler(src).build();
        assertEquals(src.getString("hoge"), dest.getString("hoge"));
        assertEquals(src.getInt("fuga"), dest.getInt("fuga"));
        assertEquals(src.getBoolean("is_hage"), dest.getBoolean("is_hage"));
        assertNotSame(src, dest);
    }

    /**
     * 各種 put メソッドが正しく動作することを確認する.
     */
    public void testPut() {
        final StringBuilder sb = new StringBuilder("b");  // CharSequence
        final Intent intent = new Intent();  // Parcelable
        final Date date = new Date();  // Serializable
        final Bundle bundle = new Bundle();
        final SparseArray<Parcelable> sparseArray = new SparseArray<Parcelable>();
        sparseArray.put(0, new Intent());
        Bundle b;
        b = new Bundler()
                .put("boolean", true)
                .put("boolean_array", new boolean[] {true})
                .put("bundle", bundle)
                .put("byte", (byte) 1)
                .put("byte_array", new byte[] {(byte) 2})
                .put("char", 'a')
                .put("char_array", new char[] {'A'})
                .put("char_sequence", (CharSequence) sb)
                .put("char_sequence_array", new CharSequence[] {sb})
                .put("double", 3d)
                .put("double_array", new double[] {4d})
                .put("float", 5f)
                .put("float_array", new float[] {6f})
                .put("int", 7)
                .put("int_array", new int[] {8})
                .put("long", 9L)
                .put("long_array", new long[] {10L})
                .put("parcelable", intent)
                .put("parcelable_array", new Parcelable[] {intent})
                .put("serializable", date)
                .put("short", (short) 11)
                .put("short_array", new short[] {(short) 12})
                .put("sparse_array", sparseArray)
                .put("string", "c")
                .put("string_array", new String[] {"C"})
                .build();

        // それぞれ想定した型として Bundle.getXXX できることを確認
        assertTrue(b.getBoolean("boolean"));
        assertTrue(b.getBooleanArray("boolean_array")[0]);
        assertSame(bundle, b.getBundle("bundle"));
        assertEquals((byte) 1, b.getByte("byte"));
        assertEquals((byte) 2, b.getByteArray("byte_array")[0]);
        assertEquals('a', b.getChar("char"));
        assertEquals('A', b.getCharArray("char_array")[0]);
        assertSame(sb, b.getCharSequence("char_sequence"));
        assertEquals(sb, b.getCharSequenceArray("char_sequence_array")[0]);
        assertEquals(3d, b.getDouble("double"));
        assertEquals(4d, b.getDoubleArray("double_array")[0]);
        assertEquals(5f, b.getFloat("float"));
        assertEquals(6f, b.getFloatArray("float_array")[0]);
        assertEquals(7, b.getInt("int"));
        assertEquals(8, b.getIntArray("int_array")[0]);
        assertEquals(9L, b.getLong("long"));
        assertEquals(10L, b.getLongArray("long_array")[0]);
        assertSame(intent, b.getParcelable("parcelable"));
        assertSame(intent, b.getParcelableArray("parcelable_array")[0]);
        assertSame(date, b.getSerializable("serializable"));
        assertEquals((short) 11, b.getShort("short"));
        assertEquals((short) 12, b.getShortArray("short_array")[0]);
        assertSame(sparseArray, b.getSparseParcelableArray("sparse_array"));
        assertEquals("c", b.getString("string"));
        assertEquals("C", b.getStringArray("string_array")[0]);
    }
}
```

#2015/7 追記: 厳密性に欠けるがもっと簡単に書けるユーティリティメソッド

いちいち new して put() していくのさえも面倒という方 (筆者もだが) 向けのもっと簡易的なものも作成してみた。

```java
/**
 * Bundle に連続して値を詰める為のショートカット.
 *
 * @param args キーと値のペア
 * @return Bundle
 * @throws IllegalArgumentException args が偶数でない場合, 奇数個目（キー）が String でない場合
 */
@NonNull
public static Bundle bundle(@NonNull final Object...args) {
    if (args.length % 2 != 0) {
        throw new IllegalArgumentException("引数が偶数個になっていない.");
    }
    final Bundle bundle = new Bundle();
    for (int i = 0, size = args.length; i < size; i += 2) {
        if (args[i] instanceof String == false) {
            throw new IllegalArgumentException("キーにあたるパラメータが String で与えられていない.");
        }
        final String key = (String) args[i];
        final Object value = args[i + 1];
        if (value == null) {
            bundle.putString(key, null);
        } if (value instanceof Boolean) {
            bundle.putBoolean(key, (Boolean) value);
        } else if (value instanceof boolean[]) {
            bundle.putBooleanArray(key, (boolean[]) value);
        } else if (value instanceof Bundle) {
            bundle.putBundle(key, (Bundle) value);
        } else if (value instanceof Byte) {
            bundle.putByte(key, (Byte) value);
        } else if (value instanceof byte[]) {
            bundle.putByteArray(key, (byte[]) value);
        } else if (value instanceof String) {
            bundle.putString(key, (String) value);
        } else if (value instanceof String[]) {
            bundle.putStringArray(key, (String[]) value);
        } else if (value instanceof Character) {
            bundle.putChar(key, (Character) value);
        } else if (value instanceof char[]) {
            bundle.putCharArray(key, (char[]) value);
        } else if (value instanceof CharSequence) {
            bundle.putCharSequence(key, (CharSequence) value);
        } else if (value instanceof CharSequence[]) {
            bundle.putCharSequenceArray(key, (CharSequence[]) value);
        } else if (value instanceof Double) {
            bundle.putDouble(key, (Double) value);
        } else if (value instanceof double[]) {
            bundle.putDoubleArray(key, (double[]) value);
        } else if (value instanceof Float) {
            bundle.putFloat(key, (Float) value);
        } else if (value instanceof float[]) {
            bundle.putFloatArray(key, (float[]) value);
        } else if (value instanceof Short) {
            bundle.putShort(key, (Short) value);
        } else if (value instanceof short[]) {
            bundle.putShortArray(key, (short[]) value);
        } else if (value instanceof Integer) {
            bundle.putInt(key, (Integer) value);
        } else if (value instanceof int[]) {
            bundle.putIntArray(key, (int[]) value);
        } else if (value instanceof Long) {
            bundle.putLong(key, (Long) value);
        } else if (value instanceof long[]) {
            bundle.putLongArray(key, (long[]) value);
        } else if (value instanceof Parcelable) {
            bundle.putParcelable(key, (Parcelable) value);
        } else if (value instanceof Parcelable[]) {
            bundle.putParcelableArray(key, (Parcelable[]) value);
        } else if (value instanceof Serializable) {
            bundle.putSerializable(key, (Serializable) value);
        } else {
            throw new IllegalArgumentException(key + "に対応する値が解釈できない.");
        }
    }
    return bundle;
}
```

使い方は非常に簡単で以下の様に書けばよい:

```java
final Bundle bundle = bundle("hoge", "fuga", "hage", 1);  // {"hoge": "fuga", "hage": 1} といった Bundle 
```

引数を必ず key と value の順番の偶数個にすることに注意。
また ArrayList に関して `ArrayList<Integer>` と `ArrayList<String>` と `ArrayList<? extends Parcelable>` の判定が難しいかと思ったが ArrayList は Serializable なのでこのコードで問題なかった。Bundle のソースコードを追ってみると `Bundle#putIntegerArrayList()` も `Bundle#putStringArrayList()` も全部結局フィールドに持っている `ArrayMap<String, Object>` に一様に `put(key, value)` しているので詰める段階でどのメソッドを使ったかは関係ないようだ。つまり ArrayList 系は `Bundle#putSerializable()` で詰められさえすれば全く問題ない。Bundle の putXXX 系のメソッドはあくまで詰められる型を復元可能な型に制限したいが為にあるように見える。

これを使い出すとほとんどの場面でこのメソッドで足りてしまうため、個人的に前述の Bundler は未使用になってしまった……。