1

I have a function "a()" that calls another function "b()" that writes to stdout. I cannot modify "b()", but I want to be able to read what "b" is writing and write back to stdout for "b" to read, meaning:

public void a() {
    // start a thread that listens to stdout.
    // the thread should print a name to stdout after "b" print "Please enter your name"
    b();
}

public void b() { // I cannot modify this function
   System.out.println("Welcome! The time is " + System.currentTimeMillis());
   System.out.println("Please enter your name");
   String name = ...
   // ... b reads here the name that the thread from function a() will write
   // ...
   System.out.println("This is the name that was entered: " + name);
}

I thought about starting "b" in a new process but I wasn't sure how unless I wrap "b" in a main function and run it using a command line - I'd be happy for suggestions. If it's not a process, I'm not sure how to implement the thread that will be activated by "a()".

I tried using:

BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = stdin.readLine()) != null) {
...
}

but it doesn't catch what "b" is writing.

Thanks for the help

Liz
  • 395
  • 1
  • 5
  • 12
  • You might be able to do it with `System.setIn` and `System.setOut`, but it's a pretty horrible way to do things. It would be much better to parametrize `b`'s input and output streams. – trutheality Jun 25 '11 at 21:23
  • Thanks. how can I parametrize b's input and output streams? (I don't have access to b's code). – Liz Jun 25 '11 at 21:25
  • @Liz if you don't have access to the code then you can't. You have to go with user270349's solution. – trutheality Jun 25 '11 at 21:30
  • If you want to start a second JVM this may help http://stackoverflow.com/questions/1229605/is-this-really-the-best-way-to-start-a-second-jvm-from-java-code – aalku Jun 25 '11 at 21:38
  • Creating an output stream that split the out depending on the thread that writes sounds quite fun. – aalku Jun 25 '11 at 21:40
  • @user270349 - it's either this or using a new process. The fastest way is probably a new process, but it does make sense to try and avoid it... thanks – Liz Jun 25 '11 at 21:55

3 Answers3

3

You can run b() in another process but you don't need to do so.

System.out is a PrintStream. If you read the javadoc carefully you will notice System.setOut method. With it you can replace System.out with another PrintStream.

Example (not tested):

PrintStream originalOut = System.out; // To get it back later
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream newOut = new PrintStream(baos);
System.setOut(newOut);

b();
System.out.flush();

System.setOut(originalOut); // So you can print again

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

// Now you can read from bais what b() wrote to System.out

This solution has the problem of not being thread safe. If any other thread write to System.out when it is 'changed' the output will get redirected too. To get rid of this problem I think you need to run b() on another JVM or use a PrintStream that split (deMux) the output depending on the thread or context.

aalku
  • 2,860
  • 2
  • 23
  • 44
  • Thanks. I don't want to change the System.out as it will affect all other threads that write to stdout/logs etc. So I guess that a process is the best option here... – Liz Jun 25 '11 at 21:28
1

Unfortunately there is not easy way of doing this in Java. The biggest problem is that System.out and System.in are two separate files, created from FileDescriptor.out and FileDescriptor.in respectively. They are not connected in any way, and therefore you can't write to System.out and expect to see it in System.in.

Your options are:

  1. Run b() in external process somehow. Yes, you'll need to put it in a class with main() function and do lots of complicated process setup, like getting the path to java.exe and setting up classpaths etc. The good part is that writing to and reading from the process will work as you expect.

  2. Create two custom Input and Output streams that can duplicate all traffic to another in/out stream, as well as sending it to System.{in,out}, and set them using System.set{In,Out}. This way you can monitor those streams without affecting other code that might by using System.{in,out}.

As an example of the custom OutputStream mentioned in 2, try something like this:

class CopyOutputStream extends OutputStream {
    private final OutputStream str1;
    private final OutputStream str2;

    public CopyOutputStream(OutputStream str1, OutputStream str2) {
        this.str1 = str1;
        this.str2 = str2;
    }

    @Override
    public void write(int b) throws IOException {
        str1.write(b);
        str2.write(b);
    }

    // ...

    @Override
    public void close() throws IOException {
        try {
            str1.close();
        } finally {
            str2.close();
        }
    }
}

// then in a() do

public void a(){
    //Create a pipe to capture data written to System.out
    final PipedInputStream pipeIn = new PipedInputStream();
    final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);

    OutputStream out = new CopyOutputStream(System.out, pipeOut);
    //From now on everything written to System.out will be sent to
    // System.out first and then copied to pipeOut and will be available
    // to read from pipeIn.
    System.setOut(new PrintStream(out));

    // In another thread: read data from System.out
    BufferedReader reader = new BufferedReader(new InputStreamReader(pipeIn));
    String name = reader.readLine();
}

Unfortunately you will have to repeat the above process for System.in, which means more crazy code, but I don't think it will get easier than this.

If you are ready for some really crazy action, maybe you can get hold of some java library (most likely with native code), that can give you the Process object of the currently running JVM, and then use get{Input,Output}Stream() methods to do the job.

rodion
  • 14,729
  • 3
  • 53
  • 55
0

How about setting System.out with System.setOut

James Scriven
  • 7,784
  • 1
  • 32
  • 36