Lombok: Allow usage of annotations as meta-annotations

Created on 14 Jul 2015  Â·  46Comments  Â·  Source: projectlombok/lombok

_Migrated from Google Code (issue 522)_

Most helpful comment

:bust_in_silhouette: oliver.[email protected]   :clock8: May 26, 2013 at 14:19 UTC

Very often I keep reusing the same set of annotations or even a particular configuration of an annotation in different places of my application. E.g. assume the following usage to automatically create an all-args constructor annotated with @ Autowired.

@ AllArgsConstructor(onConstructor = @ _(@ Autowired))

This expression feels rather technical and if I wanted to switch from @ Autowired to @ Inject, I'd have to trace down all usages and change them.

If @ AllArgsConstructor was allowed on annotations as well, I could introduce the following annotation:

@ Target(ElementType.TYPE)
@ Retention(RetentionPolicy.SOURCE)
@ AllArgsConstructor(onConstructor = @ _(@ Autowired))
@ interface AutowiredConstructor {

}

and then go ahead and use the annotation as follows:

@ AutowiredConstructor
class MyComponent {

private final @ NonNull Dependency dependency;

// business methods
}

To enable this behavior, two changes are required:

  1. Add ElementType.ANNOTATION to the annotations currently allowed at types (@ AllArgsConstructor is definitely not the only annotation that might make sense on).
  2. Alter the annotation detection to traverse annotation hierarchies.

All 46 comments

:bust_in_silhouette: oliver.[email protected]   :clock8: May 26, 2013 at 14:19 UTC

Very often I keep reusing the same set of annotations or even a particular configuration of an annotation in different places of my application. E.g. assume the following usage to automatically create an all-args constructor annotated with @ Autowired.

@ AllArgsConstructor(onConstructor = @ _(@ Autowired))

This expression feels rather technical and if I wanted to switch from @ Autowired to @ Inject, I'd have to trace down all usages and change them.

If @ AllArgsConstructor was allowed on annotations as well, I could introduce the following annotation:

@ Target(ElementType.TYPE)
@ Retention(RetentionPolicy.SOURCE)
@ AllArgsConstructor(onConstructor = @ _(@ Autowired))
@ interface AutowiredConstructor {

}

and then go ahead and use the annotation as follows:

@ AutowiredConstructor
class MyComponent {

private final @ NonNull Dependency dependency;

// business methods
}

To enable this behavior, two changes are required:

  1. Add ElementType.ANNOTATION to the annotations currently allowed at types (@ AllArgsConstructor is definitely not the only annotation that might make sense on).
  2. Alter the annotation detection to traverse annotation hierarchies.

:bust_in_silhouette: thomas.darimont   :clock8: Aug 29, 2013 at 09:11 UTC

Yep this would be very handy indeed :)

:bust_in_silhouette: Maaartinus   :clock8: Dec 15, 2013 at 15:24 UTC

This is the same request as issue #602. I don't think that changing @ Target is a good idea, but there's already a syntax allowing nesting arbitrary annotations (and the OP is using it above). So it could look like

@ Target(ElementType.TYPE)
@ Retention(RetentionPolicy.SOURCE)

@ lombok.Metaannotation(@ _({
@ AllArgsConstructor(onConstructor = @ _(@ Autowired)),
@ SomeOtherFancyStuff(1, 2, 3)
)})

@ interface AutowiredConstructor {
}

So I believe the syntax is no problem, but I can't tell if this feature can be implemented.

_End of migration_

@Maaartinus - Any chance you elaborate on why you thing the change to @Target is a bad idea?

@olivergierke Sure: 1. I find it confusing, but that's subjective. 2. In the end, you'd have to add ElementType.ANNOTATION to all lombok annotations as why not allow to create @CleanupUsingDispose or @NoGetterNoSetter (the latter is something I could actually use a few times).

But it's not really bad, though I'd prefer the Metaannotation syntax. However, I have no idea if this is possible and those who do seem to be busy.

I still don't think I get you. Why is that confusing? Creating composable annotations is exactly what @Target(ANNOTATION) exists for. Your sample from above:

@lombok.Metaannotation(@__({
  @AllArgsConstructor(onConstructor = @_(@Autowired)),
  @SomeOtherFancyStuff(1, 2, 3)
)})
@interface AutowiredConstructor {}

Would simple down to:

@AllArgsConstructor(onConstructor = @_(@Autowired))
@SomeOtherFancyStuff(1, 2, 3)
@interface AutowiredConstructor {}

How is that more confusing or more complex?

As a side note: Groovy AST Transformations provide this feature in the form of an AnnotationCollector.

