/* * 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); } }