Introduction
The source code is available on GitHub. |
Text-IO is a library for creating Java console applications. It can be used in applications that need to read interactive input from the user.
Features
-
supports reading values with various data types.
-
allows masking the input when reading sensitive data.
-
allows selecting a value from a list.
-
allows to specify constraints on the input values (format patterns, value ranges, length constraints etc.).
-
provides different terminal implementations and offers a Service Provider Interface (SPI) for configuring additional text terminals.
By default, Text-IO tries to use text terminals backed by java.io.Console. If no console device is present (which may happen, for example, when running the application in your IDE), a Swing-based terminal is used instead.
Example
TextIO textIO = TextIoFactory.getTextIO();
String user = textIO.newStringInputReader()
.withDefaultValue("admin")
.read("Username");
String password = textIO.newStringInputReader()
.withMinLength(6)
.withInputMasking(true)
.read("Password");
int age = textIO.newIntInputReader()
.withMinVal(13)
.read("Age");
Month month = textIO.newEnumInputReader(Month.class)
.read("What month were you born in?");
TextTerminal terminal = textIO.getTextTerminal();
terminal.printf("\nUser %s is %d years old, was born in %s and has the password %s.\n",
user, age, month, password);
Click on the image below to see the output of the above example in a Swing-based terminal.
You can also use a web-based terminal, which allows you to access your application via a browser, as shown in the image below.
Demo
Download the binary distribution and unzip it into a directory of your choice.
Make sure your JAVA_HOME environment variable correctly points to a JDK 8u40 or later.
Start the demo by executing 'textio-demo' or 'textio-demo.bat' in the bin directory.
Change the configuration by editing the properties files and see how your changes affect the behavior and the visual appearance of the application.
Have a look at the list of demos, examples and projects that use Text-IO. |
Getting Started
You need Java 8 or newer in order to use Text-IO. |
Text-IO is available in Maven Central and JCenter.
Maven | Gradle |
---|---|
<dependency> <groupId>org.beryx</groupId> <artifactId>text-io</artifactId> <version>3.4.1</version> </dependency> |
compile 'org.beryx:text-io:3.4.1' |
In order to use the WebTextTerminal,
you have to include the text-io-web
artifact:
Maven | Gradle |
---|---|
<dependency> <groupId>org.beryx</groupId> <artifactId>text-io-web</artifactId> <version>3.4.1</version> </dependency> |
compile 'org.beryx:text-io-web:3.4.1' |
Additionally, you have to include the artifacts needed by the DataServer implementation you want to use.
For RatpackDataServer:
Maven | Gradle |
---|---|
<dependency> <groupId>io.ratpack</groupId> <artifactId>ratpack-core</artifactId> <version>1.5.1</version> </dependency> <dependency> <groupId>io.ratpack</groupId> <artifactId>ratpack-session</artifactId> <version>1.5.1</version> </dependency> |
compile 'io.ratpack:ratpack-core:1.5.1' compile 'io.ratpack:ratpack-session:1.5.1' |
For SparkDataServer:
Maven | Gradle |
---|---|
<dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-core</artifactId> <version>2.7.1</version> </dependency> |
compile 'com.sparkjava:spark-core:2.7.1' |
User Guide
The JavaDoc is available here. |
Text Terminals
A text terminal is an abstraction layer used by the Text-IO library in order to provide device independence. Text terminals must implement the TextTerminal interface, which requires to allow at least:
-
reading a one-line text, optionally masking the input.
-
writing a one-line text.
-
writing a line separator.
The following concrete implementations are provided by the Text-IO library:
-
ConsoleTextTerminal, which is backed by a java.io.Console.
-
JLineTextTerminal, which is backed by a JLine ReadConsole.
-
MockTextTerminal, which simulates a terminal session by providing preconfigured values for the input and stores the output in a string buffer.
-
SwingTextTerminal, which uses a JTextPane inside a JFrame.
-
SystemTextTerminal, which uses System.out, System.in and Scanner. It is not capable to mask input strings, therefore not recommended when reading sensitive data.
-
WebTextTerminal, which allows accessing your application via a browser. See Using the WebTextTerminal for more details.
Advanced features
The TextTerminal interface provides a number of optional methods, which are supported only by some terminals. Calling one of these methods on a terminal that does not support the corresponding feature returns a value of false and has usually no effect.
Line handling
You can use moveToLineStart() to place the cursor at the beginning of the current line and resetLine() to clear the current line.
Look at the Weather example in the demo application for usage details. |
Currently, only JLineTextTerminal, SwingTextTerminal and WebTextTerminal support these methods. The default implementation for the other terminals performs a println().
Bookmarking
With setBookmark(String bookmark) you can attach a bookmark to the current line of text. By calling resetToBookmark(String bookmark), you can clear all the text after the given bookmark.
Look at the Weather example in the demo application for usage details. |
Currently, only SwingTextTerminal and WebTextTerminal support these methods. The default implementation of resetToBookmark for the other terminals performs a println().
Read handlers
You can allow users to perform additional actions during a read operation, by registering handlers to given key combinations via registerHandler().
Examples of actions you may want to associate with specific key combinations include: displaying a help text, saving the data collected so far, aborting the current read operation.
Look at the ShoppingList and ContactInfo examples in the demo application for usage details. |
Some terminals may generate a user-interrupt when a specific key combination (such as Ctrl+C) is pressed. This usually terminates the current program. The method registerUserInterruptHandler() lets you configure the action to be taken when the user-interrupt key combination is pressed.
Currently, only JLineTextTerminal, SwingTextTerminal and WebTextTerminal support registering handlers.
The key combination for user-interrupt handlers can be changed for SwingTextTerminal and WebTextTerminal through the property user.interrupt.key. See Supported properties for more details.
Input Readers
Input readers are able to read values of a specific type. They are subclasses of InputReader, which offers (via methods with names of the form withXXX()) a fluent interface for configuring various settings such as:
-
input masking - useful when reading sensitive data such as passwords.
-
defaultValue - the value to be used if the user pressed Enter.
-
possible values - necessary if the value to be read must be chosen from a list of allowed values.
-
parse error messages - used to provide custom parse error messages.
-
value checkers - used to check for constraint violations.
-
item name - if configured, the name of the item to be read will appear in error messages.
See the javadoc for more configuration methods.
The following concrete implementations are available:
-
BooleanInputReader - A reader for boolean values. Allows configuring which string value should be interpreted as true and which as false.
-
ByteInputReader - A reader for byte values. Allows configuring the minimum and maximum permitted values.
-
CharInputReader - A reader for char values. Allows configuring the minimum and maximum permitted values.
-
DoubleInputReader - A reader for double values. Allows configuring the minimum and maximum permitted values.
-
EnumInputReader - A reader for enum values. It allows selecting one of the constants defined by the given enum type.
-
FloatInputReader - A reader for float values. Allows configuring the minimum and maximum permitted values.
-
GenericInputReader - A reader for values of a given type, for which a parser is passed as constructor argument.
-
IntInputReader - A reader for int values. Allows configuring the minimum and maximum permitted values.
-
LongInputReader - A reader for long values. Allows configuring the minimum and maximum permitted values.
-
ShortInputReader - A reader for short values. Allows configuring the minimum and maximum permitted values.
-
StringInputReader - A reader for string values. Allows configuring the minimum and maximum permitted length, as well as a regex pattern.
Reading values
After configuring an input reader, you can use read(String… prompt) or read(List<String> prompt) to read a single value and readList(String… prompt) or readList(List<String> prompt) to read a comma-separated list of values. These methods repeatedly prompt the user to enter a value or a comma-separated list of values, until a valid input is provided.
Look at the source code of TextIoReadSpec.groovy and TextIoReadListSpec.groovy for examples of using the input readers. |
TextIO
The TextIO class provides factory methods for creating input readers.
These methods have names of the form newXXXInputReader()
, such as
newIntInputReader().
All InputReaders created by the same TextIO instance share the same TextTerminal, which can be retrieved by calling the getTextTerminal() method.
TextIoFactory
Although you can create yourself a TextIO instance by passing the desired TextTerminal as constructor argument, it is preferable to use the TextIoFactory for this task.
The TextIoFactory takes the following steps in order to choose the TextTerminal associated with the TextIO instance to be created:
-
If the system property
org.beryx.textio.TextTerminal
is defined, then it is taken to be the fully-qualified name of a concrete TextTerminal class. The class is loaded and instantiated. If this process fails, then the next step is executed. -
A ServiceLoader loads the configured TextTerminalProviders and searches for the first one capable to provide a TextTerminal instance. If none is found, then the next step is executed.
-
A default implementation is provided as follows:
-
if System.console() is not null, and a JLine ConsoleReader can be created, then a JLineTextTerminal is provided;
-
else, if System.console() is not null, a ConsoleTextTerminal is provided;
-
else, if the system is not headless, a SwingTextTerminal is provided;
-
else, a SystemTextTerminal is provided.
-
Look at the source code of UserDataCollector.java for an example of using the default TextTerminal provided by TextIofactory, and TextIoDemo.java for examples of using custom TextTerminals. |
Terminal properties
TextIO uses the TextTerminal interface as an abstraction layer that provides device independence. However, some terminals may have capabilities beyond those exposed by the TextTerminal API. Such capabilities include the possibility to use colors or emphasis (bold, underline, italic). TextIO lets you make use of these capabilities through terminal properties.
Terminal properties can be statically configured in a properties file or dynamically set at runtime. You can also combine these two techniques. TextIO uses the following strategy for locating the file containing terminal properties:
-
search for a file at the location given by the value of the system property
textio.properties.location
. -
search for a file named
textio.properties
located in the current directory. -
search for a file named
textio.properties
in the classpath.
For a given property, you may configure the same value for all terminal types, or you may assign different values to different terminal types. This is possible by using property prefixes. Each terminal has a list of accepted prefixes, as in the table below:
Terminal type | Property prefix |
---|---|
<generic> |
textio |
ConsoleTextTerminal |
console |
JLineTextTerminal |
jline |
MockTerminal |
mock |
SwingTextTerminal |
swing |
SystemTextTerminal |
system |
WebTextTerminal |
web |
A terminal accepts the generic prefix textio
and the prefix corresponding to its type.
For example, a SwingTextTerminal accepts the prefixes textio
and swing
.
Consider, for example, the following configuration:
textio.input.color = yellow
textio.prompt.color = cyan
swing.prompt.color = #2bf3c5
The property input.color
will have the value yellow
, irrespective of the terminal type.
For prompt.color
, the actual value depends on the type of terminal used:
it will be #2bf3c5
for a SwingTextTerminal, and cyan
for any other type.
Each terminal type has its own set of supported properties.
The behavior of a terminal is not affected by the values of the properties it does not support.
It is therefore safe to configure the value of a certain property for all terminals
(that is, using the generic prefix textio
), even if it is not supported by all terminal types.
Currently, only the JLineTextTerminal, SwingTextTerminal and WebTextTerminal types have a non-empty set of supported properties, which are shown in the table below:
Property name | JLine | Swing | Web | Comment |
---|---|---|---|---|
ansi.color.mode |
✓ |
- |
- |
The ANSI color mode. |
input.bgcolor |
✓ |
✓ |
✓ |
The background color of the input text. |
input.bold |
✓ |
✓ |
✓ |
|
input.color |
✓ |
✓ |
✓ |
The color of the input text. |
input.font.family |
- |
✓ |
- |
The font family of the input text. |
input.font.size |
- |
✓ |
- |
The font size of the input text. |
input.italic |
✓ |
✓ |
✓ |
|
input.style.class |
- |
- |
✓ |
The CSS class used for styling the input text. |
input.subscript |
- |
✓ |
- |
|
input.superscript |
- |
✓ |
- |
|
input.underline |
✓ |
✓ |
✓ |
|
pane.bgcolor |
- |
✓ |
✓ |
The background color of the terminal pane. |
pane.height |
- |
✓ |
- |
The height of the terminal pane. |
pane.icon.file |
- |
✓ |
- |
The path to the file containing the icon to be used in the title bar of the terminal pane. |
pane.icon.resource |
- |
✓ |
- |
The name of the resource containing the icon to be used in the title bar of the terminal pane. |
pane.icon.url |
- |
✓ |
- |
The URL of the icon to be used in the title bar of the terminal pane. |
pane.style.class |
- |
- |
✓ |
The CSS style class of the terminal pane. |
pane.title |
- |
✓ |
- |
The text to appear in the title bar of the terminal pane. |
pane.width |
- |
✓ |
- |
The width of the terminal pane. |
prompt.bgcolor |
✓ |
✓ |
✓ |
The background color of the prompt text. |
prompt.bold |
✓ |
✓ |
✓ |
|
prompt.color |
✓ |
✓ |
✓ |
The color of the prompt text. |
prompt.font.family |
- |
✓ |
- |
The font family of the prompt text. |
prompt.font.size |
- |
✓ |
- |
The font size of the prompt text. |
prompt.italic |
✓ |
✓ |
✓ |
|
prompt.style.class |
- |
- |
✓ |
The CSS class used for styling the prompt text. |
prompt.subscript |
- |
✓ |
- |
|
prompt.superscript |
- |
✓ |
- |
|
prompt.underline |
✓ |
✓ |
✓ |
|
user.interrupt.key |
- |
✓ |
✓ |
The key combination used to interrupt the program. |
The values of the color properties are interpreted using the
ColorFactory.web(String colorString).
method.
This means that you can specify colors in various ways, such as: red
, #aa38e0
, 0x40A8CC
, rgba(112,36,228,0.9)
, hsla(270,100%,100%,1.0)
etc.
In the standard
and indexed
mode, JLineTextTerminal has a limited number of colors available.
Therefore, it tries to map the provided value to the nearest available color.
The properties of a TextTerminal can be accessed at runtime through the method getProperties(), which returns a TerminalProperties instance. Using this TerminalProperties, you can dynamically configure properties by calling the 'put(String key, Object value)' method. Additionally, convenience methods are available for frequently used properties (for example: setInputBold(boolean bold) or setPromptColor(Color color)).
You can learn how to configure and use terminal properties by looking at the source code and the configuration files of the demo application. |
TextTerminal temporary properties
Sometimes you want to temporarily change some TextTerminal properties and revert them to their previous values after a couple of operations. You can achieve this by passing the sequence of operations to be executed with modified properties as argument to the executeWithPropertiesConfigurator() method, as shown in the example below.
textTerminal.getProperties().setPromptColor("cyan");
textTerminal.println("1. Choose the desired hard drive.");
textTerminal.executeWithPropertiesConfigurator(
props -> props.setPromptColor("red"),
t -> t.println("2. Backup all your data."));
textTerminal.println("3. Start the formatting process.");
The second message will appear in red, while the other two will be printed in cyan.
The code above uses hard-coded property values.
A more elegant solution is to specify these values in the textio.properties
file.
TextTerminal offers the
executeWithPropertiesPrefix()
convenience method to help you accomplish this task.
Consider the code below:
textTerminal.println("1. Choose the desired hard drive.");
textTerminal.executeWithPropertiesPrefix("warn",
t -> t.println("2. Backup all your data."));
textTerminal.println("3. Start the formatting process.");
and let’s assume that your textio.properties
contains:
textio.prompt.color = cyan
textio.warn.prompt.color = red
Then, the second message will appear in red, while the other two will be printed in cyan.
Look at the source code of Cuboid.java for an example of using temporary TextTerminal properties. |
Error message prefix
When printing error messages, an InputReader temporarily changes the TextTerminal properties using the prefix error
.
For example, in order to have error messages displayed in red, you can insert the following line into textio.properties
:
textio.error.prompt.color = red
InputReader-specific properties
If you want to apply some temporary TextTerminal properties only during the next read operation, you can call the withPropertiesConfigurator() method of your InputReader, as shown in the example below.
textIO.getTextTerminal().getProperties().setPromptColor("cyan");
String user = textIO.newStringInputReader().read("User name");
boolean eraseAll = textIO.newBooleanInputReader()
.withPropertiesConfigurator(props -> props.setPromptColor("red"))
.read("Erase all data?");
String directory = textIO.newStringInputReader().read("Home directory");
The question "Erase all data?" will appear in red, while "User name" and "Home directory" will be printed in cyan.
The code above uses hard-coded property values.
A more elegant solution is to specify these values in the textio.properties
file.
InputReader offers the
withPropertiesPrefix()
convenience method to help you accomplish this task.
Consider the code below:
String user = textIO.newStringInputReader().read("User name");
textIO.newBooleanInputReader()
.withPropertiesPrefix("warn")
.read("Erase all data?");
String directory = textIO.newStringInputReader().read("Home directory");
and let’s assume that your textio.properties
contains:
textio.prompt.color = green
textio.input.color = yellow
textio.warn.prompt.color = red
textio.warn.input.color = orange
Then, the question "Erase all data?" will appear in red and its corresponding user input in orange. For the other two read operations the questions will be displayed in green and the user input in yellow.
Look at the source code of Cuboid.java for an example of using InputReader-specific properties. |
Using the WebTextTerminal
The WebTextTerminal works only in conjunction with a web server supporting the DataApi (such as the SparkDataServer or the RatpackDataServer) and a web page that contains code for accessing this API. Typically, the web server is managed by an implementation of TextIoApp (such as SparkTextIoApp or RatpackTextIoApp), while the web page makes use of the textterm.js library included in the text-io npm package, as shown in the code snippet below.
<div id="textterm">
<span class="textterm-pair" class="textterm-pane">
<span class="textterm-prompt"></span>
<span contenteditable="true" class="textterm-input"></span>
</span>
</div>
<script>
var textTerm = createTextTerm(document.getElementById("textterm"));
textTerm.execute();
</script>
Run the demo application and select the Web terminal option to see a WebTextTerminal in action. Look at the source code of WebTextIoExecutor.java and web-demo.html for more usage details. |
Currently, only WebKit-based browsers (such as Chrome, Opera or Safari) are able to mask input strings. Keep this in mind when working with sensitive data.
The client-side library
The textterm.js client-side library provides the JavaScript functionality needed to connect your web page to a DataServer. You can integrate this library in your web applications in several ways:
-
make textterm.js and textterm.css available as local resources and reference them in your web page. This approach is used by the demo application.
-
use the unpkg CDN:
<link rel="stylesheet" href="https://unpkg.com/text-io@3.4.1/textterm.css">
<script src="https://unpkg.com/text-io@3.4.1/textterm.js"></script>
-
install the text-io npm package:
npm install text-io
The text-io-web-example uses this approach.
Library API:
createTextTerm(textTermElement)
Creates and returns a TextTerm
object for the given DOM element.
-
textTermElement
(DOM Element)
Class: TextTerm
execute([initData])
Executes the server-side Text-IO application.
-
initData
- the data used to initialize the server-side Text-IO application.
onDataReceived(data)
The TextTerminal calls this method each time it receives new data.
-
data
- the data sent by the Text-IO application.
By default, this method does nothing more than logging the data
.
You may assign a custom implementation.
onDispose(resultData)
This method is usually triggered by the termination of the server-side Text-IO application.
-
resultData
- the result of the server-side Text-IO application.
By default, this method does nothing more than logging the resultData
.
You may assign a custom implementation. Example:
<div id="textterm" class="textterm-pane">
<span class="textterm-pair">
<span class="textterm-prompt"></span>
<span contenteditable="true" class="textterm-input"></span>
</span>
</div>
<h3 id="app-done"> </h3>
<script>
var textTerm = createTextTerm(document.getElementById("textterm"));
textTerm.onDispose = function(resultData) {
document.getElementById("app-done").textContent =
"Result: " + resultData + ". You can now close this window.";
}
textTerm.onAbort = function() {
document.getElementById("app-done").textContent =
"Program aborted by the user. You can now close this window.";
}
textTerm.execute();
</script>
onAbort()
This method is usually triggered when the user aborts the server-side Text-IO application.
By default, this method does nothing more than logging the abort operation. You may assign a custom implementation similar to the one given in the above example.
onSessionExpired()
This method is usually triggered when the session has expired.
By default, this method restarts the server-side Text-IO application with the initData
used by the previous call of execute
.
You may change this default behavior by assigning a custom implementation.
onServerError()
This method is usually triggered when the server encountered an unexpected condition.
By default, this method restarts the server-side Text-IO application with the initData
used by the previous call of execute
.
You may change this default behavior by assigning a custom implementation.
displayMessage(message[, specialPromptStyleClass])
Displays a prompt message.
-
message
(String) - the message to be displayed. -
specialPromptStyleClass
(String) - if provided, represents the CSS class used to style this message.
displayError(message)
Displays an error message.
-
message
(String) - the error message to be displayed.
The error message is styled using the CSS class textterm-error-prompt
.
resetTextTerm()
Resets the text terminal. All content will be erased.
restart()
Restarts the server-side Text-IO application with the initData
used by the previous call of execute
.
sendUserInterrupt()
Sends a userInterrupt to the server, in order to abort the Text-IO application.
terminate()
Removes the event listeners. The text terminal should no longer be used after calling this method.
specialKeyPressHandler(event)
Default value: null. If a custom implementation is provided, it will be used instead of the default keyPress handler.
-
event
(KeyboardEvent) - the keypress event.
setLogLevelOff()
Turns off the console logging.
setLogLevelError()
Allows logging messages with level ERROR or higher.
setLogLevelWarn()
Allows logging messages with level WARN or higher.
setLogLevelInfo()
Allows logging messages with level INFO or higher.
setLogLevelDebug()
Allows logging messages with level DEBUG or higher.
setLogLevelError()
Allows logging messages with level TRACE or higher.
textTerminalInitPath
The pathForInitData
used by the DataServer.
Default value: '/textTerminalInit'.
textTerminalDataPath
The pathForGetData
used by the DataServer.
Default value: '/textTerminalData'.
textTerminalInputPath
The pathForInputData
used by the DataServer.
Default value: '/textTerminalInput'.
uuid
The uuid
that uniquely identifies this text terminal.
settings
The settings
object exposes a series of properties for configuring the text terminal.
Properties affecting the DOM Element with class textterm-pane
:
-
paneBackgroundColor
(String) -
paneStyleClass
(String)
Properties affecting the DOM Element with class textterm-prompt
:
-
promptBackgroundColor
(String) -
promptBold
(Boolean) -
promptColor
(String) -
promptItalic
(Boolean) -
promptStyleClass
(String) -
promptUnderline
(Boolean)
Properties affecting the DOM Element with class textterm-input
:
-
inputBackgroundColor
(String) -
inputColor
(String) -
inputBold
(Boolean) -
inputItalic
(Boolean) -
inputStyleClass
(String) -
inputUnderline
(Boolean)
Properties affecting the userInterrupt key combination:
-
userInterruptKeyCode
(String): defaultValue = 'Q' -
userInterruptKeyCtrl
(Boolean): defaultValue = true -
userInterruptKeyShift
(Boolean): defaultValue = false -
userInterruptKeyAlt
(Boolean): defaultValue = false