捕获Java控制台输出

Listing 5的ConsoleTextArea类扩展Swing JTextArea捕获控制台输出。不要对这个类有这么多代码感到惊讶,必须指出的是,ConsoleTextArea类有超过50%的代码用来进行测试。

【Listing 5:截获Java控制台输出】

import java.io.*; 

import java.util.*; 

import javax.swing.*; 

import javax.swing.text.*; 

public class ConsoleTextArea extends JTextArea { 

    public ConsoleTextArea(InputStream[] inStreams) { 

        for(int i = 0; i < inStreams.length; ++i) 

            startConsoleReaderThread(inStreams[i]); 

    } // ConsoleTextArea() 

    public ConsoleTextArea() throws IOException { 

        final LoopedStreams ls = new LoopedStreams(); 

        // 重定向System.out和System.err 

        PrintStream ps = new PrintStream(ls.getOutputStream()); 

        System.setOut(ps); 

        System.setErr(ps); 

        startConsoleReaderThread(ls.getInputStream()); 

    } // ConsoleTextArea() 

    private void startConsoleReaderThread( 

        InputStream inStream) { 

        final BufferedReader br = 

            new BufferedReader(new InputStreamReader(inStream)); 

        new Thread(new Runnable() { 

            public void run() { 

                StringBuffer sb = new StringBuffer(); 

                try { 

                    String s; 

                    Document doc = getDocument(); 

                    while((s = br.readLine()) != null) { 

                        boolean caretAtEnd = false; 

                        caretAtEnd = getCaretPosition() == doc.getLength() ? 

                            true : false; 

                        sb.setLength(0); 

                        append(sb.append(s).append(′/n′).toString()); 

                        if(caretAtEnd) 

                            setCaretPosition(doc.getLength()); 

                    } 

                } 

                catch(IOException e) { 

                    JOptionPane.showMessageDialog(null, 

                        "从BufferedReader读取错误:" + e); 

                    System.exit(1); 

                } 

            } 

        }).start(); 

    } // startConsoleReaderThread() 

    // 该类剩余部分的功能是进行测试 

    public static void main(String[] args) { 

        JFrame f = new JFrame("ConsoleTextArea测试"); 

        ConsoleTextArea consoleTextArea = null; 

        try { 

            consoleTextArea = new ConsoleTextArea(); 

        } 

        catch(IOException e) { 

            System.err.println( 

                "不能创建LoopedStreams:" + e); 

            System.exit(1); 

        } 

        consoleTextArea.setFont(java.awt.Font.decode("monospaced")); 

        f.getContentPane().add(new JScrollPane(consoleTextArea), 

            java.awt.BorderLayout.CENTER); 

        f.setBounds(50, 50, 300, 300); 

        f.setVisible(true); 

        f.addWindowListener(new java.awt.event.WindowAdapter() { 

            public void windowClosing( 

                java.awt.event.WindowEvent evt) { 

                System.exit(0); 

            } 

        }); 

        // 启动几个写操作线程向 

        // System.out和System.err输出 

        startWriterTestThread( 

            "写操作线程 #1", System.err, 920, 50); 

        startWriterTestThread( 

            "写操作线程 #2", System.out, 500, 50); 

        startWriterTestThread( 

            "写操作线程 #3", System.out, 200, 50); 

        startWriterTestThread( 

            "写操作线程 #4", System.out, 1000, 50); 

        startWriterTestThread( 

            "写操作线程 #5", System.err, 850,    50); 

    } // main() 

    private static void startWriterTestThread( 

        final String name, final PrintStream ps, 

        final int delay, final int count) { 

        new Thread(new Runnable() { 

            public void run() { 

                for(int i = 1; i <= count; ++i) { 

                    ps.println("***" + name + ", hello !, i=" + i); 

                    try { 

                        Thread.sleep(delay); 

                    } 

                    catch(InterruptedException e) {} 

                } 

            } 

        }).start(); 

    } // startWriterTestThread() 

} // ConsoleTextArea 



