読者です 読者をやめる 読者になる 読者になる

【Twitter4J】CursorSupportをIterableにする

Twitter4JにはCursorSupportと言うインターフェースがあります。

普段使っている分には中々見ないインターフェースです。これは基本的にIDsPagableResponseList<T>と言った形で返ってきます。そちらの方は度々見かけると思います。

さて、このCursorSupportなんですが、メソッド一覧をパッと見た限りだとIteratorパターンっぽいです。が、foreachを使うことは出来ません。getNextCursor/getPreviousCursorも返ってくるのはlongでしかありません。

この返ってきたlong(=Cursor)をTwitter配下のメソッド(例えばgetFriendsIDsメソッド)の引数に渡してやり、また返ってきたIDsなりPagableResponseList<T>なりからCursorを取得する必要があるためです。

APIの枯渇などによる例外処理はそっちの方がしやすいのでそれでもいいんですが、出来ればとりあえずとれるとこまで全部とってきて欲しいです。

こんな形になっている理由は「再度投げ直すためのメソッドがCursorSupport側でわからない」の一点のみです。逆に言えば、投げ直すためのメソッドをパラメータ化できれば、Iteratorパターンに出来るはずです。

ええ、またクロージャの話ですよ。

とりあえず基本形を作ろう

以前AdapterをIteratorパターンに変えた時のようにとりあえず必須のメソッドだけでも考えましょう。

public final class IterableCursor<T extends CursorSupport> implements Iterable<T>, Iterator<T> {
    
    private CursorSupport _cursor;
    private long _cursorValue = -1;
    private R1<T, Long> _cursorProvider;
    
    public IterableCursor(R1<T, Long> cursorProvider) {
        _cursorProvider = cursorProvider;
    }
    
    @Override
    public Iterator<T> iterator() {
        return this;
    }
    
    @Override
    public boolean hasNext() {
        if(_cursor == null) return true;
        
        if(_cursor.hasNext()) {
            _cursorValue = _cursor.getNextCursor();
            return true;
        } else {
            return false;
        }
    }
    
    @Override
    public T next() {
        _cursor = _cursorProvider.call(_cursorValue);
        return (T) _cursor;
    }
    
}

うーん、あんまりクールじゃないですね。

nextで返ってきて欲しいのはCursorSupportを実装した何か(つまり、IDsPagableResponseList<T>)です。なので、境界を設けています。

TwitterExceptionを対処する

基本的な考え方はこれだけでOKなんですが、問題があります。CursorSupportを手に入れるFunctionでは、ほぼ間違いなくTwitterExceptionが飛んでくる可能性があると言う問題です。

Function内はもうどうにもなりません。とりあえずRuntimeExceptionあたりにラップして、throwするのが関の山でしょう。

R1<IDs, Long> cursorProvider = new R1<IDs, Long>() {
    @Override
    public IDs call(Long cursor) {
        try {
            return twitter.getFollowersIDs(userId, cursor);
        } catch(TwitterException e) {
            throw new RuntimeException(e);
        }
    }
};

問題はこの例外を受け取るIterableCursorがするべき振る舞いです。とりあえず先ほどのnext内にtry-catchを追加します。

@Override
public T next() throws RuntimeException {
    try {
        _cursor = _cursorProvider.call(_cursorValue);
    } catch(RuntimeException e) {
        throw e;
    }
    return (T) _cursor;
}

TwitterExceptionは元々チェック例外ですので、流石に握りつぶすのはちょっとまずいです。かと言ってスタックトレースだけ表示するのも、何だか目覚めが悪いと言うか、もうちょっとどうにかならないの感があります。

仕方ないので再送出してしまいます。TwitterExceptionそのものを返せないのがなんだかなー、って感じですね。

コンストラクタでV1<TwitterException>を渡し、エラーが発生したらそれに任せるなんて方法もありますが。何にしても妥協でしかありません。

まとめ

エラー処理は妥協してしまいましたが、何とか形にはなりました。

しかしこう、クロージャとチェック例外は死ぬほど相性が悪いと思うんですが、どうにかならないんですかねー。

せめてこんな形で書ければまだマシなんですが…。

try {
    R1<IDs, Long> cursorProvider = new R1<IDs, Long>() {
        @Override
        public IDs call(Long cursor) {
            return twitter.getFollowersIDs(userId, cursor);
        }
    };
} catch(TwitterException e) {
    // error handling
}

しかしまぁ、これだとcallが呼ばれたときにどおやってcatchに飛ばすの???ってのもあるわけで。難しいですね。