Skip to content

Commit 47f45d1

Browse files
committed
Implement GZIPCompressingInputStream, re #54
1 parent c5e70f6 commit 47f45d1

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* Copyright (c) 2024 LibJ
2+
*
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* You should have received a copy of The MIT License (MIT) along with this
14+
* program. If not, see <http://opensource.org/licenses/MIT/>.
15+
*/
16+
17+
package org.libj.util.zip;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.OutputStream;
22+
import java.util.zip.DeflaterOutputStream;
23+
24+
/**
25+
* Compresses an {@link InputStream} in a memory-optimal, on-demand way only compressing enough to fill a buffer.
26+
*/
27+
public class GZIPCompressingInputStream extends InputStream {
28+
private final InputStream in;
29+
private final DeflaterOutputStream out;
30+
private final byte[] readBuf = new byte[8192];
31+
32+
private byte[] buf = new byte[8192];
33+
private int read = 0;
34+
private int write = 0;
35+
36+
public GZIPCompressingInputStream(final InputStream in) {
37+
this.in = in;
38+
this.out = new UnsynchronizedGZIPOutputStream(new OutputStream() {
39+
private void ensureCapacity(final int len) {
40+
final int length = buf.length;
41+
if (write + len >= length) {
42+
final byte[] newbuf = new byte[(length + len) * 2];
43+
System.arraycopy(buf, 0, newbuf, 0, length);
44+
buf = newbuf;
45+
}
46+
}
47+
48+
@Override
49+
public void write(final byte[] b, final int off, final int len) throws IOException {
50+
ensureCapacity(len);
51+
System.arraycopy(b, off, buf, write, len);
52+
write += len;
53+
}
54+
55+
@Override
56+
public void write(final int b) throws IOException {
57+
ensureCapacity(1);
58+
buf[write++] = (byte)b;
59+
}
60+
});
61+
}
62+
63+
private void compressStream() throws IOException {
64+
// If the reader has caught up with the writer, then zero the positions out.
65+
if (read == write) {
66+
read = 0;
67+
write = 0;
68+
}
69+
70+
while (write == 0) {
71+
// Feed the gzip stream data until it spits out a block.
72+
final int val = in.read(readBuf);
73+
if (val > 0) {
74+
out.write(readBuf, 0, val);
75+
}
76+
else if (val == -1) {
77+
// Nothing left to do, we've hit the end of the stream, so close and break out.
78+
out.close();
79+
break;
80+
}
81+
}
82+
}
83+
84+
@Override
85+
public int read(final byte[] b, final int off, final int len) throws IOException {
86+
compressStream();
87+
final int noBytes = Math.min(len, write - read);
88+
if (noBytes > 0) {
89+
System.arraycopy(buf, read, b, off, noBytes);
90+
read += noBytes;
91+
}
92+
else if (len > 0) {
93+
// If bytes were requested, but we have none, then we're at the end of the stream.
94+
return -1;
95+
}
96+
97+
return noBytes;
98+
}
99+
100+
@Override
101+
public int read() throws IOException {
102+
compressStream();
103+
return write == 0 ? -1 : buf[read++] & 0xFF;
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.libj.util.zip;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.io.PipedInputStream;
10+
import java.io.PipedOutputStream;
11+
import java.util.Random;
12+
import java.util.zip.GZIPInputStream;
13+
14+
import org.junit.Test;
15+
import org.libj.lang.Strings;
16+
17+
public class GZIPCompressingInputStreamTest {
18+
private static void test(final String str) throws IOException {
19+
final byte[] bytes = str.getBytes();
20+
final InputStream in = new GZIPCompressingInputStream(new ByteArrayInputStream(bytes));
21+
final PipedInputStream pin = new PipedInputStream(8192);
22+
try (final PipedOutputStream pout = new PipedOutputStream(pin)) {
23+
for (int c; (c = in.read()) != -1;) // [ST]
24+
pout.write(c);
25+
}
26+
27+
final GZIPInputStream zin = new GZIPInputStream(pin);
28+
final ByteArrayOutputStream out = new ByteArrayOutputStream();
29+
for (int c; (c = zin.read()) != -1;) // [ST]
30+
out.write(c);
31+
32+
assertArrayEquals(bytes, out.toByteArray());
33+
}
34+
35+
@Test
36+
public void test() throws IOException {
37+
final Random random = new Random();
38+
for (int i = 0; i < 100; ++i)
39+
test(Strings.getRandomAlphaNumeric(random, i * i + 1));
40+
}
41+
}

0 commit comments

Comments
 (0)