Objectos Weekly #012: Introducing Objectos Code

Marcio Endo
February 5, 2023

Welcome to Objectos Weekly issue #012.

I am happy to announce the release of Objectos 0.4.0. This is the fourth public release of the Objectos suite of Java libraries in its new form.

Objectos 0.4.0 introduces Objectos Code.

Objectos Code

Objectos Code is an open-source Java library for generating Java source code.

The following is the obligatory "Hello, world!" using Objectos Code:

import objectos.code.JavaTemplate;

public class HelloWorld extends JavaTemplate {
  @Override
  protected final void definition() {
    // @formatter:off
    _package("com.example");

    autoImports();

    _public(); _class("HelloWorld"); body(
      _public(), _static(), _void(),
      method("main", t(t(String.class), dim()), id("args")), block(
        t(System.class), n("out"), invoke("println", s("Hello, world!"))
      )
    );
  }
}

Which can be used to generate the following Java file:

package com.example;

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, world!");
  }
}

Generating static content, like our "Hello, world" example, is probably not the best use of Objectos Code. In any case, I hope it gives a general idea of its API.

Alpha release

Please note that the current release of Objectos Code is of alpha quality.

Apart from not being stable yet, it doesn't support a large number of Java statements and expressions at the moment. For example, all of the control flow statements, like the if, for, while statements, are not supported yet.

Additionally, documentation is minimal at the moment.

Given all of this, I understand that the following might be a lot to ask. In any case, I'd like to invite you to give it a try.

Features overview

If you want to give Objectos Code a try, please refer to the following:

In the rest of this article I will discuss some of the features that are available in the current release. In particular those features that are not yet covered in the official documentation.

The autoImports instruction

You don't need to write import declarations explicitly. Objectos Code will generate them automatically for you when you use the autoImports() instruction.

The following Objectos Code:

import java.io.IOException;
import objectos.code.JavaTemplate;

public class AutoImports extends JavaTemplate {
  @Override
  protected final void definition() {
    // @formatter:off
    _package("com.example");

    autoImports();

    _public(); _class("MyException"); _extends(); t(IOException.class); body();
  }
}

Generates the following Java code:

package com.example;

import java.io.IOException;

public class MyException extends IOException {}

Notice how the java.io.IOException import was added automatically.

The t instructions: types

The t instructions are used to declare types. The autoImports instruction will generate import declarations based on the types declared with t.

The following Objectos Code template:

import java.util.Map;
import objectos.code.JavaTemplate;

public class Types extends JavaTemplate {
  @Override
  protected final void definition() {
    // @formatter:off
    _package("com.example");

    autoImports();

    _public(); _class("Types"); body(
      t("com.example", "A"), id("samePkg"),

      t("com.example.types", "A"), id("diffPkgSameName"),

      t("com.example.types", "B"), id("diffPkgDiffName"),

      t(t(Map.class), t(Integer.class), t(String.class)), id("parameterized"),

      t(_int(), dim()), id("array"),

      t(t(String.class), dim(), dim()), id("multiDimArray")
    );
  }
}

Generates the following Java code:

package com.example;

import com.example.types.B;
import java.util.Map;

public class Types {
  A samePkg;

  com.example.types.A diffPkgSameName;

  B diffPkgDiffName;

  Map<Integer, String> parameterized;

  int[] array;

  String[][] multiDimArray;
}

The first three fields declare regular class/interface types. They are meant to show how they interact with the autoImports instruction.

The fourth field, named parameterized, is an example of a parameterized type.

And the last two fields illustrate how to declare array types.

Method declarations

Next, let's take a look at how you can declare methods using Objectos Code.

The following Objectos Code template declares two methods:

import objectos.code.JavaTemplate;

public class Methods extends JavaTemplate {
  @Override
  protected final void definition() {
    // @formatter:off
    _package("com.example");

    autoImports();

    _public(); _abstract(); _class("Methods"); body(
      at(t(Override.class)),
      _public(), _abstract(), _boolean(), method("equals", t(Object.class), id("obj")),

      at(t(Override.class)),
      _public(), _final(), t(String.class), method("toString"), block(
        _return(), s("Objectos Code method examples")
      )
    );
  }
}

The Objectos Code above generates the following Java code:

package com.example;

public abstract class Methods {
  @Override
  public abstract boolean equals(Object obj);

  @Override
  public final String toString() {
    return "Objectos Code method examples";
  }
}

Notice how the second method has a return statement. So let's look at statements and expressions next.

The return statement

The return statement accepts an optional expression in its syntax. So we will use the return statement example to show some of the expressions that are available in the current release.

