Trouble with custom InputStream being used by Readers

  • Thread starter Chase Preuninger
  • Start date
C

Chase Preuninger

For some reason the following InputStream gives subclasses of Reader a
hard time because they seem to be unable to read the data. What is
wrong with my Stream. Ex. With the BufferedReader class when I call
the readLine() method it blocks forever even though there are a couple
of \n in the data that is being outputted. Also I know my stream
works because the read() method gives me an int which can be cast to a
char showing the text that the stream contains.

package com.cpsoft.console;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;

public class ConsoleInputStream extends InputStream
{
private StringBuffer buf = new StringBuffer();
private int pos = 0;
private JConsole con;
public ConsoleInputStream(JConsole c)
{
this.con = c;
synchronized(con)
{
EnterAction act = new EnterAction();
con.input.addActionListener(act);
con.enter.addActionListener(act);
}
}
public int read() throws IOException
{
while(buf.length() <= pos){}
return buf.charAt(pos++);
}
private class EnterAction implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
synchronized(con)
{
buf.append(con.input.getText() + "\n");
}
}
}
}
 
L

Lew

Peter Duniho said:
Just the code you posted looks a bit suspicious anyway, since there  
doesn't appear to be any thread synchronization or upper bound on the size  
of the buffer (just for starters).  

From the StringBuffer API docs:
I am suspicious of the Javadocs' claim that the class is "thread-
safe", though.
But at the very least, you can't block your EDT with a read from this stream
if you are depending on the EDT to provide input to the stream.

That is an issue.
You really ought to post a SSCCE though.

Amen.
 
D

Daniele Futtorovic

For some reason the following InputStream gives subclasses of Reader a
hard time because they seem to be unable to read the data. What is
wrong with my Stream. Ex. With the BufferedReader class when I call
the readLine() method it blocks forever even though there are a couple
of \n in the data that is being outputted. Also I know my stream
works because the read() method gives me an int which can be cast to a
char showing the text that the stream contains.

1. No need to make that class an InputStream -- make it a Reader;

2. A BufferedReader... buffers. He'll fill his buffer. If all to calls
to your InputStream block (because you only override read()), he won't
ever finish filling up if his internal buffer is larger than the input
(speculation).

3. Whatever JConsole is. (There isn't even an import statement for it --
or is it part of com.cpsoft.console)?

4. Heed Peter and Lew's advices.

5. The following is far from being perfect, but try if it works. As a
general rule, when writing InputStreams or Readers, always try to
override <int read(byte[], int, int)> or <int read(char[], int, int)>,
respectively.

<code imports_omitted="true">
public class ConsoleReader
extends Reader
{

private StringBuffer sbuf = new StringBuffer(1 << 5);
private JConsole con;
private final Object lock = new Object();

private boolean closed = false;

public ConsoleReader(JConsole c)
{
this.con = c;
synchronized(con)
{
EnterAction act = new EnterAction();
con.input.addActionListener(act);
con.enter.addActionListener(act);
}
}

/**
* Reads characters into a portion of an array. This method will block
* until some input is available, an I/O error occurs, or the end
of the
* stream is reached.
*
* @param cbuf Destination buffer
* @param off Offset at which to start storing characters
* @param len Maximum number of characters to read
*
* @return The number of characters read, or -1 if the end of the
* stream has been reached
*
* @exception IOException If an I/O error occurs
*/
public synchronized int read(char[] buf, int dest, int len)
throws IOException
{
if( closed ){
throw new IOException("Console closed");
}

while( sbuf.length() == 0 ){
synchronized (lock){
try{
lock.wait();
}
catch (InterruptedException x){
if( closed ){
return -1;
}
}
}
}

int rlen = Math.min(len, sbuf.length());
sbuf.getChars(0, rlen, buf, dest); //TODO: check dest + len <
buf.length?
sbuf.delete(0, rlen);

return rlen;
}

public void close(){
// 1. dispose of the console
// ...
closed = true;

// 2. notify any thread blocked on a read()
synchronized (lock){
lock.notifyAll();
}
}

private class EnterAction implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
synchronized(con) { //??
sbuf.append(con.input.getText()).append("\n");
}
synchronized (lock){
lock.notifyAll();
}
}
}
}
 
