Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Content Size information exposition to LZ4FrameInputStream #144

Merged
merged 3 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/java/net/jpountz/lz4/LZ4FrameInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class LZ4FrameInputStream extends FilterInputStream {
private final XXHash32 checksum;
private final byte[] headerArray = new byte[LZ4FrameOutputStream.LZ4_MAX_HEADER_LENGTH];
private final ByteBuffer headerBuffer = ByteBuffer.wrap(headerArray).order(ByteOrder.LITTLE_ENDIAN);
private final boolean readSingleFrame;
private byte[] compressedBuffer;
private ByteBuffer buffer = null;
private byte[] rawBuffer = null;
Expand All @@ -59,6 +60,7 @@ public class LZ4FrameInputStream extends FilterInputStream {

/**
* Creates a new {@link InputStream} that will decompress data using fastest instances of {@link LZ4SafeDecompressor} and {@link XXHash32}.
* This instance will decompress all concatenated frames in their sequential order.
*
* @param in the stream to decompress
* @throws IOException if an I/O error occurs
Expand All @@ -71,18 +73,50 @@ public LZ4FrameInputStream(InputStream in) throws IOException {
this(in, LZ4Factory.fastestInstance().safeDecompressor(), XXHashFactory.fastestInstance().hash32());
}

/**
* Creates a new {@link InputStream} that will decompress data using fastest instances of {@link LZ4SafeDecompressor} and {@link XXHash32}.
*
* @param in the stream to decompress
* @param readSingleFrame whether read is stopped after the first non-skippable frame
* @throws IOException if an I/O error occurs
*
* @see #LZ4FrameInputStream(InputStream, LZ4SafeDecompressor, XXHash32)
* @see LZ4Factory#fastestInstance()
* @see XXHashFactory#fastestInstance()
*/
public LZ4FrameInputStream(InputStream in, boolean readSingleFrame) throws IOException {
this(in, LZ4Factory.fastestInstance().safeDecompressor(), XXHashFactory.fastestInstance().hash32(), readSingleFrame);
}

/**
* Creates a new {@link InputStream} that will decompress data using the LZ4 algorithm.
* This instance will decompress all concatenated frames in their sequential order.
*
* @param in the stream to decompress
* @param decompressor the decompressor to use
* @param checksum the hash function to use
* @throws IOException if an I/O error occurs
*
* @see #LZ4FrameInputStream(InputStream, LZ4SafeDecompressor, XXHash32, boolean)
*/
public LZ4FrameInputStream(InputStream in, LZ4SafeDecompressor decompressor, XXHash32 checksum) throws IOException {
this(in, decompressor, checksum, false);
}

/**
* Creates a new {@link InputStream} that will decompress data using the LZ4 algorithm.
*
* @param in the stream to decompress
* @param decompressor the decompressor to use
* @param checksum the hash function to use
* @param readSingleFrame whether read is stopped after the first non-skippable frame
* @throws IOException if an I/O error occurs
*/
public LZ4FrameInputStream(InputStream in, LZ4SafeDecompressor decompressor, XXHash32 checksum, boolean readSingleFrame) throws IOException {
super(in);
this.decompressor = decompressor;
this.checksum = checksum;
this.readSingleFrame = readSingleFrame;
nextFrameInfo();
}

Expand Down Expand Up @@ -278,6 +312,9 @@ private void readBlock() throws IOException {
public int read() throws IOException {
while (buffer.remaining() == 0) {
if (frameInfo.isFinished()) {
if (readSingleFrame) {
return -1;
}
if (!nextFrameInfo()) {
return -1;
}
Expand All @@ -294,6 +331,9 @@ public int read(byte[] b, int off, int len) throws IOException {
}
while (buffer.remaining() == 0) {
if (frameInfo.isFinished()) {
if (readSingleFrame) {
return -1;
}
if (!nextFrameInfo()) {
return -1;
}
Expand All @@ -312,6 +352,9 @@ public long skip(long n) throws IOException {
}
while (buffer.remaining() == 0) {
if (frameInfo.isFinished()) {
if (readSingleFrame) {
return 0;
}
if (!nextFrameInfo()) {
return 0;
}
Expand Down Expand Up @@ -348,4 +391,33 @@ public boolean markSupported() {
return false;
}

/**
* Returns the optional Content Size value set in Frame Descriptor.
* If the Content Size is not set (FLG.Bits.CONTENT_SIZE not enabled) in compressed stream, -1L is returned.
* A call to this method is valid only when this instance is supposed to read only one frame (readSingleFrame == true).
*
* @return the expected content size, or -1L if no expected content size is set in the frame.
*
* @see #LZ4FrameInputStream(InputStream, LZ4SafeDecompressor, XXHash32, boolean)
*/
public long getExpectedContentSize() {
if (!readSingleFrame) {
throw new UnsupportedOperationException("Operation not permitted when multiple frames can be read");
}
return expectedContentSize;
}

/**
* Checks if the optionnal Content Size is set (FLG.Bits.CONTENT_SIZE is enabled).
*
* @return true if this instance is supposed to read only one frame and if the optional content size is set in the frame.
*/
public boolean isExpectedContentSizeDefined() {
if (readSingleFrame) {
return expectedContentSize >= 0;
} else {
return false;
}
}

}
47 changes: 44 additions & 3 deletions src/test/net/jpountz/lz4/LZ4FrameIOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -279,17 +279,42 @@ public void testInputOutputSkipped() throws IOException {
public void testStreamWithContentSize() throws IOException {
final File lz4File = Files.createTempFile("lz4test", ".lz4").toFile();
try {
final long knownSize = tmpFile.length();
try (OutputStream os = new LZ4FrameOutputStream(new FileOutputStream(lz4File),
LZ4FrameOutputStream.BLOCKSIZE.SIZE_4MB,
tmpFile.length(),
knownSize,
LZ4FrameOutputStream.FLG.Bits.BLOCK_INDEPENDENCE,
LZ4FrameOutputStream.FLG.Bits.CONTENT_CHECKSUM,
LZ4FrameOutputStream.FLG.Bits.CONTENT_SIZE)) {
try (InputStream is = new FileInputStream(tmpFile)) {
copy(is, os);
}
}
try (InputStream is = new LZ4FrameInputStream(new FileInputStream(lz4File))) {
try (LZ4FrameInputStream is = new LZ4FrameInputStream(new FileInputStream(lz4File), true)) {
Assert.assertEquals(knownSize, is.getExpectedContentSize());
Assert.assertTrue(is.isExpectedContentSizeDefined());
validateStreamEquals(is, tmpFile);
}
} finally {
lz4File.delete();
}
}

@Test
public void testStreamWithoutContentSize() throws IOException {
final File lz4File = Files.createTempFile("lz4test", ".lz4").toFile();
try {
try (OutputStream os = new LZ4FrameOutputStream(new FileOutputStream(lz4File),
LZ4FrameOutputStream.BLOCKSIZE.SIZE_4MB,
LZ4FrameOutputStream.FLG.Bits.BLOCK_INDEPENDENCE,
LZ4FrameOutputStream.FLG.Bits.CONTENT_CHECKSUM)) {
try (InputStream is = new FileInputStream(tmpFile)) {
copy(is, os);
}
}
try (LZ4FrameInputStream is = new LZ4FrameInputStream(new FileInputStream(lz4File), true)) {
Assert.assertEquals(-1L, is.getExpectedContentSize());
Assert.assertFalse(is.isExpectedContentSizeDefined());
validateStreamEquals(is, tmpFile);
}
} finally {
Expand Down Expand Up @@ -321,12 +346,28 @@ public void testInputOutputMultipleFrames() throws IOException {
}
}
}
try (InputStream is = new LZ4FrameInputStream(new FileInputStream(lz4File))) {
try (LZ4FrameInputStream is = new LZ4FrameInputStream(new FileInputStream(lz4File))) {
try {
is.getExpectedContentSize();
Assert.assertFalse(true);
} catch (UnsupportedOperationException e) {
// OK
}
Assert.assertFalse(is.isExpectedContentSizeDefined());
validateStreamEquals(is, tmpFile);
validateStreamEquals(is, tmpFile);
validateStreamEquals(is, tmpFile);
validateStreamEquals(is, tmpFile);
}
try (LZ4FrameInputStream is = new LZ4FrameInputStream(new FileInputStream(lz4File), true)) {
Assert.assertEquals(-1L, is.getExpectedContentSize());
Assert.assertFalse(is.isExpectedContentSizeDefined());
validateStreamEquals(is, tmpFile);
Assert.assertEquals(-1, is.read());
final byte[] tmpBuff = new byte[10];
Assert.assertEquals(-1, is.read(tmpBuff, 0, 10));
Assert.assertEquals(0, is.skip(1));
}
} finally {
lz4File.delete();
}
Expand Down