Designing APIs is hard: Objectos 0.4.1 released.

Marcio Endo
February 12, 2023

Welcome to Objectos Weekly #013.

I have released Objectos 0.4.1. I plan to release small iterations of Objectos every week. It may be an overly optimistic goal so I won't mind too much if I miss a release on one week. So, at a minimum, there will be a release every other week. I will see how that works in practice for a while. Or if it works at all.

The bad news is that I won't be writing about the Java language bits for a while. By that I mean writing about topics like generic constructors, wildcard type arguments or receiver parameters.

The next articles will mostly discuss the Objectos' releases. At a later time, hopefully, articles will give practical examples on how to use Objectos.

Sorry for that. Let's begin.

Designing APIs is hard

Like the previous release, Objectos 0.4.1 is all about Objectos Code. Objectos Code is a Java library for generating Java source code.

I started migrating the code generators I use internally to use the new Objectos Code API. In doing so, I quickly realized one thing: the API is far from ideal.

I feel that representing Java expressions with the new API is easier. On the other hand, writing class and method declarations is way more difficult with the new API.

So designing APIs is hard.

In the upcoming 0.4.x releases I plan to introduce a number of Objectos Code API changes. I will introduce them incrementally. Which aligns well with my previously established goal of weekly releases.

What's new in Objectos 0.4.1

In any case, let's look at the new features in this release.

All of the examples can be seen in this file.

If you are inclined to give Objectos Code a try, please know that I have included installation related information at the end of this article.

The if statement

You can now express if-then statements with Objectos Code.

The following Objectos Code snippet:

_if(n("active")), block(
  invoke("execute")
)

Generates the following Java code:

if (active) {
  invoke("execute");
}

On the other hand, if-then-else statements are not yet supported. It is scheduled for the 0.4.2 release.

The throw statement

You can now also generate a throw statement with Objectos Code.

The following snippet:

_throw(), _new(t(IOException.class), s("Not a directory"))

Can generate this Java code:

throw new java.io.IOException("Not a directory");

Of course, other kind of expressions can go to the right of the _throw() instruction.

Equality operators

The equality operator expressions are now implemented in Objectos Code.

The following illustrates both the == (equal to) and the != (not equal to) operators:

_if(n("a"), equalTo(), n("b")), block(
  t(System.class), n("out"),
  invoke("println", s("a is equal to b"))
),

_if(n("x"), notEqualTo(), n("y")), block(
  t(System.class), n("out"),
  invoke("println", s("x is not equal to y"))
)

It generates the Java code:

if (a == b) {
  java.lang.System.out.println("a is equal to b");
}
if (x != y) {
  java.lang.System.out.println("x is not equal to y");
}

Next, let's look at null literal.

The null literal

The following illustrates a few examples of the _null() instruction:

_var(), id("foo"), _null(),
_this(), n("bar"), gets(), _null(),
_if(invoke("get"), equalTo(), _null()), block(
  invoke("isNull")
),
_return(), _null()

It generates:

var foo = null;
this.bar = null;
if (get() == null) {
  isNull();
}
return null;

Just to be clear, the generated code above is not meant to be practical.

Let's look at field initializers next.

Initialize a field with a type's variable or method

You can now initialize a field using the following Objectos Code:

_private(), _int(), id("value"),
t(Integer.class), n("MAX_VALUE"),

_private(), t(String.class), id("name"),
t(Thread.class), invoke("currentThread"), invoke("getName")

Which generates the following:

private int value = java.lang.Integer.MAX_VALUE;

private java.lang.String name = java.lang.Thread.currentThread().getName();

Field initializers were already implemented. But they would fail if the expression started with a t instruction.

So I'd argue this was actually a bug. But I labeled as a feature for some reason. I kept it as it was though; the release notes were already published.

Allow include in array initializer

You can use an include instruction in an array initializer, like so:

_private(), t(t(String.class), dim()), id("cities"),
ainit(include(this::cities))

private void cities() {
  var cities = List.of(
    "Belém",
    "São Paulo",
    "Rio de Janeiro"
  );

  nl();

  for (var city : cities) {
    s(city);

    nl();
  }
}

Note that the nl instruction adds a new line to the generated output. The Objectos Code above generates the following:

private java.lang.String[] cities = {
  "Belém",
  "São Paulo",
  "Rio de Janeiro"
};

So the include allows you to write the array initializer by iterating over the elements of a list.

Allow include in method declarator

You can also use the include instruction in the declarator of a method. Like our previous example, you can use it to, for example, declare the formal parameters of a method from a collection. Another use-case is illustrated in the following example:

_abstract(), _void(), method("methodDecl", include(this::param))

private void param() {
  code(_int(), id("a"));

  if (shouldGenerateB()) {
    code(_int(), id("b"));
  }
}

private boolean shouldGenerateB() { return false; }

Which generates the following:

abstract void methodDecl(int a);

So we have used the include instruction to conditionally generate the b formal parameter.

In the example, as shouldGenerateB evaluates to false, the b parameter was not generated.

Allow type arguments in class instance creation expressions

You can now write the following expression with Objectos Code:

_var(), id("list"), _new(t(t(ArrayList.class), t(Path.class))),
_var(), id("map"), _new(t(t(HashMap.class), t(String.class), t(Integer.class)))

Which generates:

var list = new java.util.ArrayList<java.nio.file.Path>();
var map = new java.util.HashMap<java.lang.String, java.lang.Integer>();

So you can supply type arguments to a class instance creation expression.

Bug fixes

Objectos 0.4.1 comes with a few bug fixes.

If you are interested, you can read the Objectos 0.4.1 release notes here.

Installation

Objectos requires JDK 17 or later.

The Maven coordinates for the current release of Objectos Code are:

<dependencies>
    <dependency>
        <groupId>br.com.objectos</groupId>
        <artifactId>objectos-code</artifactId>
        <version>0.4.1</version>
    </dependency>
</dependencies>

If your application is modular, you should also add the requires directive to your module-info.java file like so:

module my.module {
  requires objectos.code;
}

Alpha release

Finally I would like to add that this is an alpha release.

With the zero-based version numbers I wish to convey the idea that the API is of alpha quality and, therefore, subject to change.

Until the next issue of Objectos Weekly

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

The source code of all of the examples are in this GitHub repository.

Please send me an e-mail if you have comments, questions or corrections regarding this post.