main()方法创建了一个JFrame,JFrame包含一个ConsoleTextArea的实例。这些代码并没有什么特别之处。Frame显示出来之后,main()方法启动一系列的写操作线程,写操作线程向控制台流输出大量信息。ConsoleTextArea捕获并显示这些信息,如图一所示。



ConsoleTextArea提供了两个构造函数。没有参数的构造函数用来捕获和显示所有写入到控制台流的数据,有一个InputStream[]参数的构造函数转发所有从各个数组元素读取的数据到JTextArea。稍后将有一个例子显示这个构造函数的用处。首先我们来看看没有参数的 ConsoleTextArea构造函数。这个函数首先创建一个LoopedStreams对象;然后请求Java运行时环境把控制台输出转发到 LoopedStreams提供的OutputStream;最后,构造函数调用startConsoleReaderThread(),创建一个不断地把文本行追加到JTextArea的线程。注意,把文本追加到JTextArea之后,程序小心地保证了插入点的正确位置。

一般来说,Swing部件的更新不应该在AWT事件分派线程(AWT Event Dispatch Thread,AEDT)之外进行。对于本例来说,这意味着所有把文本追加到JTextArea的操作应该在AEDT中进行,而不是在 startConsoleReaderThread()方法创建的线程中进行。然而,事实上在Swing中向JTextArea追加文本是一个线程安全的操作。读取一行文本之后,我们只需调用JText.append()就可以把文本追加到JTextArea的末尾三、捕获其他程序的控制台输出

在JTextArea中捕获Java程序自己的控制台输出是一回事,去捕获其他程序(甚至包括一些非Java程序)的控制台数据又是另一回事。ConsoleTextArea提供了捕获其他应用的输出时需要的基础功能,Listing 6的AppOutputCapture利用ConsoleTextArea,截取其他应用的输出信息然后显示在ConsoleTextArea中。

【Listing 6:截获其他程序的控制台输出】 

import java.awt.*; 

import java.awt.event.*; 

import java.io.*; 

import javax.swing.*; 

public class AppOutputCapture { 

        private static Process process; 

        public static void main(String[] args) { 

                if(args.length == 0) { 

             System.err.println("用法:java AppOutputCapture " + 

                 "<程序名字> {参数1 参数2 ...}"); 

             System.exit(0); 

                } 

                try { 

                        // 启动命令行指定程序的新进程 

                        process = Runtime.getRuntime().exec(args); 

                } 

                catch(IOException e) { 

                        System.err.println("创建进程时出错.../n" + e); 

                        System.exit(1); 

                } 

                // 获得新进程所写入的流 

                InputStream[] inStreams = 

                        new InputStream[] { 

                    process.getInputStream(),process.getErrorStream()}; 

                ConsoleTextArea cta = new 

    ConsoleTextArea(inStreams); 

                cta.setFont(java.awt.Font.decode("monospaced")); 

                JFrame frame = new JFrame(args[0] + 

                        "控制台输出"); 

                frame.getContentPane().add(new JScrollPane(cta), 

                    BorderLayout.CENTER); 

                frame.setBounds(50, 50, 400, 400); 

                frame.setVisible(true); 

                frame.addWindowListener(new WindowAdapter() { 

                        public void windowClosing(WindowEvent evt) { 

                                process.destroy(); 

                                try { 

                                        process.waitFor(); // 在Win98下可能被挂起 

                                } 

                                catch(InterruptedException e) {} 

                                        System.exit(0); 

                                } 

                        }); 

        } // main() 

} // AppOutputCapture


AppOutputCapture的工作过程如下:首先利用Runtime.exec()方法启动指定程序的一个新进程。启动新进程之后,从结果 Process对象得到它的控制台流。之后,把这些控制台流传入ConsoleTextArea(InputStream[])构造函数(这就是带参数 ConsoleTextArea构造函数的用处)。使用AppOutputCapture时,在命令行上指定待截取其输出的程序名字