Intercept console output in the Java program
content:
First, Java pipe flow
1.1 Precautions
1.2 Precautions
1.3 Precautions
1.4 solving the problem
Second, capture the Java console output
Third, capture the console output of other programs
Reference
About author
Yu Liangsong (javaman@163.net)
Software engineer, independent consultant and freelance writer
October 2001
In Java development, console output is still an important tool, but the default console output has a variety of limitations. This article describes how to use Java pipeline to intercept console output, analyze the issues that should be noted in pipeline applications, and provide examples that intercept Java programs and non-Java program console output.
The console output is still an important position in the Java program even in today's dominant dominance. The console is not only the default stack tracking and error message output window by the Java program, but also a practical debugging tool (especially those accustomed to using Println ()). However, the console window has many limitations. For example, on the Windows 9x platform, the DOS console can only accommodate 50 rows of output. It is very difficult to view this content if the Java program outputs a lot of content to the console.
For developers using Javaw, the console window is particularly valuable. Because when you start Java programs with JavaW, there will be no console window at all. If the program encounters problems and throws an exception, it is not possible to see the Java runtime environment written to the System.out or System.err call stack trace information. In order to capture the stack information, some people have adopted the way to encapsulate main (), but this method is not always effective, some of the descriptive error information will be thrown at some moments at the Java runtime. Before the exception is written to System.out and System.err; this information cannot be seen unless you can monitor these two controls.
Therefore, it is necessary to check the data of the Java runtime environment (or third-party program) to write to the control stream and take the appropriate operation. One of the topics discussed in this article is to create such an input stream that can read data from this input stream to previously written to the Java Configuration Directory (or any other program output stream). We can imagine that the data written to the output stream is immediately "reflow" to the Java program in the form of the input form.
The goal of this article is to design a Swing-based text window display console output. During this time, we will also discuss some important considerations relating to the Java pipeline (PiPedInputStream and PipedputStream). Figure 1 shows the Java program used to intercept and display the text output of the console, the core of the user interface is a JTextArea. Finally, we also have to create a simple program that captures and displays other programs (programs that can be non-Java) console output.
Figure 1: Multithreaded console output intercepting program
First, Java pipe flow
To display the console output in the text box, we must "intercept" the control stream in a way. In other words, we have to have a way to read all content written to System.out and System.err. If you are familiar with Java's pipeline PipedInputStream and PiredOutputStream, we believe that we have the most effective tools.
The data written to the PiPedoutPutStream output stream can be read from the corresponding PiPedInputStream input stream. Java's pipeline is very convenient for us to intercept console output. Listing 1 shows a very simple intercept console output scheme.
[Listing 1: Output of the console with pipeline]
PipedinputStream Pipedis = new pipedinputstream ();
PipedputStream PipedOS = new pipedoutputstream (); try {
Pipedos.connect (pipedis);
}
Catch (IOException E) {
System.err.println ("Connection Fail");
System.exit (1);
}
PrintStream PS = New PrintStream (PipedOS);
System.setOut (PS);
System.seterr (PS);
It can be seen that the code here is extremely simple. We just set up a PiPedInputStream and set it to all the ultimate destinations of the data written to the control. All data written to the control 12 is turned to PiPedoutputStream so that data can be quickly intercepted from the corresponding PiPedInputStream read. The next thing seems to only display the data read from the PIPEDIS stream in Swing JtexTarea to get a program that can display the console in the text box. Unfortunately, there are some important considerations when using Java pipelines. Only seriously treat all these precautions can ensure that the code of the Listing 1 is stably run. Let's take a look at the first consideration.
1.1 Precautions
PiPedInputStream is used to a 1024-byte fixed-size loop buffer. The data written to PipedOutputStream is actually saved to the internal buffer of the corresponding PiPedInputStream. When the read operation is performed from the PiPedInputStream, the read data is actually from this internal buffer. If the corresponding PipedInputStream input buffer is full, any thread attempt to write to PipedOutputStream will be blocked. And this write operation thread will be blocked until the operation of reading the PiPedInputStream appears from the buffer to delete data.
This means that threads write data to PiPedoutputStream should not be a unique thread responsible for reading data from corresponding PiPedInputStream. From Figure 2, you can clearly see the problem here: Hypothesis thread T is a unique thread responsible for reading data from PiPedInputStream; in addition, it is assumed that t attempt to write to the corresponding PiPedoutputstream 2000 bytes of data. Before the T thread is blocked, it can write up to 1024 bytes of data (the size of the PiPedInputStream internal buffer). However, once t is blocked, the operation of reading the pipedinputstream will never appear, because T is unique to read the thread of the PiPedInputStream. In this way, the T thread has been completely blocked, and all other threads that try to write to PipedputStream will also encounter the same situation.
Figure 2: Pipeline working process
This does not mean that more than 1024 bytes of data cannot be written in a Write () call. However, it should be ensured that there is another thread to read data from the PipedInputStream while writing data.
Listing 2 demonstrates this problem. This program reads PipedInputStream and Write PipedputStream with a thread. Each time you call WRITE () to write 20 bytes to the buffer of the PipedInputStream, each time the READ () is only read from the buffer and deletes 10 bytes. The internal buffer will eventually be written, resulting in writing operation. Since we perform read, write operations in the same thread, once the write operation is blocked, you cannot read data from the PipedInputStream.
[Listing 2: Perform a read / write operation with the same thread to cause thread blocking]
Import java.io. *;
Public class listing2 {static pipedinputstream pipedis = new pipedinputStream ();
Static PipedOutputStream PipedOS =
New PipedOutputStream ();
Public static void main (string [] a) {
Try {
Pipedis.connect (PipedOS);
}
Catch (IOException E) {
System.err.println ("Connection Fail");
System.exit (1);
}
Byte [] inArray = new byte [10];
BYTE [] outarray = new byte [20];
INT BYTESREAD = 0;
Try {
// Send 20 bytes to Pipedos
Pipedos.write (OutArray, 0, 20);
System.out.println ("20 bytes");
// In each cycle iteration, read 10 bytes
// Send 20 bytes
BYTESREAD = Pipedis.Read (Inarray, 0, 10);
INT i = 0;
While (BytesRead! = -1) {
Pipedos.write (OutArray, 0, 20);
System.out.println ("20 bytes" i);
i ;
BYTESREAD = Pipedis.Read (Inarray, 0, 10);
}
}
Catch (IOException E) {
System.err.Println ("Errors when reading pipedis:" E);
System.exit (1);
}
} // main ()
}
As long as the read / write operation is separated to different threads, Listing 2 can be easily resolved. Listing 3 is a modified version, which performs a write-to-PiPedoutputStream in a separate thread (and reading threads different from the thread). To prove that the data written can exceed 1024 bytes, we will write 2000 bytes each time the write operation thread calls the PipedOutputStream's Write () method. So, whether the thread created in the StartWritErthread () method will block it? According to the Java runtime thread scheduled mechanism, it will of course block. Writing can actually write up to 1024 bytes of payload (that is, the size of the PipedInputStream buffer) before blocking. But this does not become a problem, because the main thread (main) will quickly read data from the PiPedInputStream's loop buffer, empty out the buffer space. Finally, the write operation thread will resume from the last stop, and write the remainder in the 2000 byte payload.
[Listing 3: Separate read / write operation to different threads]
Import java.io. *;
Public class listing3 {
Static pipedinputstream pipedis =
New pipedinputstream ();
Static PipedOutputStream PipedOS =
New PipedOutputStream ();
Public static void main (String [] args) {
Try {
Pipedis.connect (PipedOS);
}
Catch (IOException E) {
System.err.println ("Connection Failure"); System.exit (1);
}
Byte [] inArray = new byte [10];
INT BYTESREAD = 0;
// Boot write operation thread
STARTWRITERTHREAD ();
Try {
BYTESREAD = Pipedis.Read (Inarray, 0, 10);
While (BytesRead! = -1) {
System.out.println ("Read"
BYTESREAD "byte ...");
BYTESREAD = Pipedis.Read (Inarray, 0, 10);
}
}
Catch (IOException E) {
System.err.println ("Read Input Error.");
System.exit (1);
}
} // main ()
// Create a separate thread
// Execute the operation of writing PiPedoutputStream
Private static void startwriterthread () {
New thread (new runnable () {
Public void run () {
Byte [] outrray = new byte [2000];
While (True) {// There is no termination condition
Try {
// Before the thread is blocked, the up to 1024 bytes of data is written.
Pipedos.write (OutArray, 0, 2000);
}
Catch (IOException E) {
System.err.Println ("Write an error");
System.exit (1);
}
System.out.println ("2000 bytes already sent ...");
}
}
}). START ();
} // startwriterthread ()
} // listing3
Perhaps we can't say this problem is a defect in Java pipe flow design, but when applied pipelines, it is a problem that must be closely paid. Let's take a look at the second more important (more dangerous) problem.
1.2 Precautions
When reading data from the PipedInputStream, if you meet the following three conditions, an ioException is exception:
Try to read data from PiPedInputStream,
PipedInputStream's buffer is "empty" (ie there is no readable data),
The last thread to the PiredOutputStream write data is no longer active (via thread.isalive ()).
This is a very delicate moment, and it is also an extremely important moment. It is assumed that there is a thread W writes data to the PiPedoutputStream; another thread R reads data from the corresponding PiPedInputStream. The following series of events will cause the R thread to encounter an ioException exception when trying to read PipedInputStream:
w Write data to PiPedOutputStream.
W End (W.isalive () returns false).
r reads the data written from the PiPedInputStream, and empty the buffer of the PipedInputStream.
r Attempts to read data from PipedInputStream again. At this time, the buffer of the PiPedInputStream is already empty, and W has ended, resulting in an ioException exception when executed during the read operation.
It is not difficult to construct a program to demonstrate this problem, just remove the While (TRUE) condition from the StartwriterThread () method of Listing 3. This change prevents the method of performing a write operation, so that the method of performing a write operation ends the operation after a write operation. As mentioned earlier, at this time the main thread attempts to read the pipedinputstraem, it will encounter an ioException exception. This is a relatively few cases, and there is no way to correct it directly. Please do not correct this problem by correcting the method of piping genre, use inheritance here is completely inappropriate. Moreover, if Sun will change the implementation method of the pipe flow, the modification now is no longer valid.
The last problem is very similar to the second problem, and it is different that it generates an ioException exception at the end of the reading thread (not a write thread).
1.3 Precautions
If a write operation is executed on the PiPedoutputStream while recently received threads read from the corresponding PiPedInputStream is no longer active (through thread.isalive () detection), the write operation will throw an IOException exception. It is assumed that there are two threads W and R, and W is written to the PipedputStream, and R is read from the corresponding PiPedInputStream. The following series of events will cause the W thread to encounter ioException when trying to write to PipedOutputStream:
Write an operation thread W has been created, but the R thread does not exist.
w Write data to PiPedOutputStream.
The reading thread r is created and data is read from the PipedInputStream.
The R thread ends.
W Attempt to write data to PiredOutputStream, find that R has ended, throwing an IOEXCEPTION exception.
In fact, this problem is not like the second question. Compared to the case of multiple read threads / single write threads, there may be more common in the application (servers as a response request) and multiple write threads (issu requests).
1.4 solving the problem
One of the problems brought by two limitations in front of the pipe flow is to use a ByteArrayoutputStream as a proxy or replacement PipedputStream. Listing 4 shows a LoopedStreams class, which provides a BYTEARRAYOUTPUTSTREAM to provide a similar feature similar to Java pipeline, but there is no deadlock and ioException exception. The interior of this class still uses pipe flow, but isolates the first two issues introduced in this article. Let's take a look at this type of public method (see Figure 3). The constructor is simple, it connects the tube channel, then calls the startByteaRayReadertHread () method (discussed later, this method will be discussed later). The getOutputStream () method returns an OutputStream (specifically, a ByteArrayoutputStream is used to replace Pipedputstream. The data written to this OutputStream will eventually appear as input in the stream returned in the GetInputStream () method. Unlike the use of PiredOutputStream, write to ByteArrayoutputStream written to the thread of the data, write data, and end will not bring negative effects.
Figure 3: BYTEARRAYOUTPUTSTREAM principle
[Listing 4: Prevent common problems in pipeline applications]
Import java.io. *;
Public class loopedstreams {
Private pipedocutstream pipedos =
NEW PIPEDOUTPUTSTRENM (); Private Boolean Keeprunning = True;
Private byteArrayoutputstream ByteArrayos =
New byteArrayoutputStream () {
Public void close () {
Keeprunning = false;
Try {
Super.close ();
pipedos.close ();
}
Catch (IOException E) {
// Record errors or other processing
/ / For a simple meter, we directly end
System.exit (1);
}
}
}
Private pipedinputstream pipedis = new pipedinputStream () {
Public void close () {
Keeprunning = false;
Try {
Super.close ();
}
Catch (IOException E) {
// Record errors or other processing
/ / For a simple meter, we directly end
System.exit (1);
}
}
}
Public loopedStreams () throws oException {
Pipedos.connect (pipedis);
StartByteaRrayReaderthread ();
} // loopedStreams ()
Public InputStream getInputStream () {
Return pipedis;
} // getInputStream ()
Public outputReam getOutputStream () {
Return ByteaRrayos;
} // getOutputStream ()
Private void startbyteaRayReaderthread () {
New thread (new runnable () {
Public void run () {
While (KeePrunning) {
/ / Check the number of bytes inside
IF (ByteArrayos.size ()> 0) {
BYTE [] Buffer = NULL;
Synchronized (ByteaRrayos) {
Buffer = ByteArrayos.tobyteaRray ();
ByteArrayos.reset (); // Clear buffer
}
Try {
// Send the extracted data to PipedputStream
Pipedos.write (buffer, 0, buffer.length);
}
Catch (IOException E) {
// Record errors or other processing
/ / For a simple meter, we directly end
System.exit (1);
}
}
Else // No data available, threads enter sleep state
Try {
// View byteArrayoutputStream check new data every 1 second
Thread.sleep (1000);
}
Catch (InterruptedExcect E) {}
}
}
}). START ();
} // startByteaRayReadertHread ()
} // loopedstreams
The StartByteArrayReaderthread () method is the entire class true key. The goal of this method is simple, that is, create a thread that periodically checks the ByteArrayoutputStream buffer. All the data found in the buffer is extracted into a BYTE array and then written to PipedputStream. Since the PiPedOutputStream corresponding to the PipedputStream is returned by GetInputStream (), the thread reads data from the input stream will read the data originally sent to ByteArrayoutputStream. As mentioned earlier, the LoopedStreams class solves the first two issues where the pipe flow exists, let's see how this is achieved. ByteArrayoutputStream has the ability to extend its internal buffer as needed. Due to the "full buffer", the thread is not blocked when the stream returned to getOutputStream () will not be blocked. Therefore, the first question will not bring us a hassle. In addition, it is necessary to say, ByteArrayoutputstream's buffer will never be reduced. For example, it is assumed that a 500 k data is written to the stream prior to extracting the data, and the buffer will always maintain a capacity of at least 500 k. If this class has a method to correct the size of the buffer after the data is extracted, it will be more perfect.
The second problem is resolved. The reason is that when only one thread is written to PipedOutputStream, this thread is thread created by StartByteaRayReaderthread (). Since this thread is completely controlled by LoopedStreams, we don't have to worry that it will generate an ioException exception.
There are some details that the LoopedStreams class is worth mentioning. First, we can see that ByteArrayos and PiPedis are actually the derived class of ByteArrayoutputStream and PipedInputStream, that is, the special behavior is added to their close () method. If the user of a LoopedStreams object off the input or output stream, the thread created in StartByteaRayReadertHread () must be turned off. The overridden close () method sets the KeePrunning tag to false to close the thread. Also, pay attention to the synchronization block in StartByteArrayReadertHread (). To ensure that the ByteArrayoutputStream buffer is not written in the TobyTearray () call and the RESET () call is essential. Since all versions of the Write () method of ByteArrayoutputStream are synchronized on this stream, we guarantee that the internal buffer of ByteArrayoutputStream is not accidentally modified.
Note that the LoopedStreams class does not involve the third issue of the pipeline. The getInputStream () method of this class returns PipedInputStream. If a thread is read from the stream, it is terminated after a period of time, the next data is transmitted from the ByteArrayoutputStream buffer to the PipedOutputStream.
Second, capture the Java console output
The ConboletExtArea class extension Swing JtextArea capture console output is output. Don't be surprised by this class, you must point out that the ConsoleTextArea class has more than 50% of the code for testing.
[Listing 5: Intercept Java Console Output]
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 StartConsolereadeRThread (Instreams [i]); } // consoletextArea () Public consolextarea () throws oException { Final loopedstreams ls = new waystreams (); / / Redirect System.out and 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); New thread (new runnable () { Public void run () { StringBuffer SB = new stringbuffer (); Try { String S; Document doc = getDocument (); While ((s = br.readline ())! = null) { Boolean Caretad = False; Caretatend = getcaretPosition () == Doc.getLength ()? True: False; sb.setlength (0); Append (sb.append (s) .append ('/ n'). TOSTRING ()); IF (Caretate) SetCareTPosition (Doc.getLength ()); } } Catch (IOException E) { JOPTIONPANE.SHOWMESSAGEDIALOG (NULL, "Read errors from BufferedReader:" E); System.exit (1); } } }). START (); } // startconsolereaderthread () / / The function of the remainder of this class is to test Public static void main (String [] args) { JFrame f = new jframe ("consoletextArea test); ConsoletExtArea consoleTextArea = NULL; Try { CONSOLETEXTAREA = New ConsoleTextArea (); } Catch (IOException E) { System.err.println "You can't create 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); } }); // Start a few write operation threads // system.out and system.err output StartwriterTestthread ( "Write operation thread # 1", system.err, 920, 50); StartwriterTestthread ( "Write operation thread # 2", system.out, 500, 50); StartwriterTestthread ( "Write Operation Thread # 3", System.out, 200, 50); StartwriterTestthread ( "Write operation thread # 4", system.out, 1000, 50); StartwriterTestthread ( "Write operation thread # 5", system.err, 850, 50); } // main () Private static void startwritertertthread ( 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 (InterruptedExcect E) {} } } }). START (); } // startwritertestthread () } // consoletextarea The main () method creates a JFrame, JFrame contains an instance of consoleTextArea. These codes have nothing special. After Frame is displayed, the main () method launches a range of write operation threads, writing the operating thread to the control gate output a lot of information. ConsoTextArea captures and displays this information as shown. ConsoleTextArea offers two constructor. The constructor without parameters is used to capture and display all the data written to the control 12, and the constructor of an inputStream [] parameter forwards all the data read from each array element to JTextarea. One example will have an example of this constructor. First let's take a look at the consoleTextArea constructor without parameters. This function first creates a loopedStreams object; then requests the Java runtime environment to forward the console to the OutputStream provided by LoopedStreams; Note that after the text is added to JTEXTAREA, the program carefully ensures the correct position of the insertion point. In general, the update of Swing parts should not be performed outside the AWT Event Dispatch Thread, AEDT. For this example, this means that all the operations of the text to jtextarea should be performed in the AEDT instead of the thread created in the startconsolereaderthread () method. However, in fact, adding text to JTextArea to JTextArea to add text security operations. After reading a line of text, we only need to call Jtext.Append () to add text to the end of JtexTarea. Third, capture the console output of other programs Capturing the Java program in JTextarea is a matter, and the console data that captures other programs (even some non-Java programs) is another thing. ConsoTextArea provides the basic feature required to capture other applications, and the AppOutputCapture of Listing 6 uses ConsoleTextArea, intercepting the output information of other applications and appears in ConsoleTextArea. [Listing 6: Intercepting the console output of other programs] Import java.awt. *; Import java.awt.event. *; Import java.io. *; Import javax.swing. *; Public class appoutputcapture { PRIVATETIC Process Process; Public static void main (String [] args) { IF (args.length == 0) { System.err.Println ("Usage: Java AppOutputCapture" " System.exit (0); } Try { / / Start the new process of the command line specified program Process = runtime.getruntime (). EXEC (ARGS); } Catch (IOException E) { System.err.Println ("Error Create a process ... / N" E); System.exit (1); } // Get the stream written by the new process InputStream [] Instreams = NEW INPUTSTREAM [] { Process.getinputStream (), process.GeterrorStream ()}; ConsoleTextArea CTA = New ConsoTextArea (Instreams); CTA.SetFont (java.awt.font.decode ("monospaced"); JFrame frame = new jframe (args [0] "Console Output"); 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 (); // may be hanged under Win98 } Catch (InterruptedExcect E) {} System.exit (0); } }); } // main ()} // AppOutputCapture The working process of AppOutputCapture is as follows: First use the runtime.exec () method to start a new process of the specified program. After starting a new process, get its control 12 from the results Process object. Thereafter, these console streams into the consolectExtArea (this is the use of the parameter consolextArea constructor). When using AppOutputCapture, specify the name of the program to be intercepted on the command line. For example, if javaw.exe appoutputcapture ping.exe www.yahoo.com is executed under Windows 2000, the result is shown in Figure 4. Figure 4: Intercepting the console output of other programs When using AppOutputCapture, it should be noted that some texts that are initially outputted by the applications that are intercepted. Because there is a short period of time between call runtime.exec () and consoletextAREA initialization. Within this time difference, the text of the application output will be lost. When the AppOutputCapture window is turned off, Process.Destory () calls the process attempting to close the Java program starting. The test results show that the Destroy () method is not always valid (at least in Windows 98). It seems that the processes are not turned off when the process is closed. In addition, the AppOutputCapture program does not end properly in this case. But under Windows NT, everything is normal. If you run AppOutputCapture with JDK v1.1.x, a nullpointerException will appear when you turn off the window. This is a JDK bug, JDK 1.2.x and JDK 1.3.x will not have problems. Please download this article here: javaconsoleoutput_code.zip reference: Java Skills 14: Redirects standard flow in Java Java Tips 33: Talk about convection to redirect How to write multi-threaded Java applications avoid the most common problems in the current programming Multi-thread in the Java program About author Yu Liangsong, software engineer, independent consultant and freelance writer. Originally engaged in PB and Oracle development, the main interest is to develop in Internet. You can contact me via javaman@163.net.