Skip to content

Commit f1d82df

Browse files
committed
Implement Zlib decompression with inflater caching
1 parent 51a8dea commit f1d82df

3 files changed

Lines changed: 86 additions & 32 deletions

File tree

src/main/java/org/glavo/nbt/internal/input/InputContext.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@
2222

2323
import java.io.Closeable;
2424
import java.io.IOException;
25+
import java.util.ArrayList;
2526
import java.util.IdentityHashMap;
2627
import java.util.Map;
27-
import java.util.Objects;
28-
import java.util.function.Supplier;
2928

3029
public final class InputContext implements Closeable {
3130
// Used for reading UTF-8 strings
@@ -37,7 +36,7 @@ public final class InputContext implements Closeable {
3736
public final InputSource source;
3837
public final MinecraftEdition edition;
3938

40-
public final DataReader rawReader;
39+
public final RawDataReader rawReader;
4140

4241
public final StringCache stringCache = DEFAULT_CACHE;
4342
@Nullable StringBuilder charsBuffer;
@@ -69,26 +68,46 @@ public void releaseDecompressBuffer(InputBuffer buffer) {
6968
}
7069

7170
@Override
71+
@SuppressWarnings({"rawtypes", "unchecked"})
7272
public void close() throws IOException {
7373
source.close();
74+
if (cacheMap != null) {
75+
for (var entry : new ArrayList<>(cacheMap.entrySet())) {
76+
((CacheKey) entry.getKey()).close(entry.getValue());
77+
}
78+
79+
cacheMap.clear();
80+
}
7481
}
7582

7683
@SuppressWarnings("unchecked")
77-
public static final class CacheKey<T> {
78-
79-
public @Nullable T get(InputContext context) {
80-
return context.cacheMap != null ? (T) context.cacheMap.remove(this) : null;
81-
}
84+
public static abstract class CacheKey<T> {
85+
86+
public T get(InputContext context) {
87+
if (context.cacheMap != null) {
88+
T value = (T) context.cacheMap.remove(this);
89+
if (value != null) {
90+
return value;
91+
}
92+
}
8293

83-
public T getOrCreate(InputContext context, Supplier<? extends T> supplier) {
84-
return Objects.requireNonNullElseGet(get(context), supplier);
94+
return create(context);
8595
}
8696

87-
public void put(InputContext context, T value) {
97+
public void release(InputContext context, T value) {
8898
if (context.cacheMap == null) {
8999
context.cacheMap = new IdentityHashMap<>();
90100
}
91-
context.cacheMap.put(this, value);
101+
102+
T oldValue = (T) context.cacheMap.put(this, value);
103+
if (oldValue != null) {
104+
close(oldValue);
105+
}
106+
}
107+
108+
protected abstract T create(InputContext context);
109+
110+
public void close(T value) {
92111
}
93112
}
94113
}

src/main/java/org/glavo/nbt/internal/input/RawDataReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import java.io.EOFException;
1919
import java.io.IOException;
2020

21-
final class RawDataReader extends DataReader {
21+
public final class RawDataReader extends DataReader {
2222

2323
/// Used for the default reader;
2424
RawDataReader(InputContext context, InputBuffer buffer) {

src/main/java/org/glavo/nbt/internal/input/ZlibDataReader.java

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,27 @@
1818
import java.io.EOFException;
1919
import java.io.IOException;
2020
import java.nio.ByteBuffer;
21+
import java.util.zip.DataFormatException;
2122
import java.util.zip.Inflater;
2223

2324
public final class ZlibDataReader extends DataReader {
24-
static final InputContext.CacheKey<Inflater> INFLATER_CACHE_KEY = new InputContext.CacheKey<>();
25+
static final InputContext.CacheKey<Inflater> INFLATER_CACHE_KEY = new InputContext.CacheKey<>() {
26+
27+
@Override
28+
protected Inflater create(InputContext context) {
29+
return new Inflater();
30+
}
31+
32+
@Override
33+
public void close(Inflater value) {
34+
value.end();
35+
}
36+
};
2537
private final Inflater inflater;
2638

2739
ZlibDataReader(InputContext context, long compressedSize) {
2840
super(context, context.getDecompressBuffer());
29-
this.inflater = INFLATER_CACHE_KEY.getOrCreate(context, Inflater::new);
41+
this.inflater = INFLATER_CACHE_KEY.get(context);
3042
this.remainingInput = compressedSize;
3143
}
3244

@@ -36,23 +48,46 @@ public void ensureBufferRemaining(int required) throws IOException {
3648
return;
3749
}
3850

51+
if (inflater.finished() || inflater.needsDictionary()) {
52+
throw new EOFException("Inflater finished or needs dictionary");
53+
}
54+
3955
buffer.ensureCapacity(required);
40-
ByteBuffer byteBuffer = this.buffer.getByteBuffer();
41-
42-
// TODO
43-
44-
// do {
45-
// if (inflater.finished() || inflater.needsDictionary()) {
46-
// throw new EOFException();
47-
// }
48-
//
49-
// if (inflater.needsInput()) {
50-
// if (context.rawReader.buffer.remaining() == 0) {
51-
// context.rawReader.ensureBufferRemaining(1);
52-
// }
53-
//
54-
// inflater.setInput(context.rawReader.buffer.getByteBuffer());
55-
// }
56-
// } while ((n = inflater.inflate(b, off, len)) == 0);
56+
ByteBuffer output = this.buffer.getByteBuffer();
57+
output.compact();
58+
59+
do {
60+
if (inflater.finished() || inflater.needsDictionary()) {
61+
throw new EOFException();
62+
}
63+
64+
if (inflater.needsInput()) {
65+
if (context.rawReader.buffer.remaining() == 0) {
66+
context.rawReader.ensureBufferRemaining(1);
67+
}
68+
inflater.setInput(context.rawReader.buffer.getByteBuffer());
69+
}
70+
71+
try {
72+
inflater.inflate(output);
73+
} catch (DataFormatException exception) {
74+
throw new IOException(exception);
75+
}
76+
77+
} while (output.position() < required);
78+
79+
inflater.setInput(EMPTY_BYTE_ARRAY);
80+
output.flip();
81+
}
82+
83+
@Override
84+
public void close() throws IOException {
85+
inflater.end();
86+
context.releaseDecompressBuffer(buffer);
87+
88+
inflater.reset();
89+
INFLATER_CACHE_KEY.release(context, inflater);
5790
}
91+
92+
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
5893
}

0 commit comments

Comments
 (0)