├── .github
├── README.md
├── renovate.json
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── binary
├── build.gradle.kts
└── src
│ ├── main
│ └── java
│ │ └── alpine
│ │ └── binary
│ │ ├── ArrayBinaryCodec.java
│ │ ├── ArrayBinaryCodecs.java
│ │ ├── BinaryCodec.java
│ │ ├── BinaryTemplate.java
│ │ ├── Either.java
│ │ ├── EitherBinaryCodec.java
│ │ ├── ListBinaryCodec.java
│ │ ├── MapBinaryCodec.java
│ │ ├── MappedBinaryCodec.java
│ │ ├── NullableBinaryCodec.java
│ │ ├── OptionalBinaryCodec.java
│ │ ├── PrimitiveBinaryCodecs.java
│ │ ├── StandardBinaryCodecs.java
│ │ ├── StringBinaryCodec.java
│ │ └── VariableBinaryCodecs.java
│ └── test
│ └── java
│ └── alpine
│ └── binary
│ └── CodecTest.java
├── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/README.md:
--------------------------------------------------------------------------------
1 | # alpine
2 | A binary(JSON soon™) serialization library for Java.
3 |
4 | 
5 |
6 | ## Installation
7 | ### Binary
8 |
9 |
10 | Gradle (Kotlin)
11 |
12 |
13 | ```kts
14 | dependencies {
15 | implementation("dev.mudkip:alpine-binary:0.1.1")
16 | implementation("io.netty:netty-buffer:4.2.0.Final")
17 | }
18 | ```
19 |
20 |
21 |
22 |
23 | Gradle (Groovy)
24 |
25 |
26 | ```groovy
27 | dependencies {
28 | implementation 'dev.mudkip:alpine-binary:0.1.1'
29 | implementation 'io.netty:netty-buffer:4.2.0.Final'
30 | }
31 | ```
32 |
33 |
34 |
35 |
36 | Maven
37 |
38 |
39 | ```xml
40 |
41 | dev.mudkip
42 | alpine-binary
43 | 0.1.1
44 |
45 |
46 |
47 | io.netty
48 | netty-buffer
49 | 4.2.0.Final
50 |
51 | ```
52 |
53 |
54 |
55 | ## Documentation
56 | The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
57 | Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.
58 |
59 | You can easily create an `Integer` codec like this:
60 | ```java
61 | public static final BinaryCodec INTEGER = new BinaryCodec<>() {
62 | @Override
63 | public Integer read(ByteBuf buffer) {
64 | return buffer.readInt();
65 | }
66 |
67 | @Override
68 | public void write(ByteBuf buffer, Integer value) {
69 | buffer.writeInt(value);
70 | }
71 | };
72 | ```
73 |
74 | ### Built-in codecs
75 | There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:
76 |
77 | | Java Type | Codec | Notes |
78 | |-------------|-------------------|-------------------------------------------------------------------------------------|
79 | | `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
80 | | `byte` | `BYTE` | |
81 | | `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
82 | | `short` | `SHORT` | |
83 | | `int` | `INTEGER` | |
84 | | `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
85 | | `long` | `LONG` | |
86 | | `float` | `FLOAT` | |
87 | | `double` | `DOUBLE` | |
88 | | `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
89 | | `UUID` | `UUID` | Encoded as two 64-bit integers. |
90 |
91 | ### Templates
92 | Complex composite types can be created using the template syntax:
93 |
94 | ```java
95 | public record User(String name, Gender gender, int age) {
96 | public static final BinaryCodec CODEC = BinaryTemplate.of(
97 | STRING, User::name,
98 | Gender.CODEC, User::gender,
99 | INTEGER, User::age,
100 | // include up to 20 fields (total)
101 | User::new);
102 | }
103 | ```
104 |
105 | ### Transformations
106 | Use these methods to map a codec to another type.
107 | - `.array()` → `T[]`
108 | - `.list()` → `List`
109 | - `.nullable()` → `@Nullable T`
110 | - `.optional()` → `Optional`
111 | - `.map(Function, Function)` → `U`
112 |
113 | ### There's more!
114 | - Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
115 | - Use `BinaryCodec.unit(Example::new)` to represent singleton types.
116 | - Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
117 | - Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
118 | - Use `BinaryCodec.of(Function, BiConsumer)` for an easier way to create a codec, especially if using the `::` syntax.
119 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ],
6 | "rangeStrategy": "bump",
7 | "assignees": [
8 | "mudkipdev"
9 | ]
10 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 |
13 | - name: Set up Java
14 | uses: actions/setup-java@v4
15 | with:
16 | distribution: temurin
17 | java-version: 21
18 |
19 | - name: Set up Gradle
20 | uses: gradle/actions/setup-gradle@v4
21 |
22 | - name: Add execute permissions for gradlew
23 | run: chmod +x gradlew
24 |
25 | - name: Build with Gradle
26 | run: ./gradlew build
27 |
28 | - name: Upload artifact
29 | uses: actions/upload-artifact@v4
30 | with:
31 | name: alpine
32 | path: |
33 | binary/build/libs/alpine-*.*.*.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | gradle.properties
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### IntelliJ IDEA ###
9 | .idea
10 | *.iws
11 | *.iml
12 | *.ipr
13 | out/
14 | !**/src/main/**/out/
15 | !**/src/test/**/out/
16 |
17 | ### Eclipse ###
18 | .apt_generated
19 | .classpath
20 | .factorypath
21 | .project
22 | .settings
23 | .springBeans
24 | .sts4-cache
25 | bin/
26 | !**/src/main/**/bin/
27 | !**/src/test/**/bin/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
39 | ### Mac OS ###
40 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 mudkip
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.
--------------------------------------------------------------------------------
/binary/build.gradle.kts:
--------------------------------------------------------------------------------
1 | description = "A binary serialization library for Java."
2 |
3 | dependencies {
4 | compileOnly("io.netty:netty-buffer:4.2.2.Final")
5 | testImplementation("io.netty:netty-buffer:4.2.2.Final")
6 | }
--------------------------------------------------------------------------------
/binary/src/main/java/alpine/binary/ArrayBinaryCodec.java:
--------------------------------------------------------------------------------
1 | package alpine.binary;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | /**
6 | * A binary codec which serializes a sequence of values as an array.
7 | * @param parent The binary codec to serialize the values with.
8 | * @param The element type.
9 | * @author mudkip
10 | */
11 | record ArrayBinaryCodec(BinaryCodec parent) implements BinaryCodec {
12 | @SuppressWarnings("unchecked")
13 | @Override
14 | public T[] read(ByteBuf buffer) {
15 | var length = VARINT.read(buffer);
16 | var data = new Object[length];
17 |
18 | for (var index = 0; index < length; index++) {
19 | data[index] = this.parent.read(buffer);
20 | }
21 |
22 | return (T[]) data;
23 | }
24 |
25 | @Override
26 | public void write(ByteBuf buffer, T[] array) {
27 | VARINT.write(buffer, array.length);
28 |
29 | for (var value : array) {
30 | this.parent.write(buffer, value);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/binary/src/main/java/alpine/binary/ArrayBinaryCodecs.java:
--------------------------------------------------------------------------------
1 | package alpine.binary;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | /**
6 | * Built-in binary codecs for various primitive arrays.
7 | * @author mudkip
8 | */
9 | interface ArrayBinaryCodecs {
10 | BinaryCodec BOOLEAN_ARRAY = new BinaryCodec<>() {
11 | @Override
12 | public boolean[] read(ByteBuf buffer) {
13 | var length = VARINT.read(buffer);
14 | var data = new boolean[length];
15 |
16 | for (var index = 0; index < length; index++) {
17 | data[index] = BOOLEAN.read(buffer);
18 | }
19 |
20 | return data;
21 | }
22 |
23 | @Override
24 | public void write(ByteBuf buffer, boolean[] array) {
25 | VARINT.write(buffer, array.length);
26 |
27 | for (var value : array) {
28 | BOOLEAN.write(buffer, value);
29 | }
30 | }
31 | };
32 |
33 | BinaryCodec CHARACTER_ARRAY = new BinaryCodec<>() {
34 | @Override
35 | public char[] read(ByteBuf buffer) {
36 | var length = VARINT.read(buffer);
37 | var data = new char[length];
38 |
39 | for (var index = 0; index < length; index++) {
40 | data[index] = CHARACTER.read(buffer);
41 | }
42 |
43 | return data;
44 | }
45 |
46 | @Override
47 | public void write(ByteBuf buffer, char[] array) {
48 | VARINT.write(buffer, array.length);
49 |
50 | for (var value : array) {
51 | CHARACTER.write(buffer, value);
52 | }
53 | }
54 | };
55 |
56 | BinaryCodec BYTE_ARRAY = new BinaryCodec<>() {
57 | @Override
58 | public byte[] read(ByteBuf buffer) {
59 | var length = VARINT.read(buffer);
60 | var data = new byte[length];
61 |
62 | for (var index = 0; index < length; index++) {
63 | data[index] = BYTE.read(buffer);
64 | }
65 |
66 | return data;
67 | }
68 |
69 | @Override
70 | public void write(ByteBuf buffer, byte[] array) {
71 | VARINT.write(buffer, array.length);
72 | buffer.writeBytes(array);
73 | }
74 | };
75 |
76 | BinaryCodec SHORT_ARRAY = new BinaryCodec<>() {
77 | @Override
78 | public short[] read(ByteBuf buffer) {
79 | var length = VARINT.read(buffer);
80 | var data = new short[length];
81 |
82 | for (var index = 0; index < length; index++) {
83 | data[index] = SHORT.read(buffer);
84 | }
85 |
86 | return data;
87 | }
88 |
89 | @Override
90 | public void write(ByteBuf buffer, short[] array) {
91 | VARINT.write(buffer, array.length);
92 |
93 | for (var value : array) {
94 | SHORT.write(buffer, value);
95 | }
96 | }
97 | };
98 |
99 | BinaryCodec INTEGER_ARRAY = new BinaryCodec<>() {
100 | @Override
101 | public int[] read(ByteBuf buffer) {
102 | var length = VARINT.read(buffer);
103 | var data = new int[length];
104 |
105 | for (var index = 0; index < length; index++) {
106 | data[index] = INTEGER.read(buffer);
107 | }
108 |
109 | return data;
110 | }
111 |
112 | @Override
113 | public void write(ByteBuf buffer, int[] array) {
114 | VARINT.write(buffer, array.length);
115 |
116 | for (var value : array) {
117 | INTEGER.write(buffer, value);
118 | }
119 | }
120 | };
121 |
122 | BinaryCodec LONG_ARRAY = new BinaryCodec<>() {
123 | @Override
124 | public long[] read(ByteBuf buffer) {
125 | var length = VARINT.read(buffer);
126 | var data = new long[length];
127 |
128 | for (var index = 0; index < length; index++) {
129 | data[index] = LONG.read(buffer);
130 | }
131 |
132 | return data;
133 | }
134 |
135 | @Override
136 | public void write(ByteBuf buffer, long[] array) {
137 | VARINT.write(buffer, array.length);
138 |
139 | for (var value : array) {
140 | LONG.write(buffer, value);
141 | }
142 | }
143 | };
144 |
145 | BinaryCodec FLOAT_ARRAY = new BinaryCodec<>() {
146 | @Override
147 | public float[] read(ByteBuf buffer) {
148 | var length = VARINT.read(buffer);
149 | var data = new float[length];
150 |
151 | for (var index = 0; index < length; index++) {
152 | data[index] = FLOAT.read(buffer);
153 | }
154 |
155 | return data;
156 | }
157 |
158 | @Override
159 | public void write(ByteBuf buffer, float[] array) {
160 | VARINT.write(buffer, array.length);
161 |
162 | for (var value : array) {
163 | FLOAT.write(buffer, value);
164 | }
165 | }
166 | };
167 |
168 | BinaryCodec DOUBLE_ARRAY = new BinaryCodec<>() {
169 | @Override
170 | public double[] read(ByteBuf buffer) {
171 | var length = VARINT.read(buffer);
172 | var data = new double[length];
173 |
174 | for (var index = 0; index < length; index++) {
175 | data[index] = DOUBLE.read(buffer);
176 | }
177 |
178 | return data;
179 | }
180 |
181 | @Override
182 | public void write(ByteBuf buffer, double[] array) {
183 | VARINT.write(buffer, array.length);
184 |
185 | for (var value : array) {
186 | DOUBLE.write(buffer, value);
187 | }
188 | }
189 | };
190 | }
191 |
--------------------------------------------------------------------------------
/binary/src/main/java/alpine/binary/BinaryCodec.java:
--------------------------------------------------------------------------------
1 | package alpine.binary;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.nio.charset.Charset;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Optional;
10 | import java.util.function.BiConsumer;
11 | import java.util.function.Function;
12 | import java.util.function.Supplier;
13 |
14 | /**
15 | * Represents something that can encode and decode a value.
16 | * @param The value type.
17 | * @author mudkip
18 | */
19 | public interface BinaryCodec extends
20 | PrimitiveBinaryCodecs,
21 | StandardBinaryCodecs,
22 | ArrayBinaryCodecs,
23 | VariableBinaryCodecs {
24 | /**
25 | * Decodes the value from the buffer.
26 | * @param buffer The binary buffer.
27 | * @return The value read from the buffer.
28 | */
29 | T read(ByteBuf buffer);
30 |
31 | /**
32 | * Encodes the value to the buffer.
33 | * @param buffer The binary buffer.
34 | * @param value The value being written to the buffer.
35 | */
36 | void write(ByteBuf buffer, T value);
37 |
38 | /**
39 | * Maps this codec to a different type using the provided functions {@code to} and {@code from}.
40 | * @param to The function to transform the original value to a new type.
41 | * @param from The function to transform the new type to the original type.
42 | * @return The mapped codec.
43 | * @param The new value type.
44 | */
45 | default BinaryCodec map(Function to, Function from) {
46 | return new MappedBinaryCodec<>(this, to, from);
47 | }
48 |
49 | default BinaryCodec> optional() {
50 | return new OptionalBinaryCodec<>(this);
51 | }
52 |
53 | default BinaryCodec<@Nullable T> nullable() {
54 | return new NullableBinaryCodec<>(this);
55 | }
56 |
57 | default BinaryCodec> list() {
58 | return new ListBinaryCodec<>(this);
59 | }
60 |
61 | default BinaryCodec array() {
62 | return new ArrayBinaryCodec<>(this);
63 | }
64 |
65 | /**
66 | * Creates a codec based on the {@code reader} and {@code writer} functions.
67 | * @param reader The function to read the value from the buffer.
68 | * @param writer The function to write the value to the buffer.
69 | * @return The newly created codec.
70 | * @param The value type.
71 | */
72 | static BinaryCodec of(Function reader, BiConsumer writer) {
73 | return new BinaryCodec<>() {
74 | @Override
75 | public T read(ByteBuf buffer) {
76 | return reader.apply(buffer);
77 | }
78 |
79 | @Override
80 | public void write(ByteBuf buffer, T value) {
81 | writer.accept(buffer, value);
82 | }
83 | };
84 | }
85 |
86 | static BinaryCodec unit(Supplier supplier) {
87 | return of(buffer -> supplier.get(), (buffer, value) -> {});
88 | }
89 |
90 | static BinaryCodec> either(BinaryCodec leftCodec, BinaryCodec rightCodec) {
91 | return new EitherBinaryCodec<>(leftCodec, rightCodec);
92 | }
93 |
94 | static BinaryCodec