Daedalus
A modern implementation of protoc to serialize, deserialize and generate java sources from protobuf schemas
Install / Use
/learn @Auties00/DaedalusREADME
ModernProtobuf
A modern implementation of the Protobuf specification for Java 21 and upwards. Both Protobuf 2 and 3 are supported.
What is ModernProtobuf
Protoc, the default compiler for protobuf schemas, can generate Java classes starting from a schema. The generated code, though, is really verbose and not up to date with modern versions of Java. Moreover, it is not really intended to be edited, which is not optimal if you want to have the protobuf to be part of a publicly available developer API. As a result, most projects that work with Google's protobuf create wrappers around the already gigantic classes generated by Google to make them more usable. While developing Cobalt, I faced these issues myself and, as a result, I decided to develop a custom Protobuf implementation: this repo is the result of this work.
Project setup
Maven
-
Dependency
<dependency> <groupId>com.github.auties00</groupId> <artifactId>protobuf-base</artifactId> <version>4.0.0</version> </dependency> -
Annotation processor
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <annotationProcessorPath> <groupId>com.github.auties00</groupId> <artifactId>protobuf-serialization-plugin</artifactId> <version>4.0.0</version> <annotationProcessorPath> <annotationProcessorPaths> <configuration> <plugin>
Gradle
-
Groovy DSL
- Dependency
implementation 'com.github.auties00:protobuf-base:4.0.0'- Annotation processor
annotationProcessor 'com.github.auties00:protobuf-serialization-plugin:4.0.0' -
Kotlin DSL
- Dependency
implementation("com.github.auties00:protobuf-base:4.0.0")- Annotation processor
annotationProcessor("com.github.auties00:protobuf-serialization-plugin:4.0.0")
Schema generation and updating
Download the CLI tool from the release tab or compile the project yourself using
mvn clean install
Run the following command:
protoc generate <proto> --output [directory]
This will generate an immutable record. If you want your model to be mutable, add --mutable.
If you don't want to use Optionals, add --nullable.
If you want to update your models, run:
protoc update <proto> <input_directory> --output [directory]
This will update all models in input_directory.
If you want your updated models to be mutable, add --mutable
If you don't want to use Optionals, add --nullable.
Comparison with Google's implementation
Let's take an example schema to make some considerations on the differences between this library and Google's implementation.
<details> <summary>ScalarMessage.proto (15 LOC)</summary>message ScalarMessage {
optional fixed32 fixed32 = 1;
optional sfixed32 sfixed32 = 2;
optional int32 int32 = 3;
optional uint32 uint32 = 4;
optional fixed64 fixed64 = 5;
optional sfixed64 sfixed64 = 6;
optional int64 int64 = 7;
optional uint64 uint64 = 8;
optional float float = 9;
optional double double = 10;
optional bool bool = 11;
optional string string = 12;
optional bytes bytes = 13;
}
</details>
Model generation
ModernProtobuf uses records by default to produce immutable and concise models. If you need your models to be mutable, ModernProtobuf's CLI tool can generate a mutable class just as easily. Google's implementation, on the other hand, produces verbose models that cannot be edited. There is no option to make your data immutable. Here are the models generated by each implementation, alongside how many lines of code were used:
<details> <summary>ModernProtobuf immutable java record (29 LOC + 201 LOC generated at compile time for ScalarMessageSpec and ScalarMessageBuilder)</summary>@ProtobufMessage
public record ScalarMessage(
@ProtobufProperty(index = 1, type = ProtobufType.FIXED32)
int fixed32,
@ProtobufProperty(index = 2, type = ProtobufType.SFIXED32)
int sfixed32,
@ProtobufProperty(index = 3, type = ProtobufType.INT32)
int int32,
@ProtobufProperty(index = 4, type = ProtobufType.UINT32)
int uint32,
@ProtobufProperty(index = 5, type = ProtobufType.FIXED64)
long fixed64,
@ProtobufProperty(index = 6, type = ProtobufType.SFIXED64)
long sfixed64,
@ProtobufProperty(index = 7, type = ProtobufType.INT64)
long int64,
@ProtobufProperty(index = 8, type = ProtobufType.UINT64)
long uint64,
@ProtobufProperty(index = 9, type = ProtobufType.FLOAT)
float _float,
@ProtobufProperty(index = 10, type = ProtobufType.DOUBLE)
double _double,
@ProtobufProperty(index = 11, type = ProtobufType.BOOL)
boolean bool,
@ProtobufProperty(index = 12, type = ProtobufType.STRING)
ProtobufString string,
@ProtobufProperty(index = 13, type = ProtobufType.BYTES)
ByteBuffer bytes
) { }
</details>
<details>
<summary>Google's Protobuf mutable java class (1832 LOC)</summary>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: scalar.proto
public final class Scalar {
private Scalar() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
public interface ScalarMessageOrBuilder extends
// @@protoc_insertion_point(interface_extends:com.github.auties00.daedalus.ScalarMessage)
com.google.protobuf.MessageOrBuilder {
/**
* <code>optional fixed32 fixed32 = 1;</code>
* @return Whether the fixed32 field is set.
*/
boolean hasFixed32();
/**
* <code>optional fixed32 fixed32 = 1;</code>
* @return The fixed32.
*/
int getFixed32();
/**
* <code>optional sfixed32 sfixed32 = 2;</code>
* @return Whether the sfixed32 field is set.
*/
boolean hasSfixed32();
/**
* <code>optional sfixed32 sfixed32 = 2;</code>
* @return The sfixed32.
*/
int getSfixed32();
/**
* <code>optional int32 int32 = 3;</code>
* @return Whether the int32 field is set.
*/
boolean hasInt32();
/**
* <code>optional int32 int32 = 3;</code>
* @return The int32.
*/
int getInt32();
/**
* <code>optional uint32 uint32 = 4;</code>
* @return Whether the uint32 field is set.
*/
boolean hasUint32();
/**
* <code>optional uint32 uint32 = 4;</code>
* @return The uint32.
*/
int getUint32();
/**
* <code>optional fixed64 fixed64 = 5;</code>
* @return Whether the fixed64 field is set.
*/
boolean hasFixed64();
/**
* <code>optional fixed64 fixed64 = 5;</code>
* @return The fixed64.
*/
long getFixed64();
/**
* <code>optional sfixed64 sfixed64 = 6;</code>
* @return Whether the sfixed64 field is set.
*/
boolean hasSfixed64();
/**
* <code>optional sfixed64 sfixed64 = 6;</code>
* @return The sfixed64.
*/
long getSfixed64();
/**
* <code>optional int64 int64 = 7;</code>
* @return Whether the int64 field is set.
*/
boolean hasInt64();
/**
* <code>optional int64 int64 = 7;</code>
* @return The int64.
*/
long getInt64();
/**
* <code>optional uint64 uint64 = 8;</code>
* @return Whether the uint64 field is set.
*/
boolean hasUint64();
/**
* <code>optional uint64 uint64 = 8;</code>
* @return The uint64.
*/
long getUint64();
/**
* <code>optional float float = 9;</code>
* @return Whether the float field is set.
*/
boolean hasFloat();
/**
* <code>optional float float = 9;</code>
* @return The float.
*/
float getFloat();
/**
* <code>optional double double = 10;</code>
* @return Whether the double field is set.
*/
boolean hasDouble();
/**
* <code>optional double double = 10;</code>
* @return The double.
*/
double getDouble();
/**
* <code>optional bool bool = 11;</code>
* @return Whether the bool field is set.
*/
boolean hasBool();
/**
* <code>optional bool bool = 11;</code>
* @return The bool.
*/
boolean getBool();
/**
* <code>optional string string = 12;</code>
* @return Whether the string field is set.
*/
boolean hasString();
/**
* <code>optional string string = 12;</code>
* @return The string.
*/
java.lang.String getString();
/**
* <code>optional string string = 12;</code>
* @return The bytes for string.
*/
com.google.protobuf.ByteString
getStringBytes();
/**
* <code>optional bytes bytes = 13;</code>
* @return Whether the bytes field is set.
*/
boolean hasBytes();
/**
* <code>optional bytes bytes = 13;</code>
* @return The bytes.
*/
com.google.protobuf.ByteString getBytes();
}
/**
* Protobuf type {@code com.github.auties00.daedalus.ScalarMessage}
*/
public static final class ScalarMessage extends
com.google.protobuf.GeneratedMessageV3 implements
// @@protoc_insertion_point(message_implements:com.github.auties00.daedalus.ScalarMessage)
ScalarMessageOrBuilder {
private static final long serialVersionUID = 0L;
// Use ScalarMessage.newBuilder() to construct.
private ScalarMessage(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
super