The latest goovy 2.5 Snapshots provide a bit more control over the annotation attribute propagation via the mode attribute on AnnotationCollector.

In the meantime, this got duplicated quite a few times (#602, #774).

Is there a way to help out with this one? I skimmed the sources and tried to find the place annotations are detected in the first place but couldn't really find it. Wouldn't mind to get my hands dirty given a few pointers where to look exactly.

@olivergierke you could have a look at this: http://blog.jadonfowler.xyz/2015/06/20/custom-annotation-lombok.html

I took an intense look at the lombok code last night - it looks as if lombok's infrastructure currently doesn't support such an indirection mechanism as you are suggesting.
As far as I could see, all of the annotations seemed to be directly translated to an AST transformation that is then compiled into the "boilerplate" code you'd have to write.

In order to implement such a feature one would probably need to implement a transformation mechanism that first collects all Annotations annotated with @ MetaAnnotation and registers the piggy-backed annotation payload with the particular anntation like @ AutowiredConstructor.
Then you'd probably need to implement a custom EclipseAnnotationHandler that can deal with any annotation and checks whether it is one of the registered MetaAnnotations. If thats not the case the handler returns otherwhise the handler replaces the use of the MetaAnnotation (e.g. @ AutowiredConstructor) with the registered piggy-backed annotations.
The code to expand the piggy-backed annotations can be found here (with onConstructor=...) as an example.

Perhaps @rspilker or @rzwitserloot could shed some more light on this.

I would think this should be a core lombok feature. For example, the @Data annotation is said to be the equivalent of @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode. If lombok annotations were composable, we could define our own @Data class according to our own needs. (For example, I don't want @RequiredArgsConstructor. It's unusable in most cases.)

Big +1 on this.

One set of my (entity) classes have these annotations:

@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor

Another set of (actor) classes have these annotations:

@Slf4j
@Singleton
@RequiredArgsConstructor(onConstructor = @__(@Inject))

Our code will be lot more cleaner (not to mention lot less error prone) if I can just write @MyEntity and @MyActor.

Please stop adding +1 comments. Instead, press the thumbs-up button on the top most comment.

This feature would require resolution in a very early stage of processing. Currently that is not possible. Possibly, since we now have the lombok configuration system, we might come up with a different solution.

@rspilker Would defining the annotation in lombok.config need resulution, too?
I hope, it wouldn't, but then, is it really worse than defining the metaannotation in code?

lombok.config:
meta.mypackage.NoEtters = @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)

@Getter
@Setter
class SomeClass {
    private int aNormalPrivateInt;
    @NoEtters
    private int aVeryPrivateInt;
}

Even if I had to additionally define the annotation in a Java file, it's worth it.

Even if it couldn't be done package-specific (so that anotherpackage.NoEtters would do the same), it's still worth it.

Hello,

what is the status of this issue?

Any chance on providing a timeframe for this feature?

Any progress here? I'd appreciate something along the lines of Spring's meta annotations. The main use case in most of my client projects is for wrapping JPA support into a single annotation.

I feel verry sad when I see 5 years old issue with second most upvotes and 0 work done on it.

No need to be sad! Check out the sources, figure out a possible solution, get the approval for your concept from the project maintainers, aaaaand: implement it.

Open source doesn't mean you get all your wishes for free. Sometimes you have to work for it if you want something.

So frequently i do the equivalent of

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public @interface DTO {
}

Just finished a proof of concept using the syntax @Maaartinus suggested:
grafik
The annotation itself is just a marker and will not be removed. That way it is also possible to use third party annotations e.g. @javax.persistence.Entity as meta annotation.
I can continue working on it if @rspilker or @rzwitserloot approve this concept/syntax.

@Rawi01 - where's you're code? I couldn't find it in your Lombok fork.

@prdoyle It is not published at the moment, I am still waiting for feedback from one of the maintainers. The current version only handles the happy path and throws exceptions/breaks if something is not as expected so it is not suited for real world usage.

@Rawi01 - well, I'd love to see it. I also think you'd be more likely to get feedback from the maintainers if you show them working, mergeable code, even if it's not ready to be turned on yet.

I have just published the current version as an extension: https://github.com/Rawi01/lombok-metaannotations
It still needs some work to be done but the basic functions should work.

I might stretch the scope of the ticket a bit but would it be terribly hard to extend the functionality to not only react on annotations present but the type implementing another one? A use case I have is the following: in the jDDD library we expose interfaces that express DDD building blocks. There's also an annotation based model but interfaces allow us to express relationship constraints using generics. One of the interfaces is Identifier. Implementations usually look like this:

