Skip to content

Commit ee61d73

Browse files
committed
* Added substream() abstract method to KaitaiStream
* Added implementations for ByteBuffer- (efficient) and RAF-backed (naive) concrete KaitaiStreams. * Started simple set of tests to test this all locally within the package.
1 parent 93b6268 commit ee61d73

7 files changed

Lines changed: 186 additions & 0 deletions

File tree

pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,13 @@
143143
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
144144
</repository>
145145
</distributionManagement>
146+
147+
<dependencies>
148+
<dependency>
149+
<groupId>org.testng</groupId>
150+
<artifactId>testng</artifactId>
151+
<version>RELEASE</version>
152+
<scope>test</scope>
153+
</dependency>
154+
</dependencies>
146155
</project>

src/main/java/io/kaitai/struct/ByteBufferKaitaiStream.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,18 @@ public byte[] readBytesTerm(byte term, boolean includeTerm, boolean consumeTerm,
361361
}
362362

363363
//endregion
364+
365+
@Override
366+
public KaitaiStream substream(long n) {
367+
if (n > Integer.MAX_VALUE) {
368+
throw new IllegalArgumentException("Java ByteBuffer can't be limited beyond Integer.MAX_VALUE");
369+
}
370+
371+
ByteBuffer newBuffer = bb.slice();
372+
newBuffer.limit((int) n);
373+
374+
bb.position(bb.position() + (int) n);
375+
376+
return new ByteBufferKaitaiStream(newBuffer);
377+
}
364378
}

