InputReader

InputReader is a utility that bridges the terminal’s push-style input (Consumer<int[]>) to Java’s pull-style java.io.Reader. This is useful when you need to read terminal input using standard Java I/O APIs — for example, wrapping a BufferedReader around terminal input, or integrating with libraries that expect a Reader.

Quick Start

The simplest way to create an InputReader is with the asReader() factory method:

import org.aesh.terminal.utils.InputReader;
import org.aesh.terminal.tty.TerminalConnection;

TerminalConnection connection = new TerminalConnection();
connection.enterRawMode();
connection.openNonBlocking();

InputReader reader = InputReader.asReader(connection);

// Now use standard Reader methods
char[] buf = new char[256];
int n = reader.read(buf, 0, buf.length);
String input = new String(buf, 0, n);

This creates an InputReader and wires it to the connection’s stdin handler in one step.

How It Works

Connection delivers input as code point arrays to a Consumer<int[]> handler. InputReader converts these into chars (splitting supplementary code points into surrogate pairs) and buffers them in a bounded queue. You then read from the queue using standard Reader methods or the timeout-aware methods.

Connection stdin ──► push(int) ──► LinkedBlockingQueue<Character> ──► read()
                     (splits supplementary                           (blocking/
                      into surrogates)                                timeout)

Creating an InputReader

From a Connection (recommended)

// Default queue capacity (4096)
InputReader reader = InputReader.asReader(connection);

// Custom queue capacity
InputReader reader = InputReader.asReader(connection, 8192);

Manual Wiring

If you need more control over how input is fed to the reader:

InputReader reader = new InputReader();

// Wire it yourself
connection.setStdinHandler(cps -> {
    for (int cp : cps) {
        reader.push(cp);
    }
});

Standalone (no Connection)

You can also push data manually — useful for testing or non-terminal input sources:

InputReader reader = new InputReader();
reader.push("Hello");
reader.push(0x1F600);  // emoji, split into surrogate pair
reader.push('!');

Reading Input

Standard Reader Methods

InputReader extends java.io.Reader, so all standard methods work:

// Read into a char array — blocks on first char, drains rest without blocking
char[] buf = new char[1024];
int n = reader.read(buf, 0, buf.length);

// Check if input is available without blocking
if (reader.ready()) {
    // Data available
}

Read with Timeout

Read a single character with a millisecond timeout. Returns InputReader.TIMEOUT (-2) if no input arrives, or InputReader.EOF (-1) on end of stream:

int ch = reader.read(50);  // 50ms timeout
if (ch == InputReader.TIMEOUT) {
    // No input within timeout
} else if (ch == InputReader.EOF) {
    // End of stream
} else {
    System.out.println("Got: " + (char) ch);
}

This is particularly useful for game loops or interactive applications that need to check for input without blocking indefinitely:

while (running) {
    int ch = reader.read(16);  // ~60fps
    if (ch != InputReader.TIMEOUT && ch != InputReader.EOF) {
        handleInput((char) ch);
    }
    updateGame();
    render();
}

Read Code Points

Read a full Unicode code point, automatically reassembling surrogate pairs:

OptionalInt cp = reader.readCodePoint(1, TimeUnit.SECONDS);
if (cp.isPresent()) {
    System.out.println("Code point: U+" + Integer.toHexString(cp.getAsInt()));
}

Push Methods

The push methods are the producer side — they feed data into the reader’s internal queue:

MethodDescription
push(char ch)Push a single char
push(int codePoint)Push a Unicode code point (supplementary code points split into surrogate pairs)
push(CharSequence csq)Push a string or other CharSequence

Push is non-blocking. If the queue is full, excess characters are silently dropped. After close(), pushes are silently ignored.

Bounded Queue

The internal queue has a bounded capacity (default 4096 chars) to prevent unbounded memory growth if the reader falls behind. You can configure the capacity via the constructor:

// Small buffer for memory-constrained environments
InputReader reader = new InputReader(256);

// Large buffer for high-throughput scenarios
InputReader reader = new InputReader(16384);

Closing

reader.close();

Closing the reader:

  • Unblocks any thread waiting on read() (returns -1 / EOF)
  • Clears the queue
  • Causes subsequent read(), ready(), and readCodePoint() to throw IOException
  • Causes subsequent push() calls to be silently ignored

Complete Example

A simple terminal echo application using InputReader:

import org.aesh.terminal.tty.Signal;
import org.aesh.terminal.tty.TerminalConnection;
import org.aesh.terminal.utils.InputReader;

public class EchoApp {
    public static void main(String[] args) throws Exception {
        try (TerminalConnection connection = new TerminalConnection()) {
            connection.enterRawMode();
            connection.openNonBlocking();

            InputReader reader = InputReader.asReader(connection);

            connection.setSignalHandler(signal -> {
                if (signal == Signal.INT) {
                    reader.close();
                }
            });

            int ch;
            while ((ch = reader.read(100)) != InputReader.EOF) {
                if (ch != InputReader.TIMEOUT) {
                    connection.write(String.valueOf((char) ch));
                    if (ch == '\r') {
                        connection.write("\n");
                    }
                }
            }
        }
    }
}