de.schlichtherle.truezip.io.SeekableByteBufferChannel Maven / Gradle / Ivy
/*
* Copyright (C) 2005-2015 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package de.schlichtherle.truezip.io;
import edu.umd.cs.findbugs.annotations.CleanupObligation;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import edu.umd.cs.findbugs.annotations.DischargesObligation;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Adapts a {@linkplain ByteBuffer byte buffer} to a seekable byte channel.
*
* @author Christian Schlichtherle
*/
@NotThreadSafe
@CleanupObligation
public class SeekableByteBufferChannel implements SeekableByteChannel {
private ByteBuffer buffer;
/**
* The position of this channel.
* Note that {@code buffer.position() can't get used.
*
* @see SeekableByteChannel#position(long)
*/
private long position;
private boolean closed;
/**
* Constructs a new seekable byte buffer channel with a
* {@linkplain ByteBuffer#duplicate() duplicate} of the given byte buffer
* as its initial {@linkplain #getByteBuffer() byte buffer}.
* Note that the buffer contents are shared between the client application
* and this class.
*
* @param buffer the initial byte buffer to read or write.
*/
@CreatesObligation
public SeekableByteBufferChannel(final ByteBuffer buffer) {
this.buffer = (ByteBuffer) buffer.duplicate().rewind();
}
/**
* Returns a {@linkplain ByteBuffer#duplicate() duplicate} of the backing
* byte buffer.
* Note that the buffer contents are shared between the client application
* and this class.
*
* @return A {@linkplain ByteBuffer#duplicate() duplicate} of the backing
* byte buffer.
*/
public ByteBuffer getByteBuffer() {
return buffer.duplicate();
}
private void checkOpen() throws ClosedChannelException {
if (!isOpen()) throw new ClosedChannelException();
}
@Override
public final int read(final ByteBuffer dst) throws IOException {
checkOpen();
int remaining = dst.remaining();
if (remaining <= 0) return 0;
final long oldPosition = this.position;
final ByteBuffer buffer = this.buffer;
if (oldPosition >= buffer.limit()) return -1;
buffer.position((int) oldPosition);
final int available = buffer.remaining();
final int srcLimit;
if (available > remaining) {
srcLimit = buffer.limit();
buffer.limit(buffer.position() + remaining);
} else {
srcLimit = -1;
remaining = available;
}
try {
dst.put(buffer);
} finally {
if (0 <= srcLimit) buffer.limit(srcLimit);
}
assert buffer.position() == oldPosition + remaining;
this.position += remaining;
return remaining;
}
@Override
public final int write(final ByteBuffer src) throws IOException {
checkOpen();
if (this.position > Integer.MAX_VALUE) throw new OutOfMemoryError();
final int oldPosition = (int) this.position;
final int remaining = src.remaining();
final int newPosition = oldPosition + remaining; // may overflow!
ByteBuffer buffer = this.buffer;
final int oldLimit = buffer.limit();
if (0 > oldLimit - newPosition) { // mind overflow!
final int oldCapacity = buffer.capacity();
if (0 <= oldCapacity - newPosition) { // mind overflow!
buffer.limit(newPosition).position(oldPosition);
} else if (0 > newPosition) {
throw new OutOfMemoryError();
} else {
if (buffer.isReadOnly())
throw new NonWritableChannelException();
int newCapacity = oldCapacity << 1;
if (0 > newCapacity - newPosition) newCapacity = newPosition;
if (0 > newCapacity) newCapacity = Integer.MAX_VALUE;
assert newPosition <= newCapacity;
this.buffer = buffer = (ByteBuffer) (buffer.isDirect()
? ByteBuffer.allocateDirect((int) newCapacity)
: ByteBuffer.allocate((int) newCapacity))
.put((ByteBuffer) buffer.position(0).limit(oldPosition))
.limit(newPosition);
}
} else {
buffer.position(oldPosition);
}
assert buffer.position() == oldPosition;
try {
buffer.put(src);
} catch (final ReadOnlyBufferException ex) {
throw new NonWritableChannelException();
}
assert buffer.position() == newPosition;
this.position = newPosition;
return remaining;
}
@Override
public final long position() throws IOException {
checkOpen();
return position;
}
@Override
public final SeekableByteBufferChannel position(long newPosition)
throws IOException {
checkOpen();
if (0 > newPosition) throw new IllegalArgumentException();
this.position = newPosition;
return this;
}
@Override
public final long size() throws IOException {
checkOpen();
return buffer.limit();
}
@Override
public final SeekableByteBufferChannel truncate(final long size)
throws IOException {
checkOpen();
if (buffer.isReadOnly()) throw new NonWritableChannelException();
if (buffer.limit() > size) buffer.limit((int) size);
if (position > size) position = size;
return this;
}
/**
* Returns always {@code true}.
*
* @return always {@code true}.
*/
@Override
public boolean isOpen() { return !closed; }
/** A no-op. */
@Override
@DischargesObligation
public void close() throws IOException { closed = true; }
}