@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
class SomeIdentifier implements Identifier { … }

Having expressed the type to be an identifier via the interface already it would be cool if I could avoid the annotations by registering:

lombok.metaAnnotations = Identifier lombok.EqualsAndHashCode lombok.RequiredArgsConstructor(staticName = "of") lombok.NoArgsConstructor(access = lombok.AccessLevel.PRIVATE, force = true)

i.e. let the trigger of the annotations to be added be an interface instead of an annotation.

That should be possible but I think we should focus on the basic stuff first.

I see two major debates here before we can proceed:

  • About syntax and implementation
  • About voodoo magic

See next comments.

Voodoo magic

There are 3 layers to this.

An annotation that has no meaning beyond lombok

it's just a marker, does not have runtime retention, and no AP other than lombok looks at it) - it is there solely to serve as meta-annotation, with lombok.config explaining what it means.

An annotation that has meaning beyond lombok

@rawi01: That way it is also possible to use third party annotations e.g. @javax.persistence.Entity as meta annotation.

Here Entity has inherent meaning, but, can also serve as a meta-annotation, causing lombok to act as if various lombok annotations, as configured in lombok.config, are also present on whatever is annoated with @Entity.

Lombok can template based on almost anything

@odrotbohm: i.e. let the trigger of the annotations to be added be an interface instead of an annotation.

As you go down this list, the 'voodoo magic' aspect of lombok becomes more pronounced.

Every time lombok comes up in a reddit thread, a bunch of to me rather specious arguments come up: They seem prejudicial - arguments that apply to almost any library, or do not seem to actually be a problem. Imagined ills, nothing that would seem real.

The thing is, and this is a lesson from focus grouping: Just because the arguments folks use to explain why they don't like a thing are a load of horse exhaust, doesn't invalidate the fact that they don't like the thing. It's just that they are positing unfair reasons (and, presumably, are unlikely to convince folks with a working common sense), and are not willing to explain why they actually don't like it. Usually, they don't know themselves.

I have an inkling, based mostly on the notion that this is the one argument that really does apply pretty much solely to lombok, does seem like it could cause actual issues, and is a common thread (almost always named, high up in the list, and gets a lot of 'me toos' in comments), that I know what it is.

Lombok is too magical.

Partly, that's just what we have to live with. Lombok is by its nature a bit magical, it does things that seem impossible to most java programmers who don't know about lombok yet. Various specs spell out that what lombok does is impossible, in fact.

However, magic is not black and white. It's shades of gray. Having @Getter generate a getter is about as obvious as we can make it: The annotation itself spells out what it does, the code will not compile without lombok on the classpath, having lombok on the classpath will (usually) mean that getter _will_ show up, and the javadoc of the annotation spells it out in further detail. The annotation's dependency is a simple jar that you can search the web for as a last resort.

