Skip to content

Commit ae94b50

Browse files
committed
Add method to stream decompressed data
1 parent e4ced04 commit ae94b50

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package software.coley.lljzip.format.compression;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.OutputStream;
6+
import java.lang.foreign.MemorySegment;
7+
import java.lang.foreign.ValueLayout;
8+
9+
final class MemorySegmentInputStream extends InputStream {
10+
private final MemorySegment data;
11+
private long read;
12+
private long markedOffset = -1;
13+
private long markedLimit;
14+
private volatile boolean closed;
15+
16+
MemorySegmentInputStream(MemorySegment data) {
17+
this.data = data;
18+
}
19+
20+
private void checkMarkLimit() {
21+
if (markedOffset > -1) {
22+
// Discard if we passed the read limit for our mark
23+
long diff = read - markedOffset;
24+
if (diff > markedLimit) {
25+
markedOffset = -1;
26+
}
27+
}
28+
}
29+
30+
@Override
31+
public boolean markSupported() {
32+
return true;
33+
}
34+
35+
@Override
36+
public synchronized void mark(int limit) {
37+
// Record current position and read-limit
38+
markedOffset = read;
39+
markedLimit = limit;
40+
}
41+
42+
@Override
43+
public synchronized void reset() {
44+
// Revert read to marked position.
45+
read = markedOffset;
46+
}
47+
48+
@Override
49+
public int read() throws IOException {
50+
ensureOpen();
51+
MemorySegment data = this.data;
52+
if (read >= data.byteSize()) {
53+
return -1;
54+
}
55+
byte b = data.get(ValueLayout.JAVA_BYTE, read++);
56+
checkMarkLimit();
57+
return b & 0xff;
58+
}
59+
60+
@Override
61+
public int read(byte[] b, int off, int len) throws IOException {
62+
ensureOpen();
63+
MemorySegment data = this.data;
64+
long read = this.read;
65+
long length = data.byteSize();
66+
if (read >= length) {
67+
return -1;
68+
}
69+
long remaining = length - read;
70+
len = (int) Math.min(remaining, len);
71+
MemorySegment.copy(data, read, MemorySegment.ofArray(b), off, len);
72+
this.read += len;
73+
checkMarkLimit();
74+
return len;
75+
}
76+
77+
@Override
78+
public byte[] readNBytes(int len) throws IOException {
79+
ensureOpen();
80+
MemorySegment data = this.data;
81+
long read = this.read;
82+
long length = data.byteSize();
83+
if (read >= length) {
84+
return new byte[0];
85+
}
86+
long remaining = length - read;
87+
len = (int) Math.min(remaining, len);
88+
byte[] buf = new byte[len];
89+
MemorySegment.copy(data, read, MemorySegment.ofArray(buf), 0, len);
90+
this.read += len;
91+
checkMarkLimit();
92+
return buf;
93+
}
94+
95+
@Override
96+
public long skip(long n) throws IOException {
97+
ensureOpen();
98+
MemorySegment data = this.data;
99+
long read = this.read;
100+
long length = data.byteSize();
101+
if (read >= length) {
102+
return 0;
103+
}
104+
n = Math.min(n, length - read);
105+
this.read += n;
106+
checkMarkLimit();
107+
return n;
108+
}
109+
110+
@Override
111+
public int available() throws IOException {
112+
ensureOpen();
113+
MemorySegment data = this.data;
114+
long length = data.byteSize();
115+
long read = this.read;
116+
if (read >= length) {
117+
return 0;
118+
}
119+
long remaining = length - read;
120+
if (remaining > Integer.MAX_VALUE)
121+
return Integer.MAX_VALUE;
122+
return (int) remaining;
123+
}
124+
125+
@Override
126+
public void close() throws IOException {
127+
closed = true;
128+
}
129+
130+
@Override
131+
public long transferTo(OutputStream out) throws IOException {
132+
ensureOpen();
133+
MemorySegment data = this.data;
134+
long length = data.byteSize();
135+
long read = this.read;
136+
if (read >= length) {
137+
return 0L;
138+
}
139+
long remaining = length - read;
140+
byte[] buffer = new byte[(int) Math.min(16384, remaining)];
141+
MemorySegment bufferSegment = MemorySegment.ofArray(buffer);
142+
while (read < length) {
143+
int copyable = (int) Math.min(buffer.length, length - read);
144+
MemorySegment.copy(data, read, bufferSegment, 0, copyable);
145+
out.write(buffer, 0, copyable);
146+
read += copyable;
147+
}
148+
this.read = length;
149+
checkMarkLimit();
150+
return remaining;
151+
}
152+
153+
private void ensureOpen() throws IOException {
154+
if (closed)
155+
throw new IOException("Stream closed");
156+
}
157+
}

src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java

+25
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import software.coley.lljzip.format.model.LocalFileHeader;
44

55
import java.io.IOException;
6+
import java.io.InputStream;
67
import java.lang.foreign.MemorySegment;
8+
import java.util.zip.DeflaterInputStream;
79

810
/**
911
* Constants for {@link LocalFileHeader#getCompressionMethod()}.
@@ -185,4 +187,27 @@ static MemorySegment decompress(LocalFileHeader header) throws IOException {
185187
}
186188
};
187189
}
190+
191+
/**
192+
* @param header
193+
* Header with {@link LocalFileHeader#getFileData()} to decompress.
194+
*
195+
* @return Stream with decompressed data.
196+
*
197+
* @throws IOException
198+
* When the decompression failed.
199+
*/
200+
static InputStream decompressStream(LocalFileHeader header) throws IOException {
201+
int method = header.getCompressionMethod();
202+
InputStream in = new MemorySegmentInputStream(header.getFileData());
203+
return switch (method) {
204+
case STORED -> in;
205+
case DEFLATED -> new DeflaterInputStream(in);
206+
default -> {
207+
// TODO: Support other decompressing techniques
208+
String methodName = getName(method);
209+
throw new IOException("Unsupported compression method: " + methodName);
210+
}
211+
};
212+
}
188213
}

0 commit comments

Comments
 (0)