S-JIS[2022-09-01/2022-10-03] 変更履歴

JLine3

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リポジトリーから取得できる。

build.gradleの例:

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を入れていないと、コマンドプロンプト用のTerminalDumbTerminalになり、色々機能が使えなくなる。


fat jar

JLine3を使ったプログラムはEclipse上で実行できるが、Eclipseのコンソールの挙動がコマンドプロンプト等と異なるため、動作確認はEclipse上では行わない方が良いと思う。

したがって、プログラムをビルド(jarファイル化)してjavaコマンドで実行することになるが、javaコマンドに依存ライブラリーを指定するのが面倒なので、fat jar(全ライブラリーが入っているjarファイル)にするのが楽。


fat jarを作るには、shadowプラグインを使うのが便利。

example-jline/build.gradleの例(抜粋):

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を使ったプログラムの例。

JLineExample.java:

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


JLineへ戻る / Javaへ戻る / 技術メモへ戻る
メールの送信先:ひしだま