Java
REPL
java8

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


はじめに

Java 8 の、REPL の作成2回目となります。

目的や注意点に関しては、1回目の記事を参照して下さい。

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


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

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



  • input() コンソールからの入力受付へのインターフェース。


  • print() 画面出力メソッドのインターフェース。


  • cleanup() Interface.Finalizable インターフェースの持つメソッドへのインターフェースです。
    Interface.Finalizeable インターフェースは、後日説明します。

interface Interface { ... } と囲っているのはミスではなく、ダミーのインターフェースです。

細々としたインターフェースを単独ファイルとして作成すると、ファイルシステムが煩雑になる為、この中にインターフェースを追加していきます。


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() で生成して受け渡すだけです。


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)を返します。
    通常のアプリケーションとして使用する場合、標準入出力のコンソールは使用されませんが、Eclipse や IntelliJ 等で実行した際に使われるコンソールウィンドウでは System.console は使用できないd為、こちらが使用されます。

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 など)で使用しているエンコーディングが初期設定されている為、使用しません(見た所、変更する方法もなさそうです)。

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

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 も修正となります。

ここでは、修正箇所のみ記載いたします。全コードは前回の記事を参照してください。



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


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に組み込むクラスの作成が続きます。