The following Objectos Code template illustrates the return statement with four different expressions. The expressions are in order:

public class ReturnStatement extends JavaTemplate {
  private static final String EXAMPLE = "com.example";

  @Override
  protected final void definition() {
    // @formatter:off
    _package(EXAMPLE);

    autoImports();

    _public(); _class("ReturnStatement"); body(
      t(String.class), id("name"),

      t(String.class), method("expressionName"), block(
        _return(), n("name")
      ),

      _int(), method("autoChain"), block(
        _return(), n("name"), invoke("length")
      ),

      _int(), method("literalInt"), block(
        _return(), i(123)
      ),

      t(EXAMPLE, "Foo"),
      method("classInstanceCreation", t(String.class), id("value")),
      block(
        _return(), _new(t(EXAMPLE, "Foo"), n("value"))
      )
    );
  }
}

The previous Objectos Code template can be used to generate the following Java class:

package com.example;

public class ReturnStatement {
  String name;

  String expressionName() {
    return name;
  }

  int autoChain() {
    return name.length();
  }

  int literalInt() {
    return 123;
  }

  Foo classInstanceCreation(String value) {
    return new Foo(value);
  }
}

Notice the method invocation expression in the autoChain method. You do not need to explicitly add the '.' (dot) character. Instead, the invoke instruction will automatically "chain" to the previous expression if possible.

You can prevent this default behavior with the end instruction.

The end instruction (statements)

As mentioned, the invoke instruction will try to automatically chain/connect to the previous element. The n instruction, which represents expression names, also has this behavior.

We can use the end instruction to "break the chain". Like so:

import objectos.code.JavaTemplate;

public class End extends JavaTemplate {
  @Override
  protected final void definition() {
    // @formatter:off
    _public(); _class("EndStatement"); body(
      _void(), method("example"), block(
        invoke("a"), end(),
        invoke("b"), invoke("c"), end(),
        invoke("d"), n("e"), n("f"), invoke("g"), end(),
        n("h"), invoke("i")
      )
    );
  }
}

The template above generates the following Java code:

public class EndStatement {
  void example() {
    a();
    b().c();
    d().e.f.g();
    h.i();
  }
}

So the end instruction prevents the next element from joining to the preceding element.

The end instruction (arguments)

The end instruction can also be used to prevent joining of expressions acting as arguments.

import objectos.code.JavaTemplate;

public class EndArgument extends JavaTemplate {
  @Override
  protected final void definition() {
    // @formatter:off
    _public(); _class("EndArgument"); body(
      _void(), method("example"), block(
        invoke("foo",
          invoke("a"), invoke("b"), end(),
          invoke("c"), end(),
          invoke("d"), invoke("e")
        )
      )
    );
  }
}

It generates the following:

public class EndArgument {
  void example() {
    foo(a().b(), c(), d().e());
  }
}

A note on the end instruction

Keep in mind that the end instruction is not required when two expression parts cannot be joined together with a '.' (dot) separator.

For example, in the following foo method invocation:

invoke("foo", s("a"), i(123));

We don't need to separate the arguments with end as the following Java code would be invalid:

// invalid Java code
foo("a".123);

So Objectos Code will generate:

foo("a", 123);

Of course, you are free to separate both expressions with the end instruction regardless. In this particular case, the end instruction would be silently ignored.

The code method

You may have noticed I've used the @formatter:off comment directive in the code examples. This prevents my IDE from auto formatting the source code.

If I did not do this, the autoImports example would have been formatted like so:

import java.io.IOException;
import objectos.code.JavaTemplate;

public class AutoImports extends JavaTemplate {
  @Override
  protected final void definition() {
    _package("com.example");

    autoImports();

    _public();
    _class("MyException");
    _extends();
    t(IOException.class);
    body();
  }
}

If you prefer not to use the @formatter:off directive or if your IDE does not support it, you can use the code method instead:

import java.io.IOException;
import objectos.code.JavaTemplate;

public class AutoImports extends JavaTemplate {
  @Override
  protected final void definition() {
    code(
      _package("com.example"),

      autoImports(),

      _public(), _class("MyException"), _extends(), t(IOException.class), body()
    );
  }
}

Please note that, when using the code method, all instructions become arguments to the code method:

The code method performs no operation. At all. Its sole purpose is to help you format your JavaTemplate.

Let's work together

Do you feel that adding features to your Java applications is taking longer as time goes by? Perhaps I can help. Let's get in touch.

You can find my contacts on this page. All work will be provided via Objectos Software LTDA based in São Paulo, Brazil.

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.