/** * Creates a new BoundedInputStream wrapping the same InputStream as this one, starting at the current position, with * the given bound. * * NOTE: both this instance and the result of this method should not be both used at the same time to read from the * InputStream. When this method returns, this instance's remaining count will be reduced by the given bound (that's * since upon closing, the BoundedInputStream will auto-advance to its bound position). * * @param bound The bound of the sub-stream. * @return A new instance of a BoundedInputStream. */ public BoundedInputStream subStream(int bound) { Preconditions.checkArgument(bound >= 0 && bound <= this.remaining, "bound must be a non-negative integer and less than or equal to the remaining length."); this.remaining -= bound; return new BoundedInputStream(this.in, bound); }
/** * Gets a value representing the length of this InputStream, in bytes, excluding the 4 bytes required for encoding * the length. */ @VisibleForTesting int getLength() { return ((BoundedInputStream) this.in).getBound(); }
private void closeLast() throws IOException { BoundedInputStream last = this.lastEntryContents; if (last != null) { last.close(); this.lastEntryContents = null; } }
val input = new ByteArrayInputStream(data); @Cleanup val bis = new BoundedInputStream(input, bound); () -> bis.subStream(bound + 1), ex -> ex instanceof IllegalArgumentException); while (bis.available() > 0) { val s = bis.subStream(size); Assert.assertEquals("Unexpected bound from sub-stream for bound " + size, size, s.getBound()); int c = s.read(buffer); AssertExtensions.assertArrayEquals("Unexpected data read into buffer for bound " + size, data, expectedPos, buffer, 0, c); Assert.assertEquals("Unexpected result from available() after reading a buffer for bound " + size, s.getBound() - c, s.available()); s.close(); expectedPos += size; Assert.assertEquals("Base BoundedInputStream's position did not advance for bound " + size, bis.getBound() - expectedPos, bis.available()); size = Math.min(size + 1, bis.available());
val input = new ByteArrayInputStream(data); @Cleanup val bis = new BoundedInputStream(input, bound); int expectedAvailable = bis.getBound(); int b1 = bis.read(); expectedAvailable--; Assert.assertEquals("Unexpected result when reading 1 byte.", data[0], b1); Assert.assertEquals("Unexpected result from available() after reading 1 byte.", expectedAvailable, bis.available()); int c = bis.read(buffer); expectedAvailable -= c; Assert.assertEquals("Unexpected number of bytes read.", buffer.length, c); AssertExtensions.assertArrayEquals("Unexpected data read into buffer.", data, 1, buffer, 0, c); Assert.assertEquals("Unexpected result from available() after reading a buffer.", expectedAvailable, bis.available()); c = bis.read(buffer, 0, buffer.length / 2); expectedAvailable -= c; Assert.assertEquals("Unexpected number of bytes read.", buffer.length / 2, c); AssertExtensions.assertArrayEquals("Unexpected data read into buffer.", data, 1 + buffer.length, buffer, 0, c); Assert.assertEquals("Unexpected result from available() after reading a buffer.", expectedAvailable, bis.available()); long s = bis.skip(10); expectedAvailable -= s; Assert.assertEquals("Unexpected number of bytes skipped.", 10, s); Assert.assertEquals("Unexpected result from available() after skipping.", expectedAvailable, bis.available()); s = bis.skip(Integer.MAX_VALUE);
/** * Tests the ability to skip remaining bytes upon closing. */ @Test public void testSkipRemainingOnClose() throws Exception { for (int size = 0; size < MAX_SIZE; size++) { int bound = size / 2; val data = construct(size); @Cleanup val input = new ByteArrayInputStream(data); @Cleanup val bis = new BoundedInputStream(input, bound); Assert.assertEquals("Unexpected value for getBound().", bound, bis.getBound()); if (size % 2 == 1) { // Only read some data for every other iteration. int bytesRead = bis.read(new byte[bound / 2]); Assert.assertEquals("Unexpected number of bytes read.", bound / 2, bytesRead); } bis.close(); Assert.assertEquals("Unexpected number of bytes remaining to be read after skipRemaining().", data.length - bis.getBound(), input.available()); } }
@Override public DataFrameEntry getNext() throws IOException { closeLast(); if (reachedEnd()) { return null; } // Integrity check. This means that we have a corrupt frame. if (this.contents.getRemaining() < EntryHeader.HEADER_SIZE) { throw new SerializationException(String.format("Data Frame is corrupt. InputStream has insufficient bytes for a new Entry Header (%d).", this.contents.getRemaining())); } // Determine the length of the next record and advance the position by the appropriate amount of bytes. ReadEntryHeader header = new ReadEntryHeader(this.contents); // Integrity check. This means that we have a corrupt frame. if (this.contents.getRemaining() < header.getEntryLength()) { throw new SerializationException(String.format("Data Frame is corrupt. Found Entry Length %d which cannot fit in the Frame's remaining length of %d.", header.getEntryLength(), this.contents.getRemaining())); } // Get the result contents && advance the positions. int frameOffset = this.bufferOffset + this.contents.getBound() - this.contents.getRemaining(); BoundedInputStream resultContents = this.contents.subStream(header.getEntryLength()); this.lastEntryContents = resultContents; return new DataFrameEntry(header, resultContents, this.frameAddress, reachedEnd(), frameOffset); }
@Override public void close() throws IOException { // Skip over the remaining bytes. Do not close the underlying InputStream. if (this.remaining > 0) { int toSkip = this.remaining; long skipped = skip(toSkip); if (skipped != toSkip) { throw new SerializationException(String.format("Read %d fewer byte(s) than expected only able to skip %d.", toSkip, skipped)); } } else if (this.remaining < 0) { throw new SerializationException(String.format("Read more bytes than expected (%d).", -this.remaining)); } }
private boolean reachedEnd() { return this.contents.getRemaining() <= 0; } }
/** * Creates a new instance of the RevisionDataInputStream class. Upon a successful call to this method, 4 bytes * will have been read from the InputStream representing the expected length of the serialization. * * @param inputStream The InputStream to wrap. * @throws IOException If an IO Exception occurred. */ static RevisionDataInputStream wrap(InputStream inputStream) throws IOException { int bound = BitConverter.readInt(inputStream); return new RevisionDataInputStream(new BoundedInputStream(inputStream, bound)); }
@VisibleForTesting int getLength() { return this.contents.getBound(); }
@Override @SneakyThrows(IOException.class) public void close() { closeLast(); this.contents.close(); }
/** * Interprets the given InputStream as a DataFrame and returns a DataFrameEntryIterator for the entries serialized * in it. * * @param source The InputStream to read from. * @param length The size of the inputStream. * @param address The DataFrame's address. * @return A new DataFrameEntryIterator. * @throws IOException If unable to parse the DataFrame's header from the InputStream. */ public static DataFrameEntryIterator read(InputStream source, int length, LogAddress address) throws IOException { // Check to see that we have enough bytes in the InputStream. ReadFrameHeader header = new ReadFrameHeader(source); if (length < ReadFrameHeader.SERIALIZATION_LENGTH + header.getContentLength()) { throw new SerializationException(String.format("Given buffer has insufficient number of bytes for this DataFrame. Expected %d, actual %d.", ReadFrameHeader.SERIALIZATION_LENGTH + header.getContentLength(), length)); } BoundedInputStream contents = new BoundedInputStream(source, header.getContentLength()); return new DataFrameEntryIterator(contents, address, ReadFrameHeader.SERIALIZATION_LENGTH); }
/** * Creates a new instance of the DataFrameEntry class. * * @param header The Header for this entry. * @param data A ByteArraySegment representing the contents of this frame. * @param frameAddress The Address of the containing DataFrame. * @param lastEntryInDataFrame Whether this is the last entry in the DataFrame. * @param frameOffset The offset within the DataFrame where this Entry starts. */ private DataFrameEntry(EntryHeader header, BoundedInputStream data, LogAddress frameAddress, boolean lastEntryInDataFrame, int frameOffset) { this.firstRecordEntry = header.isFirstRecordEntry(); this.lastRecordEntry = header.isLastRecordEntry(); this.lastEntryInDataFrame = lastEntryInDataFrame; this.frameAddress = frameAddress; this.data = data; this.length = data.getBound(); this.frameOffset = frameOffset; }