2

I'm searching for an approach to read single line from Stdin using the Cactoos library. I can do something like this:

System.out.println(
  new TextOf(
    new ReaderOf(
      new Stdin()
    )
  ).asString()
);

but this code blocks and reads Stdin/System.in until it's closed - I use Ctrl+D to stop reading and get my text printed. Is there any approach to get behavior similar to BufferedReader#readLine()?

Also I'd like to print some prompt before reading the Stdin, something like:

System.out.println(
  new TextOf(
    new PromptedReaderOf( // desired decorator if I get Cactoos ideas right
      output,             // Output to display prompt-string to user
      "Type your text and press Enter: ",  // the prompt-string
      new ReaderOf(
        new Stdin()
      )
    )
  ).asString()
);

Is it possible with Cactoos or should I write my own decorators around Stdin for such interactive console application?

1 Answers1

0

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"))
        )
    )
);
rocket-3
  • 26
  • 4