LWO #001: Objectos Code API revamp, and a use of Java's sealed interfaces

Marcio Endo
November 16, 2022

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:

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 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.