必ずファイルをクローズする全行処理メソッド

http://d.hatena.ne.jp/t_yano/20061002/1159812712
http://d.hatena.ne.jp/t_yano/20061004/1159987463
http://d.hatena.ne.jp/yojik/20061007/1160158749
http://d.hatena.ne.jp/odz/20061006/1160168799

面白そうだったので、自分でもやってみた。
みんなの議論のいいとこ取り(のはず^^;)。


まず、Closure だけど、execute() に throws Exception をつけて、検査例外があげられるようにした。
onError() 改め handleException() はデフォルト再スローの方が良いと思う。
odzさんのエントリで、dispatchErrorHandler() と onError() を分けた理由、リフレクションを使った理由はよくわからなかった。

// Closure.java
public abstract class Closure {

  // throws Exception を追加。
  public abstract void execute(T input) throws Exception;

  // onError() から名前変更。
  // デフォルトの実装を変えた。
  public void handleException(Exception e) {
    if (e instanceof RuntimeException) {
      throw (RuntimeException)e;
    } else {
      throw new CheckedException(e);
    }
  }

  // continue も追加。
  protected final void continueLoop() {
    throw new ContinueException();
  }

  // stop() から名前変更。
  protected final void breakLoop() {
    throw new BreakException();
  }

}

class ContinueException extends RuntimeException {}
class BreakException extends RuntimeException {}

検査例外のラッパークラス。

// CheckedException.java
public class CheckedException extends RuntimeException {

  public CheckedException(Exception e) {
    super(e);
  }

}

File のサブクラスを作るより、Reader にした方が使い勝手がいいんじゃないかな?
eachLine() で、Closure の処理の外で発生する IOException はそのままスロー。
while 文周辺のロジックを整理した。

// IterableReader.java
public class IterableReader {

  private BufferedReader reader;

  public IterableReader(File file) throws FileNotFoundException {
    this(toBufferedReader(file));
  }
	
  public IterableReader(Reader reader) {
    this(toBufferedReader(reader));
  }
	
  public IterableReader(BufferedReader reader) {
    this.reader = reader;
  }

  private static BufferedReader toBufferedReader(File file)
      throws FileNotFoundException {
    return toBufferedReader(new FileReader(file));
  }
	
  private static BufferedReader toBufferedReader(Reader reader) {
    if (reader instanceof BufferedReader) {
      return (BufferedReader)reader;
    } else {
      return new BufferedReader(reader);
    }
  }

  public void eachLine(Closure proc) throws IOException {
    try {
      String line = reader.readLine();
      while (line != null) {
        try {
          proc.execute(line);
        } catch (ContinueException e) {
          continue;
        } catch (BreakException e) {
          break;
        } catch (Exception e) {
          proc.handleException(e);
        }
        line = reader.readLine();
      }
    } finally {
      close();
    }
  }

  protected void close() throws IOException {
    reader.close();
    Logger.global.info("**** Reader has been closed.");
  }

}


使用例。まずは、途中で break する場合。
検査例外をあげないなら execute() に throws をつける必要はなし。

public class Example {

  public static void main(String[] args) throws Exception {
    //ファイルを作って
    File file = new File("Example.java");

    //一行ずつ処理する
    new IterableReader(file).eachLine(new Closure() {
      public void execute(String line) {
        System.out.println(line);
        if (line.startsWith("p")) breakLoop();
      }
    });
    //ここではもう閉じられている。
  }
	
}

続いて、途中で例外をあげる場合。
execute() で必要な例外を throws 指定する。検査例外は CheckedException で受ける。

public class Example2 {

  public static void main(String[] args) throws Exception {
    //ファイルを作って
    File file = new File("Example2.java");

    //一行ずつ処理する
    try {
      new IterableReader(file).eachLine(new Closure() {
        public void execute(String line) throws Exception {
          System.out.println(line);
          if (line.startsWith("p")) throw new Exception();
        }
      });
    } catch (CheckedException e) {
      e.getCause().printStackTrace();
    }
    //ここではもう閉じられている。
  }
	
}

以上。


矢野さんの
> もうネタのようなクラスとインターフェースができました。
が気になるなぁ...