Read line from Stdin with Cactoos

but this code blocks and reads Stdin/System.in until it’s closed – I use Ctrl+D to stop reading

This happens because Reader has to read everything, including ‘newline’ (\n\r) characters, until there’s nothing to read (Ctrl+D, stream becomes finite).

When you type newline character by pressing ‘enter’ key, you wait for reader to stop reading infinite stream. That behaviour is reproduced, for example, inside BufferedReader::read**Line**.

The TextOf(Reader) uses Reader::read instead (actually it happens inside ReaderAsBytes::asBytes).

Also I’d like to print some prompt before reading the Stdin Is it possible with Cactoos or should I write my own decorators around Stdin for such interactive console application?

So, yes, you need to implement new decorators to deal with your problem.

You may need to use TextOf(Input) and create an Input decorator that produces a finite stream when ‘newline’ character occurs.

public class ReaderReadLineInput implements Input {
    //we don't want to use 'new' in methods
    private final Scalar<InputStream> inputStreamScalar;

    private ReaderReadLineInput(BufferedReader bufferedReader) {
        this.inputStreamScalar = new ScalarOf<InputStream>(
            br -> new InputStreamOf(
                br.readLine() //produces finite InputStream
            ),
            bufferedReader
        );
    }
    
    public ReaderReadLineInput(Reader reader){
        this(new BufferedReader(reader));
    }

    @Override
    public InputStream stream() throws Exception {
        return inputStreamScalar.value();
    }
}

Then you may want to connect this with your actual use-case (getting input from console by typing) and not to lose reusability of previous code, so create another Input decorator

public class ManualConsoleInput implements Input {
    //and you still don't like 'new' in methods
    private final Scalar<Input> iptScalar;

    public ManualConsoleInput(Text charsetName) {
        // do you like Cactoos primitives?
        // there's a some workaround
        this.iptScalar = new ScalarOf<Input>(
            charset -> {
                return new ReaderReadLineInput(
                    new InputStreamReader(
                        new Stdin().stream(), 
                        charset.asString() 
                    )
                )
            },
            charsetName
        );
    }

    @Override
    public InputStream stream() throws Exception {
        return this.iptScalar.value().stream();
    }
}

To implement printing prompt text to console before getting user input, you also may need to create another decorator.

public class InputPrintsToConsole implements Input {  
    private final Runnable printingRunnable;
    private final Input origin;

    public InputPrintsToConsole(Text textToConsole, Input origin) {
        this.printingRunnable = new ConsolePrinting(textToConsole);
        this.origin = origin;
    }

    @Override
    public InputStream stream() throws Exception {
        printingRunnable.run();
        return origin.stream();
    }
}

Also remember that some people can use System::setOut when using your code to pipe standart output to file, for example. So you can’t just rely on System god-object to get console output stream, only use it to get reference to console output stream, when you sure:

public class ConsolePrinting extends RunnableEnvelope {
    public ConsolePrinting(Text textToPrint) {
        super(
            new OutputStreamPrinting(
                System.out, // and encapsulate somewhere 
                textToPrint
            )
        );
    }
}

// splitting responsibility of objects
// and using decorators
public class OutputStreamPrinting implements Runnable { 
    private final PrintStream printStream;
    private final Text text;

    public OutputStreamPrinting(PrintStream printStream, Text text) {
        this.printStream = printStream;
        this.text = text;
    }
    
    public OutputStreamPrinting(OutputStream outputStream, Text text) {
        this(new PrintStream(outputStream), text);
    }
    
    @Override
    public void run() {
        this.printStream.println(this.text);
    }
}

And the top-level code from your example may look like this:

System.out.println(
    new TextOf(
        new InputPrintsToConsole(
            new TextOf("Type your text and press Enter:"),
            new ManualConsoleInput(new TextOf("utf-8"))
        )
    )
);

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top