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> 」)になる。