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

marcioendo

Marcio Endo

Posted on November 18, 2022

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))
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The template can be used like the following:

var hello = new HelloTemplate();
hello.pkgName = "objectos.example";
System.out.println(hello);
Enter fullscreen mode Exit fullscreen mode

Which prints:

package objectos.example;

public class HelloWorld {
  public void sayHello() {
    System.out.println("Hello world!");
  }
}
Enter fullscreen mode Exit fullscreen mode

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!")
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Using this code:

_enum(
  _public(), id("Suit"),
  enunConstant(id("CLUBS")),
  enunConstant(id("DIAMONDS")),
  enunConstant(id("HEARTS")),
  enunConstant(id("SPADES"))
);
Enter fullscreen mode Exit fullscreen mode

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; 
}
Enter fullscreen mode Exit fullscreen mode

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"))
);
Enter fullscreen mode Exit fullscreen mode

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"))
);
Enter fullscreen mode Exit fullscreen mode

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")
);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 the JavaTemplate; and
  • the _class method cannot accept arbitrary implementations of the ClassDeclarationElement 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();  
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

So it means that the following invocation is valid:

_class(_public(), id("Foo"));
Enter fullscreen mode Exit fullscreen mode

While the following is not:

_class(_void(), id("Foo"));
Enter fullscreen mode Exit fullscreen mode

Until the next edition

So that's it for today. I hope you've enjoyed reading.

You can subscribe to the blog via RSS.

If you liked this content you can follow me on Twitter.

💖 💪 🙅 🚩
marcioendo
Marcio Endo

Posted on November 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related