As you go down the above list, these properties get _way_ worse. Basic meta-annotations now means the actual annotation (the thing you see in your code) is no longer in lombok.jar, may not be particularly clear about what it does by its very nature (Nothing is as clear as @Getter, that's sort of the point of the feature, no? Describe something more abstract and generic. Nobody wants to see @GetterAndBuilderAndToStringAndJacksonizedAndInjectedAllArgsConstructor in their source code!).

Add behaviour to an existing annotation that already has a well defined meaning, such as @Entity, and now truly we've entered the wizarding realms. If you don't have lombok on your classpath, or you don't have lombok.config, your code continues to make complete semantic sense with all deps available, and yet it either doesn't compile at all, or, worse, it does, but into class files that don't do what the author intended.

__I feel pretty strongly that this is a bridge too far; it is too much and we should not go here.__

However, how do we stop it? How can lombok know that javax.persistence.Entity means things? We can try to enforce that it has sourcecode-only retention (but this requires resolution, big problem), and actively remove the annotation from the AST to attempt to hide it from other annotation processors (but we probably can't reliably do this; lombok may not be the first processor in the line).

Another possibly more fruitful option is to make certain demands on the name of the annotation. For example, that lombok appears somewhere in its package structure. This is something we can check without resolution; we can even detect that you have one of these, then notice that no definition for what this annotation means is in your lombok.config, and generate an error instead. This solves the problem that someone with lombok installed but without the right lombok.config has mysterious compilation errors.

Syntax

Do we want to fit more within lombok.config's style, or do we want to fit more within java's 'annotations' style?

Do we want:

> cat lombok.config
lombok.toString.callSuper = true
lombok.fieldNameConstants.innerTypeName = Field

lombok.metaAnnotations += @com.foo.customcompanypkg.ServiceClass Log4j2("myTopic") AllArgsConstructor(onConstructor=?do we support this?) NoArgsConstructor(PRIVATE)

or something like:

>cat lombok.config
lombok.toString.callSuper = true
lombok.fieldNameConstants.innerTypeName = Field

@com.foo.customcompanypkg.ServiceClass {
  @Log4j2("myTopic")
  @AllArgsConstructor([email protected])
  @NoArgsConstructor(access = AccessLevel.PRIVATE)
}

I would think the latter is much, much nicer, but maybe that's just a purely personal aesthetics kind of thing. The latter may be confusing, in that one might thing you can write complete java code in there, but that can't happen (no @Foo(5+2) will be allowed here, and no non-lombok annotations can show up here). On the other hand, it'll be far more natural to write and read than a long single line.

semantics

How far do we go?

  • Do we want to support the ability to have extensive parametric support, such as specifying onConstructor, enums for accesslevel, topic defaults, and more?
  • Do we want to support the ability to set properties that the actual annotation does not have, because it does make sense on a meta annotation? There are various configurable aspects to lombok handlers which are intentionally configurable solely via lombok.config, because we imagine that if you want to change it, you want to change it for all. Such as the field name of loggers.
  • Do we allow a meta-annotation to include another meta-annotation to effectively 'extend' on it? i.e. define @com.foo.Bar as including @com.foo.Baz, where Baz is itself a meta-annotation defined elsewhere in the lombok.config chain? That would imply we need a multi-pass concept (first gather all defs, and only then untangle the mess, generating errors if you have loops).
  • Do we allow parameterization? Where the meta-annotation takes arguments? Something like:

Given:

package com.foo.company.lombokmeta;

public @interface Model {
    String name();
}

then this is allowed:

@com.foo.company.lombokmeta.Model(name) {
    @AllArgsConstructor(staticName = name)
    @Getter
}

Think of the codebases

For this to work, the tool (IDE, compiler, etc) needs to:

  • Have lombok on the classpath
  • Be aware of lombok if 'lombok on the classpath' is not enough, e.g. lombok needs to be installed in your editor.
  • The meta annotation used needs to be on the classpath
  • The relevant lombok.config section needs to be present in the directory structure

Looking at the above, I think the first, second, and third items are either priced in anyway, or are easily solved in modern codebases, but that fourth one is a problem. Most companies / codebases have way more than one project. Some use a giant monorepo, and usually have a single shared parent directory which thus provides an opportunity to have one workspace-global lombok.config straight from source control. But some don't, and this introduces a hurdle: You can 'distribute' that meta-annotation with the usual dependency distribution concepts (for example, many corps have a 'util' project that all other projects always include, distributed via the corporate sonatype maven distro or what not), but you can't distribute a lombok.config like that. We already have some basic work done on an 'includes' system and that'll help (because copy-pasting the full meta-anno definition from a root lombok.config to every project so you can check it into source control would be quite painful). With include features, each project still needs that lombok.config that includes another, but at least there's now only one place to edit if you want to change stuff. This is a problem of the config system that goes beyond this feature request, but this feature request, what with the problem of how 'magic' it is, does increase how much that problem inherent in the config system hurts.

@Rawi01 a few million points for working on this stuff. Awesome as always. Let's first hash through the design we want and assuage my fears that this is too magical (what with the holidays, @rspilker and I don't have our usual weekly walk-and-talks these 2 weeks - usually he helps out with that, too :P), then the syntax.

java16 is out in march. We are pretty much forced to release a lombok version on that day to include the fix for the --add-opens business anyway (we do not want to release this fix any earlier than jdk16 release day). I wonder if we can target that release and maybe christen that lombok v1.20. This feature does feel impactful enough to warrant a bump.

@odrotbohm (in a deleted comment): If the configuration changed to the following: (elided: example where an annotation definition is itself annotated with lombok stuff. also elided: In-depth personal opinions about the impossible annotate-the-annotation solution.)

__This is not possible, as has been repeatedly explained. Read Resolution again and then stop suggesting it.__

It's depressing to keep reading 'oh, but only if'. Yup. Absolutely. Not a feasible solution to this problem. If wishes are part of our powers, let's start with world peace instead, what do you say? :)

I deleted comments suggesting it before but evidently it needs to be repeated. In bold this time, hopefully that'll put this idea to bed.

The rest of your comments are now either irrelevant, or if you stick with them, are based on personal conjecture. It has not swayed my opinion that meta-annotations are as is far too magical in the slightest, so it's probably not worth following up on.

