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

com.microsoft.azure.storage.blob.SubStream Maven / Gradle / Ivy

There is a newer version: 8.6.6
Show newest version
/**
 * Copyright Microsoft Corporation
 * 

* 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.microsoft.azure.storage.blob; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.core.SR; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; public class SubStream extends InputStream { // A mutal exclusion lock shared between other related substream instances. private final Object lock; // Stream to be wrapped. private InputStream wrappedStream; // The current, relative position in the substream. private long substreamCurrentIndex; // The position in the wrapped stream (relative to the last mark) where the substream should logically begin. private long streamBeginIndex; // The length of the substream. private long streamLength; // Tracks the marked position in the substream. private long markIndex; // Buffer for read requests. private byte[] readBuffer; private ByteArrayInputStream readBufferStream; // Keeps track of the remaining valid bytes available in the read buffer. private int readBufferLength; /** * Creates a new substream instance that partitions the wrapped stream source from * startIndex up to streamLength. Each substream instance that wraps the same * underlying InputStream must share the same mutual exclusion lock to avoid race * conditions from concurrent operations. * * @param source The markable InputStream to be wrapped. * @param startIndex A valid index in the wrapped stream where the substream should logically begin. * @param streamLength The length of the substream. * @param lock An intrinsic lock to ensure thread-safe, concurrent operations * on substream instances wrapping the same InputStream. * @throws Exception */ public SubStream(InputStream source, long startIndex, long streamLength, Object lock) { if (startIndex < 0 || streamLength < 1) { throw new IndexOutOfBoundsException(); } else if (source == null) { throw new NullPointerException("Source stream is null."); } else if (!source.markSupported()) { throw new IllegalArgumentException("The source stream to be wrapped must be markable."); } this.wrappedStream = source; this.streamBeginIndex = startIndex; this.substreamCurrentIndex = 0; this.streamLength = streamLength; this.lock = lock; this.readBuffer = new byte[Constants.SUBSTREAM_BUFFER_SIZE]; this.readBufferStream = new ByteArrayInputStream(this.readBuffer); // Set empty read buffer to force refresh upon first read. this.readBufferLength = 0; // By default, mark the beginning of the stream. this.markIndex = 0; this.readBufferStream.mark(Integer.MAX_VALUE); } public InputStream getInputStream() { return this.wrappedStream; } public long getLength() { return this.streamLength; } /** * Reads the next byte of data from the wrapped stream. The value byte is * returned as an int in the range 0 to * 255. If no byte is available because the end of the substream * has been reached, the value -1 is returned. This method * blocks until input data is available, the end of the stream is detected, * or an exception is thrown. * * @return the next byte of data, or -1 if the end of the * substream is reached. * @throws IOException if an I/O error occurs. */ @Override public int read() throws IOException { throw new IOException(); } /** * Reads some number of bytes from the wrapped stream and stores them into * the buffer array b. The number of bytes actually read is * returned as an integer. This method blocks until input data is * available, end of file is detected, or an exception is thrown. *

*

If the length of b is zero, then no bytes are read and * 0 is returned; otherwise, there is an attempt to read at * least one byte. If no byte is available because the substream is at the * end of the file, the value -1 is returned; otherwise, at * least one byte is read and stored into b. *

*

The first byte read is stored into element b[0], the * next one into b[1], and so on. The number of bytes read is, * at most, equal to the length of b. Let k be the * number of bytes actually read; these bytes will be stored in elements * b[0] through b[k-1], * leaving elements b[k] through * b[b.length-1] unaffected. *

*

The read(b) method for class SubStream * has the same effect as:

 read(b, 0, b.length) 
* * @param b the buffer into which the data is read. * @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 the first byte cannot be read for any reason * other than the end of the file, if the wrapped stream has been closed, or * if some other I/O error occurs. * @throws NullPointerException if b is null. * @see SubStream#read(byte[], int, int) */ @Override public synchronized int read(byte[] b) throws IOException { return this.read(b, 0, b.length); } /** * Reads up to len bytes of data from the substream. Buffers data from the wrapped stream * in order to minimize skip and read overhead. The wrappedstream will only be invoked if the readBuffer * cannot fulfil the the read request. * In order to ensure valid results, the wrapped stream must be marked prior to reading from the substream. * This allows us to reset to the relative substream position in the wrapped stream. * The number of bytes actually read is returned as an integer. All these operations are done * synchronously within an intrinsic lock to ensure other concurrent requests by substream instances * do not result in race conditions. *

*

The underlying call to the read of the wrapped stream will blocks until input data * is available, end of file is detected, or an exception is thrown. *

*

If len is zero, then no bytes are read and * 0 is returned; otherwise, there is an attempt to read at * least one byte. If no byte is available because the substream is at end of * file, the value -1 is returned; otherwise, at least one * byte is read and stored into b. * * @param b the buffer into which the data is read. * @param off the start offset in array b * at which the data is written. * @param len the maximum number of bytes to read. * @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 the first byte cannot be read for any reason * other than end of file, or if the wrapped stream has been closed, or if * some other I/O error occurs. * @throws NullPointerException If b is null. * @throws IndexOutOfBoundsException If off is negative, * len is negative, or len is greater than * b.length - off * @see SubStream#read() */ @Override public synchronized int read(byte[] b, int off, int len) throws IOException { if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int bytesRead = -1; int readLength = len; // Ensure we read within the substream bounds. if (this.substreamCurrentIndex + len > this.streamLength) { readLength = (int) (this.streamLength - this.substreamCurrentIndex); } // Read from previously buffered data and only up until the valid bytes available in the buffer. int bytesFromBuffer = readBufferStream.read(b, off, Math.min(this.readBufferLength, readLength)); bytesRead = Math.max(0, bytesFromBuffer); this.readBufferLength -= bytesRead; // Read request was fully satisfied. if (bytesFromBuffer == readLength) { this.substreamCurrentIndex += bytesRead; return bytesRead; } else if (bytesFromBuffer < readLength) { // Refresh the buffer to fulfil request. this.readBufferStream.reset(); this.readBufferLength = this.readHelper(this.readBuffer, 0, readBuffer.length); if (this.readBufferLength == -1) { this.readBufferLength = 0; } } // Read the remaining bytes from the read buffer. bytesFromBuffer = readBufferStream.read(b, bytesRead + off, Math.min(this.readBufferLength, readLength - bytesRead)); if (bytesFromBuffer != -1) { bytesRead += bytesFromBuffer; this.readBufferLength -= bytesFromBuffer; } this.substreamCurrentIndex += bytesRead; return bytesRead; } private int readHelper(byte[] b, int off, int len) throws IOException { synchronized (this.lock) { wrappedStream.reset(); long bytesSkipped = 0; byte failSkipCount = 0; long streamCurrentIndex = this.streamBeginIndex + this.substreamCurrentIndex; // Must be done in a loop as skip may return less than the requested number of bytes. do { if (failSkipCount > 7) { throw new IOException(SR.STREAM_SKIP_FAILED); } long skipped = wrappedStream.skip(streamCurrentIndex - bytesSkipped); if (skipped == 0) { failSkipCount++; } else { failSkipCount = 0; bytesSkipped += skipped; } } while (bytesSkipped != streamCurrentIndex); return wrappedStream.read(b, off, len); } } /** * Advances the current position of the substream by n. * The skip method does not invoke the underlying skip method * of the wrapped stream class. The actual skipping of bytes will be accounted for * during subsequent substream read operations. * * @param n the number of bytes to be effectively skipped. * @return the actual number of bytes skipped. */ @Override public long skip(long n) { if (this.substreamCurrentIndex + n > this.streamLength) { n = this.streamLength - this.substreamCurrentIndex; } this.substreamCurrentIndex += n; this.readBufferLength = (int) Math.max(0, this.readBufferLength - n); return n; } /** * Marks the current position in the substream. A subsequent call to * the reset method will reposition the stream to this stored position. * * @param readlimit the maximum limit of bytes that can be read before * the mark position becomes invalid. * @see SubStream#reset() */ @Override public synchronized void mark(int readlimit) { this.markIndex = this.substreamCurrentIndex; } /** * Repositions the substream position to the index where the mark method * was last called. *

* The new reset position on substream does not take effect until subsequent reads. * * @see SubStream#mark(int) */ @Override public synchronized void reset() { this.substreamCurrentIndex = this.markIndex; } /** * The substream wrapper class is only compatible with markable input streams and hence * will always return true. This requirement is enforced in the class constructor. * * @return true * @see SubStream#mark(int) * @see SubStream#reset() */ @Override public boolean markSupported() { return true; } /** * Closes the substream. */ @Override public void close() throws IOException { this.wrappedStream = null; this.readBuffer = null; this.readBufferStream.close(); this.readBufferStream = null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy