com.github.dm.jrt.core.ByteChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jroutine Show documentation
Show all versions of jroutine Show documentation
Parallel programming on the go
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.dm.jrt.core;
import com.github.dm.jrt.channel.IOChannel;
import com.github.dm.jrt.channel.InputChannel;
import com.github.dm.jrt.util.WeakIdentityHashMap;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Utility class focused on the optimization of the transfer of byte chunks through routine
* channels.
*
* Each instance maintains a pool of byte buffers which are re-used to minimize memory consumption.
* When the pool is empty, additional buffers are created in order to avoid blocking the caller
* thread. Though, the pool will retain its maximum capacity and every buffer exceeding it will be
* discarded.
* Note that the streams used to write into and read from buffers should be properly closed as the
* Java best practices suggest.
*
* Created by davide-maestroni on 08/26/2015.
*/
public class ByteChannel {
/**
* The default buffer size in number of bytes.
*/
public static final int DEFAULT_BUFFER_SIZE = 16 << 10;
/**
* The default core pool size.
*/
public static final int DEFAULT_POOL_SIZE = 16;
private static final int DEFAULT_MEM_SIZE = DEFAULT_POOL_SIZE * DEFAULT_BUFFER_SIZE;
private final LinkedList mBufferPool = new LinkedList();
private final int mCorePoolSize;
private final int mDataBufferSize;
private final WeakIdentityHashMap, BufferOutputStream>
mStreams =
new WeakIdentityHashMap, BufferOutputStream>();
/**
* Constructor.
*
* @param dataBufferSize the data buffer size.
* @param corePoolSize the maximum number of retained data buffers.
* @throws java.lang.IllegalArgumentException if the specified size is 0 or negative.
*/
private ByteChannel(final int dataBufferSize, final int corePoolSize) {
if (dataBufferSize < 1) {
throw new IllegalArgumentException("the data buffer size must be greater than 0");
}
mCorePoolSize = corePoolSize;
mDataBufferSize = dataBufferSize;
}
/**
* Returns a new byte channel.
*
* @return the byte channel.
*/
@NotNull
public static ByteChannel byteChannel() {
return new ByteChannel(DEFAULT_BUFFER_SIZE, DEFAULT_POOL_SIZE);
}
/**
* Returns a new byte channel.
*
* @param dataBufferSize the size of the data buffer used to transfer the bytes through the
* routine channels.
* @return the byte channel.
* @throws java.lang.IllegalArgumentException if the specified size is 0 or negative.
*/
@NotNull
public static ByteChannel byteChannel(final int dataBufferSize) {
return new ByteChannel(dataBufferSize, DEFAULT_MEM_SIZE / Math.max(dataBufferSize, 1));
}
/**
* Returns a new byte channel.
*
* @param dataBufferSize the size of the data buffer used to transfer the bytes through the
* routine channels.
* @param corePoolSize the maximum number of data retained in the pool. Additional data
* created to fulfill the bytes requirement will be discarded.
* @return the byte channel.
* @throws java.lang.IllegalArgumentException if the specified size is 0 or negative.
*/
@NotNull
public static ByteChannel byteChannel(final int dataBufferSize, final int corePoolSize) {
return new ByteChannel(dataBufferSize, corePoolSize);
}
/**
* Creates an input stream returning the data contained in the specified buffer.
* Note that only one input stream can be created for each buffer.
*
* @param buffer the byte buffer.
* @return the input stream.
* @throws java.lang.IllegalStateException if an input stream has been already created for the
* specified buffer.
*/
@NotNull
public static BufferInputStream inputStream(@NotNull final ByteBuffer buffer) {
return buffer.getStream();
}
/**
* Creates an input stream returning the concatenation of the data contained in the specified
* buffers.
* Note that only one input stream can be created for each buffer.
*
* @param buffers the byte buffers whose data have to be concatenated.
* @return the input stream.
* @throws java.lang.IllegalStateException if an input stream has been already created for one
* of the specified buffers.
*/
@NotNull
public static BufferInputStream inputStream(@NotNull final ByteBuffer... buffers) {
return new MultiBufferInputStream(buffers);
}
/**
* Creates an input stream returning the concatenation of the data contained in the specified
* buffers.
* Note that only one input stream can be created for each buffer.
*
* @param buffers the byte buffers whose data have to be concatenated.
* @return the input stream.
* @throws java.lang.IllegalStateException if an input stream has been already created for one
* of the specified buffers.
*/
@NotNull
public static BufferInputStream inputStream(@NotNull final List buffers) {
return new MultiBufferInputStream(buffers);
}
/**
* Returns the output stream used to write bytes into the specified channel.
*
* @param channel the input channel to which pass the data.
* @return the output stream.
*/
@NotNull
public BufferOutputStream passTo(@NotNull final InputChannel super ByteBuffer> channel) {
BufferOutputStream stream;
synchronized (mStreams) {
final WeakIdentityHashMap, BufferOutputStream>
streams = mStreams;
stream = streams.get(channel);
if (stream == null) {
stream = new DefaultBufferOutputStream(channel);
streams.put(channel, stream);
}
}
return stream;
}
/**
* Returns the output stream used to write bytes into the specified channel.
* Note that the channel will be automatically closed as soon as the returned output stream is
* closed.
*
* @param channel the I/O channel to which pass the data.
* @return the output stream.
*/
@NotNull
public BufferOutputStream passTo(@NotNull final IOChannel super ByteBuffer, ?> channel) {
return new IOBufferOutputStream(passTo(channel.asInput()), channel);
}
@NotNull
private ByteBuffer acquire() {
ByteBuffer buffer = null;
synchronized (mBufferPool) {
final LinkedList bufferPool = mBufferPool;
if (!bufferPool.isEmpty()) {
buffer = bufferPool.removeFirst();
}
}
if (buffer != null) {
return buffer.unlock();
}
return new ByteBuffer(mDataBufferSize);
}
private void release(@NotNull final ByteBuffer buffer) {
synchronized (mBufferPool) {
final LinkedList bufferPool = mBufferPool;
if (bufferPool.size() < mCorePoolSize) {
bufferPool.add(buffer);
}
}
}
/**
* Internal buffer state enumeration.
*/
private enum BufferState {
WRITE, // can write data into the buffer
TRANSFER, // the buffer is being transferred through the channel
READ, // can read data from the buffer
RECYCLED // the buffer is not usable
}
/**
* Input stream used to read the data contained in a buffer instance.
*/
public static abstract class BufferInputStream extends InputStream {
/**
* Reads some bytes from the input stream and writes them into the specified output stream.
*
* @param out the output stream.
* @return the total number of bytes read into the buffer, or -1
if there is no
* more data because the end of the stream has been reached.
* @throws IOException if an I/O error occurs. In particular, an IOException
* may be thrown if the output stream has been closed.
*/
public abstract int read(@NotNull OutputStream out) throws IOException;
@Override
public abstract int read();
@Override
public abstract int read(@NotNull byte[] b);
@Override
public abstract int read(@NotNull byte[] b, int off, int len);
@Override
public abstract long skip(long n);
@Override
public abstract int available();
@Override
public abstract void close();
@Override
public abstract void mark(int readLimit);
@Override
public abstract void reset();
/**
* Reads all the bytes returned by the input stream and writes them into the specified
* output stream.
* Calling this method has the same effect as calling:
*
*
*
* while (inputStream.read(outputStream) > 0) {
*
* // Keep looping
* }
*
*
*
* @param out the output stream.
* @return the total number of bytes read.
* @throws IOException if an I/O error occurs. In particular, an IOException
* may be thrown if the output stream has been closed.
*/
public long readAll(@NotNull final OutputStream out) throws IOException {
long count = 0;
for (int b; (b = read(out)) > 0; ) {
count += b;
}
return count;
}
}
/**
* Output stream used to write data into the buffer channel.
*/
public static abstract class BufferOutputStream extends OutputStream {
/**
* Writes some bytes into the output stream by reading them from the specified input stream.
*
* @param in the input stream.
* @return the total number of bytes written into the buffer, or -1
if there is
* no more data because the end of the stream has been reached.
* @throws IOException If the first byte cannot be read for any reason other than end of
* file, or if the input stream has been closed, or if some other I/O
* error occurs.
*/
public abstract int write(@NotNull InputStream in) throws IOException;
/**
* Writes all the returned bytes into the output stream by reading them from the specified
* input stream.
* Calling this method has the same effect as calling:
*
*
*
* while (outputStream.write(inputStream) > 0) {
*
* // Keep looping
* }
*
*
*
* @param in the input stream.
* @return the total number of bytes written.
* @throws IOException If the first byte cannot be read for any reason other than end of
* file, or if the input stream has been closed, or if some other I/O
* error occurs.
*/
public long writeAll(@NotNull final InputStream in) throws IOException {
long count = 0;
for (int b; (b = write(in)) > 0; ) {
count += b;
}
return count;
}
@Override
public abstract void flush();
@Override
public abstract void close();
}
/**
* Implementation of a buffer output stream automatically closing the output I/O channel.
*/
private static class IOBufferOutputStream extends BufferOutputStream {
private final IOChannel super ByteBuffer, ?> mIOChannel;
private final BufferOutputStream mOutputStream;
/**
* Constructor.
*
* @param wrapped the wrapped stream.
* @param channel the I/O channel.
*/
private IOBufferOutputStream(@NotNull final BufferOutputStream wrapped,
@NotNull final IOChannel super ByteBuffer, ?> channel) {
mOutputStream = wrapped;
mIOChannel = channel;
}
@Override
public int write(@NotNull final InputStream in) throws IOException {
return mOutputStream.write(in);
}
@Override
public void write(final int b) throws IOException {
mOutputStream.write(b);
}
@Override
public void write(@NotNull final byte[] b) throws IOException {
mOutputStream.write(b);
}
@Override
public void write(@NotNull final byte[] b, final int off, final int len) throws
IOException {
mOutputStream.write(b, off, len);
}
@Override
public void flush() {
mOutputStream.flush();
}
@Override
public void close() {
try {
mOutputStream.close();
} finally {
mIOChannel.close();
}
}
}
/**
* Input stream returning the concatenation of a collection of byte buffer data.
*/
private static class MultiBufferInputStream extends BufferInputStream {
private final Object mMutex = new Object();
private final ArrayList mStreams;
private int mIndex;
private int mMarkIndex;
/**
* Constructor.
*
* @param buffers the array of input streams whose data have to be concatenated.
*/
private MultiBufferInputStream(@NotNull final ByteBuffer[] buffers) {
final ArrayList streams =
(mStreams = new ArrayList(buffers.length));
for (final ByteBuffer buffer : buffers) {
streams.add(buffer.getStream());
}
}
/**
* Constructor.
*
* @param buffers the list of input streams whose data have to be concatenated.
*/
private MultiBufferInputStream(@NotNull final List buffers) {
final ArrayList streams =
(mStreams = new ArrayList(buffers.size()));
for (final ByteBuffer buffer : buffers) {
streams.add(buffer.getStream());
}
}
/**
* Reads some bytes from the input stream and writes them into the specified output stream.
*
* @param out the output stream.
* @return the total number of bytes read into the buffer, or -1
if there is no
* more data because the end of the stream has been reached.
* @throws IOException if an I/O error occurs. In particular, an IOException
* may be thrown if the output stream has been closed.
*/
public int read(@NotNull final OutputStream out) throws IOException {
synchronized (mMutex) {
final ArrayList streams = mStreams;
final int size = streams.size();
if (mIndex >= size) {
return -1;
}
int read = streams.get(mIndex).read(out);
while (read < 0) {
if (++mIndex >= size) {
return -1;
}
read = streams.get(mIndex).read(out);
}
return read;
}
}
@Override
public int read() {
synchronized (mMutex) {
final ArrayList streams = mStreams;
final int size = streams.size();
if (mIndex >= size) {
return -1;
}
int read = streams.get(mIndex).read();
while (read == -1) {
if (++mIndex >= size) {
return -1;
}
read = streams.get(mIndex).read();
}
return read;
}
}
@Override
public int read(@NotNull final byte[] b) {
final int len = b.length;
if (len == 0) {
return 0;
}
synchronized (mMutex) {
final ArrayList streams = mStreams;
final int size = streams.size();
if (mIndex >= size) {
return -1;
}
int count = 0;
int read = streams.get(mIndex).read(b);
if (read > 0) {
count += read;
}
while (count < len) {
if (++mIndex >= size) {
return (count > 0) ? count : -1;
}
read = streams.get(mIndex).read(b, count, len - count);
if (read > 0) {
count += read;
}
}
return count;
}
}
@Override
@SuppressWarnings("ConstantConditions")
public int read(@NotNull final byte[] b, final int off, final int len) {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (len < 0) || (len > b.length - off) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
synchronized (mMutex) {
final ArrayList streams = mStreams;
final int size = streams.size();
if (mIndex >= size) {
return -1;
}
int count = 0;
int read = streams.get(mIndex).read(b, off, len);
if (read > 0) {
count += read;
}
while (count < len) {
if (++mIndex >= size) {
return (count > 0) ? count : -1;
}
read = streams.get(mIndex).read(b, off + count, len - count);
if (read > 0) {
count += read;
}
}
return count;
}
}
@Override
public long skip(final long n) {
synchronized (mMutex) {
final ArrayList streams = mStreams;
final int size = streams.size();
if (mIndex >= size) {
return 0;
}
long count = 0;
long skipped = streams.get(mIndex).skip(n);
if (skipped > 0) {
count += skipped;
}
while (count < n) {
if (++mIndex >= size) {
return count;
}
skipped = streams.get(mIndex).skip(n - count);
if (skipped > 0) {
count += skipped;
}
}
return count;
}
}
@Override
public int available() {
int available = 0;
synchronized (mMutex) {
final ArrayList streams = mStreams;
final int size = streams.size();
for (int i = mIndex; i < size; ++i) {
available += streams.get(i).available();
}
}
return available;
}
@Override
public void close() {
synchronized (mMutex) {
for (final BufferInputStream stream : mStreams) {
stream.close();
}
}
}
@Override
public void mark(final int readLimit) {
synchronized (mMutex) {
final int index = (mMarkIndex = mIndex);
mStreams.get(index).mark(readLimit);
}
}
@Override
public void reset() {
synchronized (mMutex) {
final int index = (mIndex = mMarkIndex);
final ArrayList streams = mStreams;
streams.get(index).reset();
final int size = streams.size();
for (int i = index + 1; i < size; ++i) {
streams.get(i).reset();
}
}
}
@Override
public boolean markSupported() {
return true;
}
}
/**
* Object acting as a buffer of bytes.
*
* Buffer instances are managed by the owning byte channel and recycled when released, in order
* to minimize memory consumption. Byte buffers are automatically acquired by
* BufferOutputStream
s and passed to the underlying channel.
* The data contained in a buffer can be read through the dedicated
* {@code BufferInputStream} returned by one of the {@code ByteChannel.inputStream()}
* methods. Note that only one input stream can be created for each buffer, any further attempt
* will generate an exception.
* Used buffers will be released as soon as the corresponding input stream is closed.
*
* @see ByteChannel#inputStream(ByteBuffer)
* @see ByteChannel#inputStream(ByteBuffer...)
* @see ByteChannel#inputStream(List)
*/
public class ByteBuffer {
private final byte[] mBuffer;
private final Object mMutex = new Object();
private final DefaultBufferInputStream mStream;
private int mSize;
private BufferState mState = BufferState.WRITE;
/**
* Constructor.
*
* @param bufferSize the internal buffer size.
*/
private ByteBuffer(final int bufferSize) {
mBuffer = new byte[bufferSize];
mStream = new DefaultBufferInputStream(this);
}
/**
* Returns the size in number of bytes of this buffer.
*
* @return the buffer size.
*/
public int getSize() {
synchronized (mMutex) {
return mSize;
}
}
@Override
public int hashCode() {
final int size = getSize();
final byte[] buffer = mBuffer;
int result = size;
for (int i = 0; i < size; ++i) {
result = 31 * result + buffer[i];
}
return result;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ByteBuffer)) {
return false;
}
final ByteBuffer that = (ByteBuffer) o;
final int size = getSize();
if (size != that.getSize()) {
return false;
}
final byte[] thisBuffer = mBuffer;
final byte[] thatBuffer = that.mBuffer;
for (int i = 0; i < size; ++i) {
if (thisBuffer[i] != thatBuffer[i]) {
return false;
}
}
return true;
}
private void changeState(@NotNull final BufferState expected,
@NotNull final BufferState updated, @NotNull final String errorMessage) {
if (mState != expected) {
throw new IllegalStateException(errorMessage + ": " + mState);
}
mState = updated;
}
@NotNull
private BufferInputStream getStream() {
synchronized (mMutex) {
changeState(BufferState.TRANSFER, BufferState.READ,
"attempting to get buffer stream while in illegal state");
return mStream;
}
}
@NotNull
private ByteBuffer lock(final int size) {
synchronized (mMutex) {
changeState(BufferState.WRITE, BufferState.TRANSFER,
"attempting to write to output while in illegal state");
mSize = size;
}
return this;
}
@NotNull
private byte[] readBuffer() {
synchronized (mMutex) {
final BufferState state = mState;
if (state != BufferState.READ) {
throw new IllegalStateException(
"attempting to read buffer data while in illegal state: " + state);
}
}
return mBuffer;
}
private void recycle() {
synchronized (mMutex) {
changeState(BufferState.READ, BufferState.RECYCLED,
"attempting to read from buffer while in illegal state");
mSize = 0;
}
release(this);
}
@NotNull
private ByteBuffer unlock() {
synchronized (mMutex) {
changeState(BufferState.RECYCLED, BufferState.WRITE,
"attempting to reuse instance while in illegal state");
}
return this;
}
@NotNull
private byte[] writeBuffer() {
synchronized (mMutex) {
final BufferState state = mState;
if (state != BufferState.WRITE) {
throw new IllegalStateException(
"attempting to write buffer data while in illegal state: " + state);
}
}
return mBuffer;
}
}
/**
* Default buffer input stream implementation.
*/
private class DefaultBufferInputStream extends BufferInputStream {
private final ByteBuffer mBuffer;
private final Object mMutex = new Object();
private boolean mIsClosed;
private int mMark;
private int mOffset;
/**
* Constructor.
*
* @param buffer the internal buffer.
*/
private DefaultBufferInputStream(@NotNull final ByteBuffer buffer) {
mBuffer = buffer;
}
@Override
public int read(@NotNull final OutputStream out) throws IOException {
synchronized (mMutex) {
final ByteBuffer buffer = mBuffer;
final int size = buffer.getSize();
final int offset = mOffset;
if (offset >= size) {
return -1;
}
final int count = size - offset;
out.write(buffer.readBuffer(), offset, count);
mOffset = size;
return count;
}
}
@Override
public int read(@NotNull final byte[] b) {
final int len = b.length;
if (len == 0) {
return 0;
}
synchronized (mMutex) {
final ByteBuffer buffer = mBuffer;
final int size = buffer.getSize();
final int offset = mOffset;
if (offset >= size) {
return -1;
}
final int count = Math.min(len, size - offset);
System.arraycopy(buffer.readBuffer(), offset, b, 0, count);
mOffset += count;
return count;
}
}
@Override
@SuppressWarnings("ConstantConditions")
public int read(@NotNull final byte[] b, final int off, final int len) {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (len < 0) || (len > b.length - off) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
synchronized (mMutex) {
final ByteBuffer buffer = mBuffer;
final int size = buffer.getSize();
final int offset = mOffset;
if (offset >= size) {
return -1;
}
final int count = Math.min(len, size - offset);
System.arraycopy(buffer.readBuffer(), offset, b, off, count);
mOffset += count;
return count;
}
}
@Override
public long skip(final long n) {
synchronized (mMutex) {
final long skipped = Math.min(mBuffer.getSize() - mOffset, n);
if (skipped > 0) {
mOffset += skipped;
}
return skipped;
}
}
@Override
public int available() {
synchronized (mMutex) {
return Math.max(0, mBuffer.getSize() - mOffset);
}
}
@Override
public void close() {
synchronized (mMutex) {
if (mIsClosed) {
return;
}
mIsClosed = true;
mMark = 0;
mBuffer.recycle();
}
}
@Override
public void reset() {
synchronized (mMutex) {
mOffset = mMark;
}
}
@Override
public int read() {
synchronized (mMutex) {
final ByteBuffer buffer = mBuffer;
final int size = buffer.getSize();
if (mOffset >= size) {
return -1;
}
return buffer.readBuffer()[mOffset++];
}
}
@Override
public void mark(final int readLimit) {
synchronized (mMutex) {
mMark = mOffset;
}
}
@Override
public boolean markSupported() {
return true;
}
}
/**
* Default buffer output stream implementation.
*/
private class DefaultBufferOutputStream extends BufferOutputStream {
private final InputChannel super ByteBuffer> mChannel;
private final Object mMutex = new Object();
private ByteBuffer mBuffer;
private boolean mIsClosed;
private int mOffset;
/**
* Constructor
*
* @param channel the input channel to which pass the data.
*/
@SuppressWarnings("ConstantConditions")
private DefaultBufferOutputStream(@NotNull final InputChannel super ByteBuffer> channel) {
if (channel == null) {
throw new NullPointerException("the input channel must not be null");
}
mChannel = channel;
}
@NotNull
private ByteBuffer getBuffer() {
final ByteBuffer byteBuffer = mBuffer;
if (byteBuffer != null) {
return byteBuffer;
}
return (mBuffer = acquire());
}
@Override
public int write(@NotNull final InputStream in) throws IOException {
final int read;
final boolean isPass;
final ByteBuffer byteBuffer;
final int size;
synchronized (mMutex) {
if (mIsClosed) {
throw new IOException("cannot write into a closed output stream");
}
byteBuffer = getBuffer();
final byte[] buffer = byteBuffer.writeBuffer();
final int length = buffer.length;
final int offset = mOffset;
read = in.read(buffer, offset, length - offset);
if (read > 0) {
mOffset += Math.max(read, 0);
size = mOffset;
isPass = (size >= length);
if (isPass) {
mOffset = 0;
mBuffer = null;
}
} else {
size = mOffset;
isPass = false;
}
}
if (isPass) {
mChannel.pass(byteBuffer.lock(size));
}
return read;
}
@Override
public void flush() {
final ByteBuffer byteBuffer;
final int size;
synchronized (mMutex) {
size = mOffset;
if (size == 0) {
return;
}
byteBuffer = getBuffer();
mOffset = 0;
mBuffer = null;
}
mChannel.pass(byteBuffer.lock(size));
}
@Override
public void close() {
synchronized (mMutex) {
if (mIsClosed) {
return;
}
mIsClosed = true;
}
flush();
}
@Override
public void write(final int b) throws IOException {
final boolean isPass;
final ByteBuffer byteBuffer;
final int size;
synchronized (mMutex) {
if (mIsClosed) {
throw new IOException("cannot write into a closed output stream");
}
byteBuffer = getBuffer();
final byte[] buffer = byteBuffer.writeBuffer();
buffer[mOffset++] = (byte) b;
size = mOffset;
isPass = (size >= buffer.length);
if (isPass) {
mOffset = 0;
mBuffer = null;
}
}
if (isPass) {
mChannel.pass(byteBuffer.lock(size));
}
}
@Override
public void write(@NotNull final byte[] b) throws IOException {
final int len = b.length;
if (len == 0) {
return;
}
int written = 0;
do {
final boolean isPass;
final ByteBuffer byteBuffer;
final int size;
synchronized (mMutex) {
if (mIsClosed) {
throw new IOException("cannot write into a closed output stream");
}
byteBuffer = getBuffer();
final byte[] buffer = byteBuffer.writeBuffer();
final int length = buffer.length;
final int offset = mOffset;
final int count = Math.min(len - written, length - offset);
System.arraycopy(b, written, buffer, offset, count);
written += count;
mOffset += count;
size = mOffset;
isPass = (size >= length);
if (isPass) {
mOffset = 0;
mBuffer = null;
}
}
if (isPass) {
mChannel.pass(byteBuffer.lock(size));
}
} while (written < len);
}
@Override
@SuppressWarnings("ConstantConditions")
public void write(@NotNull final byte[] b, final int off, final int len) throws
IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (len < 0) || (len > b.length - off) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int written = 0;
do {
final boolean isPass;
final ByteBuffer byteBuffer;
final int size;
synchronized (mMutex) {
if (mIsClosed) {
throw new IOException("cannot write into a closed output stream");
}
byteBuffer = getBuffer();
final byte[] buffer = byteBuffer.writeBuffer();
final int length = buffer.length;
final int offset = mOffset;
final int count = Math.min(len - written, length - offset);
System.arraycopy(b, off + written, buffer, offset, count);
written += count;
mOffset += count;
size = mOffset;
isPass = (size >= length);
if (isPass) {
mOffset = 0;
mBuffer = null;
}
}
if (isPass) {
mChannel.pass(byteBuffer.lock(size));
}
} while (written < len);
}
}
}