I'm currently thinking this feature can be acceptable, but only if the package name of the meta-annotation includes the package lombokmeta. Otherwise, it is an error. Any lombokmeta annotation lombok finds anywhere, will be an error unless a definition for it is found in a config file.

That would mean that any confusion about the 'magic' produces errors that spell out precisely what is going on and where to read more, which is the cure, as far as lombok is concerned, to the magic: If you (a programmer) have no idea what's going on and get no real hints, that's lombok's fault. If lombok is spelling out what is happening with links and all, and you decide to instead turn to prayer ('evil magic!') or hide behind laziness - the blame shifts to the lazy/prejudiced programmer, and thus, acceptable.

It also has the considerable benefit that lombok will always be able to very quickly analyse any AST for it: You can't star-import recursively. Either you write @foo.whatever.something.lombokmeta.Thing, as in, lombokmeta is right there in the name of the annotation, or you write import something.lombokmeta.something; up top, star import or no. We can detect either case very quickly.

But is it too restrictive to demand this?

Voodoo magic
Lombok clearly is a bit magical but there also seems to be a group of people that don't like it and at the same time like other annotation based frameworks e.g. CDI because someone wrote a JSR. That doesn't make sense but hey, thats how things are.

I thought about your arguments and think that you are right as soon as the project gets bigger. In a small project its way easier to share information with each other and everyone knows that @Entity also adds @Getter. Restricting this feature to some set of marked annotations (lombokmeta) allows adding some javadoc to describe what the meta annotation does which clearly is an improvement. As this is also the minimal version which can be extended later on I agree with you that this is how we should start.

Syntax
I also prefer the multi line syntax but I would also add the regular config key (lombok.metaAnnotations +=) instead of writing code in that file. Someone who doesn't know about meta-annotations will have no idea what that stuff is. I would suggest that we consider each indented line as part of the previous line and then remove the line breaks/whitespace to determine the final value. This minimises the config system changes.

My current WIP syntax is like this:

@fully.qualified.SourceAnnotation([parameter=default_value]*) [@fully.qualified.TargetAnnotation([parameter=value]*, [parameter=<bound parameter>]*)]+

where bound parameter is either the parameter set at the actual annotation or the default value of the source annotation.

Semantics

Do we want to support the ability to have extensive parametric support, such as specifying onConstructor, enums for accesslevel, topic defaults, and more?

I have to check it but if I remember correctly my current implementation already supports this.

Do we want to support the ability to set properties that the actual annotation does not have, because it does make sense on a meta annotation? There are various configurable aspects to lombok handlers which are intentionally configurable solely via lombok.config, because we imagine that if you want to change it, you want to change it for all. Such as the field name of loggers.

I don't get what you want to do :smile:

Do we allow a meta-annotation to include another meta-annotation to effectively 'extend' on it? i.e. define @com.foo.Bar as including @com.foo.Baz, where Baz is itself a meta-annotation defined elsewhere in the lombok.config chain? That would imply we need a multi-pass concept (first gather all defs, and only then untangle the mess, generating errors if you have loops).

The main reason to do something like that is to reduce duplicate lines of configuration. As there is nothing that cannot be done without this feature I would not focus on this one. If it turns out to be easy to implement then it is nice to have.

Do we allow parameterization? Where the meta-annotation takes arguments?

This is something that should be there, otherwise the use cases are limited.

Think of the codebases

If I use anything in lombok.config I also add it to source control. If there is a util project that contains the shared meta-annotations and there is no config file in the current project it will obviously not work as expected. I like the idea to use a seperate package name lombokmeta to detect annotations that are not configured properly.


All your examples only contains lombok target annotations, is that intentional? This feature would be way more uesful if it is not restricted that way and I don't see a reason why it should be.

I don't get what you want to do 😄

As an example, the @Log4j annotation does let you choose the name of the field that is generated. By default is is log, but you can change it with lombok.config: lombok.log.fieldName = foo, for example. You can't change it with @Log4j(name = "foo"), however - there is no such annoparam.

The reason there is no annoparam is because we did not think 'set the logger field name' was a setting that one would want to individually configure everytime you used it: We think that loggernames, if you want to mess with it at all, are a thing you want to mess with in uniform fashion throughout some codebase (package / project / workspace). Hence, it is configurable in lombok.config which has the ability to apply to entire packages/projects/workspaces, but not on the annotation itself, because that isn't.

With meta-annotations, the line: 'the annotation itself is configurable for this usage of it' and 'lombok.config cannot configure individual usages, but does configure an package/project/workspace in one go' is a lot more blurry.

I find this far less icky:

