Here’s a functioning class. You can install an instance of this into the system out and err using:
PrintStream con=new PrintStream(new TextAreaOutputStream(...));
System.setOut(con);
System.setErr(con);
Updated 2014-02-19: To use EventQueue.invokeLater() to avoid GUI threading issues which can crop up very rarely with the original.
Updated 2014-02-27: Better implementation
Updated 2014-03-25: Correct recording & deletion of lines in text area to be within the run()
method to avoid race-condition between appending and deleting which can happen if the console is flooded with output. The end result seems cleaner to me, as well.
Updated 2022-11-07: Further improve implementation to completely eliminate problems from flooding with output. Note that this version will outright suppress output (with a one-line note) when it is overwhelmed with output. Previous version would eventually begin GC thrashing and the VM response would largely freeze (without actually crashing) until it finally managed to catch up.
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.util.List;
import javax.swing.*;
public class TextAreaOutputStream
extends OutputStream
{
// *************************************************************************************************
// INSTANCE PROPERTIES
// *************************************************************************************************
private byte[] oneByte; // array for write(int val);
private Appender appender; // most recent action
// *************************************************************************************************
// INSTANCE CONSTRUCTORS/INIT/CLOSE/FINALIZE
// *************************************************************************************************
public TextAreaOutputStream(JTextArea txtara) {
this(txtara,1000);
}
public TextAreaOutputStream(JTextArea txtara, int maxlin) {
this(txtara,maxlin,null);
}
public TextAreaOutputStream(JTextArea txtara, int maxlin, Pattern rmvptn) {
if(maxlin<1) { throw new IllegalArgumentException("TextAreaOutputStream maximum lines must be positive (value="+maxlin+")"); }
oneByte=new byte[1];
appender=new Appender(txtara,maxlin,rmvptn);
}
// *************************************************************************************************
// INSTANCE METHODS - ACCESSORS
// *************************************************************************************************
/** Clear the current console text area. */
public synchronized void clear() {
if(appender!=null) { appender.clear(); }
}
// *************************************************************************************************
// INSTANCE METHODS - OUTPUT STREAM IMPLEMENTATION
// *************************************************************************************************
public synchronized void close() {
appender=null;
}
public synchronized void flush() {
}
public synchronized void write(int val) {
oneByte[0]=(byte)val;
write(oneByte,0,1);
}
public synchronized void write(byte[] ba) {
write(ba,0,ba.length);
}
public synchronized void write(byte[] ba,int str,int len) {
if(appender!=null) { appender.append(bytesToString(ba,str,len)); }
}
// *************************************************************************************************
// INSTANCE METHODS - UTILITY
// *************************************************************************************************
@edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_DEFAULT_ENCODING")
static private String bytesToString(byte[] ba, int str, int len) {
try { return new String(ba,str,len,"UTF-8"); } catch(UnsupportedEncodingException thr) { return new String(ba,str,len); } // all JVMs are required to support UTF-8
}
// *************************************************************************************************
// STATIC NESTED CLASSES
// *************************************************************************************************
static class Appender
implements Runnable
{
private final StringBuilder line = new StringBuilder(1000); // current line being assembled
private final List<String> lines = new ArrayList<String>(); // lines waiting to be appended
private final LinkedList<Integer> lengths = new LinkedList<Integer>(); // lengths of each line within text area
private final JTextArea textArea;
private final int maxLines; // maximum lines allowed in text area
private final Pattern rmvPattern;
private boolean clear;
private boolean queue;
private boolean wrapped;
Appender(JTextArea txtara, int maxlin, Pattern rmvptn) {
textArea = txtara;
maxLines = maxlin;
rmvPattern = rmvptn;
clear = false;
queue = true;
wrapped = false;
}
synchronized void append(String val) {
boolean eol = val.endsWith(EOL1) || val.endsWith(EOL2);
line.append(val);
while(line.length()>LINE_MAX) {
emitLine(line.substring(0,LINE_MAX)+EOL1);
line.replace(0,LINE_MAX,"[>>] ");
}
if(eol) {
emitLine(line.toString());
line.setLength(0);
}
}
private void emitLine(String lin) {
if(lines.size()>10_000) {
lines.clear();
lines.add("<console-overflowed>\n");
}
else {
if(rmvPattern!=null) { lin = rmvPattern.matcher(lin).replaceAll(""); }
lines.add(lin);
}
if(queue) {
queue=false;
EventQueue.invokeLater(this);
}
}
synchronized void clear() {
clear = true;
if(queue) { queue = false; EventQueue.invokeLater(this); }
wrapped = false;
}
// MUST BE THE ONLY METHOD THAT TOUCHES textArea!
public synchronized void run() {
int don = 0;
if(clear) {
lengths . clear();
lines . clear();
textArea . setText("");
clear = false;
}
for(String lin: lines) {
don += 1;
lengths.addLast(lin.length());
if(lengths.size()>=maxLines) { textArea.replaceRange("",0,lengths.removeFirst()); }
textArea.append(lin);
if(don>=100) { break; }
}
if(don==lines.size()) {
lines.clear();
queue = true;
}
else {
lines.subList(0,don).clear();
EventQueue.invokeLater(this);
}
}
static private final String EOL1 = "\n";
static private final String EOL2 = System.getProperty("line.separator",EOL1);
static private final int LINE_MAX = 1000;
}
And here’s a screenshot of it in action: