All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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; }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy