LWO #002: Annotations are just modifiers, array access curiosity, faux code generators and more
Hi, welcome to the second edition of Last Week in Objectos (LWO).
In this edition I will discuss:
-
a few of my findings about the Java language while working on Objectos Code; and
-
the last week's work on Objectos Code itself.
Let's begin.
Annotations are just modifiers (grammatically speaking)
I didn't know that, grammatically (syntactically?) speaking, annotations are just modifiers to the Java language. In other words, I have always thought that the only way to annotate, say a method, would be:
@Override
public final String toString() {
return "Annotations are just modifiers";
}
But, in fact, you can write:
public @Override final String toString() ...
Or, if you prefer:
public final @Override String toString() ...
I always thought that the last two examples would fail to compile. But they do not; all of the three declarations are syntactically correct.
Additionally, all of the three declarations are equivalent. Well, there is a small caveat in the last example regarding type annotations, which we'll look into at another time.
I found out about it when reading the grammar production of method declarations:
MethodDeclaration:
{MethodModifier} MethodHeader MethodBody
It means that a method declaration can contain zero or more MethodModifier
.
The MethodModifier
production is defined as:
MethodModifier:
(one of)
Annotation public protected private
abstract static final synchronized native strictfp
So any of the modifiers can appear in any order. Cool!
An Array Access Expression curiosity
Array access expressions are expressions to, well, access a component of an array.
A simple example can be:
String[] a = { "array", "access" };
System.out.println(a[1]);
The argument to the println
method, i.e. the a[1]
, is an array access expression.
The following is wrong and does not compile:
System.out.println(this[1]);
But why does it not compile? Well, that's what is curious to me. I thought it failed to compile simply because the "syntax is invalid". But that is not exactly what happens in this case.
Let's try to compile and run the following program:
public class Test {
public static void main(String[] args) {
new Test().execute();
}
public void execute() {
System.out.println(this[1]);
}
}
When I try to run it:
$ java Test.java
Test.java:7: error: array required, but Test found
System.out.println(this[1]);
^
1 error
error: compilation failed
It fails to compile because this
does not evaluate to an array type.
So, in some ways, the syntax itself is valid.
But, as this
resolves to a non-array type, it fails to compile.
This is expected as it is defined in the language specification.
The ArrayAccess
production is given by the following:
ArrayAccess:
ExpressionName [ Expression ]
PrimaryNoNewArray [ Expression ]
ArrayCreationExpressionWithInitializer [ Expression ]
We are interested in the second form, the one with PrimaryNoNewArray
.
PrimaryNoNewArray
is defined as:
Literal
ClassLiteral
this
TypeName . this
( Expression )
ClassInstanceCreationExpression
FieldAccess
ArrayAccess
MethodInvocation
MethodReference
So the following (wrong) examples:
1234[0];
Object.class[1];
this[2];
Outer.this[3];
All fail to compile. But not because the "syntax is invalid". Instead, they fail to compile because the part to left of the brackets do not evaluate to an array type.
Faux code generators
I am creating Objectos Code because I write a lot of code generators. But parts of Objectos Code itself might benefit of having them being generated. What to do in this case?
Well, I write a plain "faux" (ad-hoc?) code generator. It looks a bit like this:
import static java.lang.System.out;
public class ByteCodeFauxGenerator {
private int value = -1;
public static void main(String[] args) {
var gen = new ByteCodeFauxGenerator();
gen.execute();
}
public final void execute() {
comment("class");
value("CLASS");
value("MODIFIER");
value("IDENTIFIER");
comment("statements");
value("LOCAL_VARIABLE");
value("RETURN_STATEMENT");
}
private void comment(String string) {
out.println();
out.println("// " + string);
out.println();
}
private void value(String string) {
out.println("static final int " + string + " = " + value-- + ";");
}
}
This code resides in the src/test/java
directory of the project.
It has a main
method, so I can just run it.
It prints:
// class
static final int CLASS = -1;
static final int MODIFIER = -2;
static final int IDENTIFIER = -3;
// statements
static final int LOCAL_VARIABLE = -4;
static final int RETURN_STATEMENT = -5;
Then I copy this output and paste it into the actual class, located in src/main/java
:
final class ByteCode {
// class
static final int CLASS = -1;
static final int MODIFIER = -2;
static final int IDENTIFIER = -3;
// statements
static final int LOCAL_VARIABLE = -4;
static final int RETURN_STATEMENT = -5;
private ByteCode() {}
}
I call it a "faux" code generator because the process is manual. Even though it is manual, it is still effective.
Last week in Objectos Code
And here is a summary of the work I have done in Objectos Code.
Lambda inclusion support
To generate a class with a single field you write the following in Objectos Code:
_class(
id("Example"),
field(_int(), id("a"))
);
It generates the following:
class Example {
int a;
}
What if you don't know beforehand the number and names of the fields to be generated?
To this use-case, Objectos Code offers the include
directive:
public class IncludeExample extends JavaTemplate {
private final List<String> fieldNames = List.of("a", "b", "c");
@Override
protected final void definition() {
_class(
id("Example"),
include(this::generateFields)
);
}
private void generateFields() {
for (var fieldName : fieldNames) {
field(_int(), id(fieldName));
}
}
}
Which should generate:
class Example {
int a;
int b;
int c;
}
Remember that Objectos Code is a pure Java template. So the previous example is equivalent to the following string-based template:
class Example {
{{#fieldNames}
int {{.}};
{{/fieldNames}
}
Constructor declarations, assignment expressions, and field access expressions
I have started the implementation of constructor declarations. The following Objectos Code:
_class(
id("Foo"),
field(_final(), _int(), id("a")),
constructor(
param(_int(), id("a"),
assign(n(_this(), "a"), n("a"))
)
);
Generates the following Java code:
class Foo {
final int a;
Foo(int a) {
this.a = a;
}
}
More enum declarations
I have also continued the work on enum declarations. Enum class declarations are currently required in Objectos HTML.
The following Objectos Code:
var test = PackageName.of("test");
var iface = ClassName.of(test, "Iface");
_enum(
_public(), id("Test"), _implements(iface),
enumConstant(id("A"), s("a")),
enumConstant(id("B"), s("b")),
field(_private(), _final(), t(String.class), id("value")),
constructor(
_private(),
param(t(String.class), id("value")),
assign(n(_this(), "value"), n("value"))
),
method(
annotation(Override.class),
_public(), _final(), t(String.class), id("toString"),
_return(n("value"))
)
);
Generates:
public enum Test implements test.Iface {
A("a"),
B("b");
private final java.lang.String value;
private Test(java.lang.String value) {
this.value = value;
}
@java.lang.Override
public final java.lang.String toString() {
return value;
}
}
Until the next edition
So that's it for today. I hope you've enjoyed reading.
You can find the source code in this GitHub repository.
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.