/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.imagepipeline.memory;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.IOException;
import java.io.InputStream;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
/**
* An InputStream implementation over a {@link NativeMemoryChunk} instance
*/
@NotThreadSafe
public class NativeMemoryChunkInputStream extends InputStream {
@VisibleForTesting
final NativeMemoryChunk mMemoryChunk; // the underlying memory chunk we're using
@VisibleForTesting
final int mEndOffset; // end offset in chunk
@VisibleForTesting
final int mStartOffset; // initial offset into the chunk
@VisibleForTesting
int mOffset; // current offset in the chunk
@VisibleForTesting
int mMark; // position of 'mark' if any
// to avoid new allocation on every read()
byte[] mSingleByteBuffer = new byte[1];
/**
* Creates a new inputstream instance over the specific memory chunk.
* @param nativeMemoryChunk the native memory chunk
* @param startOffset start offset within the memory chunk
* @param length length of subchunk
*/
public NativeMemoryChunkInputStream(
NativeMemoryChunk nativeMemoryChunk,
int startOffset,
int length) {
super();
Preconditions.checkState(startOffset >= 0);
Preconditions.checkState(length >= 0);
mMemoryChunk = Preconditions.checkNotNull(nativeMemoryChunk);
mStartOffset = startOffset;
mEndOffset = startOffset + length > nativeMemoryChunk.getSize()
? nativeMemoryChunk.getSize() : startOffset + length;
mOffset = startOffset;
mMark = startOffset;
}
/**
* Returns the number of bytes still available to read
*/
@Override
public int available() {
return Math.max(0, mEndOffset - mOffset);
}
/**
* Sets a mark position in this inputstream.
* The parameter {@code readlimit} is ignored.
* Sending {@link #reset()} will reposition the stream back to the marked position.
* @param readlimit ignored.
*/
@Override
public void mark(int readlimit) {
mMark = mOffset;
}
/**
* Returns {@code true} since this class supports {@link #mark(int)} and {@link #reset()}
* methods
*/
@Override
public boolean markSupported() {
return true;
}
@Override
public int read() throws IOException {
int len = this.read(mSingleByteBuffer, 0, 1);
int b = (int) mSingleByteBuffer[0] & 0xFF;
return (len == 1) ? b : -1;
}
/**
* Reads at most {@code length} bytes from this stream and stores them in byte array
* {@code buffer} starting at {@code offset}.
* @param buffer the buffer to read data into
* @param offset start offset in the buffer
* @param length max number of bytes to read
* @return number of bytes read
*/
@Override
public int read(byte[] buffer, int offset, int length) {
if (offset < 0 || length < 0 || offset + length > buffer.length) {
throw new ArrayIndexOutOfBoundsException(
"length=" + buffer.length +
"; regionStart=" + offset +
"; regionLength=" + length);
}
if (mOffset >= mEndOffset) {
return -1;
}
if (length == 0) {
return 0;
}
int numToRead = Math.min(available(), length);
int numRead = mMemoryChunk.read(mOffset, buffer, offset, numToRead);
mOffset += numRead;
return numRead;
}
/**
* Resets this stream to the last marked location. This implementation
* resets the position to either the marked position, the start position
* supplied in the constructor or 0 if neither has been provided.
*/
@Override
public void reset() {
mOffset = mMark;
}
/**
* Skips byteCount (or however many bytes are available) bytes in the stream
* @param byteCount number of bytes to skip
* @return number of bytes actually skipped
* @throws IOException
*/
@Override
public long skip(long byteCount) {
// skip-count is zero or less; or we're already past the end of stream? Just return 0
if (byteCount <= 0 ||
(mOffset >= mEndOffset)) {
return 0;
}
int temp = mOffset;
mOffset = (mEndOffset - mOffset) < byteCount ? mEndOffset : (int)(mOffset + byteCount);
return Math.max(0, mOffset - temp);
}
}