Mixin Script Function in JavaFX Part 3

BufferedReader in JavaFXMixin Script Function in JavaFX Part 2 で BufferedReader を JavaFX で安全に利用するための方法について紹介してきましたが、少し 他のスクリプト言語を意識しすぎて、良くも悪くも JavaFX らしさを失っていたことに今更ながら ふと気がついてしまった。

と言うのも...
JavaFX では Java の 拡張 for 文 に似た for 式 が用意されています。
前回 紹介したサンプルコードは わざわざ、他のスクリプト言語のマネなんかしなくても、以下のようにちゃんと標準で用意されている for 式 だけで実現できてしまうのです。 この方が JavaFX らしいですね。

JavaFX の for は Java や その他の言語 とは異なり、文ではなく、式なので、ちゃんと結果も返してくれます。
初めはちょっと違和感があるものの結構使える。

Main.fx
public class Main extends $ {}

public function run() {

   // ファイルの内容を標準出力
   var r = new FileReader("test.txt");
   try {
       for (line in $R(r)) println(line);
   } finally {
       r.close();
   }

   // ファイルの内容を Window へ出力
   r = new FileReader("test.txt");
   try {
       Stage {
         scene: Scene {
           content: VBox {
             content: for (line in $R(r)) Text { content: line }
           }
         }
       }
   } finally {
       r.close();
   }
}

前回との違いは _R クラスで Iterable を実装して、for 式で利用できるようにしたことです。
詳細は Cast to sequence in JavaFX でも紹介しましたが...
JavaFX の for 式でループできるのは 配列 と Iterable ですので、Iterable を実装すれば、for 式で反復処理できるのです。

ただ、ちよっと残念なのは、呼び出し側で 入力ストリームの close() を呼び出さなくてはならないこと。
Iterator で 確実に 入力ストリームの close() を呼び出す方法が思いつかない...
考えられる手としては

  1. Iterator#next() の反復処理の最終回
  2. Iterator#finalize()
  3. FX.addShutdownAction(function())
などが挙げられるが、どれもイマイチ...
その理由としては
  1. Iterator#next() が何らかの理由で最終回まで実行されない可能性がある。
  2. Iterator#finalize() は仕様上呼び出されるかどうかわからない。
  3. FX.addShutdownAction(function()) は実行タイミングが遅すぎる。
のような感じです。
まぁ、子供の頃、親に言われたように「開けたら閉める!!」と言うことで 呼び出し側で close() しましょう。

$.fx
public mixin class $ {}

protected function $R(r: Reader) { _R { reader: r }; }

protected class _R extends Iterable {
   public-init var reader: Reader;
   override function iterator(): Iterator {
       Iterator {
           var r = new BufferedReader(reader);
           var s: Object = r.readLine();
           override function hasNext(): Boolean { s != null; }

           override function next(): Object {
               var ret = s;
               s = r.readLine();   // next line
               return ret;
           }

           override function remove(): Void {
               throw new java.lang.UnsupportedOperationException();
           }
       }
   }
}