-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Start cleanup of AnnotationTarget #507
Conversation
/** | ||
* Target of this annotation. | ||
* That is, the declaration, the type parameter or the type use on which this annotation is present. | ||
* TODO what if this annotation is a nested annotation? | ||
* TODO what if this annotation doesn't have a known target (e.g. qualifier of a synthetic bean)? | ||
* TODO Do we need this? Retrieving the target from an annotation value is not supported in Micronaut |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can probably remove this.
In Quarkus, the equivalent is used quite often, because we often start by simply looking up all annotations of some type, and then filter based on their target etc. But at the moment, I can't think of a way that would enable something like this in CDI Lite. I think we only allow top-down access, never bottom-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will remove it in the PR
|
||
/** | ||
* Fully qualified name of this annotation. | ||
* Equivalent to {@code declaration().name()}. | ||
* | ||
* @return fully qualified name of this annotation | ||
* @return fully qualified name of this annotation, never {@code null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Off topic, since this was introduced earlier and you didn't change it, but I want to bring this up anyway.
I recently learned that the JLS makes an important distinction between fully qualified name and binary name. They are identical for top-level types, but not for nested types.
For example:
package com.example;
public class MyClass {
public @interface MyAnnotation {
}
}
Fully qualified name of the MyAnnotation
type is com.example.MyClass.MyAnnotation
, while binary name is com.example.MyClass$MyAnnotation
.
I think we should probably settle on binary names, and I started doing that here: https://github.com/eclipse-ee4j/cdi/pull/506/files#diff-a8c5a19048a48d991137c3ecb9f78f593b33a264b81a41bd5fa543cf9ac7ae73
WDYT about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for binary names
* @return value of this annotation's attribute with given {@code name} | ||
* @param name attribute name, never {@code null} | ||
* @return value of this annotation's attribute with given {@code name} or {@code null} if it doesn't exist. | ||
* @throws java.lang.IllegalArgumentException if the argument is {@code null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about the distintion between NullPointerException
and IllegalArgumentException
for null
arguments? I personally like IllegalArgumentException
more, but the convention seems to be (and Effective Java advises the same) to use NPE.
On the other hand, if the rest of CDI uses IAE, then I'd say we follow suit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps NullPointerException
is fine, consistency is the key. I have tended to use IAE but then again methods like Objects.requireNonNull
throw NPE
|
||
/** | ||
* Returns whether this annotation has a value defined using the {@link #MEMBER_VALUE} member. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Returns whether this annotation has a value defined using the {@link #MEMBER_VALUE} member. | |
* Returns whether this annotation has the {@link #MEMBER_VALUE} member. |
Reads better to me?
@@ -1,8 +1,8 @@ | |||
package jakarta.enterprise.lang.model; | |||
|
|||
// TODO "attribute" is a colloquial expression, perhaps use something closer to the JLS? AnnotationMember? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to AnnotationMember
, we can remove this TODO comment.
@@ -5,7 +5,7 @@ | |||
import java.util.List; | |||
|
|||
// TODO "attribute" is a colloquial expression, perhaps use something closer to the JLS? AnnotationMember? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to AnnotationMemberValue
, we can remove this TODO comment.
*/ | ||
public interface AnnotationTarget { | ||
// TODO specify equals/hashCode (for the entire .lang.model hierarchy) | ||
// TODO settle on using Optional everywhere, or allowing null everywhere; it's a mix right now | ||
// TODO what about declared vs not declared annotations? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we remove AnnotationInfo.target
, as you propose, will there still be a difference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It relates to inheritance. For example if an annotation has @Inherited
and is defined on the superclass it should appear in getAnnotation(..)
methods but not getDeclaredAnnotation(..)
methods. There appears to be no distinction in this API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah gotcha. I originally intended to only return [directly] declared annotations and let users traverse the hierarchy if they also need inherited annotations, but we can certainly revisit that.
The Annotated.getAnnotation[s]
methods from Portable Extensions always return all annotations, including inherited, and don't have a way to only return directly declared annotations. We could move in that direction for sure. (Implementation in Quarkus will become a little more complex, but it's perfectly possible.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok yes I see, seems that the core CDI API makes no distinction so we probably don't need to in this API
*/ | ||
public interface AnnotationTarget { | ||
// TODO specify equals/hashCode (for the entire .lang.model hierarchy) | ||
// TODO settle on using Optional everywhere, or allowing null everywhere; it's a mix right now | ||
// TODO what about declared vs not declared annotations? | ||
// TODO I am wondering why we are deviating from javax.lang.model names here like AnnotatedConstruct, Element etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I find the javax.lang.model
hierarchy quite counter-intuitive (e.g. using VariableElement
for both fields and parameters). The Portable Extensions types are much nicer, and I'd be in favor of moving towards similar names -- except that makes for rather poor coding experience.
I added When it comes to a builder API for constructing annotations, I totally agree the ClassConfig clazz = ...;
clazz.addAnnotation(MyAnnotation.class, (builder) -> {
builder.value(someValue); // shortcut for .member("value", someValue); arrayValue or annotationValue for non-scalar members
builder.member("name", myStringValue);
builder.member("something", someOtherValue);
builder.arrayMember("someArray", (valueBuilder) -> {
valueBuilder.of(1);
valueBuilder.of(2);
valueBuilder.of(3);
});
builder.annotationMember("nestedAnnotation", SomeOtherAnnotation.class, (builder2) -> {
builder2.member("x", x);
builder2.member("y", y);
});
builder.arrayMember("nestedAnnotationArray", (valueBuilder) -> {
valueBuilder.ofAnnotation(YetAnotherAnnotation.class, (builder2) -> { ... });
valueBuilder.ofAnnotation(YetAnotherAnnotation.class, (builder2) -> { ... });
});
}); This allows keeping everything in the spec as interfaces (adding implementation of anything directly to the spec API should be a last resort kind of thing IMHO). At the same time, I also feel it's quite unusual. Another option would be a more traditional builder. Something like: ClassConfig clazz = ...;
clazz.addAnnotation(AnnotationBuilder.of(MyAnnotation.class)
.value(someValue), // shortcut for .member("value", someValue); arrayValue or annotationValue for non-scalar members
.member("name", myStringValue), // for scalar values
.arrayMember("someArray").add(1).add(2).add(3).build(),
.annotationMember("nestedAnnotation", AnnotationBuilder.of(SomeOtherAnnotation.class)
.member("x", x)
.member("y", y)
.build()),
.arrayMember("nestedAnnotationArray")
.add(AnnotationBuilder.of(YetAnotherAnnotation.class).add(...).add(...).build())
.add(AnnotationBuilder.of(YetAnotherAnnotation.class).add(...).add(...).build())
.build()
.build()
); This is a bit more readable and more common, but requires the CDI spec to ship some code. We could probably make That would actually probably be best... |
The former approach is almost identical to what we have in Micronaut: The main different is we don't have a specific method for arrays and just use varargs and for nested annotations we have a specific type, so it could look like: ClassConfig clazz = ...;
clazz.addAnnotation(MyAnnotation.class, (builder) -> {
builder.value(someValue);
builder.member("name", myStringValue);
builder.member("something", someOtherValue);
builder.member("someArray", 1,2,3);
builder.member("nestedAnnotation", AnnotationValue.builder(NestedAnnotation.cass).value("someValue"));
}); Note as well Micronaut's API allows you to specify the retention policy if you only want to use the annotation for build time metadata. I am not that bothered about |
After realizing that if we remove |
Looks like a good improvement some feedback:
|
Sure, I used |
Done on the same branch. I also removed the I'm quite satisfied with how the |
@Ladicek sounds good let me just push a few more changes I had been working on and then mark it for review |
Ok this is ready for review |
* | ||
* @param <T> The annotation type. | ||
*/ | ||
public interface AnnotationInfo<T extends Annotation> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this needs to be parameterized, but let's discuss that later.
(As for ClassInfo
, I'd pretty much love to remove the type parameter -- I originally added it to be able to express a query, but we don't allow "injecting" ClassInfo
anymore and we want ClassConfig
to not inherit from ClassInfo
, so that reason no longer applies.)
* | ||
* @return The {@link jakarta.enterprise.lang.model.types.Type} of the value. | ||
*/ | ||
Type asType(); // can be a VoidType, PrimitiveType or ClassType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally forgot it can also be an array, but I can amend that in my follow-up PR.
Initial progress on cleanup of annotation API. I think we should re-consider the annotation member value coercion APIs as they add a little bit overhead as you have to wrap ever value in
AnnotationMemberValue
(renamed fromAnnotationAttributeValue
in this PR)In Micronaut these are resolved through fine grained methods like
intValue
https://github.com/micronaut-projects/micronaut-core/blob/09a46c9a54a8704ae7cc20572ed08520e8c006ed/core/src/main/java/io/micronaut/core/annotation/AnnotationValueResolver.java#L27There is also a
AnnotationValueBuilder
which is simpler and more fluent than theAnnotations
interface that is proposed. See https://github.com/micronaut-projects/micronaut-core/blob/09a46c9a54a8704ae7cc20572ed08520e8c006ed/core/src/main/java/io/micronaut/core/annotation/AnnotationValueBuilder.java#L32Either way it is technically possible in Micronaut it just seems this API could be a better IMO