DEV Community

Marcio Endo
Marcio Endo

Posted on • Originally published at objectos.com.br

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

But, in fact, you can write:

public @Override final String toString() ...
Enter fullscreen mode Exit fullscreen mode

Or, if you prefer:

public final @Override String toString() ...
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

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

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

So the following (wrong) examples:

1234[0];
Object.class[1];
this[2];
Outer.this[3];
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

It generates the following:

class Example {
  int a;
}
Enter fullscreen mode Exit fullscreen mode

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

Which should generate:

class Example {
  int a;

  int b;

  int c;
}
Enter fullscreen mode Exit fullscreen mode

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

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

Generates the following Java code:

class Foo {
  final int a;

  Foo(int a) {
    this.a = a;
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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.

Top comments (0)