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"))
)
)
);