src/main/java/io/kaitai/struct/KaitaiStream.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,15 @@ public static byte[] processZlib(byte[] data) {
434434

435435
//region Misc runtime operations
436436

437+
/**
438+
* Reserves next `n` bytes from current stream as a KaitaiStream-compatible substream.
439+
* Substream has its own pointer and addressing in the range of [0, n) bytes. This
440+
* stream's pointer is advanced to the position right after this substream.
441+
* @param n number of bytes to reserve for a substream
442+
* @return substream covering n bytes from the current position
443+
*/
444+
abstract public KaitaiStream substream(long n);
445+
437446
/**
438447
* Performs modulo operation between two integers: dividend `a`
439448
* and divisor `b`. Divisor `b` is expected to be positive. The

src/main/java/io/kaitai/struct/RandomAccessFileKaitaiStream.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,21 @@ public byte[] readBytesTerm(byte term, boolean includeTerm, boolean consumeTerm,
363363
}
364364
}
365365

366+
@Override
367+
public KaitaiStream substream(long n) {
368+
// This implementation mirrors what ksc was doing up to v0.10, and the fallback that
369+
// it is still doing in case something non-trivial has to happen with the byte contents.
370+
//
371+
// Given that RandomAccessFile-based stream is not really efficient anyway, this seems
372+
// to be a reasonable fallback without resorting to a special limiting implementation.
373+
//
374+
// If and when somebody will come up with a reason why substreams have to implemented
375+
// for RAF, feel free to contribute relevant implementation with some rationale (e.g. a
376+
// benchmark).
377+
378+
return new ByteBufferKaitaiStream(readBytes(n));
379+
}
380+
366381
//region Helper methods
367382

368383
private ByteBuffer wrapBufferLe(int count) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.kaitai.struct;
2+
3+
import java.nio.BufferUnderflowException;
4+
5+
public class ByteBufferKaitaiStreamTest extends KaitaiStreamTest {
6+
@org.testng.annotations.BeforeMethod
7+
public void setUp() {
8+
stream = new ByteBufferKaitaiStream(new byte[] { '1', '2', '3', '4', '5' });
9+
}
10+
11+
@Override
12+
Class getEOFClass() {
13+
return BufferUnderflowException.class;
14+
}
15+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.kaitai.struct;
2+
3+
import org.testng.annotations.Test;
4+
5+
import java.nio.BufferUnderflowException;
6+
7+
import static org.testng.Assert.*;
8+
9+
public abstract class KaitaiStreamTest {
10+
KaitaiStream stream;
11+
12+
abstract Class getEOFClass();
13+
14+
@org.testng.annotations.Test
15+
public void testReadS1() {
16+
short first = stream.readS1();
17+
assertEquals(first, 0x31);
18+
short second = stream.readS1();
19+
assertEquals(second, 0x32);
20+
}
21+
22+
@org.testng.annotations.Test
23+
public void testReadS2be() {
24+
short first = stream.readS2be();
25+
assertEquals(first, 0x3132);
26+
short second = stream.readS2be();
27+
assertEquals(second, 0x3334);
28+
29+
assertThrows(getEOFClass(), new ThrowingRunnable() {
30+
@Override
31+
public void run() throws Throwable {
32+
stream.readS2be();
33+
}
34+
});
35+
}
36+
37+
@org.testng.annotations.Test
38+
public void testReadBytes5() {
39+
byte[] actual = stream.readBytes(5);
40+
assertEquals(actual, new byte[] {'1', '2', '3', '4', '5'});
41+
}
42+
43+
@org.testng.annotations.Test
44+
public void testReadBytes6() {
45+
assertThrows(getEOFClass(), new ThrowingRunnable() {
46+
@Override
47+
public void run() throws Throwable {
48+
stream.readBytes(6);
49+
}
50+
});
51+
}
52+
53+
@org.testng.annotations.Test
54+
public void testSubstream() {
55+
stream.seek(1);
56+
assertEquals(stream.pos(), 1);
57+
58+
final KaitaiStream substream = stream.substream(3);
59+
60+
assertEquals(substream.pos(), 0);
61+
assertEquals(stream.pos(), 4);
62+
63+
byte byte0Sub = substream.readS1();
64+
assertEquals(byte0Sub, '2');
65+
assertEquals(substream.pos(), 1);
66+
assertEquals(stream.pos(), 4);
67+
68+
byte byte1Sub = substream.readS1();
69+
assertEquals(byte1Sub, '3');
70+
assertEquals(substream.pos(), 2);
71+
assertEquals(stream.pos(), 4);
72+
73+
byte byte4Main = stream.readS1();
74+
assertEquals(byte4Main, '5');
75+
assertEquals(substream.pos(), 2);
76+
assertEquals(stream.pos(), 5);
77+
78+
byte byte2Sub = substream.readS1();
79+
assertEquals(byte2Sub, '4');
80+
assertEquals(substream.pos(), 3);
81+
assertEquals(stream.pos(), 5);
82+
83+
assertThrows(getEOFClass(), new ThrowingRunnable() {
84+
@Override
85+
public void run() throws Throwable {
86+
substream.readS1();
87+
}
88+
});
89+
90+
assertTrue(substream.isEof());
91+
}
92+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.kaitai.struct;
2+
3+
import java.io.EOFException;
4+
import java.io.File;
5+
import java.io.FileWriter;
6+
import java.io.IOException;
7+
import java.nio.BufferUnderflowException;
8+
9+
import static org.testng.Assert.*;
10+
11+
public class RandomAccessFileKaitaiStreamTest extends KaitaiStreamTest {
12+
static String TEST_SCRATCH_DIR = "target/test-scratch";
13+
14+
@org.testng.annotations.BeforeMethod
15+
public void setUp() throws IOException {
16+
File testScratchDir = new File(TEST_SCRATCH_DIR);
17+
if (!testScratchDir.exists()) testScratchDir.mkdirs();
18+
19+
String testFileName = testScratchDir + "/12345.bin";
20+
21+
FileWriter writer = new FileWriter(testFileName);
22+
writer.write("12345");
23+
writer.close();
24+
25+
stream = new RandomAccessFileKaitaiStream(testFileName);
26+
}
27+
28+
@Override
29+
Class getEOFClass() {
30+
return RuntimeException.class;
31+
}
32+
}

0 commit comments

Comments
 (0)