Skip to content
Draft
  •  
  •  
  •  
105 changes: 42 additions & 63 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# alpine
A binary<sup>(JSON soon™)</sup> serialization library for Java.
A binary and JSON serialization library for Java.

![](https://wakatime.com/badge/github/mudkipdev/alpine.svg)

Expand All @@ -12,7 +12,7 @@ A binary<sup>(JSON soon™)</sup> serialization library for Java.

```kts
dependencies {
implementation("dev.mudkip:alpine-binary:0.1.1")
implementation("dev.mudkip:alpine-binary:0.2.0")
implementation("io.netty:netty-buffer:4.2.0.Final")
}
```
Expand All @@ -25,7 +25,7 @@ dependencies {

```groovy
dependencies {
implementation 'dev.mudkip:alpine-binary:0.1.1'
implementation 'dev.mudkip:alpine-binary:0.2.0'
implementation 'io.netty:netty-buffer:4.2.0.Final'
}
```
Expand All @@ -40,7 +40,7 @@ dependencies {
<dependency>
<groupId>dev.mudkip</groupId>
<artifactId>alpine-binary</artifactId>
<version>0.1.1</version>
<version>0.2.0</version>
</dependency>

<dependency>
Expand All @@ -52,67 +52,46 @@ dependencies {

</details>

## Documentation
The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.

You can easily create an `Integer` codec like this:
```java
public static final BinaryCodec<Integer> INTEGER = new BinaryCodec<>() {
@Override
public Integer read(ByteBuf buffer) {
return buffer.readInt();
}

@Override
public void write(ByteBuf buffer, Integer value) {
buffer.writeInt(value);
}
};
### JSON

<details>
<summary>Gradle (Kotlin)</summary>
<br>

```kts
dependencies {
implementation("dev.mudkip:alpine-json:0.2.0")
}
```

### Built-in codecs
There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:

| Java Type | Codec | Notes |
|-------------|-------------------|-------------------------------------------------------------------------------------|
| `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
| `byte` | `BYTE` | |
| `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
| `short` | `SHORT` | |
| `int` | `INTEGER` | |
| `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
| `long` | `LONG` | |
| `float` | `FLOAT` | |
| `double` | `DOUBLE` | |
| `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
| `UUID` | `UUID` | Encoded as two 64-bit integers. |

### Templates
Complex composite types can be created using the template syntax:

```java
public record User(String name, Gender gender, int age) {
public static final BinaryCodec<User> CODEC = BinaryTemplate.of(
STRING, User::name,
Gender.CODEC, User::gender,
INTEGER, User::age,
// include up to 20 fields (total)
User::new);
</details>

<details>
<summary>Gradle (Groovy)</summary>
<br>

```groovy
dependencies {
implementation 'dev.mudkip:alpine-json:0.2.0'
}
```

### Transformations
Use these methods to map a codec to another type.
- `.array()` → `T[]`
- `.list()` → `List<T>`
- `.nullable()` → `@Nullable T`
- `.optional()` → `Optional<T>`
- `.map(Function<T, U>, Function<U, T>)` → `U`

### There's more!
- Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
- Use `BinaryCodec.unit(Example::new)` to represent singleton types.
- Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
- Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
- Use `BinaryCodec.of(Function<ByteBuf, T>, BiConsumer<ByteBuf, T>)` for an easier way to create a codec, especially if using the `::` syntax.
</details>

<details>
<summary>Maven</summary>
<br>

```xml
<dependency>
<groupId>dev.mudkip</groupId>
<artifactId>alpine-json</artifactId>
<version>0.2.0</version>
</dependency>
```

</details>

## Documentation
- [Binary](./binary.md)
- [JSON](./json.md)
64 changes: 64 additions & 0 deletions .github/binary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Documentation
The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.

You can easily create an `Integer` codec like this:
```java
public static final BinaryCodec<Integer> INTEGER = new BinaryCodec<>() {
@Override
public Integer read(ByteBuf buffer) {
return buffer.readInt();
}

@Override
public void write(ByteBuf buffer, Integer value) {
buffer.writeInt(value);
}
};
```

### Built-in codecs
There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:

| Java Type | Codec | Notes |
|-------------|-------------------|-------------------------------------------------------------------------------------|
| `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
| `byte` | `BYTE` | |
| `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
| `short` | `SHORT` | |
| `int` | `INTEGER` | |
| `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
| `long` | `LONG` | |
| `float` | `FLOAT` | |
| `double` | `DOUBLE` | |
| `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
| `UUID` | `UUID` | Encoded as two 64-bit integers. |

### Templates
Complex composite types can be created using the template syntax:

```java
public record User(String name, Gender gender, int age) {
public static final BinaryCodec<User> CODEC = BinaryTemplate.of(
STRING, User::name,
Gender.CODEC, User::gender,
INTEGER, User::age,
// include up to 20 fields (total)
User::new);
}
```

### Transformations
Use these methods to map a codec to another type.
- `.array()` → `T[]`
- `.list()` → `List<T>`
- `.nullable()` → `@Nullable T`
- `.optional()` → `Optional<T>`
- `.map(Function<T, U>, Function<U, T>)` → `U`

### There's more!
- Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
- Use `BinaryCodec.unit(Example::new)` to represent singleton types.
- Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
- Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
- Use `BinaryCodec.of(Function<ByteBuf, T>, BiConsumer<ByteBuf, T>)` for an easier way to create a codec, especially if using the `::` syntax.
2 changes: 2 additions & 0 deletions .github/json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Documentation
This is not finished yet. [Take a look at the source code?](../json/src/main/java/alpine/json)
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ jobs:
with:
name: alpine
path: |
binary/build/libs/alpine-*.*.*.jar
binary/build/libs/binary-*.*.*.jar
json/build/libs/json-*.*.*.jar
16 changes: 16 additions & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("me.champeau.jmh") version "0.7.3"
}

dependencies {
jmhImplementation(project(":json"))
jmhImplementation("org.openjdk.jmh:jmh-core:1.37")
jmhImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
jmhImplementation("com.google.code.gson:gson:2.13.1")
}

jmh {
warmupIterations.set(3)
iterations.set(5)
fork.set(1)
}
46 changes: 46 additions & 0 deletions benchmark/src/jmh/java/alpine/json/JsonBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package alpine.json;

import com.google.gson.JsonParser;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

@State(Scope.Thread)
public class JsonBenchmark {
private static final String COMPLEX_JSON = """
{
"name": "Mudkip",
"type": "Water",
"stats": {
"hp": 50,
"attack": 70,
"defense": 50,
"speed": 40,
"abilities": ["Torrent", "Damp"]
},
"evolution": {
"level": 16,
"next": "Marshtomp"
},
"moves": [
{"name": "Water Gun", "power": 40},
{"name": "Tackle", "power": 35},
{"name": "Mud-Slap", "power": 20}
],
"metadata": {
"origin": "Hoenn",
"isLegendary": false
}
}
""";

@Benchmark
public void alpine() throws ParsingException {
Json.read(COMPLEX_JSON);
}

@Benchmark
public void gson() {
JsonParser.parseString(COMPLEX_JSON);
}
}
14 changes: 13 additions & 1 deletion binary/src/main/java/alpine/binary/BinaryCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.function.Supplier;

/**
* Represents something that can encode and decode a value.
* Represents something that can encode and decode a value to a byte buffer.
* @param <T> The value type.
* @author mudkip
*/
Expand Down Expand Up @@ -83,6 +83,18 @@ public void write(ByteBuf buffer, T value) {
};
}

static <T> BinaryCodec<T> decodeOnly(Function<ByteBuf, T> reader) {
return of(reader, (buffer, value) -> {
throw new UnsupportedOperationException("This binary codec does not support encoding!");
});
}

static <T> BinaryCodec<T> encodeOnly(BiConsumer<ByteBuf, T> writer) {
return of(buffer -> {
throw new UnsupportedOperationException("This binary codec does not support decoding!");
}, writer);
}

static <T> BinaryCodec<T> unit(Supplier<T> supplier) {
return of(buffer -> supplier.get(), (buffer, value) -> {});
}
Expand Down
5 changes: 5 additions & 0 deletions binary/src/main/java/alpine/binary/Either.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Something that can be represented as one of two types.
* @param <L> The left type.
* @param <R> The right type.
* @author mudkip
*/
public final class Either<L, R> {
enum Type {
Expand Down Expand Up @@ -89,6 +90,10 @@ public void consume(Consumer<L> leftConsumer, Consumer<R> rightConsumer) {
this.ifRight(rightConsumer);
}

public Either<R, L> swap() {
return this.isLeft() ? right(this.left) : left(this.right);
}

public boolean isLeft() {
return this.type == Type.LEFT;
}
Expand Down
12 changes: 5 additions & 7 deletions binary/src/main/java/alpine/binary/EitherBinaryCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @param <L> The left value type.
* @param <R> The right value type.
* @see Either
* @author mudkip
*/
record EitherBinaryCodec<L, R>(BinaryCodec<L> leftCodec, BinaryCodec<R> rightCodec) implements BinaryCodec<Either<L, R>> {
@Override
Expand All @@ -22,12 +23,9 @@ public Either<L, R> read(ByteBuf buffer) {

@Override
public void write(ByteBuf buffer, Either<L, R> value) {
if (value.isLeft()) {
BOOLEAN.write(buffer, false);
this.leftCodec.write(buffer, value.expectLeft());
} else {
BOOLEAN.write(buffer, true);
this.rightCodec.write(buffer, value.expectRight());
}
BOOLEAN.write(buffer, value.isRight());
value.consume(
left -> this.leftCodec.write(buffer, left),
right -> this.rightCodec.write(buffer, right));
}
}
2 changes: 2 additions & 0 deletions binary/src/main/java/alpine/binary/MapBinaryCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public Map<K, V> read(ByteBuf buffer) {

@Override
public void write(ByteBuf buffer, Map<K, V> value) {
VARINT.write(buffer, value.size());

for (var entry : value.entrySet()) {
this.keyCodec.write(buffer, entry.getKey());
this.valueCodec.write(buffer, entry.getValue());
Expand Down
Loading