views:

948

answers:

3

I realize there are similar questions on SO, but they don't quite solve my problem.

I would like a method that, given a Class object, will invoke the "main" method, that is to say, public static void main, on that Class (if it exists) and capture the console output of that main method. The class doing the invocation is a non-daemon thread.

I have part of the code already, but I'm not sure how to capture the console output, and on top of that, how to only capture it for *this specific thread. Here's what I have so far:

public class Output extends Thread {
    private Class testClass;

    public Output(Class clazz) {
        this.testClass = clazz;
    }

    private Method getMainMethod(Class clazz) {
     Method[] methods = clazz.getMethods();
     for (Method method : methods) {
      if (isMainMethod(method)) { 
       return method;
      }
     }

     return null;
    }

    private Boolean isMainMethod(Method method) {
     return (method.getName().equals("main") &&
       Modifier.isStatic(method.getModifiers()) &&
       method.getReturnType().equals(Void.class));
    }

    public void run() {
        Method mainMethod = null;

        if ((mainMethod = getMainMethod(this.testClass)) == null) {
         //if there's no static void main method, throw exception
         throw new YouFuckedItUpException();
        }

        mainMethod.invoke(this.testClass, new String[0]);

        return heresWhereImStuckIWantToCaptureTheConsoleOutputAndReturnIt();
    }
}

All I need is some code to, or a link to an answer on how to, capture the System.out and System.err output of the method being invoked. Any help someone can give would be much appreciated.

Thanks in advance!

EDIT: This is NOT for testing only, this will eventually be something put into production.

EDIT 2: This will need to be thread-safe. Multiple main methods will be invoked by other threads possibly at the same time, and I want each thread to only capture its own specific output.

+1  A: 

Use System.setOut and then write a subclass of printstream that overrides all print/write methods and logs the data if it comes from a thread you want to monitor.

Pseudocode:

public class HackedPrintStream extends PrintStream {
    private PrintStream originalStream;
    private HashMap<Thread, PrintStream> loggerStreams = new HashMap<Thread, PrintStream>();

    public HackedPrintStream(PrintStream originalStream) {
        this.originalStream = originalStream;
    }

    public synchronized void logForThread(Thread threadToLogFor, PrintStream streamToLogTo) {
        loggerStreams.put(threadToLogFor, streamToLogTo);
    }

    /** example overridden print method, you need to override all */
    public synchronized void println(String ln) {
        PrintStream logPS = loggerStreams.get(Thread.currentThread());
        if (logPS != null) { logPS.println(ln); }
        originalStream.println(ln);
    }
}

Then you can create and use this stream with

HackedPrintStream hps = new HackedPrintStream(System.out);
System.setOut(hps);

I would really advise you to look hard for another solution though, as this is not pretty.

Zarkonnen
see Edit 2 above. This will not work in that situation, yes?
Alex Beardsley
It will work thread-safely, but it's horrible.
Zarkonnen
A: 

You can use System.setOut() and System.setErr() but of course, the output will also be capture for the rest of the program.

Maurice Perry
+2  A: 

Since you use the main() method - do you need it to be in the same process? If not, you may try to create it as a new Process (java.lang.Process).

The Process class provides the necessary methods to capture StdOut, StdErr and/or StdIn.

Note Since everything runs in it's own process, there should be no issues with thread-safety. However you will still have to find the location, the .class file (or at least the root) is located in, so that you can run/create the Process with java here.goes.your.ClassName.

Marcel J.
I was thinking this actually. This is technically possible because the class files are actually on the file system, but since I already have the class loaded via a classloader (to do other operations on it), it would be nice if I could just use that.
Alex Beardsley
That's definitely the better solution. Use separate processes.
Zarkonnen
Has been discuessed here (http://stackoverflow.com/questions/342435/java-run-a-callable-in-a-separate-process). Looks like a circular problem. new File(new URI(Test.class.getResource("Test.class").toString())); maybe? You need a new JavaVM for this one, so you have to go down to the File again.
Marcel J.
I'm using custom classloaders to load the .class files from the file system, so getting it is easy in this case. I think this is going to be the best solution.
Alex Beardsley