├── .travis.yml
├── .gitignore
├── src
├── main
│ └── java
│ │ └── me
│ │ └── hugmanrique
│ │ └── jacobin
│ │ ├── base
│ │ ├── package-info.java
│ │ ├── BaseByteStreamWriter.java
│ │ └── BaseByteStreamReader.java
│ │ ├── inout
│ │ ├── package-info.java
│ │ ├── BigEndianInOutByteStream.java
│ │ ├── LittleEndianInOutByteStream.java
│ │ ├── InOutByteStreamBuilder.java
│ │ └── InOutByteStream.java
│ │ ├── reader
│ │ ├── package-info.java
│ │ ├── BigEndianByteStreamReader.java
│ │ ├── LittleEndianByteStreamReader.java
│ │ ├── ByteStreamReaderBuilder.java
│ │ └── ByteStreamReader.java
│ │ ├── writer
│ │ ├── package-info.java
│ │ ├── BigEndianByteStreamWriter.java
│ │ ├── LittleEndianByteStreamWriter.java
│ │ ├── ByteStreamWriter.java
│ │ └── ByteStreamWriterBuilder.java
│ │ ├── order
│ │ ├── BigEndianReader.java
│ │ ├── BigEndianWriter.java
│ │ ├── LittleEndianReader.java
│ │ ├── LittleEndianWriter.java
│ │ ├── ByteOrderWriter.java
│ │ └── ByteOrderReader.java
│ │ └── util
│ │ └── Unsigned.java
└── test
│ └── java
│ └── me
│ └── hugmanrique
│ └── jacobin
│ ├── UnsignedTest.java
│ ├── inout
│ ├── InOutByteStreamTest.java
│ └── LittleEndianInOutByteStreamTest.java
│ ├── writer
│ ├── WriterSkipTest.java
│ ├── LittleEndianByteStreamWriterTest.java
│ └── ByteStreamWriterTest.java
│ └── reader
│ ├── LittleEndianByteStreamReaderTest.java
│ ├── BigEndianByteStreamReaderTest.java
│ ├── ReaderPositionTest.java
│ └── ByteStreamReaderTest.java
├── overview.html
├── LICENSE
├── README.md
└── pom.xml
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | notifications:
4 | email: false
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | out/
3 |
4 | .idea
5 | *.iml
6 |
7 | dependency-reduced-pom.xml
8 | bash.exe.stackdump
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/base/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains base abstract implementations for
3 | * {@link me.hugmanrique.jacobin.reader.ByteStreamReader}s and
4 | * {@link me.hugmanrique.jacobin.writer.ByteStreamWriter}s.
5 | *
6 | * @author Hugo Manrique
7 | * @since 03/09/2018
8 | */
9 | package me.hugmanrique.jacobin.base;
10 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/inout/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains {@link me.hugmanrique.jacobin.inout.InOutByteStream}
3 | * implementations for different byte orders, as well as a builder class
4 | * ({@link me.hugmanrique.jacobin.inout.InOutByteStreamBuilder}).
5 | * @author Hugo Manrique
6 | * @since 03/09/2018
7 | */
8 | package me.hugmanrique.jacobin.inout;
9 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/reader/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains {@link me.hugmanrique.jacobin.reader.ByteStreamReader}
3 | * implementations for different byte orders, as well as a builder class
4 | * ({@link me.hugmanrique.jacobin.reader.ByteStreamReaderBuilder}).
5 | * @author Hugo Manrique
6 | * @since 03/09/2018
7 | */
8 | package me.hugmanrique.jacobin.reader;
9 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/writer/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains {@link me.hugmanrique.jacobin.writer.ByteStreamWriter}
3 | * implementations for different byte orders, as well as a builder class
4 | * ({@link me.hugmanrique.jacobin.writer.ByteStreamWriterBuilder}).
5 | *
6 | * @author Hugo Manrique
7 | * @since 03/09/2018
8 | */
9 | package me.hugmanrique.jacobin.writer;
10 |
--------------------------------------------------------------------------------
/overview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Jacobin provides high performance binary streams reader and writer implementations
4 | located at the {@link me.hugmanrique.jacobin.reader} and {@link me.hugmanrique.jacobin.writer}
5 | packages respectively, as well as bidirectional stream implementations located at the
6 | {@link me.hugmanrique.jacobin.inout} package. Both little-endian and big-endian implementations
7 | are available to ease the reading of larger int types without the cost and complexities associated with using
8 | {@link java.nio.ByteBuffer}.
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/reader/BigEndianByteStreamReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import me.hugmanrique.jacobin.base.BaseByteStreamReader;
4 | import me.hugmanrique.jacobin.order.BigEndianReader;
5 |
6 | import java.io.InputStream;
7 |
8 | /**
9 | * Big-endian implementation of {@link ByteStreamReader}.
10 | *
11 | * @author Hugo Manrique
12 | * @since 02/09/2018
13 | */
14 | public class BigEndianByteStreamReader extends BaseByteStreamReader implements BigEndianReader {
15 | public BigEndianByteStreamReader(InputStream stream) {
16 | super(stream);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/writer/BigEndianByteStreamWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import me.hugmanrique.jacobin.base.BaseByteStreamWriter;
4 | import me.hugmanrique.jacobin.order.BigEndianWriter;
5 |
6 | import java.io.OutputStream;
7 |
8 | /**
9 | * Big-endian implementation of {@link ByteStreamWriter}.
10 | *
11 | * @author Hugo Manrique
12 | * @since 03/09/2018
13 | */
14 | public class BigEndianByteStreamWriter extends BaseByteStreamWriter implements BigEndianWriter {
15 | public BigEndianByteStreamWriter(OutputStream stream) {
16 | super(stream);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/reader/LittleEndianByteStreamReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import me.hugmanrique.jacobin.base.BaseByteStreamReader;
4 | import me.hugmanrique.jacobin.order.LittleEndianReader;
5 |
6 | import java.io.InputStream;
7 |
8 | /**
9 | * Little-endian implementation of {@link ByteStreamReader}.
10 | *
11 | * @author Hugo Manrique
12 | * @since 02/09/2018
13 | */
14 | public class LittleEndianByteStreamReader extends BaseByteStreamReader implements LittleEndianReader {
15 | public LittleEndianByteStreamReader(InputStream stream) {
16 | super(stream);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/writer/LittleEndianByteStreamWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import me.hugmanrique.jacobin.base.BaseByteStreamWriter;
4 | import me.hugmanrique.jacobin.order.LittleEndianWriter;
5 |
6 | import java.io.OutputStream;
7 |
8 | /**
9 | * Little-endian implementation of {@link ByteStreamWriter}.
10 | *
11 | * @author Hugo Manrique
12 | * @since 03/09/2018
13 | */
14 | public class LittleEndianByteStreamWriter extends BaseByteStreamWriter implements LittleEndianWriter {
15 | public LittleEndianByteStreamWriter(OutputStream stream) {
16 | super(stream);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/inout/BigEndianInOutByteStream.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.inout;
2 |
3 | import me.hugmanrique.jacobin.order.BigEndianReader;
4 | import me.hugmanrique.jacobin.order.BigEndianWriter;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | /**
10 | * Big-endian implementation of {@link InOutByteStream}.
11 | *
12 | * @author Hugo Manrique
13 | * @since 03/09/2018
14 | */
15 | public class BigEndianInOutByteStream extends InOutByteStream implements BigEndianReader, BigEndianWriter {
16 | public BigEndianInOutByteStream(File file, boolean synchronousWrites) throws IOException {
17 | super(file, synchronousWrites);
18 | }
19 |
20 | public BigEndianInOutByteStream(File file) throws IOException {
21 | super(file);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/inout/LittleEndianInOutByteStream.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.inout;
2 |
3 | import me.hugmanrique.jacobin.order.LittleEndianReader;
4 | import me.hugmanrique.jacobin.order.LittleEndianWriter;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | /**
10 | * Little-endian implementation of {@link InOutByteStream}.
11 | *
12 | * @author Hugo Manrique
13 | * @since 03/09/2018
14 | */
15 | public class LittleEndianInOutByteStream extends InOutByteStream implements LittleEndianReader, LittleEndianWriter {
16 | public LittleEndianInOutByteStream(File file, boolean synchronousWrites) throws IOException {
17 | super(file, synchronousWrites);
18 | }
19 |
20 | public LittleEndianInOutByteStream(File file) throws IOException {
21 | super(file);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/UnsignedTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin;
2 |
3 | import me.hugmanrique.jacobin.util.Unsigned;
4 | import org.junit.Test;
5 |
6 | import static org.junit.Assert.assertEquals;
7 |
8 | /**
9 | * @author Hugo Manrique
10 | * @since 02/09/2018
11 | */
12 | public class UnsignedTest {
13 | @Test
14 | public void testUnsigned() {
15 | assertEquals(
16 | Short.toUnsignedInt(Short.MIN_VALUE),
17 | Unsigned.unsignedInt16(Short.MIN_VALUE)
18 | );
19 |
20 | assertEquals(
21 | Integer.toUnsignedLong(Integer.MIN_VALUE),
22 | Unsigned.unsignedInt32(Integer.MIN_VALUE)
23 | );
24 |
25 | assertEquals(
26 | Long.toUnsignedString(Long.MIN_VALUE),
27 | Long.toUnsignedString(Unsigned.unsignedInt64(Long.MIN_VALUE))
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/inout/InOutByteStreamTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.inout;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.nio.ByteOrder;
6 |
7 | /**
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public class InOutByteStreamTest {
12 | private static int fileCount = 0;
13 |
14 | protected final ByteOrder byteOrder;
15 |
16 | public InOutByteStreamTest(ByteOrder byteOrder) {
17 | this.byteOrder = byteOrder;
18 | }
19 |
20 | private File createTempFile() throws IOException {
21 | File temp = File.createTempFile(byteOrder + "inOutTest" + fileCount++, ".tmp");
22 | temp.deleteOnExit();
23 |
24 | return temp;
25 | }
26 |
27 | protected InOutByteStream createStream() throws IOException {
28 | return new InOutByteStreamBuilder()
29 | .file(createTempFile())
30 | .order(byteOrder)
31 | .build();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/writer/WriterSkipTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import me.hugmanrique.jacobin.reader.ByteStreamReader;
4 | import org.junit.Test;
5 |
6 | import java.io.IOException;
7 | import java.nio.ByteOrder;
8 |
9 | import static org.junit.Assert.assertEquals;
10 |
11 | /**
12 | * @author Hugo Manrique
13 | * @since 03/09/2018
14 | */
15 | public class WriterSkipTest extends ByteStreamWriterTest {
16 | public WriterSkipTest() {
17 | super(ByteOrder.LITTLE_ENDIAN);
18 | }
19 |
20 | @Test
21 | public void testPositiveSkip() throws Exception {
22 | // Write two 0x0 bytes
23 | writer.skip(2);
24 |
25 | ByteStreamReader reader = newReader();
26 | assertEquals(0x0, reader.readByte());
27 | assertEquals(0x0, reader.readByte());
28 | assertEquals(2, writer.getOffset());
29 | }
30 |
31 | @Test(expected = IllegalArgumentException.class)
32 | public void testNegativeSkip() throws IOException {
33 | writer.skip(-2);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/reader/LittleEndianByteStreamReaderTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * @author Hugo Manrique
7 | * @since 02/09/2018
8 | */
9 | public class LittleEndianByteStreamReaderTest extends ByteStreamReaderTest {
10 | @Test
11 | public void testLittleEndian() throws Exception {
12 | assertReadByte(bytes(0x12), 0x12);
13 | assertReadByte(bytes(0xFE), 0xFE);
14 |
15 | assertReadInt16(bytes(0x34, 0x12), (short) 0x1234);
16 | assertReadInt16(bytes(0xCD, 0xAB), (short) 0xABCD);
17 |
18 | assertReadInt32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678);
19 | assertReadInt32(bytes(0xF0, 0xDE, 0xBC, 0x9A), 0x9ABCDEF0);
20 |
21 | assertReadInt64(
22 | bytes(0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12),
23 | 0x123456789ABCDEF0L
24 | );
25 |
26 | assertReadInt64(
27 | bytes(0x78, 0x56, 0x34, 0x12, 0xF0, 0xDE, 0xBC, 0x9A),
28 | 0x9ABCDEF012345678L
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Hugo Manrique
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/writer/LittleEndianByteStreamWriterTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import org.junit.Test;
4 |
5 | import java.nio.ByteOrder;
6 |
7 | /**
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public class LittleEndianByteStreamWriterTest extends ByteStreamWriterTest {
12 | public LittleEndianByteStreamWriterTest() {
13 | super(ByteOrder.LITTLE_ENDIAN);
14 | }
15 |
16 | @Test
17 | public void testByteWrite() throws Exception {
18 | writer.writeByte(0x12);
19 | assertWriteByte(0x12);
20 | }
21 |
22 | @Test
23 | public void testInt16Write() throws Exception {
24 | writer.writeInt16((short) 0x1234);
25 | assertWriteInt16((short) 0x1234);
26 | }
27 |
28 | @Test
29 | public void testInt32Write() throws Exception {
30 | writer.writeInt32(0x12345678);
31 | assertWriteInt32(0x12345678);
32 | }
33 |
34 | @Test
35 | public void testInt64Write() throws Exception {
36 | writer.writeInt64(0x123456789ABCDEF0L);
37 | assertWriteInt64(0x123456789ABCDEF0L);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/reader/BigEndianByteStreamReaderTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import org.junit.Test;
4 |
5 | import java.nio.ByteOrder;
6 |
7 | /**
8 | * @author Hugo Manrique
9 | * @since 02/09/2018
10 | */
11 | public class BigEndianByteStreamReaderTest extends ByteStreamReaderTest {
12 | public BigEndianByteStreamReaderTest() {
13 | this.byteOrder = ByteOrder.BIG_ENDIAN;
14 | }
15 |
16 | @Test
17 | public void testLittleEndian() throws Exception {
18 | assertReadByte(bytes(0x12), 0x12);
19 | assertReadByte(bytes(0xFE), 0xFE);
20 |
21 | assertReadInt16(bytes(0x12, 0x34), (short) 0x1234);
22 | assertReadInt16(bytes(0xAB, 0xCD), (short) 0xABCD);
23 |
24 | assertReadInt32(bytes(0x12, 0x34, 0x56, 0x78), 0x12345678);
25 | assertReadInt32(bytes(0x9A, 0xBC, 0xDE, 0xF0), 0x9ABCDEF0);
26 |
27 | assertReadInt64(
28 | bytes(0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0),
29 | 0x123456789ABCDEF0L
30 | );
31 |
32 | assertReadInt64(
33 | bytes(0x9A, 0xBC, 0xDE, 0xF0, 0x12, 0x34, 0x56, 0x78),
34 | 0x9ABCDEF012345678L
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/order/BigEndianReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.order;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Big-endian byte stream reader implementation.
7 | *
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public interface BigEndianReader extends ByteOrderReader {
12 | @Override
13 | default short readInt16() throws IOException {
14 | return (short) (((readByte() & 0xFF) << 8)
15 | | (readByte() & 0xFF));
16 | }
17 |
18 | @Override
19 | default int readInt32() throws IOException {
20 | return ((readByte() & 0xFF) << 24)
21 | | ((readByte() & 0xFF) << 16)
22 | | ((readByte() & 0xFF) << 8)
23 | | (readByte() & 0xFF);
24 | }
25 |
26 | @Override
27 | default long readInt64() throws IOException {
28 | return ((readByte() & 0xFFL) << 56)
29 | | ((readByte() & 0xFFL) << 48)
30 | | ((readByte() & 0xFFL) << 40)
31 | | ((readByte() & 0xFFL) << 32)
32 | | ((readByte() & 0xFFL) << 24)
33 | | ((readByte() & 0xFFL) << 16)
34 | | ((readByte() & 0xFFL) << 8)
35 | | (readByte() & 0xFFL);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/inout/LittleEndianInOutByteStreamTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.inout;
2 |
3 | import org.junit.Test;
4 |
5 | import java.nio.ByteOrder;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | /**
10 | * @author Hugo Manrique
11 | * @since 03/09/2018
12 | */
13 | public class LittleEndianInOutByteStreamTest extends InOutByteStreamTest {
14 | public LittleEndianInOutByteStreamTest() {
15 | super(ByteOrder.LITTLE_ENDIAN);
16 | }
17 |
18 | @Test
19 | public void testWriteAndRead() throws Exception {
20 | try (InOutByteStream stream = createStream()) {
21 | stream.writeByte(0x12);
22 |
23 | // Go back one byte and read
24 | stream.setOffset(0);
25 | assertEquals(0x12, stream.readByte());
26 | }
27 | }
28 |
29 | @Test
30 | public void testAheadRead() throws Exception {
31 | try (InOutByteStream stream = createStream()) {
32 | stream.skip(2);
33 | stream.writeByte(0x34);
34 |
35 | stream.setOffset(0);
36 |
37 | // These two bytes should be zeroed out
38 | assertEquals(0x0, stream.readByte());
39 | assertEquals(0x0, stream.readByte());
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/order/BigEndianWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.order;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Big-endian byte stream writer implementation.
7 | *
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public interface BigEndianWriter extends ByteOrderWriter {
12 | @Override
13 | default void writeInt16(short value) throws IOException {
14 | writeByte((value >> 8) & 0xFF);
15 | writeByte(value & 0xFF);
16 | }
17 |
18 | @Override
19 | default void writeInt32(int value) throws IOException {
20 | writeByte((value >> 24) & 0xFF);
21 | writeByte((value >> 16) & 0xFF);
22 | writeByte((value >> 8) & 0xFF);
23 | writeByte(value & 0xFF);
24 | }
25 |
26 | @Override
27 | default void writeInt64(long value) throws IOException {
28 | writeByte((byte) ( value >> 56) & 0xFF);
29 | writeByte((byte) ( value >> 48) & 0xFF);
30 | writeByte((byte) ( value >> 40) & 0xFF);
31 | writeByte((byte) ( value >> 32) & 0xFF);
32 | writeByte((byte) ( value >> 24) & 0xFF);
33 | writeByte((byte) ( value >> 16) & 0xFF);
34 | writeByte((byte) ( value >> 8) & 0xFF);
35 | writeByte((byte) (value & 0xFF));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/order/LittleEndianReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.order;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Little-endian byte stream reader implementation.
7 | *
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public interface LittleEndianReader extends ByteOrderReader {
12 | @Override
13 | default short readInt16() throws IOException {
14 | return (short) (((readByte() & 0xFF))
15 | | ((readByte() & 0xFF) << 8));
16 | }
17 |
18 | @Override
19 | default int readInt32() throws IOException {
20 | return ((readByte() & 0xFF))
21 | | ((readByte() & 0xFF) << 8)
22 | | ((readByte() & 0xFF) << 16)
23 | | ((readByte() & 0xFF) << 24);
24 | }
25 |
26 | @Override
27 | default long readInt64() throws IOException {
28 | return ((readByte() & 0xFFL)
29 | | ((readByte() & 0xFFL) << 8)
30 | | ((readByte() & 0xFFL) << 16)
31 | | ((readByte() & 0xFFL) << 24)
32 | | ((readByte() & 0xFFL) << 32)
33 | | ((readByte() & 0xFFL) << 40)
34 | | ((readByte() & 0xFFL) << 48)
35 | | ((readByte() & 0xFFL) << 56));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/order/LittleEndianWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.order;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Little-endian byte stream writer implementation.
7 | *
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public interface LittleEndianWriter extends ByteOrderWriter {
12 | @Override
13 | default void writeInt16(short value) throws IOException {
14 | writeByte(value & 0xFF);
15 | writeByte((value >> 8) & 0xFF);
16 | }
17 |
18 | @Override
19 | default void writeInt32(int value) throws IOException {
20 | writeByte(value & 0xFF);
21 | writeByte((value >> 8) & 0xFF);
22 | writeByte((value >> 16) & 0xFF);
23 | writeByte((value >> 24) & 0xFF);
24 | }
25 |
26 | @Override
27 | default void writeInt64(long value) throws IOException {
28 | writeByte((byte) (value & 0xFF));
29 | writeByte((byte) ( value >> 8) & 0xFF);
30 | writeByte((byte) ( value >> 16) & 0xFF);
31 | writeByte((byte) ( value >> 24) & 0xFF);
32 | writeByte((byte) ( value >> 32) & 0xFF);
33 | writeByte((byte) ( value >> 40) & 0xFF);
34 | writeByte((byte) ( value >> 48) & 0xFF);
35 | writeByte((byte) ( value >> 56) & 0xFF);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/util/Unsigned.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.util;
2 |
3 | /**
4 | * Unsigned integer utils.
5 | *
6 | * @author Hugo Manrique
7 | * @since 02/09/2018
8 | */
9 | public class Unsigned {
10 | /**
11 | * Returns the two least-significant bits of {@code value} from
12 | * the zero-extended {@code int16}.
13 | *
14 | * @param value a zero-extended 16-bit value
15 | * @return A value in the range {@code 0} through {@code 65,535}
16 | */
17 | public static int unsignedInt16(short value) {
18 | return value & 0xFFFF;
19 | }
20 |
21 | /**
22 | * Returns the four least-significant bits of {@code value} from
23 | * the zero-extended {@code int32}.
24 | *
25 | * @param value a zero-extended 32-bit value
26 | * @return A value in the range {@code 0} through {@code 4,294,967,295}
27 | */
28 | public static long unsignedInt32(int value) {
29 | return value & 0xFFFFFFFFL;
30 | }
31 |
32 | /**
33 | * Returns the eight least-significant bits of {@code value} from
34 | * the zero-extended {@code int64}.
35 | *
36 | * Java can handle unsigned longs by using the utils contained in the
37 | * {@link Long} class such as {@link Long#compareUnsigned(long, long)}.
38 | *
39 | * @param value a zero-extended 64-bit value
40 | * @return A value in the range {@code 0} through {@code 18,446,744,073,709,551,615}
41 | */
42 | public static long unsignedInt64(long value) {
43 | return value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/reader/ReaderPositionTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.EOFException;
6 | import java.io.IOException;
7 |
8 | import static org.junit.Assert.assertEquals;
9 |
10 | /**
11 | * @author Hugo Manrique
12 | * @since 02/09/2018
13 | */
14 | public class ReaderPositionTest extends ByteStreamReaderTest {
15 | @Test
16 | public void testPositiveSkip() throws IOException {
17 | ByteStreamReader reader = newReader(bytes(0x12, 0x34, 0x56));
18 |
19 | // Skip one byte
20 | reader.skip(1);
21 | assertEquals(0x34, reader.readByte());
22 |
23 | // Don't skip any bytes
24 | reader.skip(0);
25 | assertEquals(0x56, reader.readByte());
26 |
27 | assertDataConsumed(3, reader);
28 | }
29 |
30 | @Test
31 | public void testNegativeSkip() throws IOException {
32 | ByteStreamReader reader = newReader(bytes(0x12, 0x34, 0x56));
33 |
34 | // Skip two bytes and go back
35 | reader.skip(2);
36 | reader.skip(-1);
37 | assertEquals(0x34, reader.readByte());
38 |
39 | // Go back to the origin position
40 | reader.skip(-2);
41 | assertEquals(0x12, reader.readByte());
42 | }
43 |
44 | @Test
45 | public void testSetOffset() throws IOException {
46 | ByteStreamReader reader = newReader(bytes(0x12, 0x34, 0x56));
47 |
48 | reader.setOffset(2);
49 | assertEquals(0x56, reader.readByte());
50 |
51 | // Offset should get incremented
52 | assertEquals(3, reader.getOffset());
53 | }
54 |
55 | @Test(expected = EOFException.class)
56 | public void testSkipThrowsEOF() throws IOException {
57 | ByteStreamReader reader = newReader(bytes(0x12));
58 |
59 | reader.skip(2);
60 | reader.readByte();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/base/BaseByteStreamWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.base;
2 |
3 | import me.hugmanrique.jacobin.writer.ByteStreamWriter;
4 |
5 | import java.io.IOException;
6 | import java.io.OutputStream;
7 | import java.util.concurrent.atomic.AtomicLong;
8 |
9 | import static com.google.common.base.Preconditions.checkArgument;
10 | import static com.google.common.base.Preconditions.checkNotNull;
11 |
12 | /**
13 | * @author Hugo Manrique
14 | * @since 03/09/2018
15 | */
16 | public abstract class BaseByteStreamWriter implements ByteStreamWriter {
17 | protected final OutputStream stream;
18 | protected final AtomicLong offset;
19 |
20 | public BaseByteStreamWriter(OutputStream stream) {
21 | this.stream = checkNotNull(stream, "stream");
22 | this.offset = new AtomicLong();
23 | }
24 |
25 | @Override
26 | public long getOffset() {
27 | return offset.get();
28 | }
29 |
30 | @Override
31 | public void skip(int size) throws IOException {
32 | checkArgument(size >= 0, "Attempted to skip a negative number of bytes");
33 |
34 | byte[] zeroed = new byte[size];
35 |
36 | stream.write(zeroed);
37 | offset.addAndGet(size);
38 | }
39 |
40 | @Override
41 | public void close() throws IOException {
42 | stream.close();
43 | }
44 |
45 | @Override
46 | public void write(byte[] data, int offset, int length) throws IOException {
47 | stream.write(data, offset, length);
48 | this.offset.addAndGet(length);
49 | }
50 |
51 | @Override
52 | public void write(byte[] data) throws IOException {
53 | stream.write(data);
54 | this.offset.addAndGet(data.length);
55 | }
56 |
57 | @Override
58 | public void writeByte(int value) throws IOException {
59 | stream.write(value);
60 | this.offset.incrementAndGet();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/writer/ByteStreamWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import me.hugmanrique.jacobin.order.ByteOrderWriter;
4 |
5 | import java.io.Closeable;
6 | import java.io.IOException;
7 |
8 | /**
9 | * Provides a way to write data into a stream.
10 | *
11 | * @author Hugo Manrique
12 | * @since 03/09/2018
13 | */
14 | public interface ByteStreamWriter extends ByteOrderWriter, Closeable {
15 | /**
16 | * Returns this writer's position, i.e. the offset of the internal stream.
17 | *
18 | * @return the position of this writer
19 | * @throws IOException if an I/O error occurs
20 | */
21 | long getOffset() throws IOException;
22 |
23 | /**
24 | * Writes {@code 0x0 size} bytes of data to the internal stream. Does not close the
25 | * internal stream.
26 | *
27 | * @param size the non-negative number of bytes to null and skip
28 | * @throws IOException if an I/O error occurs
29 | * @throws IllegalArgumentException if {@code size} is negative
30 | */
31 | void skip(int size) throws IOException;
32 |
33 | /**
34 | * Writes {@code length} bytes from the {@code data} array, in order, to the internal stream.
35 | *
36 | * @param data the data to write to the stream
37 | * @param offset an int specifying the offset in the data
38 | * @param length an int specifying the number of bytes to write
39 | * @throws IOException if an I/O error occurs
40 | * @throws NullPointerException if {@code data} is null
41 | * @throws IndexOutOfBoundsException if {@code offset} is negative, or {@code length} is negative, or
42 | * {@code offset + length} is greater than the length of the array
43 | * {@code data}.
44 | */
45 | void write(byte[] data, int offset, int length) throws IOException;
46 |
47 | /**
48 | * Writes all the bytes in array {@code data} to the internal stream.
49 | *
50 | * @param data the data to write to the stream
51 | * @throws IOException if an I/O error occurs
52 | * @throws NullPointerException if {@code data} is null
53 | */
54 | void write(byte[] data) throws IOException;
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/writer/ByteStreamWriterTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import me.hugmanrique.jacobin.reader.ByteStreamReader;
4 | import me.hugmanrique.jacobin.reader.ByteStreamReaderBuilder;
5 | import org.junit.Before;
6 |
7 | import java.io.ByteArrayOutputStream;
8 | import java.nio.ByteOrder;
9 |
10 | import static org.junit.Assert.assertEquals;
11 |
12 | /**
13 | * @author Hugo Manrique
14 | * @since 03/09/2018
15 | */
16 | public class ByteStreamWriterTest {
17 | protected final ByteOrder byteOrder;
18 |
19 | protected ByteArrayOutputStream stream; // Note: this stream doesn't need to be closed
20 | protected ByteStreamWriter writer;
21 | private ByteStreamReader reader;
22 |
23 | public ByteStreamWriterTest(ByteOrder byteOrder) {
24 | this.byteOrder = byteOrder;
25 | }
26 |
27 | @Before
28 | public void createWriter() {
29 | stream = new ByteArrayOutputStream();
30 | writer = new ByteStreamWriterBuilder()
31 | .stream(stream)
32 | .order(byteOrder)
33 | .build();
34 | }
35 |
36 | protected ByteStreamReader newReader() {
37 | return this.reader = new ByteStreamReaderBuilder()
38 | .stream(stream.toByteArray())
39 | .order(byteOrder)
40 | .build();
41 | }
42 |
43 | protected void assertWriteByte(int value) throws Exception {
44 | newReader();
45 |
46 | assertEquals(value, reader.readByte());
47 | assertEquals("Writer must write one byte", 0, reader.available());
48 | }
49 |
50 | protected void assertWriteInt16(short value) throws Exception {
51 | newReader();
52 |
53 | assertEquals(value, reader.readInt16());
54 | assertEquals("Writer must write 2 bytes", 0, reader.available());
55 | }
56 |
57 | protected void assertWriteInt32(int value) throws Exception {
58 | newReader();
59 |
60 | assertEquals(value, reader.readInt32());
61 | assertEquals("Writer must write 4 bytes", 0, reader.available());
62 | }
63 |
64 | protected void assertWriteInt64(long value) throws Exception {
65 | newReader();
66 |
67 | assertEquals(value, reader.readInt64());
68 | assertEquals("Writer must write 8 bytes", 0, reader.available());
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/inout/InOutByteStreamBuilder.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.inout;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.nio.ByteOrder;
6 |
7 | import static com.google.common.base.Preconditions.checkNotNull;
8 |
9 | /**
10 | * Creates a new {@link InOutByteStream} that will read and write
11 | * to/from the given file with a defined {@link ByteOrder}.
12 | * @author Hugo Manrique
13 | * @since 03/09/2018
14 | */
15 | public class InOutByteStreamBuilder {
16 | private File file;
17 | private boolean synchronous;
18 | private ByteOrder order = ByteOrder.nativeOrder();
19 |
20 | public InOutByteStreamBuilder() {}
21 |
22 | /**
23 | * Creates a new {@link InOutByteStream} instance to read
24 | * and write from/to the given {@link File}.
25 | */
26 | public InOutByteStreamBuilder file(File file) {
27 | this.file = file;
28 | return this;
29 | }
30 |
31 | /**
32 | * Requires each update to the file's content to be written
33 | * synchronously to the underlying storage device.
34 | */
35 | public InOutByteStreamBuilder synchronous() {
36 | this.synchronous = true;
37 | return this;
38 | }
39 |
40 | /**
41 | * Sets the byte order of the {@link InOutByteStream}.
42 | */
43 | public InOutByteStreamBuilder order(ByteOrder order) {
44 | this.order = checkNotNull(order, "byte order");
45 | return this;
46 | }
47 |
48 | /**
49 | * Creates a new {@link InOutByteStream} that will read and write
50 | * to/from the given file with a defined {@link ByteOrder} (which
51 | * defaults to the native endianness).
52 | *
53 | * @return the new {@link InOutByteStream}
54 | * @throws IOException if an I/O error occurs
55 | * @see ByteOrder#nativeOrder()
56 | */
57 | public InOutByteStream build() throws IOException {
58 | checkNotNull(file, "file");
59 |
60 | if (order.equals(ByteOrder.LITTLE_ENDIAN)) {
61 | return new LittleEndianInOutByteStream(file, synchronous);
62 | } else if (order.equals(ByteOrder.BIG_ENDIAN)) {
63 | return new BigEndianInOutByteStream(file, synchronous);
64 | }
65 |
66 | throw new UnsupportedOperationException(order + " byte order not supported");
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/me/hugmanrique/jacobin/reader/ByteStreamReaderTest.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteOrder;
5 |
6 | import static org.junit.Assert.assertEquals;
7 |
8 | /**
9 | * @author Hugo Manrique
10 | * @since 02/09/2018
11 | */
12 | public class ByteStreamReaderTest {
13 | protected static byte[] bytes(int... bytesAsInts) {
14 | byte[] bytes = new byte[bytesAsInts.length];
15 |
16 | for (int i = 0; i < bytesAsInts.length; i++) {
17 | bytes[i] = (byte) bytesAsInts[i];
18 | }
19 |
20 | return bytes;
21 | }
22 |
23 | protected void assertDataConsumed(int length, ByteStreamReader reader) throws IOException {
24 | assertEquals(
25 | "Reader offset should be moved by " + length + " positions",
26 | length,
27 | reader.getOffset()
28 | );
29 | }
30 |
31 | protected void assertDataConsumed(byte[] data, ByteStreamReader reader) throws IOException {
32 | assertDataConsumed(data.length, reader);
33 | }
34 |
35 | protected ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN;
36 |
37 | // Note: this reader doesn't need to be closed
38 | protected ByteStreamReader newReader(byte[] data) {
39 | return new ByteStreamReaderBuilder()
40 | .stream(data)
41 | .order(byteOrder)
42 | .build();
43 | }
44 |
45 | protected void assertReadByte(byte[] data, int value) throws Exception {
46 | ByteStreamReader reader = newReader(data);
47 |
48 | assertEquals(value, reader.readByte());
49 | assertDataConsumed(data, reader);
50 | }
51 |
52 | protected void assertReadInt16(byte[] data, short value) throws Exception {
53 | ByteStreamReader reader = newReader(data);
54 |
55 | assertEquals(value, reader.readInt16());
56 | assertDataConsumed(data, reader);
57 | }
58 |
59 | protected void assertReadInt32(byte[] data, int value) throws Exception {
60 | ByteStreamReader reader = newReader(data);
61 |
62 | assertEquals(value, reader.readInt32());
63 | assertDataConsumed(data, reader);
64 | }
65 |
66 | protected void assertReadInt64(byte[] data, long value) throws Exception {
67 | ByteStreamReader reader = newReader(data);
68 |
69 | assertEquals(value, reader.readInt64());
70 | assertDataConsumed(data, reader);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/writer/ByteStreamWriterBuilder.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.writer;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.OutputStream;
5 | import java.nio.ByteOrder;
6 |
7 | import static com.google.common.base.Preconditions.checkNotNull;
8 |
9 | /**
10 | * Creates a new {@link ByteStreamWriter} from different kinds of
11 | * sources with a defined {@link ByteOrder}.
12 | *
13 | * @author Hugo Manrique
14 | * @since 03/09/2018
15 | */
16 | public final class ByteStreamWriterBuilder {
17 | private OutputStream stream;
18 | private ByteOrder order = ByteOrder.nativeOrder();
19 |
20 | public ByteStreamWriterBuilder() {}
21 |
22 | /**
23 | * Creates a new {@link ByteStreamWriter} instance to write to
24 | * the given {@link OutputStream}.
25 | */
26 | public ByteStreamWriterBuilder stream(OutputStream stream) {
27 | this.stream = stream;
28 | return this;
29 | }
30 |
31 | /**
32 | * Creates a new {@link ByteStreamWriter} instance to write to
33 | * a {@link ByteArrayOutputStream}.
34 | */
35 | public ByteStreamWriterBuilder bytes() {
36 | this.stream = new ByteArrayOutputStream();
37 | return this;
38 | }
39 |
40 | /**
41 | * Creates a new {@link ByteStreamWriter} instance write to a
42 | * {@link ByteArrayOutputStream} which will hold {@code size} bytes
43 | * before resizing.
44 | *
45 | * @throws IllegalArgumentException if {@code size} is negative
46 | */
47 | public ByteStreamWriterBuilder bytes(int size) {
48 | this.stream = new ByteArrayOutputStream(size);
49 | return this;
50 | }
51 |
52 | /**
53 | * Sets the byte order of the {@link ByteStreamWriter}.
54 | */
55 | public ByteStreamWriterBuilder order(ByteOrder order) {
56 | this.order = checkNotNull(order, "byte order");
57 | return this;
58 | }
59 |
60 | /**
61 | * Creates a new {@link ByteStreamWriter} from different kinds of
62 | * sources with a defined {@link ByteOrder} (which
63 | * defaults to the native endianness).
64 | *
65 | * @return the new {@link ByteStreamWriter}
66 | * @see ByteOrder#nativeOrder()
67 | */
68 | public ByteStreamWriter build() {
69 | checkNotNull(stream, "stream");
70 |
71 | if (order.equals(ByteOrder.LITTLE_ENDIAN)) {
72 | return new LittleEndianByteStreamWriter(stream);
73 | } else if (order.equals(ByteOrder.BIG_ENDIAN)) {
74 | return new BigEndianByteStreamWriter(stream);
75 | }
76 |
77 | throw new UnsupportedOperationException(order + " byte order not supported");
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/order/ByteOrderWriter.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.order;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * A byte-order dependent byte stream writer interface.
7 | *
8 | * @author Hugo Manrique
9 | * @since 03/09/2018
10 | */
11 | public interface ByteOrderWriter {
12 | /**
13 | * Writes the 8 low-order bits of {@code value} to the internal stream.
14 | * The 24-high order bits of {@code value} are ignored.
15 | *
16 | * @param value the 8-bit value to be written
17 | * @throws IOException if an I/O error occurs
18 | */
19 | void writeByte(int value) throws IOException;
20 |
21 | /**
22 | * Writes two bytes to the internal stream.
23 | *
24 | * @param value the 16-bit value to be written
25 | * @throws IOException if an I/O error occurs
26 | */
27 | void writeInt16(short value) throws IOException;
28 |
29 | /**
30 | * Writes two bytes in the range {@code 0} through {@code 65,535}
31 | * to the internal stream.
32 | *
33 | * @param value the unsigned 16-bit value to be written
34 | * @throws IOException if an I/O error occurs
35 | */
36 | default void writeUInt16(int value) throws IOException {
37 | writeInt16((short) value);
38 | }
39 |
40 | /**
41 | * Writes four bytes to the internal stream.
42 | *
43 | * @param value the 32-bit value to be written
44 | * @throws IOException if an I/O error occurs
45 | */
46 | void writeInt32(int value) throws IOException;
47 |
48 | /**
49 | * Writes four bytes in the range {@code 0} through
50 | * {@code 4,294,967,295} to the internal stream.
51 | *
52 | * @param value the unsigned 32-bit value to be written
53 | * @throws IOException if an I/O error occurs
54 | */
55 | default void writeUInt32(long value) throws IOException {
56 | writeInt32((int) value);
57 | }
58 |
59 | /**
60 | * Writes eight bytes to the internal stream.
61 | *
62 | * @param value the 64-bit value to be written
63 | * @throws IOException if an I/O error occurs
64 | */
65 | void writeInt64(long value) throws IOException;
66 |
67 | /**
68 | * Writes eight bytes in the range {@code 0} through
69 | * {@code 9,223,372,036,854,775,807} to the internal stream.
70 | *
71 | * Java doesn't have a native {@code uint64} primitive type so values
72 | * above {@code 2^63-1} are not guaranteed to be written correctly.
73 | *
74 | * @param value the unsigned 64-bit value to be written
75 | * @throws IOException if an I/O error occurs
76 | */
77 | default void writeUInt64(long value) throws IOException {
78 | // TODO Print warning?
79 | writeInt64(value);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :electric_plug: Jacobin
2 |
3 | [![jitpack][jitpack]][jitpack-url]
4 | [![javadocs][javadocs]][javadocs-url]
5 | [![tests][tests]][tests-url]
6 | [![license][license]][license-url]
7 |
8 | Jacobin provides high performance binary streams reader and writer implementations, as well as bidirectional stream implementations.
9 |
10 | ## Features
11 |
12 | - Supports efficient offset setting (by only keeping the needed byte buffers in memory)
13 | - Has different implementations for each [endianness](https://en.wikipedia.org/wiki/Endianness) type
14 | - Easy to make stream accesses and writes thread-safe
15 | - Uses the [Builder pattern](https://en.wikipedia.org/wiki/Builder_pattern) to create the reader, writers and bidirectional streams
16 |
17 | ## Getting started
18 |
19 | Install Jacobin using [Maven](https://maven.apache.org/) by adding the JitPack repository to your `pom.xml` file:
20 |
21 | ```xml
22 |
23 |
24 | jitpack.io
25 | https://jitpack.io
26 |
27 |
28 | ```
29 |
30 | Next, add the `Jacobin` dependency:
31 |
32 | ```xml
33 |
34 | com.github.hugmanrique
35 | Jacobin
36 | master-SNAPSHOT
37 |
38 | ```
39 |
40 | You will need to have Java 8 version 45 or later (older versions _might_ work).
41 |
42 | ## Creating a ByteStreamReader
43 |
44 | Let's get started by creating a little-endian `ByteStreamReader`:
45 |
46 | ```java
47 | ByteStreamReader reader = new ByteStreamReaderBuilder()
48 | .stream(new byte[] { 0x34, 0x12 })
49 | .build();
50 | ```
51 |
52 | In this case we used the `byte[]` stream method, which will internally create a `ByteArrayInputStream`. Jacobin will use the native [endianness](https://en.wikipedia.org/wiki/Endianness) by default (which in [Intel and AMD modern CPUs is little-endian](https://en.wikipedia.org/wiki/Endianness#Current_architectures)), but you can change this behaviour by calling `#order(ByteOrder)`.
53 |
54 | That's it! We can now call any method available in `ByteStreamReader` e.g.:
55 |
56 | ```java
57 | try {
58 | reader.readInt16(); // will be 0x1234
59 | } catch (IOException e) {
60 | e.printStackTrace();
61 | }
62 | ```
63 |
64 | ## Creating a ByteStreamWriter
65 |
66 | TODO
67 |
68 | ## Creating an InOutByteStream
69 |
70 | TODO
71 |
72 | Check out the [Javadocs][javadocs-url] to see a list of all the available classes and methods.
73 |
74 | ## License
75 |
76 | [MIT](LICENSE) © [Hugo Manrique](https://hugmanrique.me)
77 |
78 | [jitpack]: https://jitpack.io/v/hugmanrique/Jacobin.svg
79 | [jitpack-url]: https://jitpack.io/#hugmanrique/Jacobin
80 | [javadocs]: https://img.shields.io/badge/javadocs-master--SNAPSHOT-green.svg
81 | [javadocs-url]: https://jitpack.io/com/github/hugmanrique/Jacobin/master-SNAPSHOT/javadoc/
82 | [tests]: https://img.shields.io/travis/hugmanrique/Jacobin/master.svg
83 | [tests-url]: https://travis-ci.org/hugmanrique/Jacobin
84 | [license]: https://img.shields.io/github/license/hugmanrique/Jacobin.svg
85 | [license-url]: LICENSE
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/base/BaseByteStreamReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.base;
2 |
3 | import me.hugmanrique.jacobin.reader.ByteStreamReader;
4 |
5 | import java.io.EOFException;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.util.concurrent.atomic.AtomicLong;
9 |
10 | import static com.google.common.base.Preconditions.checkNotNull;
11 |
12 | /**
13 | * @author Hugo Manrique
14 | * @since 02/09/2018
15 | */
16 | public abstract class BaseByteStreamReader implements ByteStreamReader {
17 | private static final int MAX_NEGATIVE_SKIP_LIMIT = Integer.MAX_VALUE;
18 |
19 | protected final InputStream stream;
20 | protected final AtomicLong offset;
21 |
22 | public BaseByteStreamReader(InputStream stream) {
23 | this.stream = checkNotNull(stream, "stream");
24 | this.offset = new AtomicLong();
25 |
26 | if (supportsNegativeSkips()) {
27 | stream.mark(MAX_NEGATIVE_SKIP_LIMIT);
28 | }
29 | }
30 |
31 | /**
32 | * Goes back to the start of the stream and sets a mark.
33 | *
34 | * @throws IOException if this reader doesn't support negative skips
35 | * @see #supportsNegativeSkips() to check whether this reader supports negative skips
36 | */
37 | private void reset() throws IOException {
38 | offset.set(0);
39 | stream.reset();
40 | stream.mark(MAX_NEGATIVE_SKIP_LIMIT);
41 | }
42 |
43 | @Override
44 | public long getOffset() {
45 | return offset.get();
46 | }
47 |
48 | @Override
49 | public void setOffset(long newPosition) throws IOException {
50 | skip(newPosition - getOffset());
51 | }
52 |
53 | @Override
54 | public void skip(long size) throws IOException {
55 | if (size < 0) {
56 | if (!supportsNegativeSkips()) {
57 | throw new UnsupportedOperationException("This reader does not support negative skips");
58 | }
59 |
60 | // Since we're going back to 0, we need to calculate
61 | // the absolute byte position of the skip
62 | size = offset.get() + size;
63 | reset();
64 | }
65 |
66 | offset.addAndGet(stream.skip(size));
67 | }
68 |
69 | @Override
70 | public boolean supportsNegativeSkips() {
71 | return stream.markSupported();
72 | }
73 |
74 | @Override
75 | public void close() throws IOException {
76 | stream.close();
77 | }
78 |
79 | @Override
80 | public int available() throws IOException {
81 | return stream.available();
82 | }
83 |
84 | @Override
85 | public int read(byte[] buffer, int offset, int length) throws IOException {
86 | int readBytes = stream.read(buffer, offset, length);
87 | this.offset.addAndGet(readBytes);
88 |
89 | return readBytes;
90 | }
91 |
92 | @Override
93 | public int readByte() throws IOException {
94 | int value = stream.read();
95 |
96 | if (value == -1) {
97 | throw new EOFException();
98 | }
99 |
100 | offset.incrementAndGet();
101 |
102 | return value;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/inout/InOutByteStream.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.inout;
2 |
3 | import me.hugmanrique.jacobin.reader.ByteStreamReader;
4 | import me.hugmanrique.jacobin.writer.ByteStreamWriter;
5 |
6 | import java.io.Closeable;
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.io.RandomAccessFile;
10 |
11 | import static com.google.common.base.Preconditions.checkArgument;
12 | import static com.google.common.base.Preconditions.checkNotNull;
13 |
14 | /**
15 | * Combined byte streams reader and writer class.
16 | *
17 | * @author Hugo Manrique
18 | * @since 03/09/2018
19 | */
20 | public abstract class InOutByteStream implements ByteStreamReader, ByteStreamWriter, Closeable {
21 | private final RandomAccessFile file;
22 |
23 | /**
24 | * Creates a new {@link InOutByteStream} instance that will read and write
25 | * to/from the given file.
26 | *
27 | * @param file the file to be read and written
28 | * @param synchronousWrites whether each update to the file's content should be written
29 | * synchronously to the underlying storage device.
30 | * @throws IOException if an I/O error occurs
31 | */
32 | public InOutByteStream(File file, boolean synchronousWrites) throws IOException {
33 | checkNotNull(file, "file");
34 | this.file = new RandomAccessFile(file, synchronousWrites ? "rwd" : "rw");
35 | }
36 |
37 | public InOutByteStream(File file) throws IOException {
38 | this(file, false);
39 | }
40 |
41 | @Override
42 | public long getOffset() throws IOException {
43 | return file.getFilePointer();
44 | }
45 |
46 | @Override
47 | public void setOffset(long newPosition) throws IOException {
48 | checkArgument(newPosition >= 0, "Attempted to set a negative offset");
49 |
50 | file.seek(newPosition);
51 | }
52 |
53 | @Override
54 | public void skip(long size) throws IOException {
55 | setOffset(getOffset() + size);
56 | }
57 |
58 | @Override
59 | public void skip(int size) throws IOException {
60 | skip((long) size);
61 | }
62 |
63 | @Override
64 | public int available() {
65 | throw new UnsupportedOperationException();
66 | }
67 |
68 | @Override
69 | public void close() throws IOException {
70 | file.close();
71 | }
72 |
73 | @Override
74 | public int read(byte[] buffer, int offset, int length) throws IOException {
75 | return file.read(buffer, offset, length);
76 | }
77 |
78 | @Override
79 | public int readByte() throws IOException {
80 | return file.read();
81 | }
82 |
83 | @Override
84 | public void write(byte[] data, int offset, int length) throws IOException {
85 | file.write(data, offset, length);
86 | }
87 |
88 | @Override
89 | public void write(byte[] data) throws IOException {
90 | file.write(data);
91 | }
92 |
93 | @Override
94 | public void writeByte(int value) throws IOException {
95 | file.write(value);
96 | }
97 |
98 | @Override
99 | public boolean supportsNegativeSkips() {
100 | return true;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/order/ByteOrderReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.order;
2 |
3 | import me.hugmanrique.jacobin.util.Unsigned;
4 |
5 | import java.io.EOFException;
6 | import java.io.IOException;
7 |
8 | /**
9 | * A byte-order dependent byte stream reader interface.
10 | *
11 | * @author Hugo Manrique
12 | * @since 03/09/2018
13 | */
14 | public interface ByteOrderReader {
15 | /**
16 | * Reads and returns one byte from the stream, zero-extends it to type {@code int}, and returns
17 | * the result, which is therefore in the range {@code 0} through {@code 255}.
18 | *
19 | * @return the unsigned 8-bit value read
20 | * @throws EOFException if the stream reaches the end before reading the byte
21 | * @throws IOException if an I/O error occurs
22 | */
23 | int readByte() throws IOException;
24 |
25 | /**
26 | * Reads two input bytes and returns a {@code short} value.
27 | *
28 | * @return the 16-bit value read
29 | * @throws EOFException if the stream reaches the end before reading all the bytes
30 | * @throws IOException if an I/O error occurs
31 | */
32 | short readInt16() throws IOException;
33 |
34 | /**
35 | * Reads two input bytes and returns an {@code int} value in the range {@code 0}
36 | * through {@code 65,535}.
37 | *
38 | * @return the unsigned 16-bit value read
39 | * @throws EOFException if the stream reaches the end before reading all the bytes
40 | * @throws IOException if an I/O error occurs
41 | */
42 | default int readUInt16() throws IOException {
43 | return Unsigned.unsignedInt16(readInt16());
44 | }
45 |
46 | /**
47 | * Reads four input bytes and returns an {@code int} value.
48 | *
49 | * @return the 32-bit value read
50 | * @throws EOFException if the stream reaches the end before reading all the bytes
51 | * @throws IOException if an I/O error occurs
52 | */
53 | int readInt32() throws IOException;
54 |
55 | /**
56 | * Reads four input bytes and returns a {@code long} value in the range {@code 0}
57 | * through {@code 4,294,967,295}.
58 | *
59 | * @return the unsigned 32-bit value read
60 | * @throws EOFException if the stream reaches the end before reading all the bytes
61 | * @throws IOException if an I/O error occurs
62 | */
63 | default long readUInt32() throws IOException {
64 | return Unsigned.unsignedInt32(readInt32());
65 | }
66 |
67 | /**
68 | * Reads eight input bytes and returns a {@code long} value.
69 | *
70 | * @return the 64-bit value read
71 | * @throws EOFException if the stream reaches the end before reading all the bytes
72 | * @throws IOException if an I/O error occurs
73 | */
74 | long readInt64() throws IOException;
75 |
76 | /**
77 | * Reads eight input bytes and returns a {@code long} value in the range {@code 0}
78 | * through {@code 18,446,744,073,709,551,615}. The {@link Long} class contains
79 | * methods to handle unsigned longs.
80 | *
81 | * @return the unsigned 64-bit value read
82 | * @throws EOFException if the stream reaches the end before reading all the bytes
83 | * @throws IOException if an I/O error occurs
84 | */
85 | default long readUInt64() throws IOException {
86 | return Unsigned.unsignedInt64(readInt64());
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.hugmanrique
8 | jacobin
9 | 2.0.0-SNAPSHOT
10 |
11 |
12 | UTF-8
13 | UTF-8
14 |
15 |
16 |
17 |
18 | sonatype
19 | https://oss.sonatype.org/content/repositories/snapshots/
20 |
21 |
22 |
23 |
24 |
25 | com.google.guava
26 | guava
27 | 26.0-jre
28 |
29 |
30 | junit
31 | junit
32 | 4.12
33 | test
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.apache.maven.plugins
41 | maven-compiler-plugin
42 | 3.7.0
43 |
44 | 1.8
45 | 1.8
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-shade-plugin
51 | 3.1.0
52 |
53 |
54 | package
55 |
56 | shade
57 |
58 |
59 |
60 |
61 |
62 | org.apache.maven.plugins
63 | maven-javadoc-plugin
64 | 2.7
65 |
66 |
67 | attach-javadocs
68 |
69 | jar
70 |
71 |
72 |
73 |
74 |
75 | false
76 |
77 | https://docs.oracle.com/javase/9/docs/api/
78 |
79 | ${basedir}/overview.html
80 | Jacobin (${project.version})
81 | Copyright © Hugo Manrique. Released under the MIT License
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/reader/ByteStreamReaderBuilder.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.InputStream;
5 | import java.nio.ByteOrder;
6 |
7 | import static com.google.common.base.Preconditions.checkNotNull;
8 | import static com.google.common.base.Preconditions.checkPositionIndex;
9 |
10 | /**
11 | * Creates a new {@link ByteStreamReader} from different kinds of
12 | * sources with a defined {@link ByteOrder}.
13 | *
14 | * @author Hugo Manrique
15 | * @since 02/09/2018
16 | */
17 | public final class ByteStreamReaderBuilder {
18 | private InputStream stream;
19 | private ByteOrder order = ByteOrder.nativeOrder();
20 |
21 | public ByteStreamReaderBuilder() {}
22 |
23 | /**
24 | * Creates a new {@link ByteStreamReader} instance to read from the
25 | * given {@link InputStream}.
26 | */
27 | public ByteStreamReaderBuilder stream(InputStream stream) {
28 | this.stream = stream;
29 | return this;
30 | }
31 |
32 | /**
33 | * Creates a new {@link ByteStreamReader} instance to read from the
34 | * {@code bytes} array from the beginning.
35 | */
36 | public ByteStreamReaderBuilder stream(byte[] bytes) {
37 | stream(bytes, 0);
38 | return this;
39 | }
40 |
41 | /**
42 | * Creates a new {@link ByteStreamReader} instance to read from the
43 | * {@code bytes} array, starting at the given position.
44 | *
45 | * @throws IndexOutOfBoundsException if {@code start} is negative or greater than the
46 | * length of the array
47 | */
48 | public ByteStreamReaderBuilder stream(byte[] bytes, int start) {
49 | stream(bytes, start, bytes.length);
50 | return this;
51 | }
52 |
53 | /**
54 | * Creates a new {@link ByteStreamReader} instance to read {@code length} bytes
55 | * from the {@code bytes} array, starting at the given position.
56 | *
57 | * @throws IndexOutOfBoundsException if {@code start} or {@code length} are negative
58 | * or greater than the length of the array
59 | */
60 | public ByteStreamReaderBuilder stream(byte[] bytes, int start, int length) {
61 | checkNotNull(bytes, "bytes");
62 | checkPositionIndex(start, bytes.length, "start offset");
63 | checkPositionIndex(length, bytes.length, "length");
64 |
65 | this.stream = new ByteArrayInputStream(bytes, start, length);
66 | return this;
67 | }
68 |
69 | /**
70 | * Sets the byte order of the {@link ByteStreamReader}.
71 | */
72 | public ByteStreamReaderBuilder order(ByteOrder order) {
73 | this.order = checkNotNull(order, "byte order");
74 | return this;
75 | }
76 |
77 | /**
78 | * Creates a new {@link ByteStreamReader} from different kinds of
79 | * sources with a defined {@link ByteOrder} (which
80 | * defaults to the native endianness).
81 | *
82 | * @return the new {@link ByteStreamReader}
83 | * @see ByteOrder#nativeOrder()
84 | */
85 | public ByteStreamReader build() {
86 | checkNotNull(stream, "stream");
87 |
88 | if (order.equals(ByteOrder.LITTLE_ENDIAN)) {
89 | return new LittleEndianByteStreamReader(stream);
90 | } else if (order.equals(ByteOrder.BIG_ENDIAN)) {
91 | return new BigEndianByteStreamReader(stream);
92 | }
93 |
94 | throw new UnsupportedOperationException(order + " byte order not supported");
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/me/hugmanrique/jacobin/reader/ByteStreamReader.java:
--------------------------------------------------------------------------------
1 | package me.hugmanrique.jacobin.reader;
2 |
3 | import com.google.common.base.Charsets;
4 | import com.google.errorprone.annotations.CanIgnoreReturnValue;
5 | import me.hugmanrique.jacobin.order.ByteOrderReader;
6 |
7 | import java.io.Closeable;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 |
11 | /**
12 | * Provides a way to read data from a stream.
13 | *
14 | * @author Hugo Manrique
15 | * @since 02/09/2018
16 | */
17 | public interface ByteStreamReader extends ByteOrderReader, Closeable {
18 | /**
19 | * Returns this reader's position, i.e. the offset of the internal stream.
20 | *
21 | * @return the position of this reader
22 | * @throws IOException if an I/O error occurs
23 | */
24 | long getOffset() throws IOException;
25 |
26 | /**
27 | * Sets this reader's position, i.e. the offset of the internal stream.
28 | *
29 | * @param newPosition the new position value; must be non-negative and no
30 | * larger than the internal stream's size.
31 | * @throws IOException if the new position is larger than the internal stream's size
32 | */
33 | void setOffset(long newPosition) throws IOException;
34 |
35 | /**
36 | * Discards {@code size} bytes of data from the internal stream. This method will block
37 | * until the full amount has been skipped. Does not close the internal stream.
38 | *
39 | * @param size the number of bytes to skip
40 | * @throws IOException if an I/O error occurs
41 | */
42 | void skip(long size) throws IOException;
43 |
44 | /**
45 | * Returns an estimate of the number of bytes that can be reade from the
46 | * internal stream without blocking by the next read (or skip) invocation.
47 | *
48 | * @throws IOException if an I/O error occurs
49 | */
50 | int available() throws IOException;
51 |
52 | /**
53 | * Reads some bytes from the internal stream and stores them into the buffer array {@code buffer}.
54 | * This method blocks until {@code length} bytes of input data have been read into the array, or
55 | * end of file is detected. The number of bytes read is returned, possibly zero. Does not close
56 | * the internal stream.
57 | *
58 | * A caller can detect EOF if the number of bytes read is less than {@code length}. All subsequent
59 | * calls on the same stream will return zero.
60 | *
61 | * @param buffer the buffer into which the data is read
62 | * @param offset an int specifying the offset into the data
63 | * @param length an int specifying the number of bytes to read
64 | * @return the number of bytes read
65 | * @throws IOException if an I/O error occurs
66 | * @throws NullPointerException if {@code buffer} is null
67 | * @throws IndexOutOfBoundsException if {@code offset} is negative, or {@code length} is negative, or
68 | * {@code offset + length} is greater than the length of the array
69 | * {@code buffer}.
70 | */
71 | @CanIgnoreReturnValue
72 | int read(byte[] buffer, int offset, int length) throws IOException;
73 |
74 | /**
75 | * Returns whether this reader supports going back to an already read
76 | * byte offset by checking if the underlaying {@link InputStream} supports
77 | * marks.
78 | *
79 | * @see #getOffset()
80 | * @see InputStream#markSupported()
81 | */
82 | boolean supportsNegativeSkips();
83 |
84 | /**
85 | * Returns an UTF8 String read from the stream given {@code offset} and a {@code length}
86 | *
87 | * @param offset an int specifying the offset into the data
88 | * @param length an int specifying the number of bytes to read.
89 | * @throws IOException if an I/O error occurs
90 | */
91 | default String readUTF8String(int offset, int length) throws IOException {
92 | byte[] buffer = new byte[length];
93 | read(buffer, offset, length);
94 | return new String(buffer, Charsets.UTF_8);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------