views:

2309

answers:

4

Given an instance x of Callable<T>, how can I run x in a separate process such that I can redirect the standard input and output of the process? For example, is there a way to build a Process from a Callable? Is there a standard Executor that gives control over input and output?

[UPDATE] It's not important that the Callable execute in a new process as opposed to a new thread. What I want is to put the Callable instance in a "harness", so that I can control its stdin/stdout. AFAIK, this calls for a new process.

A: 

Have a look at ProcessBuilder class, it gives you the option to capture the stdout and stderr of the process that it launches.

You can create a Main class that runs a Callable, and then launch it as another jvm with a ProcessBuilder. The Main class can accept your callable's class name as command line input parameter and load it with Class.forName() and Class.newInstance().

An alternative, if you want it to run a specific callable instance, is to serialize the instance to a file before launching the other process (that would be a very crude form of communicating between the two processes).

The question is, why do you want to do it? can't you just run your callable in another thread? java.util.concurrent has some useful thread pools, just for that.

Yoni
ProcessBuilder, AFAICT, wants a command-line string and not a Callable. I definitely want to invoke the instance and not create a new instance based on the Class Name. As for process vs. thread, see above.
Chris Conway
I meant that you give java.exe as the command to ProcessBUilder, and for the Main class you give a wrapper that runs the callable. Of course, you'll need to serialize the instance and pass the file name as a parameter to your main class.Or just use System.setIn()/out() etc.
Yoni
+1  A: 

More generally:

Given an instance x of Callable utilizing global variables A and B, how can I run x concurrently such that x sees custom values for A and B, rather than the "original" values of A and B?

And the best answer is, don't use global variables. Dependency inject things like that. Extend Callable and add methods setStdIn, setStdOut (and setStdErr if you need it).

I know this isn't the answer you're looking for, but the solutions I have seen for this all require a new process, and the only way you are getting that Callable into a new process is to change the code of the Callable so it is serializable, or provides a class name, or some other hack, so instead of making changes that will give you a nasty, brittle solution, just do it right*

* "right" being to use the widely accepted pattern of dependency injection to provide loose coupling. YMMV

UPDATE: In response to comment 1. Here is your new interface:

import java.io.InputStream;
import java.io.PrintStream;
import java.util.concurrent.Callable;


public interface MyCallable<V> extends Callable<V> {
  void setStdIn(InputStream in);
  void setStdOut(PrintStream out);  
}

and your tasks would look like:

import java.io.InputStream;
import java.io.PrintStream;


public class CallableTask implements MyCallable<Object> {

    private InputStream in = System.in;
    private PrintStream out = System.out;

    public void setStdIn(InputStream in) {
     this.in = in;
    }

    public void setStdOut(PrintStream out) {
     this.out = out;
    }

    public Object call() throws Exception {
     out.write(in.read());
     return null;
    }

}

No need for a process. Any solution (even one using processes) is almost certainly going to require code changes to the Callables in some way. This is the simplest (just replace System.out with this.out).

noah
How would I implement your suggestion, without first answering this question?
Chris Conway
"Dependency injection" is not a magical pixie dust that make the entire problem go away, you know.
Chris Conway
It's not magic, but global variables are well recognized as a code smell. Dependency injection is a good alternative.
noah
OK, now I see what you mean. That might work....
Chris Conway
+1  A: 

stdin/stdout can be redirected for the entire system, but I'm not sure it can be redirected for a single thread--you are always just getting it from System. (You can make System.out go to another stream though, I've used that to catch stack traces before there was a method to get the trace as a string)

You could redirect stdin/stdout, run the callable then redirect it back, but if anything else uses System.out while it's redirected, that will also go to your new file.

The methods would be System.setIn() and System.setOut() (and System.setErr()) if you want to go that route.

Bill K
+1  A: 

Strive to structure your code using Parameterisation from Above, Dependency Injection, or whatever you want to call it. Avoid statics, even those dressed up as singletons or hidden in a bad library.

If you don't want to remove usage of System.in/out/err you are in luck. These can be set globally with System.set(In/Out/Err). To be able to stream differently for individual threads set an implementation that uses ThreadLocal to find a target to delegate to. Thread-locals are generally evil, but can be useful in unfortunate situations.

From clarifications, it appears that the original poster has no need to create a separate process.

Tom Hawtin - tackline