> cat lombok.config
@com.foo.chatservice.lombokmeta.BotCommand {
    @Log4j(fieldName = "logChannel")
}

Even though fieldName is not an annoparam of @Log4j. Probably not worth complicating matters with this, forget I mentioned it.

All your examples only contains lombok target annotations, is that intentional?

Yes. Lombok can't add anything else in a way that an annotation processor that already ran can witness, no?

I think it is possible but it requires changes in all handlers, right? Seems to be a lot of work :smile:

Yes. Lombok can't add anything else in a way that an annotation processor that already ran can witness, no?

Right, but thats already the case. If lombok is not the first annotaton processor other annotation processors might produce wrong results because they have no idea of new fields, methods, types and annotations added by lombok. So everyone who uses more than one processor should have already defined the order.

So everyone who uses more than one processor should have already defined the order.

Defining the order is non-trivial. Yes, what you say is true, and this has led to quite a long and arduous clustermess e.g. with mapstruct, where we (team lombok and team mapstruct) worked together to invent a whole new scheme so Annotation Processors can register both [A] that they exist in a way that earlier-running APs can see this, and [B] a promise system where an AP can say: Don't do anything, let me run first, I _promise_ I'll force a second round so you can do your stuff then.

I don't want to dig that particular hole any deeper.

The problem with annotations is: The odds that an annotation interacts with an annotation processor is rather large. It's half of the point of annotations in the first place: To trigger and interact with APs (the other half is runtime reflective introspection. Admittedly, that is probably a little larger than half).

That's why I'm way more hesitant on that one. There is no easy fix; you cannot easily order APs.

Defining the order is non-trivial.

It is. The annotation processors should be ordered in the same way as in the class path, annotation processor path or -processor flag. I'm not completly sure about the classpath but I checked the sources and it seems like it shares the load logic with the annotation class path feature. The only thing we can not do is changing the order in lombok itself because it can not turn back time and undo everything that happens before. But we can show a warning and there already is some code to do that.

Oof. This is making it so complicated. No, we're not doing this, not for first release.

At launch of this feature, no external annotations allowed. We can revisit later easily enough. Pulling the rug out from under this feature later because it causes too many issues is a lot more difficult, even if we start in experimental.

To elaborate on why I think that is very complicated:

  • Lombok itself needs a detection scheme to figure out that it isn't the first annotation processor in the lineup. Keeping in mind that this may require separate code for at least ecj and javac because I don't think you can ordinarily check.
  • It then needs to check if all processors that are ahead of it are recognized as supporting the round-delay feature. The only processor that is currently capable of doing that, is recent versions of mapstruct. If so, there is no need to raise any issues. Presumably we can remove any such APs from our 'ran before us' list.
  • This detection scheme cannot actually error. It just needs to store this someplace and do nothing.
  • The meta-annotation feature does not inject annotations at all, at least, not lombok annotations: It just uses those to trigger handlers, it doesn't actually add them just for the handlers to remove them again, seems weird to do that. However, for non-lombok annos, it WOULD inject them, _AND_ fetch from the framework layer if lombok is not first-in-line. If not first in line, even though we have no idea if the processors before us are affected, we must error and give advice on how to ensure lombok runs first.
  • We should investigate if we need to make it possible for the user to specify a list of APs that they know cannot possibly be affected by this. Any annotation whose purpose is runtime reflection is irrelevant (so do we need a list of those, too?), as is any AP that doesn't scan for annotations in the first place, which is more rare but happens. And there is, of course, the notion that the meta-annotation includes non-lombok annotations, but no annotations that affect specifically the APs that ran before lombok. But it feels like a ton of work to try to explain to the programmer what they need to do: Where to configure this stuff and what are guaranteeing when they do.

The advice on how to make lombok run first is then non-trivial: Yes, we can explain that if lombok is first on the classpath, __and__ you aren't using an explicit -processor switch, it _should_ be fine, but most java programmers don't run javac, they run mvn or gradle, and if you are using modules, the way APs are run in a modular build are quite different, so the actual explanation on how to get lombok to run first requires 10 explanations: non-modular and modular (2 choices), multiplied by about 4 popular build systems (javac, mvn, gradle, sbt, and 'exotics'. eclipse and intellij usually count too, but due to plugins/agents we are always first in line on those platforms).

Bundle that together and I'm drawing the conclusion that this is not a good idea for the first release of this new feature, but if there are easier routes and/or this is considered important enough to warrant this kind of effort, maybe we should just go for it. Seemed a bit dictatorial to just shoot it down based on my internal, unstated imaginations 😛

