├── .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 | --------------------------------------------------------------------------------