LWO #001: Objectos Code API revamp, and a use of Java's sealed interfaces
Hi, welcome to the first edition of "Last Week in Objectos" (LWO), my new writing project.
I enjoy writing. But the deep technical writings of the blog are very time consuming. Time spent in the writing itself, of course, but also spent on figuring out what to write about.
This is an attempt to solve this; spend less time on writing related tasks while still providing value to readers.
I will write about what happened the previous week in Objectos. The idea is to share the things I learned, the problems I faced and how I solve them. Or how I didn't solve them...
Also, the idea is for this to become a newsletter. Once I sort out the software, that is. It will have a different name though.
All right, enough of this introduction. Let's begin.
Objectos Code API revamp
Objectos Code is a Java library for generating Java source code. Later, it will also provide utilities for writing annotation processors.
I am currently working on revamping its API.
The new API
My idea is to provide a "template engine" for generating Java source code. However, unlike traditional template engines, the template itself is not string based. Templates in Objectos Code are written in pure Java instead.
You create a template by extending the JavaTemplate
class.
The following is a sort-of hello world example:
import objectos.code.JavaTemplate;
class HelloTemplate extends JavaTemplate {
String pkgName = "com.example";
String name = "HelloWorld";
String message = "Hello world!";
@Override
protected final void definition() {
_package(pkgName);
_class(
_public(), id(name),
_method(
_public(), _void(), id("sayHello"),
invoke(n(System.class, "out"), "println", s(message))
)
);
}
}
The template can be used like the following:
var hello = new HelloTemplate();
hello.pkgName = "objectos.example";
System.out.println(hello);
Which prints:
package objectos.example;
public class HelloWorld {
public void sayHello() {
System.out.println("Hello world!");
}
}
The new API is a work in progress. It does not support array types yet, nor can you declare method parameters. This is the reason why the example is not a "proper" hello world.
Consistent API across Objectos' libraries
So, why am I creating a new API? Was there something wrong with the old one?
There are a few things I dislike about the old API. But the main reason for the new API is to have a consistent API throughout all of the Objectos' libraries.
For example, Objectos HTML is an unreleased Java library to generate HTML using pure Java:
class MyTemplate extends HtmlTemplate {
@Override
protected final void definition() {
doctype();
html(
lang("en"),
head(
meta(charset("utf-8")),
meta(httpEquiv("x-ua-compatible"), content("ie=edge")),
meta(
name("viewport"),
content("width=device-width, initial-scale=1, shrink-to-fit=no")
)
),
body(
p("Hello world!")
)
);
}
}
As you can see, it uses the same idea of "a template in pure Java".
Objectos CSS, used to generate CSS style sheets, also uses the same idea.
Work on Objectos Code
Now that I've given an introduction to Objectos Code, here's what I've actually work on this past week.
Initial enum declaration support
I've started adding the enum declaration support to Objectos Code.
You can generate a simple enum like the following:
public enum Suit {
CLUBS,
DIAMONDS,
HEARTS,
SPADES
}
Using this code:
_enum(
_public(), id("Suit"),
enunConstant(id("CLUBS")),
enunConstant(id("DIAMONDS")),
enunConstant(id("HEARTS")),
enunConstant(id("SPADES"))
);
Initial field declaration support
I also started migrating field declarations to the new API.
The following class:
class Fields {
Integer a;
final String b = "b";
private static final String C = "C";
String d, e = b;
}
Can be generated with:
_class(
id("Fields"),
field(t(Integer.class), id("a")),
field(_final(), t(String.class), id("b"), s("b")),
field(_private(), _static(), _final(), t(String.class), id("C"), s("C")),
field(t(String.class), id("d"), id("e"), n("b"))
);
You should note that:
-
the
id
method, which is short for identifier, is for naming declarations, i.e., the class name, the enum class name, the field name, etc. -
the
t
method is for types -
the
s
method is for string literals -
the
n
method is for expression names
A (somewhat) type-safe API
The examples might give the impression that the Objectos Code API is free-form. But it is not.
As an example, the following does not compile:
// does not compile!!
_class(
enumConstant(id("FAIL"))
);
As the _class
method does not accept as argument the return value of the enumConstant
method.
Similarly, the following does not compile either:
// does not compile!!!
_enum(
_public(), _final(), id("Fail")
);
As the _enum
method does not accept as argument the return value of the _final()
method.
Using Java's sealed interfaces
To provide the type-safety of the Objectos Code API I am using Java's sealed interfaces.
The _class
method is an instance method of the JavaTemplate
class.
The signature of the method is the following:
protected final ClassDeclaration _class(
ClassDeclarationElement... elements);
So it only accepts instances of the ClassDeclarationElement
interface.
But it cannot accept any implementation of said interface.
In fact, it must only accept implementations provided by Objectos Code itself.
So there are two conflicting problems:
-
the
ClassDeclarationElement
interface must be accessible to subclasses of theJavaTemplate
; and -
the
_class
method cannot accept arbitrary implementations of theClassDeclarationElement
interface.
The way I solved it (I think) is by using sealed interfaces.
Here's a simplified version of the InternalApi
class.
The InternalApi
class declares all of the sealed hierarchy:
public final class InternalApi {
public sealed interface ClassDeclaration {}
public sealed interface ClassDeclarationElement {}
public sealed interface Identifier
extends ClassDeclarationElement {}
public sealed interface PublicModifier
extends ClassDeclarationElement {}
public sealed interface VoidInvocation {}
private static final class Ref
implements
ClassDeclaration,
Identifier,
PublicModifier,
VoidInvocation {
private Ref() {}
}
public static final Ref REF = new Ref();
}
And, in the JavaTemplate
class, we write:
protected final ClassDeclaration _class(
ClassDeclarationElement... elements) {
// actual impl...
return InternalApi.REF;
}
protected final PublicModifier _public() {
// actual impl...
return InternalApi.REF;
}
protected final VoidInvocation _void() {
// actual impl...
return InternalApi.REF;
}
protected final Identifier id(String value) {
// actual impl...
return InternalApi.REF;
}
So it means that the following invocation is valid:
_class(_public(), id("Foo"));
While the following is not:
_class(_void(), id("Foo"));
Until the next edition
So that's it for today. I hope you've enjoyed reading.
Please send me an e-mail if you have comments, questions or corrections regarding this post.
If you liked this content you can follow me on Twitter.