Skip to content

archiecobbs/java-console-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java Console Toolkit

A toolkit for adding a command line interface (CLI) console to a Java application.

Why?

There are lots of bits and pieces out there that can be useful for adding a command line interface (CLI) console to a Java application. However, it can be confusing to understand how they all (maybe) fit together. The goal of the java-console-toolkit (JCT) project is to simplify life for Java developers by providing some "glue" around a few of those bits and pieces and providing an easy path to get up and running quickly.

Considerations

Suppose you have a Java application of some kind and you want to add a command line interface (CLI).

What are some issues that may come up?

  • Can I have auto-discovered "pluggable" commands?
  • Is it possible to make the console accessible securely via SSH?
  • Can I have bash-like line editing and command history?
  • Can I attach this console to the system console (stdin, stdout, stderr)?
  • Can I implement my own custom read-eval-print loop?
  • Is it possible to integrate JShell so I can access Java objects?
  • Will this console work properly on different operating systems?
  • Is there some simple code that gets me started but doesn't ultimately limit me?

This project seeks to clarify these questions and allow you to answer YES.

Status

This project is a work in progress.

Features implemented so far:

  • Command and shell abstractions
  • Direct command execution from command line
  • Basic command line parser with quoting
  • Basic command shell with read-eval-print loop (REPL)
  • Terminal line editing support via Jline3
  • SSH server integration
    • Public key authentication (required)
    • SSH remote command execution
    • SSH remote shell execution
  • JShell integration
    • Direct execution as primary shell
    • Subshell execution via separate command
    • Local execution + class path/loading fixes

Possible future features:

  • Glue for Spring Shell
  • Glue for PicoCLI
  • More elaborate command line features
    • Persistent history
    • Colors
    • Etc.
  • Batch script support
  • Your well designed contributions

JCT Concepts

Let's nail down some concepts used by this library.

An I/O stream is a simple byte-oriented conduit, that is, an InputStream or an OutputStream. Actually we want PrintStream instead of OutputStream because we're going to assume that there is some defined character encoding; this allows us to print text.

A command is a lot like a function. It has a name and it takes zero or more parameters which are all strings. Some of the parameters may be flag-like options, but it's really entirely up to the command as to how it interprets the parameters. It executes for a while, does something (hopefully useful), and then it completes. It can be interrupted if we get tired of waiting for it to complete. On completion, it may or may not return some value, which could boolean (i.e., success or failure), or an integer code (zero for succes, non-zero for error), etc. In this project, commands return integers.

When a command executes, it is given access to the three standard I/O streams: input, output, and error. They will usually contain human-readable content because there's usually a human at the other end (i.e., using the console), but that is entirely up to the command.

