Here's a class for streaming to a TextArea. Comments?

F

Felix Dejavu

Hello y'all,

Here's a little applet that streams to a TextArea control. The important
class is TextAreaOutputStream which allows a TextArea to be written to
like a stream. You can see that it's useful in adapting a console oriented
program to an applet, or for displaying debugging messages.

Please tell me what you think about it. Am I reinventing the wheel? Is
there an better or established way to stream to TextArea's? Thanks for
your input.
-fd


---- Start StreamToTextArea.java ----

import java.applet.*;
import java.awt.*;s
import java.io.*;
import java.net.*;

class TextAreaOutputStream extends OutputStream {
TextArea ta;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
synchronized(this) {
ta.append(new String(b, off, len));
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}

class QuickAndDirtyPipe implements Runnable {
private InputStream i;
private OutputStream o;
public QuickAndDirtyPipe(InputStream i, OutputStream o, boolean
block) throws IOException {
this.i = i;
this.o = o;
if(block) pipeData();
else new Thread(this).start();
}
public void run() {
try {
pipeData();
} catch(IOException ex) {
throw new Error(ex);
}
}
public void pipeData() throws IOException {
int n;
byte[] b = new byte[2048];
while(true) {
n = i.read(b);
if(n == -1) break;
o.write(b, 0, n);
}
}
}

// Make our own stderr because unsigned Applets can't redirect System.err
class Debug {
public static PrintStream out = System.err;
public static void redirect(OutputStream o) {
out = new PrintStream(o);
}
}

public class StreamToTextAreaTest extends Applet {
// can't use this example because unsigned Applets can't make
connections to 3rd party
public static void getPage(OutputStream dst) {
Debug.out.println("\n---- Getting Page ----");
try {
Socket s = new Socket("www.google.com", 80);
s.getOutputStream().write(new String("GET /
HTTP/1.0\r\n\r\n").getBytes("US-ASCII"));
new QuickAndDirtyPipe(s.getInputStream(), dst,
true);
s.close();
} catch(IOException ex) {
throw new Error(ex);
}
Debug.out.println("\n---- Page Complete ----");
}
public static void getFakePage(OutputStream dst) {
String fakePage =
"<html>\r\n" +
"<head>\r\n" +
"<title>My Fake Page</title>\r\n" +
"</head>\r\n" +
"<body>\r\n" +
"<h1>This is my fake page.</h1>\r\n" +
"</body>\r\n" +
"</html>";
Debug.out.println("\n---- Getting Page ----");
try {
ByteArrayInputStream src = new
ByteArrayInputStream(new String(fakePage).getBytes("US-ASCII"));
new QuickAndDirtyPipe(src, dst, true);
} catch(Exception ex) {
throw new Error(ex);
}
Debug.out.println("\n---- Page Complete ----");
}
public void init() {
setLayout(new GridLayout());
TextArea ta = new TextArea();
add(ta);
validate();

TextAreaOutputStream tout = new TextAreaOutputStream(ta);
Debug.redirect(tout);
getFakePage(tout);
}
public static void main(String[] args) {
getPage(System.out);
}
}

---- End of File ----
 
A

Anton Spaans

One comment:

In your TextAreaOutputStream class, i would only physically update the
TextArea upon a flush() or close(). This is for performance reasons. To do
this, embed a StringWriter int the TextAreaOutputStream (btw.
JTextArea.append() is threadsafe already, if you can use swing instead of
awt).

public TextAreaOutputStream(JTextArea ta) {
this.ta = ta;
this.buf = new StringWriter();
}

public void write(char cbuf[], int off, int len) throws IOException
{
buf.write(cbuf, off, len);
}

public void flush() throws IOException
{
buf.flush();
ta.append(buf.toString());
buf.getBuffer().setLength(0);
}

public void close() throws IOException
{
buf.flush();
ta.append(buf.toString());
buf = null;
}

protected void finalize() throws Throwable
{
buf.flush();
ta.append(buf.toString());
}

-- Anton Spaans

Felix Dejavu said:
Hello y'all,

Here's a little applet that streams to a TextArea control. The important
class is TextAreaOutputStream which allows a TextArea to be written to
like a stream. You can see that it's useful in adapting a console oriented
program to an applet, or for displaying debugging messages.

Please tell me what you think about it. Am I reinventing the wheel? Is
there an better or established way to stream to TextArea's? Thanks for
your input.
-fd


---- Start StreamToTextArea.java ----

import java.applet.*;
import java.awt.*;s
import java.io.*;
import java.net.*;

class TextAreaOutputStream extends OutputStream {
TextArea ta;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
synchronized(this) {
ta.append(new String(b, off, len));
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}

class QuickAndDirtyPipe implements Runnable {
private InputStream i;
private OutputStream o;
public QuickAndDirtyPipe(InputStream i, OutputStream o, boolean
block) throws IOException {
this.i = i;
this.o = o;
if(block) pipeData();
else new Thread(this).start();
}
public void run() {
try {
pipeData();
} catch(IOException ex) {
throw new Error(ex);
}
}
public void pipeData() throws IOException {
int n;
byte[] b = new byte[2048];
while(true) {
n = i.read(b);
if(n == -1) break;
o.write(b, 0, n);
}
}
}

// Make our own stderr because unsigned Applets can't redirect System.err
class Debug {
public static PrintStream out = System.err;
public static void redirect(OutputStream o) {
out = new PrintStream(o);
}
}

public class StreamToTextAreaTest extends Applet {
// can't use this example because unsigned Applets can't make
connections to 3rd party
public static void getPage(OutputStream dst) {
Debug.out.println("\n---- Getting Page ----");
try {
Socket s = new Socket("www.google.com", 80);
s.getOutputStream().write(new String("GET /
HTTP/1.0\r\n\r\n").getBytes("US-ASCII"));
new QuickAndDirtyPipe(s.getInputStream(), dst,
true);
s.close();
} catch(IOException ex) {
throw new Error(ex);
}
Debug.out.println("\n---- Page Complete ----");
}
public static void getFakePage(OutputStream dst) {
String fakePage =
"<html>\r\n" +
"<head>\r\n" +
"<title>My Fake Page</title>\r\n" +
"</head>\r\n" +
"<body>\r\n" +
"<h1>This is my fake page.</h1>\r\n" +
"</body>\r\n" +
"</html>";
Debug.out.println("\n---- Getting Page ----");
try {
ByteArrayInputStream src = new
ByteArrayInputStream(new String(fakePage).getBytes("US-ASCII"));
new QuickAndDirtyPipe(src, dst, true);
} catch(Exception ex) {
throw new Error(ex);
}
Debug.out.println("\n---- Page Complete ----");
}
public void init() {
setLayout(new GridLayout());
TextArea ta = new TextArea();
add(ta);
validate();

TextAreaOutputStream tout = new TextAreaOutputStream(ta);
Debug.redirect(tout);
getFakePage(tout);
}
public static void main(String[] args) {
getPage(System.out);
}
}

---- End of File ----
 
C

Chris Smith

Felix said:
Please tell me what you think about it.
Okay.

class TextAreaOutputStream extends OutputStream {
TextArea ta;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
synchronized(this) {
ta.append(new String(b, off, len));
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}

This is pretty workable, I think. The one problem is that text area
deal with characters and an InputStream deals with bytes, and you're
assuming the default encoding in the translation. I'd provide an
alternate constructor that allows the user to specify the text encoding
of the incoming stream.
class QuickAndDirtyPipe implements Runnable {
private InputStream i;
private OutputStream o;
public QuickAndDirtyPipe(InputStream i, OutputStream o, boolean
block) throws IOException {
this.i = i;
this.o = o;
if(block) pipeData();
else new Thread(this).start();
}

This is extending way beyond the abstraction. QuickAndDirtyPipe is a
Runnable, which means that it represents a task to be performed, and not
the means of accomplishing that task. Whether the task is performed in
a new thread, in an existing one, scheduled in a thread pool, or
whatever can be decided in the client code. If you want to write a
class that makes such a decision, then by all means go for it, but that
class shouldn't implement Runnable; it should use another class
(implementing Runnable) that defines the actual task to perform, or at
least encapsulate the Runnable implementation into a non-exposed inner
class somewhere.
public void run() {
try {
pipeData();
} catch(IOException ex) {
throw new Error(ex);
}
}

Throwing an Error from your own code is abusing the purpose of that
class. If you want to throw an unchecked Throwable, then use
RuntimeException or define a subclass thereof. Same thing when you
repeat the process later.
public void init() {
setLayout(new GridLayout());
TextArea ta = new TextArea();
add(ta);
validate();

TextAreaOutputStream tout = new TextAreaOutputStream(ta);
Debug.redirect(tout);
getFakePage(tout);
}

The last half of that should be triggered by start(), not init(), and
you should account for stop() and a second start() by quitting your
activity.

--
www.designacourse.com
The Easiest Way to Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
F

Felix Dejavu

One comment:

In your TextAreaOutputStream class, i would only physically update the
TextArea upon a flush() or close(). This is for performance reasons.
To do this, embed a StringWriter int the TextAreaOutputStream (btw.
JTextArea.append() is threadsafe already, if you can use swing instead
of awt).

My thought was to do it raw and to allow the programmer to add buffering
via encapsulation in a BufferedOutputStream.

OutputStream o = new BufferedOutputStream(new TextAreaOutputStream(ta);

That's the approach Sun seems to use. Is there an advantage to buffering
myself? Sometimes (like for stderr) I want responsiveness more than
efficiency. If I buffered internally I guess I could wrap it with an
OutputStream that flushed on every write (such as PrintStream or this):

class DebufferedOutputStream extends FilterOutputStream {
public DebufferedOutputStream(OutputStream out) {
super(out);
}
public void write(byte[] b) throws IOException {
super.write(b);
flush();
}
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
flush();
}
public void write(int b) throws IOException {
super.write(b);
flush();
}
}

I dunno. Is it better practice to buffer whenever possible and leave it
to the programmer to flush manually, or to do direct i/o whenever possible
and leave it to the programmer to buffer manually?

I figure the more hidden buffers you have, the more times the data must
be copied, and the end programmer is the only one who really knows where
the buffer, if any, should be...

Considering all this, I guess I should flush at the end of my
QuickAndDirtyPipe.

-fd
 
F

Felix Dejavu

Felix said:
Please tell me what you think about it.
Okay.

class TextAreaOutputStream extends OutputStream {
TextArea ta;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
synchronized(this) {
ta.append(new String(b, off, len));
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}

Thanks! Your input is very insightful. Here is my revised class:

import java.awt.*;
import java.io.*;
import java.nio.charset.*;

class TextAreaOutputStream extends OutputStream {
TextArea ta;
String charset;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
charset = null;
}
public TextAreaOutputStream(TextArea ta, String charset) throws
UnsupportedCharsetException {
this(ta);
// force exception on unsupported charset now to avoid
exceptions in write()
Charset.forName(charset);
this.charset = charset;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
// XXX TextArea.append() is not threadsafe. Use JTextArea.
String s;
try {
if(charset == null) s = new String(b, off, len);
else s = new String(b, off, len, charset);
} catch(UnsupportedEncodingException ex) {
throw new Error("encoding support was already
verified", ex);
}
synchronized(ta) {
ta.append(s);
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}
This is pretty workable, I think. The one problem is that text area
deal with characters and an InputStream deals with bytes, and you're
assuming the default encoding in the translation. I'd provide an
alternate constructor that allows the user to specify the text encoding
of the incoming stream.


This is extending way beyond the abstraction. QuickAndDirtyPipe is a
Runnable, which means that it represents a task to be performed, and not
the means of accomplishing that task. Whether the task is performed in
a new thread, in an existing one, scheduled in a thread pool, or
whatever can be decided in the client code. If you want to write a
class that makes such a decision, then by all means go for it, but that
class shouldn't implement Runnable; it should use another class
(implementing Runnable) that defines the actual task to perform, or at
least encapsulate the Runnable implementation into a non-exposed inner
class somewhere.

Alright. Is this more like it?

import java.io.*;

class QuickerPipe {
public QuickerPipe(InputStream in, OutputStream out, boolean
block) throws IOException {
if(block) (new QuickPipe(in, out)).run();
else new Thread(new QuickPipe(in, out)).start();
}
}

class QuickPipe implements Runnable {
protected InputStream in;
protected OutputStream out;
public QuickPipe(InputStream in, OutputStream out) throws
IOException {
this.in = in;
this.out = out;
}
public void run() {
try {
int n;
byte[] buf = new byte[2048];
while(true) {
n = in.read(buf);
if(n == -1) break;
out.write(buf, 0, n);
}
out.flush();
} catch(IOException ex) {
throw new RuntimeException(ex);
}
}
}

Throwing an Error from your own code is abusing the purpose of that
class. If you want to throw an unchecked Throwable, then use
RuntimeException or define a subclass thereof. Same thing when you
repeat the process later.


The last half of that should be triggered by start(), not init(), and
you should account for stop() and a second start() by quitting your
activity.

Thanks for your help,

-fd
 
B

Bryan E. Boone

I've had better luck creating an "OutputStreamDocument".
That is, create an extension of an OutputStream that writes
to a Document when the writes are called. The class, of course,
has a "getDocument". This way I can display the same document
in multiple text components.

-Bryan

Felix Dejavu said:
Felix said:
Please tell me what you think about it.
Okay.

class TextAreaOutputStream extends OutputStream {
TextArea ta;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
synchronized(this) {
ta.append(new String(b, off, len));
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}

Thanks! Your input is very insightful. Here is my revised class:

import java.awt.*;
import java.io.*;
import java.nio.charset.*;

class TextAreaOutputStream extends OutputStream {
TextArea ta;
String charset;
public TextAreaOutputStream(TextArea ta) {
this.ta = ta;
charset = null;
}
public TextAreaOutputStream(TextArea ta, String charset) throws
UnsupportedCharsetException {
this(ta);
// force exception on unsupported charset now to avoid
exceptions in write()
Charset.forName(charset);
this.charset = charset;
}
public void write(byte[] b) {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) {
// XXX TextArea.append() is not threadsafe. Use JTextArea.
String s;
try {
if(charset == null) s = new String(b, off, len);
else s = new String(b, off, len, charset);
} catch(UnsupportedEncodingException ex) {
throw new Error("encoding support was already
verified", ex);
}
synchronized(ta) {
ta.append(s);
}
}
public void write(int b) {
byte[] tmp = {(byte)b};
write(tmp);
}
}
This is pretty workable, I think. The one problem is that text area
deal with characters and an InputStream deals with bytes, and you're
assuming the default encoding in the translation. I'd provide an
alternate constructor that allows the user to specify the text encoding
of the incoming stream.


This is extending way beyond the abstraction. QuickAndDirtyPipe is a
Runnable, which means that it represents a task to be performed, and not
the means of accomplishing that task. Whether the task is performed in
a new thread, in an existing one, scheduled in a thread pool, or
whatever can be decided in the client code. If you want to write a
class that makes such a decision, then by all means go for it, but that
class shouldn't implement Runnable; it should use another class
(implementing Runnable) that defines the actual task to perform, or at
least encapsulate the Runnable implementation into a non-exposed inner
class somewhere.

Alright. Is this more like it?

import java.io.*;

class QuickerPipe {
public QuickerPipe(InputStream in, OutputStream out, boolean
block) throws IOException {
if(block) (new QuickPipe(in, out)).run();
else new Thread(new QuickPipe(in, out)).start();
}
}

class QuickPipe implements Runnable {
protected InputStream in;
protected OutputStream out;
public QuickPipe(InputStream in, OutputStream out) throws
IOException {
this.in = in;
this.out = out;
}
public void run() {
try {
int n;
byte[] buf = new byte[2048];
while(true) {
n = in.read(buf);
if(n == -1) break;
out.write(buf, 0, n);
}
out.flush();
} catch(IOException ex) {
throw new RuntimeException(ex);
}
}
}

Throwing an Error from your own code is abusing the purpose of that
class. If you want to throw an unchecked Throwable, then use
RuntimeException or define a subclass thereof. Same thing when you
repeat the process later.


The last half of that should be triggered by start(), not init(), and
you should account for stop() and a second start() by quitting your
activity.

Thanks for your help,

-fd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
DewittMill
Top