C

Chase Preuninger

Well how does any other console stop blocking so that a BufferedReader
can stop buffering? Does it just return -1 when it has more data.
But what if it acquires more later on?
 
C

Chase Preuninger

Using a reader may work but I also want to be able to redirect
System.in to use my InputStream.
 
D

Daniele Futtorovic

Well how does any other console stop blocking so that a BufferedReader
can stop buffering? Does it just return -1 when it has more data.
But what if it acquires more later on?

I'm sorry but I don't understand what you're saying.
 
D

Daniele Futtorovic

Using a reader may work but I also want to be able to redirect
System.in to use my InputStream.

But then you cannot simply return a char in read()! On an InputStream,
read() is supposed to return a byte value. It won't work unless a) all
your character data is in the ASCII range, and b) you specify US-ASCII
(or something like that?) on the eventual InputStreamReader -- I doubt
they'll except that encoding by themselves.
 
T

Tom Anderson

Well how does any other console stop blocking so that a BufferedReader
can stop buffering? Does it just return -1 when it has more data. But
what if it acquires more later on?

I suspect, but am not certain, that Daniele is wrong about
BufferedReader's behaviour.

Also, if you could quote the post you're replying to, that would help
those of us not using Google Groups etc immensely. Thanks,

Anyway, the two problems i see in your code are the busy-wait in read()
and the fact that you're piling up an ever-increasing amount of chars in
your buffer (including all the chars that have been typed in and then
read, and which will never be looked at again).

My strategy would be not to write a custom stream class at all. Instead,
use PipedInputStream and PipedOutputStream. Wrap the PipedOutputStream in
a Writer, then write an ActionListener that handles events by writing the
text to the writer. Like so:

public class ConsoleUtil {
public static InputStream getInputStream(JConsole console) {
OutputStream out = new PipedOutputStream() ;
String charset = you figure this out ;
final Writer wout = new OutputStreamWriter(out, charset) ;
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
// is this really right?
wout.write(con.input.getText()) ;
wout.write('\n') ;
}
}
console.input.addActionListener(al) ;
console.enter.addActionListener(al) ;
return new PipedInputStream(out) ;
}
}

tom

--
Formal logical proofs, and therefore programs - formal logical proofs
that particular computations are possible, expressed in a formal system
called a programming language - are utterly meaningless. To write a
computer program you have to come to terms with this, to accept that
whatever you might want the program to mean, the machine will blindly
follow its meaningless rules and come to some meaningless conclusion. --
Dehnadi and Bornat
 
D

Daniele Futtorovic

I suspect, but am not certain, that Daniele is wrong about
BufferedReader's behaviour.

Might always be the case.

But:

private static class BogusInputStream
extends InputStream
{
private final int size;
private int pos;

public BogusInputStream(int size){
this.size = size;
}

public int read()
throws IOException
{
if( pos < size ){
pos++; return 'a';
}
else{
for(;;);
}
}
}

public static void main(String[] ss)
throws Exception
{
Reader r1 = new InputStreamReader(new BogusInputStream(1<<13));
Reader r2 = new InputStreamReader(new BogusInputStream(1<<12));

r1.read(); // <- works

r2.read(); // <- blocks
}

From which we can infer that on my machine, the sun.nio.cs.StreamDecoder
has an 8k buffer.

Granted, that's not the BufferedReader, but I think it's pretty much a
given that that infinite loop is the root of the problem. That, and not
overriding int read(byte[], int, int) or int read(char[], int, int).
 

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,981
Messages
2,570,188
Members
46,731
Latest member
MarcyGipso

Latest Threads

Top