Inspired in this answer I came up with a bit more object-oriented solution.
This is only valid if you're intending to read characters
You can override BufferedReader and implement something like this:
public class SafeBufferedReader extends BufferedReader{
private long millisTimeout;
( . . . )
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
try {
waitReady();
} catch(IllegalThreadStateException e) {
return 0;
}
return super.read(cbuf, off, len);
}
protected void waitReady() throws IllegalThreadStateException, IOException {
if(ready()) return;
long timeout = System.currentTimeMillis() + millisTimeout;
while(System.currentTimeMillis() < timeout) {
if(ready()) return;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break; // Should restore flag
}
}
if(ready()) return; // Just in case.
throw new IllegalThreadStateException("Read timed out");
}
}
Here's an almost complete example.
I'm returning 0 on some methods, you should change it to -2 to meet your needs, but I think that 0 is more suitable with BufferedReader contract. Nothing wrong happened, it just read 0 chars. readLine method is a horrible performance killer. You should create a entirely new BufferedReader if you actually want to use readLine. Right now, it is not thread safe. If someone invokes an operation while readLines is waiting for a line, it will produce unexpected results
I don't like returning -2 where I am. I'd throw an exception because some people may just be checking if int < 0 to consider EOS. Anyway, those methods claim that "can't block", you should check if that statement is actually true and just don't override'em.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/**
*
* readLine
*
* @author Dario
*
*/
public class SafeBufferedReader extends BufferedReader{
private long millisTimeout;
private long millisInterval = 100;
private int lookAheadLine;
public SafeBufferedReader(Reader in, int sz, long millisTimeout) {
super(in, sz);
this.millisTimeout = millisTimeout;
}
public SafeBufferedReader(Reader in, long millisTimeout) {
super(in);
this.millisTimeout = millisTimeout;
}
/**
* This is probably going to kill readLine performance. You should study BufferedReader and completly override the method.
*
* It should mark the position, then perform its normal operation in a nonblocking way, and if it reaches the timeout then reset position and throw IllegalThreadStateException
*
*/
@Override
public String readLine() throws IOException {
try {
waitReadyLine();
} catch(IllegalThreadStateException e) {
//return null; //Null usually means EOS here, so we can't.
throw e;
}
return super.readLine();
}
@Override
public int read() throws IOException {
try {
waitReady();
} catch(IllegalThreadStateException e) {
return -2; // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
}
return super.read();
}
@Override
public int read(char[] cbuf) throws IOException {
try {
waitReady();
} catch(IllegalThreadStateException e) {
return -2; // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
}
return super.read(cbuf);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
try {
waitReady();
} catch(IllegalThreadStateException e) {
return 0;
}
return super.read(cbuf, off, len);
}
@Override
public int read(CharBuffer target) throws IOException {
try {
waitReady();
} catch(IllegalThreadStateException e) {
return 0;
}
return super.read(target);
}
@Override
public void mark(int readAheadLimit) throws IOException {
super.mark(readAheadLimit);
}
@Override
public Stream<String> lines() {
return super.lines();
}
@Override
public void reset() throws IOException {
super.reset();
}
@Override
public long skip(long n) throws IOException {
return super.skip(n);
}
public long getMillisTimeout() {
return millisTimeout;
}
public void setMillisTimeout(long millisTimeout) {
this.millisTimeout = millisTimeout;
}
public void setTimeout(long timeout, TimeUnit unit) {
this.millisTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
}
public long getMillisInterval() {
return millisInterval;
}
public void setMillisInterval(long millisInterval) {
this.millisInterval = millisInterval;
}
public void setInterval(long time, TimeUnit unit) {
this.millisInterval = TimeUnit.MILLISECONDS.convert(time, unit);
}
/**
* This is actually forcing us to read the buffer twice in order to determine a line is actually ready.
*
* @throws IllegalThreadStateException
* @throws IOException
*/
protected void waitReadyLine() throws IllegalThreadStateException, IOException {
long timeout = System.currentTimeMillis() + millisTimeout;
waitReady();
super.mark(lookAheadLine);
try {
while(System.currentTimeMillis() < timeout) {
while(ready()) {
int charInt = super.read();
if(charInt==-1) return; // EOS reached
char character = (char) charInt;
if(character == '\n' || character == '\r' ) return;
}
try {
Thread.sleep(millisInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore flag
break;
}
}
} finally {
super.reset();
}
throw new IllegalThreadStateException("readLine timed out");
}
protected void waitReady() throws IllegalThreadStateException, IOException {
if(ready()) return;
long timeout = System.currentTimeMillis() + millisTimeout;
while(System.currentTimeMillis() < timeout) {
if(ready()) return;
try {
Thread.sleep(millisInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore flag
break;
}
}
if(ready()) return; // Just in case.
throw new IllegalThreadStateException("read timed out");
}
}