Skip to content

Commit

Permalink
Fix issues in PumpReader discovered using the unit test
Browse files Browse the repository at this point in the history
- Fix count of read bytes if offset > 0
- Buffer bytes in input stream if necessary (see comment in code)
  • Loading branch information
stephan-gh committed Sep 20, 2017
1 parent bb59951 commit 51c41b6
Showing 1 changed file with 83 additions and 14 deletions.
97 changes: 83 additions & 14 deletions terminal/src/main/java/org/jline/utils/PumpReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,21 +157,25 @@ public synchronized int read() throws IOException {
return b;
}

private int copyChars(char[] cbuf, int off, int len) {
private int copyFromBuffer(char[] cbuf, int off, int len) {
len = Math.min(len, readBuffer.remaining());
readBuffer.get(cbuf, off, len);
return len;
}

@Override
public synchronized int read(char[] cbuf, int off, int len) throws IOException {
if (len == 0) {
return 0;
}

if (!waitForInput()) {
return EOF;
}

int count = copyChars(cbuf, off, len);
int count = copyFromBuffer(cbuf, off, len);
if (rewindReadBuffer() && count < len) {
count += copyChars(cbuf, off + count, len - count);
count += copyFromBuffer(cbuf, off + count, len - count);
rewindReadBuffer();
}

Expand All @@ -180,6 +184,10 @@ public synchronized int read(char[] cbuf, int off, int len) throws IOException {

@Override
public int read(CharBuffer target) throws IOException {
if (!target.hasRemaining()) {
return 0;
}

if (!waitForInput()) {
return EOF;
}
Expand All @@ -193,19 +201,30 @@ public int read(CharBuffer target) throws IOException {
return count;
}

private void encodeBytes(CharsetEncoder encoder, ByteBuffer output) throws IOException {
CoderResult result = encoder.encode(readBuffer, output, false);
if (rewindReadBuffer() && result.isUnderflow()) {
encoder.encode(readBuffer, output, false);
rewindReadBuffer();
}
}

synchronized int readBytes(CharsetEncoder encoder, byte[] b, int off, int len) throws IOException {
if (!waitForInput()) {
return EOF;
return 0;
}

ByteBuffer output = ByteBuffer.wrap(b, off, len);
CoderResult result = encoder.encode(readBuffer, output, false);
if (rewindReadBuffer() && result.isUnderflow()) {
encoder.encode(readBuffer, output, false);
rewindReadBuffer();
encodeBytes(encoder, output);
return output.position() - off;
}

synchronized void readBytes(CharsetEncoder encoder, ByteBuffer output) throws IOException {
if (!waitForInput()) {
return;
}

return output.position();
encodeBytes(encoder, output);
}

synchronized void write(char c) throws IOException {
Expand Down Expand Up @@ -304,28 +323,78 @@ private static class InputStream extends java.io.InputStream {
private final PumpReader reader;
private final CharsetEncoder encoder;

// To encode a character with multiple bytes (e.g. certain Unicode characters)
// we need enough space to encode them. Reading would fail if the read() method
// is used to read a single byte in these cases.
// Use this buffer to ensure we always have enough space to encode a character.
private final ByteBuffer buffer;

private InputStream(PumpReader reader, Charset charset) {
this.reader = reader;
this.encoder = charset.newEncoder()
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(CodingErrorAction.REPLACE);
this.buffer = ByteBuffer.allocate((int) Math.ceil(encoder.maxBytesPerChar()));

// No input available after initialization
buffer.limit(0);
}

@Override
public int available() throws IOException {
return (int) (reader.available() * (double) this.encoder.averageBytesPerChar());
return (int) (reader.available() * (double) this.encoder.averageBytesPerChar()) + buffer.remaining();
}

@Override
public int read() throws IOException {
byte[] buf = new byte[1];
int count = read(buf);
return count == 1 ? buf[0] : EOF;
if (!buffer.hasRemaining() && !readUsingBuffer()) {
return EOF;
}

return buffer.get();
}

private boolean readUsingBuffer() throws IOException {
buffer.clear(); // Reset buffer
reader.readBytes(encoder, buffer);
buffer.flip();
return buffer.hasRemaining();
}

private int copyFromBuffer(byte[] b, int off, int len) {
len = Math.min(len, buffer.remaining());
buffer.get(b, off, len);
return len;
}

@Override
public int read(byte[] b, int off, int len) throws IOException {
return reader.readBytes(this.encoder, b, off, len);
if (len == 0) {
return 0;
}

int read;
if (buffer.hasRemaining()) {
read = copyFromBuffer(b, off, len);
if (read == len) {
return len;
}

off += read;
len -= read;
} else {
read = 0;
}

// Do we have enough space to avoid buffering?
if (len >= buffer.capacity()) {
read += reader.readBytes(this.encoder, b, off, len);
} else if (readUsingBuffer()) {
read += copyFromBuffer(b, off, len);
}

// Return EOF if we didn't read any bytes
return read == 0 ? EOF : read;
}

@Override
Expand Down

0 comments on commit 51c41b6

Please sign in to comment.