10

How do I write a Java inner class with custom properties at compile time using annotations?

For instance, I want this :

@Generate
class Person {
     String firstname, lastname;
}

to generate:

class Person {
    String firstname, lastname;

    public static class $Fields { 
           public static String firstname = "firstname";
           public static String lastname  = "lastname";
    } 
}

How can I write the interface:

@Retention(RetentionPolicy.SOURCE)
public @interface Generate {
     // ... 
}

I understand I need to do some kind of AST transformation to make this magical.

I am also aware of project lombok, but I want to know what the least common denominator is with a simple example, preferably within one method, and preferably something that a good editor would consider automatically, for instance RetentionPolicy.SOURCE for the javac compiler, which can be used in Intellij IDEA.

Project lombok is a beast code wise and is tough place to start.

It must be simpler than that, is it not?

Any ideas?

mjs
  • 21,431
  • 31
  • 118
  • 200
  • 2
    I have a feeling it's not possible, but it would be cool if it were :) –  Sep 23 '14 at 20:20
  • 4
    Annotation processors would be a good place to start looking - they're in the `javax.annotation.processing` package. – Makoto Sep 23 '14 at 20:26
  • You can modify the generated bytecode with `javassist` – dieend Sep 23 '14 at 20:29
  • I think it might be possible! I found this just now : http://notatube.blogspot.com/2010/12/project-lombok-creating-custom.html, which is pretty condensed info. Unsure if javac compiler used with Intellij will work automagically. I will try tomorrow and hopefully get back with an answer. – mjs Sep 23 '14 at 20:31
  • 1
    Have you considered using _Scala macros_? (_Scala_ is a _JVM_ language that has full interoperability with _Java_ that also works with IntelliJ IDEA.) This may be a bit of a steep learning curve for you, but you can do all kinds of powerful compile-time operations with them... – Mike Allen Sep 23 '14 at 20:50
  • What's the use of such a generated class? – Holger Sep 24 '14 at 17:12
  • @Holger Alot, but primarily you get static access control to fields and methods. For instance, you could map a client hashmap against a field, for instance person.setName( map.get(Person.$Fields.name) ) or you might add an access control layer for methods and fields, Auth.addRule("admin", Person.$Methods.setName) ); There is a wide area of usage and it would make Java more "static". As of now there is no way to refer to methods and fields other than through reflection and error prone strings – mjs Sep 24 '14 at 18:24
  • Sounds to me like an `enum` would be more suitable. Generating such a class would be easier if you don’t insist of it being an inner class. If you generate a stand-alone class (it might still have a name like e.g. `Person$Fields` the standard annotation processing API would be sufficient. And it’s supported by all compilers (since Java 6, annotation processor can run automatically when compiling an annotated class). – Holger Sep 25 '14 at 08:19
  • Project lombok does exactly that. I don't see how it is "a beast code wise". I think it works very well. – Claude Martin Dec 03 '14 at 10:46
  • @СӏаџԁеМаятіи Does what, generate the Fields I am after? It must have been added after this post and suggestions from me to the developers. Can you provide a link so I can see how this is possible? – mjs Dec 03 '14 at 12:19
  • @SecretService: Maybe I misunderstood. I think what you need is this: http://www.ibm.com/developerworks/library/j-typesafejpa/ So instead of "Person.$Fields.firstname" you'd use "Person_.firstname". – Claude Martin Dec 10 '14 at 15:19

1 Answers1

3

You can do this by reflection, but your new class won't be an inner class; but be warned, you will lose static type safety.

It can be done in 2 steps:

  1. Read the annotated class via reflection and transform it into a String which represents the source code of your new class.
  2. Write this string to file, compile this String using the Java compiler API and then load and instantiate the new class, all programatically; see exact steps here.

Alternatives to achieving similar functionality can also be obtained by bytecode instrumentation (see cglib or javassist) or maybe even with proxies.

Community
  • 1
  • 1
Random42
  • 8,989
  • 6
  • 55
  • 86