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 method to stream decompressed data #30

Merged
merged 2 commits into from
Apr 28, 2024
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package software.coley.lljzip.format.compression;

import software.coley.lljzip.format.model.LocalFileHeader;
import software.coley.lljzip.util.MemorySegmentInputStream;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.lang.foreign.MemorySegment;
import java.util.zip.DeflaterInputStream;

/**
* Constants for {@link LocalFileHeader#getCompressionMethod()}.
Expand Down Expand Up @@ -173,7 +177,8 @@ static String getName(int method) {
* @throws IOException
* When the decompression failed.
*/
static MemorySegment decompress(LocalFileHeader header) throws IOException {
@Nonnull
static MemorySegment decompress(@Nonnull LocalFileHeader header) throws IOException {
int method = header.getCompressionMethod();
return switch (method) {
case STORED -> header.getFileData();
Expand All @@ -185,4 +190,28 @@ static MemorySegment decompress(LocalFileHeader header) throws IOException {
}
};
}

/**
* @param header
* Header with {@link LocalFileHeader#getFileData()} to decompress.
*
* @return Stream with decompressed data.
*
* @throws IOException
* When the decompression failed.
*/
@Nonnull
static InputStream decompressStream(@Nonnull LocalFileHeader header) throws IOException {
int method = header.getCompressionMethod();
InputStream in = new MemorySegmentInputStream(header.getFileData());
return switch (method) {
case STORED -> in;
case DEFLATED -> new DeflaterInputStream(in);
default -> {
// TODO: Support other decompressing techniques
String methodName = getName(method);
throw new IOException("Unsupported compression method: " + methodName);
}
};
}
}
184 changes: 184 additions & 0 deletions src/main/java/software/coley/lljzip/util/MemorySegmentInputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package software.coley.lljzip.util;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;

/**
* Input stream implementation backed by {@link MemorySegment}.
*
* @author xDark
*/
public class MemorySegmentInputStream extends InputStream {
private final MemorySegment data;
private long read;
private long markedOffset = -1;
private long markedLimit;
private volatile boolean closed;

public MemorySegmentInputStream(@Nonnull MemorySegment data) {
this.data = data;
}

@Nonnull
public MemorySegment getData() {
return data;
}

public long getRead() {
return read;
}

public long getMarkedOffset() {
return markedOffset;
}

public long getMarkedLimit() {
return markedLimit;
}

public boolean isClosed() {
return closed;
}

private void checkMarkLimit() {
if (markedOffset > -1) {
// Discard if we passed the read limit for our mark
long diff = read - markedOffset;
if (diff > markedLimit) {
markedOffset = -1;
}
}
}

@Override
public boolean markSupported() {
return true;
}

@Override
public synchronized void mark(int limit) {
// Record current position and read-limit
markedOffset = read;
markedLimit = limit;
}

@Override
public synchronized void reset() {
// Revert read to marked position.
read = markedOffset;
}

@Override
public int read() throws IOException {
ensureOpen();
MemorySegment data = this.data;
if (read >= data.byteSize()) {
return -1;
}
byte b = data.get(ValueLayout.JAVA_BYTE, read++);
checkMarkLimit();
return b & 0xff;
}

@Override
public int read(@Nonnull byte[] b, int off, int len) throws IOException {
ensureOpen();
MemorySegment data = this.data;
long read = this.read;
long length = data.byteSize();
if (read >= length) {
return -1;
}
long remaining = length - read;
len = (int) Math.min(remaining, len);
MemorySegment.copy(data, read, MemorySegment.ofArray(b), off, len);
this.read += len;
checkMarkLimit();
return len;
}

@Override
public byte[] readNBytes(int len) throws IOException {
ensureOpen();
MemorySegment data = this.data;
long read = this.read;
long length = data.byteSize();
if (read >= length) {
return new byte[0];
}
long remaining = length - read;
len = (int) Math.min(remaining, len);
byte[] buf = new byte[len];
MemorySegment.copy(data, read, MemorySegment.ofArray(buf), 0, len);
this.read += len;
checkMarkLimit();
return buf;
}

@Override
public long skip(long n) throws IOException {
ensureOpen();
MemorySegment data = this.data;
long read = this.read;
long length = data.byteSize();
if (read >= length) {
return 0;
}
n = Math.min(n, length - read);
this.read += n;
checkMarkLimit();
return n;
}

@Override
public int available() throws IOException {
ensureOpen();
MemorySegment data = this.data;
long length = data.byteSize();
long read = this.read;
if (read >= length) {
return 0;
}
long remaining = length - read;
if (remaining > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
return (int) remaining;
}

@Override
public void close() {
closed = true;
}

@Override
public long transferTo(OutputStream out) throws IOException {
ensureOpen();
MemorySegment data = this.data;
long length = data.byteSize();
long read = this.read;
if (read >= length) {
return 0L;
}
long remaining = length - read;
byte[] buffer = new byte[(int) Math.min(16384, remaining)];
MemorySegment bufferSegment = MemorySegment.ofArray(buffer);
while (read < length) {
int copyable = (int) Math.min(buffer.length, length - read);
MemorySegment.copy(data, read, bufferSegment, 0, copyable);
out.write(buffer, 0, copyable);
read += copyable;
}
this.read = length;
checkMarkLimit();
return remaining;
}

private void ensureOpen() throws IOException {
if (closed)
throw new IOException("Stream closed");
}
}
Loading