Skip to content

Commit abe2c77

Browse files
committed
Refactor NBTReader to DataReader and add InputContext
1 parent c1ec4a4 commit abe2c77

18 files changed

Lines changed: 210 additions & 86 deletions

src/main/java/org/glavo/nbt/internal/input/NBTReader.java renamed to src/main/java/org/glavo/nbt/internal/input/DataReader.java

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,59 +16,34 @@
1616
package org.glavo.nbt.internal.input;
1717

1818
import org.glavo.nbt.MinecraftEdition;
19-
import org.glavo.nbt.internal.IOUtils;
20-
import org.glavo.nbt.internal.StringCache;
21-
import org.jetbrains.annotations.Nullable;
2219

2320
import java.io.Closeable;
2421
import java.io.IOException;
2522
import java.nio.ByteBuffer;
2623
import java.nio.charset.StandardCharsets;
2724

28-
public final class NBTReader implements Closeable {
29-
30-
// Used for reading UTF-8 strings
31-
private static final StringCache CACHE = new StringCache(
32-
"data", "Data", "DataVersion"
33-
// TODO: More tag names
34-
);
35-
36-
private final InputSource source;
37-
private final InputBuffer buffer;
38-
private final MinecraftEdition edition;
39-
private final long initialPosition;
40-
41-
/// Used for reading UTF-8 strings
42-
private @Nullable StringBuilder charsBuffer;
43-
44-
public NBTReader(InputSource source, MinecraftEdition edition) throws IOException {
45-
this.source = source;
46-
this.buffer = InputBuffer.allocate(IOUtils.DEFAULT_BUFFER_SIZE, source.supportDirectBuffer(), edition.byteOrder());
47-
this.edition = edition;
48-
49-
try {
50-
this.initialPosition = source.position();
51-
} catch (Throwable e) {
52-
try {
53-
source.close();
54-
} catch (IOException e2) {
55-
e.addSuppressed(e2);
56-
}
57-
throw e;
58-
}
59-
}
25+
public abstract class DataReader implements Closeable {
26+
final InputContext context;
27+
final InputBuffer buffer;
28+
long remainingInput = -1L;
6029

61-
public MinecraftEdition getEdition() {
62-
return edition;
30+
protected DataReader(InputContext context, InputBuffer buffer) {
31+
this.context = context;
32+
this.buffer = buffer;
6333
}
6434

35+
public abstract void ensureBufferRemaining(int required) throws IOException;
36+
6537
@Override
6638
public void close() throws IOException {
67-
source.close();
68-
}
69-
70-
private void fillBuffer(int required) throws IOException {
71-
source.fillBuffer(buffer, required);
39+
if (remainingInput > 0) {
40+
int bufferRemaining = context.rawReader.buffer.remaining();
41+
if (bufferRemaining > 0) {
42+
int bytesDrop = (int) Math.min(bufferRemaining, remainingInput);
43+
context.rawReader.buffer.drop(bytesDrop);
44+
}
45+
this.remainingInput -= bufferRemaining;
46+
}
7247
}
7348

7449
public byte[] readByteArray() throws IOException {
@@ -77,7 +52,7 @@ public byte[] readByteArray() throws IOException {
7752
throw new IOException("Array length too large");
7853
}
7954

80-
fillBuffer(len);
55+
ensureBufferRemaining(len);
8156
return buffer.getByteArray(len);
8257
}
8358

@@ -87,7 +62,7 @@ public int[] readIntArray() throws IOException {
8762
throw new IOException("Array length too large");
8863
}
8964

90-
fillBuffer(len * Integer.BYTES);
65+
ensureBufferRemaining(len * Integer.BYTES);
9166
return buffer.getIntArray(len);
9267
}
9368

@@ -97,13 +72,13 @@ public long[] readLongArray() throws IOException {
9772
throw new IOException("Array length too large");
9873
}
9974

100-
fillBuffer(len * Long.BYTES);
75+
ensureBufferRemaining(len * Long.BYTES);
10176
return buffer.getLongArray(len);
10277
}
10378

10479
/// Read a byte from the input stream.
10580
public byte readByte() throws IOException {
106-
fillBuffer(Byte.BYTES);
81+
ensureBufferRemaining(Byte.BYTES);
10782
return buffer.getByte();
10883
}
10984

@@ -114,7 +89,7 @@ public int readUnsignedByte() throws IOException {
11489

11590
/// Read a short from the input stream.
11691
public short readShort() throws IOException {
117-
fillBuffer(Short.BYTES);
92+
ensureBufferRemaining(Short.BYTES);
11893
return buffer.getShort();
11994
}
12095

@@ -125,7 +100,7 @@ public int readUnsignedShort() throws IOException {
125100

126101
/// Read an int from the input stream.
127102
public int readInt() throws IOException {
128-
fillBuffer(Integer.BYTES);
103+
ensureBufferRemaining(Integer.BYTES);
129104
return buffer.getInt();
130105
}
131106

@@ -136,24 +111,24 @@ public long readUnsignedInt() throws IOException {
136111

137112
/// Read a long from the input stream.
138113
public long readLong() throws IOException {
139-
fillBuffer(Long.BYTES);
114+
ensureBufferRemaining(Long.BYTES);
140115
return buffer.getLong();
141116
}
142117

143118
/// Read a float from the input stream.
144119
public float readFloat() throws IOException {
145-
fillBuffer(Float.BYTES);
120+
ensureBufferRemaining(Float.BYTES);
146121
return buffer.getFloat();
147122
}
148123

149124
/// Read a double from the input stream.
150125
public double readDouble() throws IOException {
151-
fillBuffer(Double.BYTES);
126+
ensureBufferRemaining(Double.BYTES);
152127
return buffer.getDouble();
153128
}
154129

155130
private String getUTF8(ByteBuffer buffer, int offset, int length) {
156-
String cached = CACHE.get(buffer, offset, length);
131+
String cached = context.stringCache.get(buffer, offset, length);
157132
if (cached != null) {
158133
return cached;
159134
}
@@ -178,7 +153,7 @@ public String readString() throws IOException {
178153
return "";
179154
}
180155

181-
fillBuffer(len);
156+
ensureBufferRemaining(len);
182157

183158
ByteBuffer bytes = buffer.bytesBuffer();
184159
int offset = bytes.position();
@@ -187,7 +162,7 @@ public String readString() throws IOException {
187162
bytes.position(limit);
188163

189164
// For Minecraft Bedrock Edition, the string is encoded in standard UTF-8
190-
if (edition == MinecraftEdition.BEDROCK_EDITION) {
165+
if (context.edition == MinecraftEdition.BEDROCK_EDITION) {
191166
return getUTF8(bytes, offset, len);
192167
}
193168

@@ -207,10 +182,13 @@ public String readString() throws IOException {
207182
}
208183

209184
// Slow path
210-
if (charsBuffer != null) {
211-
charsBuffer.setLength(0);
185+
StringBuilder charsBuffer;
186+
if (context.charsBuffer != null) {
187+
charsBuffer = context.charsBuffer;
188+
context.charsBuffer.setLength(0);
212189
} else {
213190
charsBuffer = new StringBuilder(len);
191+
context.charsBuffer = charsBuffer;
214192
}
215193

216194
int c, char2, char3;
@@ -262,5 +240,4 @@ public String readString() throws IOException {
262240
}
263241
return charsBuffer.toString();
264242
}
265-
266243
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.glavo.nbt.internal.input;
1717

18+
import java.nio.BufferOverflowException;
1819
import java.nio.ByteBuffer;
1920
import java.nio.ByteOrder;
2021

@@ -43,6 +44,26 @@ public ByteOrder order() {
4344
return bytesBuffer.order();
4445
}
4546

47+
public void drop() {
48+
bytesBuffer.position(0);
49+
bytesBuffer.limit(0);
50+
}
51+
52+
public void drop(int n) {
53+
int remaining = this.remaining();
54+
if (n < remaining) {
55+
bytesBuffer.position(bytesBuffer.position() + n);
56+
} else if (n == remaining) {
57+
drop();
58+
} else {
59+
throw new BufferOverflowException();
60+
}
61+
}
62+
63+
public int remaining() {
64+
return bytesBuffer.remaining();
65+
}
66+
4667
public byte getByte() {
4768
return bytesBuffer.get();
4869
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2026 Glavo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.glavo.nbt.internal.input;
17+
18+
import org.glavo.nbt.MinecraftEdition;
19+
import org.glavo.nbt.internal.IOUtils;
20+
import org.glavo.nbt.internal.StringCache;
21+
import org.jetbrains.annotations.Nullable;
22+
23+
import java.io.Closeable;
24+
import java.io.IOException;
25+
26+
public final class InputContext implements Closeable {
27+
// Used for reading UTF-8 strings
28+
private static final StringCache DEFAULT_CACHE = new StringCache(
29+
"data", "Data", "DataVersion"
30+
// TODO: More tag names
31+
);
32+
33+
public final InputSource source;
34+
public final MinecraftEdition edition;
35+
36+
@Nullable StringBuilder charsBuffer;
37+
38+
public final DataReader rawReader;
39+
40+
public final StringCache stringCache = DEFAULT_CACHE;
41+
42+
public InputContext(InputSource source, MinecraftEdition edition) {
43+
this.source = source;
44+
this.edition = edition;
45+
this.rawReader = new RawDataReader(
46+
this,
47+
InputBuffer.allocate(IOUtils.DEFAULT_BUFFER_SIZE, source.supportDirectBuffer(), edition.byteOrder()));
48+
}
49+
50+
@Override
51+
public void close() throws IOException {
52+
source.close();
53+
}
54+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2026 Glavo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.glavo.nbt.internal.input;
17+
18+
import java.io.EOFException;
19+
import java.io.IOException;
20+
21+
final class RawDataReader extends DataReader {
22+
23+
/// Used for the default reader;
24+
RawDataReader(InputContext context, InputBuffer buffer) {
25+
super(context, buffer);
26+
}
27+
28+
public RawDataReader(InputContext context, long limit) {
29+
super(context, context.rawReader.buffer);
30+
this.remainingInput = limit;
31+
}
32+
33+
@Override
34+
public void ensureBufferRemaining(int required) throws IOException {
35+
if (remainingInput >= 0 && remainingInput < required) {
36+
throw new EOFException("Not enough data to read, required: " + required + ", remaining: " + remainingInput);
37+
}
38+
39+
context.source.fillBuffer(buffer, required);
40+
}
41+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2026 Glavo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.glavo.nbt.internal.input;
17+
18+
import java.io.IOException;
19+
20+
public final class ZlibDataReader extends DataReader {
21+
ZlibDataReader(InputContext context, InputBuffer buffer) {
22+
super(context, buffer);
23+
}
24+
25+
@Override
26+
public void ensureBufferRemaining(int required) throws IOException {
27+
28+
}
29+
}

src/main/java/org/glavo/nbt/tag/ByteArrayTag.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.glavo.nbt.tag;
1717

18-
import org.glavo.nbt.internal.input.NBTReader;
18+
import org.glavo.nbt.internal.input.DataReader;
1919

2020
import java.io.IOException;
2121
import java.util.Arrays;
@@ -103,7 +103,7 @@ public Byte next() {
103103
}
104104

105105
@Override
106-
protected void readContent(NBTReader reader) throws IOException {
106+
protected void readContent(DataReader reader) throws IOException {
107107
value = reader.readByteArray();
108108
}
109109

src/main/java/org/glavo/nbt/tag/ByteTag.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.glavo.nbt.tag;
1717

18-
import org.glavo.nbt.internal.input.NBTReader;
18+
import org.glavo.nbt.internal.input.DataReader;
1919

2020
import java.io.IOException;
2121

@@ -75,7 +75,7 @@ public void setBoolean(boolean value) {
7575
}
7676

7777
@Override
78-
protected void readContent(NBTReader reader) throws IOException {
78+
protected void readContent(DataReader reader) throws IOException {
7979
set(reader.readByte());
8080
}
8181

0 commit comments

Comments
 (0)