Objectos Weekly #012: Introducing Objectos Code
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:
-
documentation; and
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:
-
both are annotated with
Override
; -
the first one is abstract and declares a single formal parameter; and
-
the second one is non-abstract and, therefore, declares a method body.
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:
-
a expression name;
-
method invocation expression (with a primary expression);
-
an integer literal; and
-
a class instance creation expression.
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:
-
in the first form the separator is the ';' (semicolon) character
-
in the second form the separator becomes the ',' (comma) character.
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.