A terminal is a text-based user interface. It will be associated with a keyboard of some kind for input and a textual display of some kind for output. Examples include (a server's local representation of) SSH and telnet clients, and the system console from which a Java process is launched. In this project, a terminal is represented by an instance of JLine3's Terminal class.

A terminal communicates using two underlying I/O streams, one for each direction. The data transmitted on these streams is encoded according to a protocol defined by the terminal type. This protocol allows for sending not only normal text "as is" but also special output commands like "clear the screen" and special input commands like "signal an interrupt" (might be sent when someone on the remote end presses Control-C). Using the protocol for sending and receiving "as is" text, a Terminal can provide the input and output streams that a command expects.

Note, however, terminals don't have a separate stream for error output: instead, any command error output is instead just treated like normal output. Using this simple trick, a terminal can provide any command with the three I/O streams it needs. Therefore, any command can execute on either three raw streams or on a terminal. However, the converse is not always true: some commands may require a terminal to function properly, for example, a text editor.

A shell is software that allows a terminal to be used interactively to execute commands via some kind of Read-Eval-Print Loop. It normally takes a line of text as input, parses it into a command name and arguments, and uses those to choose and execute some command. The shell must implement some kind of syntax and parsing behavior, for example, by splitting on whitespace and providing some way to quote whitespace. There is no universal standard for command line parsing and quoting, so each shell must define its own syntax. Ideally, a shell should also support terminal-enabled features like command line editing, command history, tab completion, etc.

A subshell is a shell that is started by executing a command in another, outer shell. While a subshell is executing, it takes control of the terminal and/or I/O streams of the parent shell. Upon exit from the subshell, the outer shell resumes control and continues as before.

A batch script is a text file containing multiple commands intended to be executed non-interactively. The syntax for the file typically closely mirrors the syntax of some shell's interactive input, but no shell is required to execute a batch script. Instead, batch scripts are typically handled by executing a command that takes the script filename as a parameter or reads the script from standard input.

These concepts are realized via the following Java classes:

  • Exec - A thing that executes commands
  • Shell - A thing that implements shell functionality
  • SimpleCommand - A simple command abstraction
  • CommandBundle - A registry of commands
  • SimpleExec - An Exec that executes commands out of a CommandRegistry
  • SimpleShell - A Shell that exposes the commands in a CommandRegistry
  • CommandLineParser - Used by SimpleShell to parse command lines

Additional "glue":

Note: JShell support is only available on JDK 9 or later.

Demonstration

The demo module allows you to test out the current JCT features (and see some sample code):

$ java -jar java-console-toolkit-demo-1.1.0.jar --help
Usage:
    jct-demo [options] [command ...]

Options:
    --no-console                 Don't start command line console
    --ssh                        Enable SSH server
    --ssh-auth-keys-file path    Specify SSH authorized users file (default ${user.home}/.ssh/authorized_keys)
    --ssh-host-key-file path     Specify SSH host key file (default hostkey)
    --ssh-listen-port port       Specify SSH server TCP port (default 9191)
    --help                       Display this usage message

Commands:
=== Java Console Toolkit built-in simple commands

  date   Display the current time and date.
  echo   Echoes command line arguments.
  exit   Exit the shell.
  help   Displays information about available commands.
  quit   Exit the shell.
  sleep  Sleep for a while.

=== Java Console Toolkit JShell commands

  jshell  Fire up a JShell console.

=== Java Console Toolkit demonstration commands

  exec  Execute an arbitrary system command.

$ java -jar java-console-toolkit-demo-1.1.0.jar date
Wed Jan 21 15:13:14 CST 2026
$ java -jar java-console-toolkit-demo-1.1.0.jar
Welcome to org.dellroad.jct.core.simple.SimpleShell
jct> help

=== Java Console Toolkit built-in simple commands

  date   Display the current time and date.
  echo   Echoes command line arguments.
  exit   Exit the shell.
  help   Displays information about available commands.
  quit   Exit the shell.
  sleep  Sleep for a while.

=== Java Console Toolkit JShell commands

  jshell  Fire up a JShell console.

=== Java Console Toolkit demonstration commands

  exec  Execute an arbitrary system command.

jct> jshell

*** Welcome to the Java Console Toolkit JShell demo from "startup.jsh".
*** The DemoMain singleton is available as "demo".
*** The JShellShellSession singleton is available as "session".

|  Welcome to JShell -- Version 17.0.13
|  For an introduction type: /help intro

jshell> /vars
|    ClassLoader loader = jdk.internal.loader.ClassLoaders$AppClassLoader@42110406
|    Object session = org.dellroad.jct.demo.DemoMain$DemoJShellCommand$1$1@68afcd02
|    PrintStream out = org.dellroad.jct.core.util.ConsoleUtil$1@3682a5b
|    Object demo = org.dellroad.jct.demo.DemoMain@1f25860f

jshell> demo.hashCode()
$1 ==> 522552847

jshell> 2 + 2
$2 ==> 4

jshell> /exit
|  Goodbye
jct> echo "foo  bar  \nnext line"
foo  bar  
next line
jct> sleep 9999
^C
jct> exec bash -c "echo \"this is a test\""
this is a test
jct> exit 123
$ echo $?
123

Here's an example of running the command line interface on an SSH connection instead of the system console. Doing so allows you to add a sescure SSH console to any Java application, regardless of how it is launched.

In terminal window #1:

$ ssh-keygen -t ecdsa -f my_host_key
Generating public/private ecdsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in my_host_key
Your public key has been saved in my_host_key.pub
The key fingerprint is:
SHA256:LECChUtYcKMcOjyrIOd1y8vuCI6BRXrhIYvfyW1mqy0 archie@myhost
The key's randomart image is:
+---[ECDSA 256]---+
|oB* .            |
|B+.+             |
|*=+ .            |
|oBoo . .         |
|*.= . o S        |
|=* + = o         |
|+ + + B          |
| + .EB o         |
|. . o=B          |
+----[SHA256]-----+
$ java -jar java-console-toolkit-demo-1.1.0.jar --ssh --ssh-host-key-file my_host_key --no-console
INFO: No detected/configured IoServiceFactoryFactory; using Nio2ServiceFactoryFactory
jct-demo: started SSH server on port 9191
...

In terminal window #2:

$ ssh -p 9191 localhost
The authenticity of host '[localhost]:9191 ([127.0.0.1]:9191)' can't be established.
ECDSA key fingerprint is SHA256:LECChUtYcKMcOjyrIOd1y8vuCI6BRXrhIYvfyW1mqy0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:9191' (ECDSA) to the list of known hosts.
Welcome to org.dellroad.jct.core.simple.SimpleShell
jct> echo test "1  2  3"
test 1  2  3
jct> quit
Connection to localhost closed.

More Docs

About

A toolkit for adding a command line interface (CLI) console to a Java application

Topics

Resources

License

Stars

Watchers

Forks

Languages