- Java 94.9%
- ANTLR 3.2%
- Shell 1.9%
| pannkaka-assembly | ||
| pannkaka-cli | ||
| pannkaka-generator | ||
| pannkaka-maven-plugin | ||
| pannkaka-parser | ||
| .gitignore | ||
| Jenkinsfile | ||
| LICENSE | ||
| pom.xml | ||
| publishing.txt | ||
| README.md | ||
pannkaka
Pannkaka ('pancake') is a tool for generating Java source code from a DSL.
Pannkaka uses the Apache Software Licenese 2.0
The code it generates consists of java Records and De-/Serializers using DataOutputStream and DataInputStream. The use case is to describe your data and messages in the DSL, then generate the code to Marshall those messages to an external format suitable for IO/Network transfer.
Pannkaka is not a generic framework for serialization like protobuf/Kryo or similar. It is meant to be a light-weight alternative for simple use cases. You generate the code, maybe even just one time and then treat it as source code in your own project.
How to install
Pannkaka requires Java 25 or later.
Download the archive from the release page and extract it. There is a bin directory with a launcher script that should work in bash.
How to write a DSL.
Here is an empty sample DSL:
config {}
records {}
messages {}
Config block
The config block is optional. It can be used to set package name and message interface name for the generated code.
config {
package: my.example.messages,
messageInterface: MyMessage
}
Records block
The records block defines the data structures (Java records) that will be generated.
Each entry becomes a Java record with the same name and fields in the same order.
In the sample DSL:
records {
Foo(
int intbar,
boolean boolbar,
char charbar,
float floatbar,
double doublebar,
byte bytebar,
short shortbar,
long longbar,
String stringbar
);
Bar(String baz, Foo foo);
}
This will generate two records, Foo and Bar. Foo contains primitive and String fields, while Bar shows that
records can reference other records (Foo foo).
Primitive types
The types in the Foo record above are all the primitive types pannkaka supports.
Messages block
The messages block defines the messages that will be sent over the wire.
Each message is also generated as a Java record and typically composes previously defined records.
In the sample DSL:
messages {
AddFoToBarMessage(Foo foo, Bar bar, boolean fakeFlag);
RemoveBarFromFooMessage(char fakeChar, Bar bar, Foo foo);
}
Here, AddFoToBarMessage and RemoveBarFromFooMessage are messages that use Foo and Bar as payload types,
along with primitive fields. These message definitions will be turned into serializable Java records together with
matching de-/serializers.
The generated code
The generated code consists of three classes for each record and message:
- A record class with the same name as the record in the DSL.
- A serializer class with the same name as the record in the DSL, but suffixed with "Serializer".
- A deserializer class with the same name as the record in the DSL, but suffixed with "Deserializer".
The generated code
SerDe
For each DSL, Pannkaka generates a SerDe (serializer/deserializer) class for your messages.
Its name is derived from the messageInterface value in the config block:
- If you configure
messageInterface: MyMessage - The generated SerDe class will be named
MyMessageSerDe
This class exposes static methods to write and read messages using DataOutputStream and DataInputStream.
A typical usage from Java code looks like this:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
// import your generated types and SerDe class:
// import my.example.messages.*; // e.g. MyMessage, Foo, Bar, MyMessageSerDe
public class Example {
public static void main(String[] args) throws Exception {
// Construct a message using generated records
Foo foo = new Foo(1, true, 'x', 1.0f, 2.0, (byte)3, (short)4, 5L, "hello");
Bar bar = new Bar("baz", foo);
MyMessage message = new AddFoToBarMessage(foo, bar, false);
// Serialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (DataOutputStream dos = new DataOutputStream(baos)) {
MyMessageSerDe.write(message, dos);
}
byte[] bytes = baos.toByteArray();
// Deserialize
try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes))) {
MyMessage roundTripped = MyMessageSerDe.read(dis);
// Use the deserialized message (pattern match / instanceof / switch, etc.)
if (roundTripped instanceof AddFoToBarMessage add) {
System.out.println("Got AddFoToBarMessage with baz = " + add.bar().baz());
}
}
}
}
In summary:
- Construct your generated message record(s).
- Use the generated
*MessageSerDe.write(message, DataOutputStream)to serialize. - Use
*MessageSerDe.read(DataInputStream)to get back an instance of your message interface. - Dispatch on the concrete message type (
switchorinstanceof) to handle different messages.
Maven
There is also a mven plugin to generate sources from DSL files. See pannkaka-maven-plugin