Capture console output in Java program
Content:
1. Java pipeline stream
1.1 Note 1
1.2 Note 2
1.3 Note 3
1.4 solve the problem
Ii. Capture Java console output
3. Capture console output of other programs
References
About the author
Yu LiangSong (javaman@163.net)
Software engineers, independent consultants and freelance writers
October 2001
In Java Development, console output is still an important tool, but the default console output has various limitations. This article describes how to use the Java pipeline stream to intercept console output, analyzes the issues that should be paid attention to in the pipeline stream application, and provides examples of intercepting Java programs and non-Java program console output.
That is to say, in today's dominant position in the graphic user interface, console output still plays an important role in Java programs. The console is not only the default stack trace and error message output window for Java programs, but also a practical debugging tool (especially for those who are used to using println ). However, console windows have many limitations. For example, on Windows 9x, the DOS console can only accommodate 50 rows of output. If a Java program outputs a large amount of content to the console at a time, it is very difficult to view the content.
The console window is especially valuable for developers who use the javaw Startup Program. Because when you start a Java program using Java W, there is no console window. If the program encounters a problem and throws an exception, it cannot view the call stack trace information written to system. out or system. err in the Java Runtime Environment. To capture stack information, some people use try/catch () block to encapsulate main (), but this method is not always effective. At some time points during Java runtime, some descriptive error messages will be written to system before an exception is thrown. out and system. err; this information is invisible unless the two console streams can be monitored.
Therefore, it is necessary to check the data written to the console stream in the Java Runtime Environment (or a third-party Program) and take appropriate operations. One of the topics discussed in this article is to create an input stream from which you can read data previously written into the Java console stream (or the output stream of any other program. We can imagine that the data written to the output stream is immediately "reflux" to the Java program in the form of input.
The purpose of this article is to design a swing-based text window display console output. During this period, we will also discuss important considerations related to Java pipeline streams (pipedinputstream and pipedoutputstream. Figure 1 shows the Java program used to intercept and Display Console text output. The core of the user interface is a jtextarea. Finally, we need to create a simple program that can capture and display other programs (which can be non-Java programs) output on the console.
Figure 1: multi-threaded console output interceptor
1. Java pipeline stream
To display console output in the text box, we must "intercept" the console stream in some way. In other words, we need a method to efficiently read the content written to system. Out and system. Err. If you are familiar with the Java pipeline stream pipedinputstream and pipedoutputstream, we believe that we already have the most effective tool.
Data written to the pipedoutputstream output stream can be read from the corresponding pipedinputstream input stream. The Java pipeline stream greatly facilitates the capture of console output. Listing 1 shows a very simple screenshot console output solution.
[Listing 1: Console output with pipeline stream capture]
Pipedinputstream pipedis = new pipedinputstream ();
Pipedoutputstream pipedos = new pipedoutputstream ();
Try {
Pipedos. Connect (pipedis );
}
Catch (ioexception e ){
System. Err. println ("connection failed ");
System. Exit (1 );
}
Printstream PS = new printstream (pipedos );
System. setout (PS );
System. seterr (PS );
As you can see, the code here is extremely simple. We just created a pipedinputstream and set it as the final destination of all data written to the console stream. All data written to the console stream is transferred to pipedoutputstream. In this way, data written to the console stream can be quickly intercepted by reading from the corresponding pipedinputstream. Next, it seems that only the data read from the pipedis stream is displayed in swing jtextarea to get a program that can display console output in the text box. Unfortunately, there are some important notes when using Java pipeline streams. Only by taking all these precautions seriously can the Listing 1 code run stably. Next let's take a look at the first note.
1.1 Note 1
Pipedinputstream uses a circular buffer with a fixed size of 1024 bytes. The data written to pipedoutputstream is actually saved to the internal buffer zone of the corresponding pipedinputstream. When a read operation is performed from pipedinputstream, the data read actually comes from this internal buffer zone. If the corresponding pipedinputstream input buffer area is full, any thread attempting to write pipedoutputstream will be blocked. The write operation thread will be blocked until the operation to read pipedinputstream is executed to delete data from the buffer zone.
This means that the thread writing data to pipedoutputstream should not be responsible for reading data from the corresponding pipedinputstream. As shown in figure 2, we can clearly see the problem here: Assume that thread t is the only thread responsible for reading data from pipedinputstream; in addition, assume that t attempts to write () to pipedoutputstream at a time () the method is called to write 2000 bytes of data to the corresponding pipedoutputstream. Before the T thread is congested, it can write up to 1024 bytes of data (the size of the internal buffer of pipedinputstream ). However, once t is blocked, the operation to read pipedinputstream will no longer appear, because T is the only thread to read pipedinputstream. In this way, the T thread is completely blocked. At the same time, all other threads trying to write data to pipedoutputstream will also encounter the same situation.
Figure 2: Pipeline Workflow
This does not mean that more than 1024 bytes of data cannot be written in a write () call. However, it should be ensured that while writing data, another thread reads data from pipedinputstream.
Listing 2 demonstrates this problem. This program uses a thread to alternately read pipedinputstream and write pipedoutputstream. Each call to write () writes 20 bytes to the buffer zone of pipedinputstream. Each call to read () only reads and deletes 10 bytes from the buffer zone. The internal buffer will eventually be full, leading to write operation congestion. Since we use the same thread to perform read and write operations, once the write operation is blocked, we cannot read data from pipedinputstream.
[Listing 2: using the same thread to execute read/write operations causes thread blocking]
Import java. Io .*;
Public class listing2 {
Static pipedinputstream pipedis = new pipedinputstream ();
Static pipedoutputstream pipedos =
New pipedoutputstream ();
Public static void main (string [] ){
Try {
Pipedis. Connect (pipedos );
}
Catch (ioexception e ){
System. Err. println ("connection failed ");
System. Exit (1 );
}
Byte [] inarray = new byte [10];
Byte [] outarray = new byte [20];
Int bytesread = 0;
Try {
// Send 20 bytes of data to pipedos
Pipedos. Write (outarray, 0, 20 );
System. Out. println ("20 Bytes Sent ...");
// Read 10 bytes in each loop iteration
// 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 Sent..." + I );
I ++;
Bytesread = pipedis. Read (inarray, 0, 10 );
}
}
Catch (ioexception e ){
System. Err. println ("error when reading pipedis:" + E );
System. Exit (1 );
}
} // Main ()
}
The Listing 2 problem can be easily solved by separating read/write operations into different threads. Listing 3 is the modified version of Listing 2. It writes pipedoutputstream in a separate thread (different from the reading thread ). To prove that the data written at a time can exceed 1024 bytes, the write operation thread writes 2000 bytes each time it calls the pipedoutputstream's write () method. Is the thread created in the startwriterthread () method blocked? According to the thread scheduling mechanism during Java runtime, it will certainly be blocked. Before blocking, write operations can write a maximum of 1024 bytes of payload (the size of the pipedinputstream buffer ). But this will not be a problem, because the main thread will soon read data from the loop buffer of pipedinputstream and empty the buffer space. In the end, the write operation thread starts from the last stop and writes the remaining part of the 2000-byte payload.
[Listing 3: Separate read/write operations 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 failed ");
System. Exit (1 );
}
Byte [] inarray = new byte [10];
Int bytesread = 0;
// Start the 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 an independent thread
// Write to pipedoutputstream
Private Static void startwriterthread (){
New thread (New runnable (){
Public void run (){
Byte [] outarray = new byte [2000];
While (true) {// loop without termination conditions
Try {
// Before the thread is blocked, a maximum of 1024 bytes of data are written.
Pipedos. Write (outarray, 0, 2000 );
}
Catch (ioexception e ){
System. Err. println ("write operation error ");
System. Exit (1 );
}
System. Out. println ("2000 bytes sent ...");
}
}
}). Start ();
} // Startwriterthread ()
} // Listing3
Maybe we cannot say that this problem is a defect in Java pipeline stream design, but it must be paid close attention to when applying pipeline stream. Next let's take a look at the second more important (more dangerous) problem.
1.2 Note 2
When reading data from pipedinputstream, if the following three conditions are met, an ioexception occurs:
Trying to read data from pipedinputstream,
The buffer zone of pipedinputstream is "null" (that is, there is no readable data ),
The last thread that writes data to pipedoutputstream is no longer active (detected by thread. isalive ).
This is a very delicate moment and an extremely important moment. Assume that one thread W writes data to pipedoutputstream, And the other thread R reads data from the corresponding pipedinputstream. The following series of events will cause the r thread to encounter ioexception when trying to read pipedinputstream:
W writes data to pipedoutputstream.
W (W. isalive () returns false ).
R reads data written by W from pipedinputstream and clears the buffer zone of pipedinputstream.
R tries to read data from pipedinputstream again. At this time, the buffer of pipedinputstream is empty and W is over, which leads to an ioexception during read operation execution.
Creating a program to demonstrate this problem is not difficult. You only need to delete the while (true) condition from the startwriterthread () method of listing 3. This change prevents the write operation method from being executed cyclically, so that the write operation execution method ends after a write operation. As mentioned above, when the main thread tries to read pipedinputstraem, it will encounter an ioexception.
This is a rare case and there is no direct correction method. Please do not solve this problem by using the method of the Child class from the pipeline genre ?? It is totally inappropriate to use inheritance here. In addition, if sun later changes the pipeline stream implementation method, the modifications made now will not be valid.
The last problem is very similar to the second one. The difference is that it generates an ioexception when the reading thread ends (rather than the writing thread.
1.3 Note 3
If a write operation is executed on pipedoutputstream and the thread recently read from the corresponding pipedinputstream is no longer active (detected by thread. isalive (), the write operation throws an ioexception. Assume that there are two threads W and R, W writes data to pipedoutputstream, and r reads data from the corresponding pipedinputstream. The following series of events will cause the W thread to encounter an ioexception when attempting to write pipedoutputstream:
The write operation thread W has been created, but the r thread does not exist.
W writes data to pipedoutputstream.
The read thread R is created and reads data from pipedinputstream.
The r thread ends.
W attempted to write data to pipedoutputstream and found that R has ended, throwing an ioexception.
In fact, this problem is not as tricky as the second one. Compared with multiple read threads/single write threads, it may be more common to have one read thread (as the server responding to the request) and multiple write threads (sending requests) in the application.
1.4 solve the problem
To prevent problems caused by the first two limitations of the pipeline stream, one method is to use a bytearrayoutputstream as a proxy or replace pipedoutputstream. Listing 4 shows a loopedstreams class. It uses a bytearrayoutputstream to provide similar functions as a Java pipeline stream, but does not encounter deadlocks or ioexception exceptions. This class still uses pipeline streams internally, but isolates the first two problems described in this article. Let's take a look at the public methods of this class (see figure 3 ). The constructor is simple. It connects to the pipeline stream and then calls the startbytearrayreaderthread () method (this method will be discussed later ). The getoutputstream () method returns an outputstream (specifically, a bytearrayoutputstream) to replace pipedoutputstream. Data written to this outputstream will eventually appear as input in the stream returned by the getinputstream () method. Unlike pipedoutputstream, activation, data writing, and termination of the thread that writes data to bytearrayoutputstream do not bring negative effects.
Figure 3: bytearrayoutputstream Principle
[Listing 4: prevent common problems in pipeline flow applications]
Import java. Io .*;
Public class loopedstreams {
Private pipedoutputstream pipedos =
New pipedoutputstream ();
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 handling
// For simple calculation, we will end it here
System. Exit (1 );
}
}
};
Private pipedinputstream pipedis = new pipedinputstream (){
Public void close (){
Keeprunning = false;
Try {
Super. Close ();
}
Catch (ioexception e ){
// Record errors or other handling
// For simple calculation, we will end it here
System. Exit (1 );
}
}
};
Public loopedstreams () throws ioexception {
Pipedos. Connect (pipedis );
Startbytearrayreaderthread ();
} // Loopedstreams ()
Public inputstream getinputstream (){
Return pipedis;
} // Getinputstream ()
Public outputstream getoutputstream (){
Return bytearrayos;
} // Getoutputstream ()
Private void startbytearrayreaderthread (){
New thread (New runnable (){
Public void run (){
While (keeprunning ){
// Check the number of bytes in the stream
If (bytearrayos. Size ()> 0 ){
Byte [] buffer = NULL;
Synchronized (bytearrayos ){
Buffer = bytearrayos. tobytearray ();
Bytearrayos. Reset (); // clear the buffer
}
Try {
// Send the extracted data to pipedoutputstream
Pipedos. Write (buffer, 0, buffer. Length );
}
Catch (ioexception e ){
// Record errors or other handling
// For simple calculation, we will end it here
System. Exit (1 );
}
}
Else // No data is available and the thread enters sleep state
Try {
// View bytearrayoutputstream every 1 second to check new data
Thread. Sleep (1000 );
}
Catch (interruptedexception e ){}
}
}
}). Start ();
} // Startbytearrayreaderthread ()
} // Loopedstreams
The startbytearrayreaderthread () method is the real key of the entire class. The goal of this method is to create a thread that regularly checks the bytearrayoutputstream buffer. All data found in the buffer is extracted to a byte array and then written to pipedoutputstream. Because the pipedinputstream corresponding to pipedoutputstream is returned by getinputstream (), the threads that read data from the input stream will read the data originally sent to bytearrayoutputstream. As mentioned above, the loopedstreams class solves the first two problems in the pipeline stream. Let's take a look at how this works.
Bytearrayoutputstream can expand its internal buffer as needed. Because there is a "full buffer", the thread will not be blocked when writing data to the stream returned by getoutputstream. Therefore, the first problem will not cause us any more trouble. In addition, bytearrayoutputstream's buffer will never be reduced. For example, if a piece of 500 k Data is written to the stream before data can be extracted, the buffer will always maintain a capacity of at least 500 K. If this class has a method that can modify the buffer size after the data is extracted, it will be improved.
The second problem is solved because only one thread writes data to pipedoutputstream at any time. This thread is created by startbytearrayreaderthread. Because this thread is completely controlled by the loopedstreams class, we don't have to worry about it will generate an ioexception.
The loopedstreams class has some details worth mentioning. First, we can see that bytearrayos and pipedis are actually instances of the derived classes of bytearrayoutputstream and pipedinputstream, that is, they add special actions in their close () method. If the user of a loopedstreams object disables the input or output stream, the thread created in startbytearrayreaderthread () must be disabled. The overwrite close () method sets the keeprunning flag to false to close the thread. In addition, note the synchronization block in startbytearrayreaderthread. Ensure that the bytearrayoutputstream buffer between the tobytearray () call and the reset () call is not modified by the thread that writes the stream. This is essential. Because all versions of the write () method of bytearrayoutputstream are synchronized on this stream, we ensure that the internal buffer of bytearrayoutputstream is not accidentally modified.
Note that the loopedstreams class does not involve the third issue of MPs queue streams. The getinputstream () method of this class returns pipedinputstream. If a thread reads data from this stream and terminates after a period of time, the ioexception will occur the next time data is transmitted from the bytearrayoutputstream buffer to pipedoutputstream.
Ii. Capture Java console output
The consoletextarea class of Listing 5 extends swing jtextarea to capture console output. Don't be surprised that this class has so much code. It must be noted that the leletextarea class has more than 50% of the Code for testing.
[Listing 5: intercepting Java console output]
Import java. Io .*;
Import java. util .*;
Import javax. Swing .*;
Import javax. Swing. Text .*;
Public class leletextarea extends jtextarea {
Public consoletextarea (inputstream [] instreams ){
For (INT I = 0; I <instreams. length; ++ I)
Startconsolereaderthread (instreams [I]);
} // Leletextarea ()
Public consoletextarea () throws ioexception {
Final loopedstreams ls = new loopedstreams ();
// Redirect system. Out and system. Err
Printstream PS = new printstream (LS. getoutputstream ());
System. setout (PS );
System. seterr (PS );
Startconsolereaderthread (LS. getinputstream ());
} // Leletextarea ()
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,
"Read error from bufferedreader:" + E );
System. Exit (1 );
}
}
}). Start ();
} // Startconsolereaderthread ()
// The remaining part of this class is used for testing.
Public static void main (string [] ARGs ){
Jframe F = new jframe ("leletextarea test ");
Leletextarea consoletextarea = NULL;
Try {
Leletextarea = new consoletextarea ();
}
Catch (ioexception e ){
System. Err. println (
"Loopedstreams cannot be created:" + E );
System. Exit (1 );
}
Leletextarea. setfont (Java. AWT. Font. Decode ("monospaced "));
F. getcontentpane (). Add (New jscrollpane (leletextarea ),
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. javaswevent EVT ){
System. Exit (0 );
}
});
// Start several 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 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 ()
} // Leletextarea
The main () method creates a jframe. The jframe contains an consoletextarea instance. There is nothing special about the code. After the frame is displayed, the main () method starts a series of write operation threads, and the write operation thread outputs a large amount of information to the console stream. Leletextarea captures and displays this information, as shown in 1.
Consoletextarea provides two constructors. The constructor without parameters is used to capture and display all the data written to the console stream. An inputstream [] constructor forwards all the data read from each array element to jtextarea. An example will be provided later to show the usefulness of this constructor. First, let's take a look at the leletextarea constructor without parameters. This function first creates a loopedstreams object, and requests the Java Runtime Environment to forward the console output to the outputstream provided by loopedstreams. Finally, the constructor calls startconsolereaderthread (), create a thread that constantly appends text lines to jtextarea. Note: After the text is appended 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. In this example, this means that all operations to append text to jtextarea should be performed in aedt, rather than in the thread created by startconsolereaderthread () method. However, in fact, appending text to jtextarea in swing is a thread-safe operation. After reading a line of text, you only need to call jtext. append () to append the text to the end of jtextarea.
3. Capture console output of other programs
Capturing the console output of Java programs in jtextarea is one thing. Capturing console data of other programs (or even some non-Java programs) is another thing. Consoletextarea provides the basic functions required to capture the output of other applications. appoutputcapture of Listing 6 uses consoletextarea to capture the output information of other applications and then displays it in leletextarea.
[Listing 6: intercepting console output of other programs]
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 ("Usage: Java appoutputcapture" +
"<Program name> {parameter 1 parameter 2 ...}");
System. Exit (0 );
}
Try {
// Start the new process of the specified program on the command line
Process = runtime.getruntime(cmd.exe C (ARGs );
}
Catch (ioexception e ){
System. Err. println ("An error occurred while creating the process.../N" + E );
System. Exit (1 );
}
// Obtain the stream written by the new process
Inputstream [] instreams =
New inputstream [] {
Process. getinputstream (), process. geterrorstream ()};
Consoletextarea CTA = new
Leletextarea (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 (incluwevent EVT ){
Process. Destroy ();
Try {
Process. waitfor (); // It may be suspended under Win98
}
Catch (interruptedexception e ){}
System. Exit (0 );
}
});
} // Main ()
} // Appoutputcapture
The procedure of appoutputcapture is as follows: first, use the runtime.exe C () method to start a new process of the specified program. After starting a new process, obtain its console stream from the result process object. Then, spread these consoles into the leletextarea (inputstream []) constructor (this is the use of the consoletextarea constructor with the parameter ). When using appoutputcapture, specify the program name to be truncated on the command line. For example, if javaw.exe appoutputcapture ping.exe www.yahoo.com is executed under Windows 2000, Result 4 is displayed.
Figure 4: capture the console output of other programs
When using appoutputcapture, note that some text originally output by the intercepted application may not be truncated. There is a short time difference between runtime.exe C () and leletextarea initialization. During this time difference, the text output by the application will be lost. When the appoutputcapture window is closed, process. destory () calls the process created when an attempt is made to close the Java program. The test results show that the destroy () method is not always valid (at least on Windows 98 ). It seems that when the process to be closed starts additional processes, those processes will not be closed. In this case, the appoutputcapture program does not seem to end normally. But in Windows NT, everything works normally. If JDK v1.1.x is used to run appoutputcapture, an nullpointerexception occurs when the window is closed. This is a JDK bug. JDK 1.2.x and JDK 1.3.x do not cause any problems.
Download the complete code of this article: javaconsoleoutput_code.zip
Refer:
Java tip 14: Redirect standard streams in Java
Java tip 33: Redirect streaming again
How to avoid the most common problems in current programming by writing multi-threaded Java applications
Multithreading in Java programs