Name: rmT116609 Date: 08/12/2003
FULL PRODUCT VERSION :
java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)
A DESCRIPTION OF THE PROBLEM :
The javadoc for ByteBuffer.compact() includes this code illustrating the basic
loop for copying bytes from one channel to another.
buf.clear(); // Prepare buffer for use
for (;;) {
if (in.read(buf) < 0 && !buf.hasRemaining())
break; // No more bytes to transfer
buf.flip();
out.write(buf);
buf.compact(); // In case of partial write
}
I believe that the use of hasRemaining() is wrong here. After a read() call, the buffer's position marks the last character read and the limit is set to the capacity, so hasRemaining() will return true unless the buffer is full.
hasRemaining() will only work as intended after the call to flip().
I believe that the code you want instead is buf.position() > 0
REPRODUCIBILITY :
This bug can be reproduced always.
(Incident Review ID: 198738)
======================================================================
Name: rmT116609 Date: 10/15/2003
FULL PRODUCT VERSION :
java version "1.4.2_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_01-b06)
Java HotSpot(TM) Client VM (build 1.4.2_01-b06, mixed mode)
Documentation problem available at:
http://java.sun.com/j2se/1.4.2/docs/api/java/nio/ByteBuffer.html#compact()
FULL OS VERSION :
Microsoft Windows XP [Version 5.1.2600]
(since this is a documentation bug, it applies for all OSes)
A DESCRIPTION OF THE PROBLEM :
The documentation for java.nio.ByteBuffer.compact() provides an an example that copies data from one channel to another:
buf.clear(); // Prepare buffer for use
for (;;) {
if (in.read(buf) < 0 && !buf.hasRemaining())
break; // No more bytes to transfer
buf.flip();
out.write(buf);
buf.compact(); // In case of partial write
}
This example is incorrect and may result in data not being copied to the destination channel. This example is incorrect because it does maintain a proper loop invariant. The !buf.hasRemaining() is determining whether there is no more room in the buffer, ignoring whether there is data in the buffer that still needs to be written.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The following class demonstrates the problem. It constructs a source channel that provides a fixed number of bytes as source data and a destination channel that outputs bytes it receives after a large number of requests to send the data to the output channel. The class then uses three methods to copy the data from the source channel to the destination channel. The first uses the code from the API, the second and third provide correct code to perform the same task with the second mimicing the style of the existing API example, and the third in a style I much prefer.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
All three steps to copy data from the source channel to the destination channel should result in the source data being displayed to the screen.
ACTUAL -
The example provided in the API does not display anything, while the corrected code displays the copied data correctly.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
public class Compact {
public static void main(String[] args) throws IOException {
final int size = 10;
ReadableByteChannel in = new ReadableByteChannel() {
int pos = 0;
public boolean isOpen() { return true; }
public void close() { pos = 0; }
public int read(ByteBuffer buf) {
int count = 0;
while(buf.hasRemaining() && pos < size) {
buf.put((byte)pos);
pos++;
count++;
}
if(pos == size && count == 0) return -1;
return count;
}
};
WritableByteChannel out = new WritableByteChannel() {
int count = 0;
public boolean isOpen() { return true; }
public void close() { }
public int write(ByteBuffer buf) {
count++;
if(count > 1000) {
while(buf.hasRemaining()) {
System.out.print("[" + buf.get() + "]");
}
}
return 0;
}
};
ByteBuffer buf = ByteBuffer.allocate(size);
// according to the documentation in ByteBuffer.compact(), the following
// loop can be used to copy data from the input to the output.
buf.clear(); // Prepare buffer for use
for (;;) {
if (in.read(buf) < 0 && !buf.hasRemaining())
break; // No more bytes to transfer
buf.flip();
out.write(buf);
buf.compact(); // In case of partial write
}
System.out.println();
System.out.println("loop complete");
in.close(); // reset input
buf.clear();
for(;;) {
if(in.read(buf) < 0 && buf.position() == 0) {
break;
}
buf.flip();
out.write(buf);
buf.compact();
}
System.out.println();
System.out.println("second loop complete");
in.close(); // reset input
// I always hated using for(;;) with a break, and consider this much
// cleaner:
buf.clear();
while(in.read(buf) >= 0 || buf.position() != 0) {
buf.flip();
out.write(buf);
buf.compact();
}
System.out.println();
System.out.println("third loop complete");
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use the following instead of the example code:
buf.clear();
while(in.read(buf) >= 0 || buf.position() != 0) {
buf.flip();
out.write(buf);
buf.compact();
}
(Review ID: 215683)
======================================================================