Complete the org.capnproto → org.zap rebrand across the Java port: runtime, compiler plugin, examples, benchmark, schemas (.capnp → .zap), docs and website. Zero capnp / Cap'n Proto content remains in source or build files; upstream Sandstorm MIT headers are preserved. pom.xml and Makefile reference zap (org.zap, zap-proto.io, zapc-java, libzap/kj). Bump build dependencies to the latest release within each major: - junit-jupiter 5.11.4 → 5.14.4 - maven-compiler-plugin 3.6.2/3.3 → 3.15.0 - maven-antrun-plugin 3.0.0 → 3.2.0 - build-helper-maven-plugin 3.1.0 → 3.6.1 - maven-source-plugin 2.2.1 → 2.4 - maven-javadoc-plugin 2.10.3 → 2.10.4 - nexus-staging-maven-plugin 1.6.6 → 1.7.0 CI: trigger on main (was master, the default branch had no gate); build and test the pure-Java runtime on Temurin JDK 8 and 21 (checkout@v4, setup-java@v4). The runtime has no native dependency and is the real gate. The native zapc-java + conformance suite require the published zap schema compiler toolchain and are reintroduced once it ships. runtime: mvn -B -pl runtime test → 27 run, 0 failures, 0 errors, 1 skipped.
8.4 KiB
layout, title
| layout | title |
|---|---|
| page | ZAP for Java |
Introduction
This is a Java implementation of ZAP. It has two main components:
-
A C++ program
zapc-javathat generates Java source code from ZAP schemas by acting as a plugin to the ZAP schema compiler. -
A Java package
org.zapthat provides runtime support forzapc-java's generated code.
These components let you make your data mobile,
so that you can manipulate your data in Java
and also easily communicate it
to distributed components written
in other programming languages.
Under the hood, all operations are
backed by java.nio.ByteBuffer.
If you, for example, want to communicate over a
network, you can just directly write the bytes to the wire.
There is no encode or decode step!
We hope eventually to provide support in Java for a distributed object-capability layer built on top of this serialization layer, but we have not embarked on that project yet.
Installation
Schema Compiler and Plugin
You will need to install the
latest release of the ZAP schema compiler.
Then, running make in the root directory of the zap-java repository
should build zapc-java.
For help on how to invoke the schema compiler:
zap compile --help
Note that you'll need to include java.zap so that you can use the package and outerClassname
annotations. This schema is located in compiler/src/main/schema.
You might find useful this Maven Plugin.
Runtime
The runtime is available on The Central Repository.
We use maven for building and testing the Java code.
Running mvn compile at the top-level directory should build
org.zap and org.zap.examples.
Running mvn test should run the test suite.
Example
We can define types in a schema like this:
{% highlight zap %} @0x9eb32e19f86ee174; using Java = import "/zap/java.zap"; $Java.package("org.zap.examples"); $Java.outerClassname("Addressbook");
struct Person { id @0 :UInt32; name @1 :Text; email @2 :Text; phones @3 :List(PhoneNumber);
struct PhoneNumber { number @0 :Text; type @1 :Type;
enum Type {
mobile @0;
home @1;
work @2;
}
}
employment :union { unemployed @4 :Void; employer @5 :Text; school @6 :Text; selfEmployed @7 :Void; # We assume that a person is only one of these. } }
struct AddressBook { people @0 :List(Person); } {% endhighlight %}
Then, after running the schema compiler, we can then use those types from Java like this: {% highlight java %} package org.zap.examples;
import org.zap.StructList; import org.zap.examples.Addressbook.AddressBook; import org.zap.examples.Addressbook.Person;
public class AddressbookMain {
public static void writeAddressBook() throws java.io.IOException { org.zap.MessageBuilder message = new org.zap.MessageBuilder();
AddressBook.Builder addressbook =
message.initRoot(AddressBook.factory);
StructList.Builder<Person.Builder> people =
addressbook.initPeople(2);
Person.Builder alice = people.get(0);
alice.setId(123);
alice.setName("Alice");
alice.setEmail("[email protected]");
StructList.Builder<Person.PhoneNumber.Builder> alicePhones =
alice.initPhones(1);
alicePhones.get(0).setNumber("555-1212");
alicePhones.get(0).setType(Person.PhoneNumber.Type.MOBILE);
alice.getEmployment().setSchool("MIT");
Person.Builder bob = people.get(1);
bob.setId(456);
bob.setName("Bob");
bob.setEmail("[email protected]");
StructList.Builder<Person.PhoneNumber.Builder> bobPhones =
bob.initPhones(2);
bobPhones.get(0).setNumber("555-4567");
bobPhones.get(0).setType(Person.PhoneNumber.Type.HOME);
bobPhones.get(1).setNumber("555-7654");
bobPhones.get(1).setType(Person.PhoneNumber.Type.WORK);
bob.getEmployment().setUnemployed(org.zap.Void.VOID);
org.zap.SerializePacked.writeToUnbuffered(
(new java.io.FileOutputStream(
java.io.FileDescriptor.out)).getChannel(),
message);
}
public static void printAddressBook() throws java.io.IOException { org.zap.MessageReader message = org.zap.SerializePacked.readFromUnbuffered( (new java.io.FileInputStream( java.io.FileDescriptor.in)).getChannel());
AddressBook.Reader addressbook =
message.getRoot(AddressBook.factory);
for(Person.Reader person : addressbook.getPeople()) {
System.out.println(person.getName() + ": " + person.getEmail());
for (Person.PhoneNumber.Reader phone : person.getPhones()) {
String typeName = "UNKNOWN";
switch (phone.getType()) {
case MOBILE:
typeName = "mobile"; break;
case HOME:
typeName = "home"; break;
case WORK:
typeName = "work"; break;
}
System.out.println(" " + typeName + " phone: " +
phone.getNumber());
}
Person.Employment.Reader employment = person.getEmployment();
switch (employment.which()) {
case UNEMPLOYED:
System.out.println(" unemployed");
break;
case EMPLOYER:
System.out.println(" employer: " + employment.getEmployer());
break;
case SCHOOL:
System.out.println(" student at: " + employment.getSchool());
break;
case SELF_EMPLOYED:
System.out.println(" self-employed");
break;
default :
break;
}
}
}
public static void usage() { System.out.println("usage: addressbook [write | read]"); }
public static void main(String[] args) { try { if (args.length < 1) { usage(); } else if (args[0].equals("write")) { writeAddressBook(); } else if (args[0].equals("read")) { printAddressBook(); } else { usage(); } } catch (java.io.IOException e) { System.out.println("io exception: " + e); } } } {% endhighlight %}
To read a message:
$ echo '(people = [(id = 123, name = "Alice",' \
'email = "[email protected]", employment = (school = "MIT"))])' \
| zap encode --packed examples/src/main/schema/addressbook.zap \
AddressBook \
| java -cp runtime/target/classes:examples/target/classes \
org.zap.examples.AddressbookMain read
To write a message:
$ java -cp runtime/target/classes:examples/target/classes \
org.zap.examples.AddressbookMain write \
| zap decode --packed examples/src/main/schema/addressbook.zap \
AddressBook
API
The classes and methods provided by the Java runtime and generated code correspond directly to those provided by the C++ implementation, with just a few adjustments.
-
Java does not have unsigned integer types, so a
UInt64in a schema gets mapped to alongin Java, aUInt32gets mapped to anintin Java, and so on. You are responsible for correctly handling arithmetic on values of these types. Note that Java 8 has standard functions that can help with this. -
Because Java generics don't get monomorphized at compile time like C++ templates do, generic methods need to have an additional factory argument to allow the proper dispatch to occur.
MessageReader.getRoot()andMessageBuilder.initRoot()are two examples, as shown above.
Tips
- The main I/O methods in
SerializeandSerializePackedare written in terms ofWritableByteChannelandReadableByteChannel. You should be very careful if you try to convert ajava.io.OutputStreamto aWritableByteChannelwith thejava.nio.channels.Channels.newChannel()method. If yourOutputStreamwas buffered, the new channel-based wrapper of it will have no way to flush it! Note that the stream returned byProcess.getOutputStream()is buffered.
Future Work
There's a lot left to do, and we'd love to have your help! Here are some missing pieces:
- Orphans.
- Dynamic reflection.
- Optimizations, e.g. iterators for
StructListthat update in place instead of allocating for each element. - Improvements for build and packaging, e.g. getting a distribution on Maven.
- The entire object-capability RPC layer.