5
\$\begingroup\$

I neglected java.nio package in the past and now I am wondering if this is the best way to copy everything from a Readable to an OutputStream:

public static void copy(@Nonnull Readable src, @WillNotClose @Nonnull OutputStream dest, @Nullable Charset targetCharset) throws IOException {
    targetCharset = safeCharset(targetCharset);
    CharBuffer buf = CharBuffer.allocate(CHAR_BUF_SIZE);
    ByteBuffer byteBuffer = null;
    WritableByteChannel outChannel = Channels.newChannel(dest);

    while (src.read(buf) != -1) {
      buf.flip();
      byteBuffer = targetCharset.encode(buf);
      outChannel.write(byteBuffer);
      buf.clear();
    }
}

General

Is this the best way to do it? Am I missing something? What should I do to improve this code?

Closing the channel or not

I wonder if I must/should close the channel created by Channels.newChannel(dest)? From the general contract of my class the caller of this method here is responsible for closing the provided OutputStream, because every resource should be closed where it was created. It's bad that closing the channel also closes the underlying stream, too.

On the other hand not closing the channel relies on undocumented implementation details of Channels$WritableByteChannelImpl where calling close() is just a delegate to the underlying stream and setting a close flag.

I do not want to break my general contract and I do not want to rely on undocumented implementation details.

The third option is most probably the best and addressing the source of the problem: Write the content directly to the OutputStream without using a channel or any other wrapper around it which must be closed then. But I could not figure out how to achieve this.

\$\endgroup\$
2
  • \$\begingroup\$ It seems you forgot .flush() - have a look in new Java7 package nio.2 : docs.oracle.com/javase/tutorial/essential/io/fileio.html, very useful when Java7 will be in production state \$\endgroup\$ – cl-r Aug 24 '12 at 7:53
  • \$\begingroup\$ Thanks for the link. I'll look into it right now. I do not forgot flush - it's not my job to flush here. I should have mentioned that this is a utility method like the ones you find in Apache IOUtils or Google Guava Files, CharStreams and ByteStreams classes. Callers of these methods expect the provided streams to be not flushed and not closed. It's caller's responsibility. \$\endgroup\$ – Fabian Barney Aug 24 '12 at 8:44
1
\$\begingroup\$

Well, last night I came up with that. I am pretty satisfied with it right now but would be happy to see if I am still missing something.

This solution does not rely on implementation details any more and provided streams are left open as the general contract demands it.

public static void copy(@Nonnull Readable src, @Nonnull @WillNotClose OutputStream dest, @Nullable Charset destCharset) throws IOException {
    CharBuffer charBuf = CharBuffer.allocate(CHAR_BUF_SIZE);

    while (src.read(charBuf) != -1) {
      charBuf.flip();
      copy(charBuf, dest, destCharset);
      charBuf.clear();
    }
}


public static void copy(@Nonnull CharBuffer src, @Nonnull @WillNotClose OutputStream dest, @Nullable Charset destCharset) throws IOException {
    copy(safeCharset(destCharset).encode(src), dest);
}


public static void copy(@Nonnull ByteBuffer src, @Nonnull @WillNotClose OutputStream dest) throws IOException {
    int len = src.remaining();
    int totalWritten = 0;
    byte[] buf = new byte[0];

    while (totalWritten < len) {
        int bytesToWrite = Math.min((len - totalWritten), BYTE_BUF_SIZE);

        if (buf.length < bytesToWrite) {
            buf = new byte[bytesToWrite];
        }

        src.get(buf, 0, bytesToWrite);
        dest.write(buf, 0, bytesToWrite);
        totalWritten += bytesToWrite;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.