JavaでREPLを実現する高機能なライブラリーであるJLine3のメモ。
|
|
JLineはJavaでREPL(対話型コンソールツール)を実現できるライブラリー。BSDライセンス。
JLine3を使ったプログラムはEclipse上で実行できるが、Eclipseのコンソールの挙動がコマンドプロンプト等と異なるため(カーソル キーの上下でコンソール内を自由に移動できる等)、動作確認はEclipse上では行わない方が良いと思う。
Java12以降のJShellでもJLine3が使われている。
(JDKのソースのjdk.shell/jdk/internal/jshell/tool/ConsoleIOContext.java参照)
(パッケージ名はjdk.internal.org.jlineなので、JLine3のライブラリー(org.jline)がそのまま使われているわけではない)
JLine3はMavenリポジトリーから取得できる。
plugins { id 'java' id 'eclipse' } java { sourceCompatibility = JavaVersion.VERSION_18 targetCompatibility = JavaVersion.VERSION_18 } compileJava { options.encoding = 'UTF-8' } compileTestJava { options.encoding = 'UTF-8' } repositories { mavenCentral() } dependencies { // https://mvnrepository.com/artifact/org.jline/jline implementation group: 'org.jline', name: 'jline', version: '3.21.0' implementation group: 'net.java.dev.jna', name: 'jna', version: '5.12.1' implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.12.1' }
Windowsのコマンドプロンプトで使用する場合、JNAを利用するので、JNAのライブラリーも必要となる。
依存関係にJNAを入れていないと、コマンドプロンプト用のTerminalがDumbTerminalになり、色々機能が使えなくなる。
JLine3を使ったプログラムはEclipse上で実行できるが、Eclipseのコンソールの挙動がコマンドプロンプト等と異なるため、動作確認はEclipse上では行わない方が良いと思う。
したがって、プログラムをビルド(jarファイル化)してjavaコマンドで実行することになるが、javaコマンドに依存ライブラリーを指定するのが面倒なので、fat jar(全ライブラリーが入っているjarファイル)にするのが楽。
fat jarを作るには、shadowプラグインを使うのが便利。
plugins { id 'java' id 'eclipse' id 'com.github.johnrengelman.shadow' version '7.1.2' } 〜 jar { manifest { attributes "Main-Class": "com.example.jline.JLineExample" } }
jar.manifest.attributesでMain-Classを指定しておくと、javaコマンドの-jarオプションでそのクラスを実行できる。
$ ./gradlew clean shadowJar $ ls build/libs/ example-jline-all.jar $ java -jar build/libs/example-jline-all.jar $ java -cp build/libs/example-jline-all.jar com.example.jline.JLineExample
なお、gradleが使用するjavaコマンドのバージョンがbuild.gradleのsourceCompatibilityやtargetCompatibilityに指定しているバージョンより古い場合は、ビルドエラーになる。
この場合は、新しいバージョンのjavaのJAVA_HOMEを明示してビルド・実行すればよい。
Windows(コマンドプロンプト) | Linux |
---|---|
set JAVA_HOME="C:\Program Files\Java\jdk-18"
|
export JAVA_HOME=〜 |
gradlew shadowJar
-Dorg.gradle.java.home=%JAVA_HOME% |
./gradlew shadowJar
-Dorg.gradle.java.home="$JAVA_HOME" |
%JAVA_HOME%\bin\java -jar
build/libs/example-jline-all.jar |
"$JAVA_HOME"/bin/java -jar
build/libs/example-jline-all.jar |
ちなみに、Gradleのapplicationプラグインを入れると「gradlew run」でJavaアプリケーションを実行できるのだが、
JLine3(というより標準入力を使用する対話型アプリケーション)は扱えない。
(入力が即座に終了し、EndOfFileExceptionが発生する)
JLine3を使ったプログラムの例。
package com.example.jline; import java.io.IOException; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder;
public class JLineExample { public static void main(String[] args) throws IOException { try (Terminal terminal = TerminalBuilder.terminal()) { LineReader lineReader = LineReaderBuilder.builder() // .terminal(terminal) // .build(); for (;;) { String line = lineReader.readLine("jline> "); // System.out.println(line); terminal.writer().println(line); } } } }
Terminalが端末(キー入力や画面出力)を扱うクラス。
デフォルトでは標準入力・標準出力となる。
LineReaderがユーザーからの入力を処理するクラス。
デフォルトではユーザーが1行入力すると(ENTERキーを押すと)readLine()から1行分のデータが返ってくる。
(→複数行の入力)
出力はterminal.writer()(PrintWriter)に対して行うのが良さそう。
このサンプルは無限ループだが、Ctrl+Dを押せば強制的に終了できる。
(EndOfFileExceptionが発生する)
(デフォルトではCtrl+Cでも強制終了できる→Ctrl+C無効化)
jline> aaa aaa jline> bbb bbb jline>
lineReader.readLine()で指定した文字列がプロンプト(この例では「jline>
」)になる。