REPL(?) 作成への道　(2)

# はじめに

Java 8 の、[REPL](https://qiita.com/tags/repl) の作成２回目となります。
目的や注意点に関しては、1回目の記事を参照して下さい。

* [REPL(?) 作成への道　(1)](https://qiita.com/KG_dash/items/d44e5521594c4107fc8a)
 

それでは早速、ソースの説明に入ります。

# コンソールインターフェース

Windows の cmd や、Eclipse のコンソールへのアクセスを提供するインターフェースです。

* ```input()``` コンソールからの入力受付へのインターフェース。
* ```print()``` 画面出力メソッドのインターフェース。
* ```cleanup()```  Interface.Finalizable インターフェースの持つメソッドへのインターフェースです。<br>Interface.Finalizeable インターフェースは、後日説明します。

```interface Interface { ... }``` と囲っているのはミスではなく、ダミーのインターフェースです。
細々としたインターフェースを単独ファイルとして作成すると、ファイルシステムが煩雑になる為、この中にインターフェースを追加していきます。

```java:Interface.java
package console;

interface Interface {
    /** console interface. */
    interface Console extends Interface.Finalizable {
        // methods for input string from console ======================================
        default String input(String prompt);

        // methods for output string to console =======================================
        void print(String format, Object... args);
        void print(String s);

        // extends Inteeface.Finalizeable =============================================
        void cleanup();
    }
    // ... more interface ...
}
```

# コンソールサービス
このファイルも詰め込んでいます...。
具象コンソールクラスは、```Console.create()``` で生成して受け渡すだけです。

```java:Service.java
package console;

public interface Service {
    class Console {
        public  static final String newLine = System.lineSeparator();
        private static final String defaultEncoding = "UTF-8";

        public  static Interface.Console create(String encoding) {
            if ( encoding == null  ||  encoding.trim().isEmpty() ) { 
                encoding = defaultEncoding; 
            }
            if ( !java.nio.charset.Charset.isSupported(encoding) ) {
                encoding = defaultEncoding; 
            }
            return    (System.console() != null)
                    ? new SystemConsole(System.console())
                    : new StandardConsole(encoding);
        }

        // ///////////////////////////////////////////////////////////////////////////////
        /** console uses standard in/out class.
         */ // ///////////////////////////////////////////////////////////////////////////
        private static class StandardConsole implements Interface.Console {
            protected StandardConsole(String encoding) {
                try {
                    java.io.InputStreamReader istream = new java.io.InputStreamReader(System.in, encoding);
                    reader_  = new java.io.BufferedReader(istream);
                    out_     = new java.io.PrintStream(System.out, true, encoding);
                } catch (java.io.IOException e) {
                    System.out.println( e.getMessage() );
                }
            }

            // methods for input string from console ===============================
            public String input(String prompt) {
                out_.printf(prompt);
                String s = "";
                try {
                    s = reader_.readLine();
                } catch (java.io.IOException e) {
                    System.out.println( e.getMEssage() );
                }
                return s;
            }

            // methods for output string to console ================================
            public void print(String format, Object... args) { out_.printf(format, args); }
            public void print(String s)                      { out_.println(s);           }

            // implements Finalizable.cleanup() ====================================
            public void cleanup() {
                out_.close();
                out_ = null;
                try {
                    reader_.close();
                    reader_ = null;
                } catch(java.io.IOException e) {
                    System.out.println( e.getMessage() );
                }
            }

            // internal fields =====================================================
            java.io.BufferedReader reader_;
            java.io.PrintStream    out_;
        }

        // ///////////////////////////////////////////////////////////////////////////////
        /** console uses System.console() class.
         */ // ///////////////////////////////////////////////////////////////////////////
        private static class SystemConsole implements Interface.Console {
            protected SystemConsole(java.io.Console con) {
                console_ = con;
            }

            // methods for input string from console ===============================
            public String input(String prompt) { return console_.readLine(prompt); }

            // methods for output string to console ================================
            public void print(String format, Object... args) {
               console_.printf(format, args); 
            }
            public void print(String s) {
                console_.printf(s + Service.Console.newLine); 
            }

            // implements Finalizable.cleanup() ====================================
            public void cleanup() { /* no operation */ }

            // internal fields =====================================================
            java.io.Console console_;
        }
    }
}
```

## 説明

### Service.Console
サービスとついていますが、実際には、Console のファクトリークラスです。
コンソールのエンコーディング名を受け取り、それを元に具象コンソールクラスを生成して返します。
不正なエンコーディング名が渡された場合は、デフォルトの "UTF-8" をセットします。

* ```System.console()``` (java 1.6 以降)が使用できる場合、こちらを利用したコンソールオブジェクト(SystemConsole)を返します。
* ```System.console()``` が使用できない場合は、標準の入出力(InputStreamReader と PrintStream)を利用したコンソールオブジェクト(StandardinConsole)を返します。<br>通常のアプリケーションとして使用する場合、標準入出力のコンソールは使用されませんが、Eclipse　や IntelliJ 等で実行した際に使われるコンソールウィンドウでは System.console は使用できないｄ為、こちらが使用されます。

```java:
public interface Service {
    class Console {
        public  static final String newLine = System.lineSeparator();
        private static final String defaultEncoding = "UTF-8";

        public  static Interface.Console create(String encoding) {
            if ( encoding == null  ||  encoding.trim().isEmpty() ) { 
                encoding = defaultEncoding; 
            }
            if ( !java.nio.charset.Charset.isSupported(encoding) ) {
                encoding = defaultEncoding; 
            }
            return    (System.console() != null)
                    ? new SystemConsole(System.console())
                    : new StandardConsole(encoding);
        }
        // ... inner class ...
}
```

### SystemConsole
Sysyem.Console を利用したコンソールクラスです。
単純に、処理を委譲しているだけですので、特に説明は不要かと思います。
上の Service.Console で、一所懸命、エンコーディングのチェックを行っていますが、こちらでは、実際にコンソール(cmd.exe など)で使用しているエンコーディングが初期設定されている為、使用しません(見た所、変更する方法もなさそうです)。

```java:
public interface Service {
    class Console {
        // ... 省略 ...

        // ///////////////////////////////////////////////////////////////////////////////
        /** console uses System.console() class.
         */ // ///////////////////////////////////////////////////////////////////////////
        private static class SystemConsole implements Interface.Console {
            protected SystemConsole(java.io.Console con) { console_ = con; }

            // methods for input string from console ===============================
            public String input(String prompt) { return console_.readLine(prompt); }

            // methods for output string to console ================================
            public void print(String format, Object... args) {
               console_.printf(format, args); 
            }
            public void print(String s) {
                console_.printf(s + Service.Console.newLine); 
            }

            // implements Finalizable.cleanup() ====================================
            public void cleanup() { /* no operation */ }

            // internal fields =====================================================
            java.io.Console console_;
        }
    }
}
```

### StandardinConsole
こちらは、標準入出力を使用したコンソールクラスです。
SystemConsole と同じことをやっているだけですが、ボイラープレートのおかげでコード量2倍となっています。
それでもファクトリーでエンコーディングチェックを行ってりる分、記述量は減っています。

* ```StandardConsole(String)```  引数で受け取ったエンコーディング名を BufferReader、PrintStream にセットしています。
* ```input(String)```  プロンプトを表示し、```BufferdReader.readLine()``` から受け取った文字列を返すだけのはずですが、2行で済むはずのコードが大変なことに...。~~こういうところが鬱陶しくてキライ~~
* ```print()```  これは、PrintStream に転送しているだけです。
* ```cleanup()```  Interface.Finalizable のインターフェースメソッドの実装です。BufferdReader と、PrintStream とで同じような処理をしてるのに、片方だけ例外チェックが必要とか...。こういうところが(ry

```java:
public interface Service {
    class Console {
        // ... 省略 ...

        // ///////////////////////////////////////////////////////////////////////////////
        /** console uses standard in/out class.
         */ // ///////////////////////////////////////////////////////////////////////////
        private static class StandardConsole implements Interface.Console {
            protected StandardConsole(String encoding) {
                try {
                    java.io.InputStreamReader istream = new java.io.InputStreamReader(System.in, encoding);
                    reader_  = new java.io.BufferedReader(istream);
                    out_     = new java.io.PrintStream(System.out, true, encoding);
                } catch (java.io.IOException e) {
                    System.out.println( e.getMessage() );
                }
            }

            // methods for input string from console ===============================
            public String input(String prompt) {
                out_.printf(prompt);
                String s = "";
                try {
                    s = reader_.readLine();
                } catch (java.io.IOException e) {
                    System.out.println( e.getMessage() );
                }
                return s;
            }

            // methods for output string to console ================================
            public void print(String format, Object... args) { out_.printf(format, args); }
            public void print(String s)                      { out_.println(s);           }

            // implements Finalizable.cleanup() ====================================
            public void cleanup() {
                out_.close();
                out_ = null;
                try {
                    reader_.close();
                    reader_ = null;
                } catch(java.io.IOException e) {
                    System.out.println( e.getMessage() );
                }
            }

            // internal fields =====================================================
            java.io.BufferedReader    reader_;
            java.io.PrintStream       out_;
    }
}
```

# BaseREPL の修正
エンコーディング名を渡す必要があることから、前回の BaseREPL も修正となります。
ここでは、修正箇所のみ記載いたします。全コードは[前回の記事](https://qiita.com/KG_dash/items/d44e5521594c4107fc8a)を参照してください。

* ```BaseREPL()``` コンストラクターで、コンソールに渡すエンコーディング名を受け取るようにしました。<br>当初の予定では、Service.Console で静的に生成したコンソールオブジェクトを渡すことを想定していましたが、インスタンス化して渡すように変更した為、初期化をコンストラクターに移動しています。

```java:BaseREPL.java
class BaseREPL {
    BaseREPL(String encoding) {
        if ( encoding == null  ||  encoding.trim().isEmpty() ) { encoding = "UTF-8"; }
        console_ = Service.Console.create(encoding);
    }
    // ... 途中省略 ...
    // internal fields =============================================================
    protected final Interface.Console console_;
}
```

# まとめ
今回は、REPLが利用するコンソールの説明でした。
当初はコンパクトだったのですが、あれこれ修正している間に、それなりのボリュームになってしまいました。

もう数回は、REPLに組み込むクラスの作成が続きます。