IterableなReaderクラスを作ってみる

Reader に文字コード指定できない件で、標準ライブラリに無ければ commons を使えばいいじゃない、という発言を見かけたので、commons.io を覗いてみた。FileUtils クラスに

FileUtils#readLines(File,String)
FileUtils#lineIterator(File,String) 

といった static メソッドがあるようだ。これの事を言っていたのだろうか?

LineIterator はその名の通り Iterator インターフェースを実装しているのだが、next() メソッドでストリーム読み込みに付き物の IOException をどう処理しているのか気になる。ソースを見ると、next() メソッド内部で IOException が発生した場合は、IllegalStateException をスローするようになっている。next() では検査例外をスローできないので仕方ないとはいえ、非検査例外を投げるというのはアリなのか?

しかし、それで問題ないのならば、拡張for文が使える今となっては Iterator を返すユーティリティメソッドよりも、ストレートに Iterable を実装した Reader クラスの方が嬉しい。というわけで書いてみた。

public class LineReader extends Reader implements Iterable<String> {
    
    private BufferedReader reader;
    
    private IOException ioException;
    
    public LineReader(BufferedReader reader) {
        super();
        this.reader = reader;
    }

    public LineReader(Reader reader) {
        this(new BufferedReader(reader));
    }
    
    private class LineIterator implements Iterator<String> {
        
        private String line;
        
        private String nextLine() throws IOException {
            String line = this.line;
            if (line == null) {
                line = reader.readLine();
                if (line == null) { 
                    throw new NoSuchElementException();
                }
            } else {
                this.line = null;
            }
            return line;
        }
        
        private boolean hasNextLine() throws IOException {
            if (this.line == null) {
                this.line = reader.readLine();
            }
            return this.line != null;
        }

        public boolean hasNext() {
            try {
                return this.hasNextLine();
            } catch (IOException e) {
                ioException = e;
                return false;
            }
        }

        public String next() {
            try {
                return this.nextLine();
            } catch (IOException e) {
                ioException = e;
                throw new IllegalStateException(e);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
    
    public boolean hasError() {
        return this.ioException != null;
    }
    
    public void raiseError() throws IOException {
        if (this.ioException != null) {
            throw ioException;
        }
    }
    
    @Override
    public void close() throws IOException {
        this.reader.close();    
    }
    
    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return reader.read(cbuf, off, len);
    }

    public Iterator<String> iterator() {
        return new LineIterator();
    }
}

前回作ったMultibyteTextReaderを使って

LineReader lr = new LineReader(new MultibyteTextReader("hoge.txt", "utf-8"));
try {
   for(String line: lr) {
       System.out.println(line);
   }
} finally {
   r.close();
}

みたいにスッキリ書ける。適当に作ったけど意外とよさげな感じ。

これなら冗長と揶揄にされるJavaのファイル読み込みも、かなーり簡単に書けるようになるんじゃないか。もっと短くしたければファイル名とエンコーディングを引数とするLineReaderコンストラクタを作るといいかもな。

あとはみんなでこーいうちょっと便利なクラスを作って登録して使えるJavaCPANみたいのがあれば完璧なんだがな〜