If we want to implement a proper detection mechanism including a configurable list of supported annotation processors some additional work is required. If that is to complex for a first version there are two other possible solutions:

  • If there is a target annotation that is not a lombok annotation lombok prints an error e.g. Additional configuration required, please read https://projectlombok.org/.... To disable that one a new config key lombok.isFirstAnnotationProcessor = true has to be set.
  • Simply ignore this case in code and only add it to the documentation. There are only a few popular annotation processors so not everyone is affect and there also is a chance that the annotation processors are already ordered correctly. If something does not work properly there hopefully will be a stackoverflow post with a link to the docs :smile: Writing the documentation obviously is a bunch of work.

The meta-annotation feature does not inject annotations at all, at least, not lombok annotations: It just uses those to trigger handlers, it doesn't actually add them just for the handlers to remove them again, seems weird to do that. However, for non-lombok annos, it WOULD inject them, _AND_ fetch from the framework layer if lombok is not first-in-line. If not first in line, even though we have no idea if the processors before us are affected, we must error and give advice on how to ensure lombok runs first.

Creating new annotations and removing them later on is how my current draft works. It might seems weird but otherwise you have to handle the ordering and conflicts with other existing lombok annotations yourself. You also have to resolve field meta-annotations

e.g.

//@PublicGetter => @Getter(AccessLevel.PUBLIC)
//@NoGetter => @Getter(AccessLevel.NONE)

@PublicGetter
public class Test {
    @NoEtter
    private String test;
}

If the meta-annotation handler calls the getter handler directly @PublicGetter creates a public getter, even if the @NoEtter annotation should prevent that. If it would simply replace the annotations everything would be fine.

Bundle that together and I'm drawing the conclusion that this is not a good idea for the first release of this new feature, but if there are easier routes and/or this is considered important enough to warrant this kind of effort, maybe we should just go for it. Seemed a bit dictatorial to just shoot it down based on my internal, unstated imaginations stuck_out_tongue

You are the maintainer, you can decide which feature gets added or not. If your dictatorship becomes too oppressive then I can still create my own meta-annotations-plus handler :stuck_out_tongue:

I understand that defining custom annotations can not work. However having a standard custom annotation has the advantage of sharing it between projects via the standard dependency mechanism.
I think we need that sharing as I would like to share company/team wide lombok meta annotations between projects. This is important as more and more teams are using different repositories for different microservices. So even a small team can have multiple repositories and they would like to share the same meta annotations in a "common" library.

As far as I know the lombok.config allows include from file system path and bubbles to the parent. This is great for many things but not so good for classpath based sharing.
Currently all I can do is to copy a shared team-lombok.config dependency via build tool (e.g.: maven) and include it in the lombok.config. That is far from ideal.
I read the #791 which states:

We don't intend for lombok.config to be searched for in the classpath, because that means inclusion of lombok.config in a jar file means all those settings all of a sudden apply to that project.

This is true but it can be solved by adding a flag to the projects lombok.config to include classpath configs.

What I am trying to say is that the more and more information you add to the lombok.config the more and more people want to share it between projects. It is important to plan according this and make sharing easier.

@Rawi01 wrote: Creating new annotations and removing them later on is how my current draft works.

If it ends up making the process simpler, more robust, and probably more future proof, then it is by (my) definition the most elegant solution, and my aesthetic sense is wrong. Carry on.

@Rawi01 There are only a few popular annotation processors...

You know, now that I think about it, what happens if we always force a new round if we add non-lombok annos? Or, force a new round if our AP ordering scan indicates lombok did not run first? The idea being that _if_ an AP ran before us that actually wanted to see those annos, they will most likely pick it up in the next round and do whatever they're supposed to do. This is no blanket guarantee - an earlier-running AP may have already decided that it has gleaned all required info from the various annotations it is triggering on, and already generated the data. Or, the second round confuses it and makes it generate whatever it was going to generate based on incomplete data.

But I bet it'll fix most scenarios. We'll just have to sort of sign up from the get go that we may have to abandon this scheme if it leads to too many issues. It feels a _tad_ mean to more or less force the community to test this stuff, but, that's what experimental is for. This is not a feature that is associated with a lombok package, but we'll splatter __experimental!__ all over it; I don't think we can fully judge the impact of all of this, or guarantee both that we aren't going to backwards break this feature.

@gebezs wrote: As far as I know the lombok.config allows include from file system path and bubbles to the parent. This is great for many things but not so good for classpath based sharing.

Yup, it's an as yet unsolved serious issue with this feature request.

We don't intend for lombok.config to be searched for in the classpath, because that means inclusion of lombok.config in a jar file means all those settings all of a sudden apply to that project.

This is true but it can be solved by adding a flag to the projects lombok.config to include classpath configs.

The comment more explains why we didn't investigate doing that: It's not a slamdunk to do this. However, __that is perhaps a pretty good idea__ – what if meta-annotation definitions __are__ picked up for these? As in, lombok.config entries on your classpath are read, but only for the definitions of meta-annos, not for settings.

One pretty major problem is that the classpath as a general concept fundamentally does not support the concept of 'listing' files, so we can't just scan a classpath for every lombok.config that it has. I see 4 solutions to this problem:

  • Look in the same place as the anno. If lombok notices that a @com.foo.lombokmeta.MetaAnno is present anywhere in a source file, lombok asks classpath for the file /com/foo/lombokmeta/lombok.config and acts accordingly, and will error on that annotation if multiple conflicting configs are present.
  • Always look in /lombok.config for ALL classpath entries. Error if conflicts.
  • Force user to write an explicit lombok.config in the usual place, but allow an import feature within lombok.config that does work via classpath.
  • Ditch all this jazz, and _do_ go with the idea that you annotate the meta-annotation. This rains on the parade of using it for non-lombok annotations as @Rawi01 wants (because they might not be configured to be allowed on annotations), and makes parameterization difficult, and requires lombok to parse that class file, which causes issues if you edit the meta-anno in your editor. I don't like this option.

I already thought this was 'worth' unique syntax within lombok.config (not lombok.meta += ...., but its own syntax), and this idea further highlights the need for this (assuming we go with one of the first 3 options).

The big question is: _can_ we easily figure out what the classpath even is? I'm guessing that we can probably do this: In javac, there's the filer, which possibly lets us do this via its own API, and even if it does not, it's likely possible to reflect our way into what we need. For intellij, well, they'd know the CP of the project no problem, surely. For eclipse, this was a lot harder than you'd think but I recall that we already do some fairly crazy shenanigans to figure out the directory a source file is in (for lombok.config), but eclipse worries me the most for this idea.

I know that OSGi is a bit different topic but they had the same problem: traversing the classpath for files. It is doable with classpath scanner libraries but they can be slow. So in the OSGi world we usually rely on the META-INF/MANIFEST.MF file. As you know that file contains only key-value pairs like the Created-By: Apache Maven Bundle Plugin. This means it is possible to store paths inside that jar like the Service-Component: OSGI-INF/pkg.Cl1.xml,OSGI-INF/pkg.Cl2.xml. The Service-Component entry tells the system to read the pgk.Cl1.xml and the pkg.Cl2.xml files from the same jar, read the xml as service definition and load the service. This solves the classpath scanning as it is not required anymore because everything starts with the META-INF/MANIFEST.MF file. Obviously there is a huge drawback: the MANIFEST.MF is so complex that we have to use compile time tools to generate them as maintaing by hand is impossible.

There is no need to stick to the MANIFEST.MF we can use the META-INF/lombok.meta or whatever sounds best.

This solution would force the library developers to explicitly export their meta annotations and it would probably help the lombok tool maintainers to find them easily.

And as I said that is the OSGi world which is a great place but a bit different than the rest of the Java universe.

I don't like the configure-by-classpath idea at all. The classpath is a result of the project/compilation settings, which are conveyed, for instance, via maven or gradle config.
Lombok, however, is generally unrelated to the classpath. It does its work only during compilation of the current compilation unit. Thus, it should only be configured by the sources and the compiler settings directly.
Letting the classpath influence Lombok's configuration introduces a level of indirection that is likely to cause confusion: Maybe you just add a new dependency, and suddenly your source files compile to different class files, because the new dependency happens to contain a lombok.config.

There is a way out: Allow Lombok to be configured via maven/gradle config. At least with maven you could use a base/parent pom, which you can share between projects.

Maven's base-parent relationship is inheritance while the classpath solution is composition. I found composition way more flexible for projects but this is just my opinion.

Spring Boot does load magic from classpath and it is a popular framework (source).

I think that even the ability to include files from classpath would be great. Something like the Spring's classpath: and classpath*::

# finds all conf/lombok-meta.config files and imports them in the classpath's order
import classpath*:conf/lombok-meta.config
# finds the lombok-main.config (first in the classpath) and imports it
import classpath:lombok-main.config

The classpath: could show a warning or could stop with an error in case of multiple files are found.
Spring supports ant-style wildcards (classpath*:config/**/*.xml) but that would require classpath scanning so that is out of question.

In this way the developers can decide which path they would like to follow.

Was this page helpful?
0 / 5 - 0 ratings