What is the meaning of defining enums and fields inside annotation declarations? - java

It seems it is possible to declare fields and enums inside the declaration of an annotation in Java. For example, javac compiles this:
#interface ClassPreamble {
public enum AnEnum {
Value;
}
String aField = "";
String author();
String date();
String currentRevision() default "";
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}
What is the meaning / usefulness of defining enums and fields inside annotation declarations?
Thanks

Java annotations are just interfaces that inherit java.lang.annotation.Annotation (*), and are treated specially by the compiler to a certain extent, but the compiler will not prevent you from doing whatever is legal within interfaces. It can even be useful (see example from JLS at bottom of this answer).
(*) Although an interface that manually extends Annotation does not define an annotation type (source: javadoc for Annotation.java)
Your annotation ClassPreamble is effectively interface ClassPreamble extends java.lang.annotation.Annotation (decompile it to take a look).
It is legal to declare an enum within an interface.
It is legal to declare a field in an interface: it will be implicitly public static final.
$ cat Funny.java
interface Funny {
public enum AnEnum { One, Two }
String aField = "";
}
$ javac Funny.java
$ javap -c Funny.class
Compiled from "Funny.java"
interface Funny extends java.lang.annotation.Annotation {
public static final java.lang.String aField;
}
$ javap -c Funny\$AnEnum.class | head
Compiled from "Funny.java"
public final class Funny$AnEnum extends java.lang.Enum<Funny$AnEnum> {
public static final Funny$AnEnum One;
public static final Funny$AnEnum Two;
public static Funny$AnEnum[] values();
Code:
0: getstatic #1 // Field $VALUES:[LFunny$AnEnum;
3: invokevirtual #2 // Method "[LFunny$AnEnum;".clone:()Ljava/lang/Object;
...
I'm not sure there is a good answer for "what is the meaning of such constructs?". I guess it was implemented this way as a design choice: whatever the annotation does is encoded as bytecode for an interface, which the JVM knows how to deal with, so they did not need to modify the JVM itself too heavily (if at all), it allowed the desired features for compile-time and for run-time, and it does not do harm (or does it?).
Edit: 2 extracts from the Java Language Specification section 9.6 about annotations (which belongs to chapter 9 about interfaces):
An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (#).
[...]
The grammar for annotation type declarations permits other element declarations besides method declarations. For example, one might choose to declare a nested enum for use in conjunction with an annotation type:
#interface Quality {
enum Level { BAD, INDIFFERENT, GOOD }
Level value();
}

Related

Is it possible to scan for annotations on the classpath in a processor?

I'd like to write an annotation processor that scans for annotations on the classpath.
The idea is something like this:
Main library
Processor implementation which looks up #Foo annotations from Dependency A and Dependency B and generates a class based on both of them.
Dependency A
Depends on Base
Declares #Foo(someParam=Bar.class) public class A {...}
Dependency B
Depends on Base
Declares annotation #Foo(someParam=Baz.class) public class B {...}
Base
Declares public #interface Foo{...}.
Is this possible? Is there maybe a better way to do it?
Yes, that is in fact the most basic and simple mode that you'd use an annotation processor in.
Any given annotation processor registers which annotations it is interested in. You can choose to say "*", in which case your annotation processor is handed every generated class model (that's the object representing a class in flight; after it has been parsed, but before it has been written to disk). More usually, you choose one or a few annotations, in which case you get notified only with those models that were annotated with one of these annotations:
Base project
MyAnno.java
package com.foo;
#Target(ElementType.TYPE) //[1]
#Retention(RetentionPolicy.SOURCE)
public #interface MyAnno {}
MyProcessor.java
#SupportedAnnotationTypes("com.foo.MyAnno") // [2]
class MyProcessor extends AbstractProcessor {
#Override public boolean process(Set<? extends TypeElement> annos, RoundEnvironment round) {
for (TypeElement annoType : annos) {
Set<? extends Element> annotatedElems = round.getElementsAnnotatedWith(annoType);
for (Element elem : annotatedElems) hello(elem);
}
return false;
}
private void hello(Element elem) {
// 'elem' can represent packages, classes, methods, fields, etc.
// because you constrainted the anno to only be allowed on types,
// it'd have to be a type (class or interface), so we can cast..
TypeElement typeElem = (TypeElement) elem;
// do whatever you want with it, here.
}
[1] this says that this annotation can only be put on types. So, for example, not on methods (trying to write #MyAnno public String foo() {} would be a compiler error, stating that #MyAnno cannot be placed on methods).
[2] There's a chicken-and-egg thing going on with annotation processors: You're still compiling code, which means the class files that represent the code possibly do not exist yet, nor does the compiler have a good idea on what methods are even available in them. Therefore, you do not get to use the usual reflection library. Instead, you get model/element types, such as TypeElement, and a lot of it is 'stringly typed' where you refer to things in string form, such as here. The fact that you don't write #SupportedAnnotationTypes(MyAnno.class) is an intentional part of the AP design.
Really, just follow your garden variety 'how do I write an annotation processor' tutorial. They should cover this.

How to visit annotation inside method in ASM

guys, I can not visit local variable annotation use asm MethodVisitor, I do not know how to do, please help me, I want get the value "in method", but the MethodVisitor visitLocalVariableAnnotation() method does not called by the ASM logic, here is my code:
#Retention(RetentionPolicy.CLASS)
#Target({ElementType.LOCAL_VARIABLE})
public #interface SendEvent {
String value() default "hello every";
}
public class AnnotationTest {
public void test() {
#SendEvent(value = "in method")
EventBase base = new EventBase();
}
}
public class AsmMethodVisitor extends MethodVisitor implements Opcodes {
public AsmMethodVisitor(MethodVisitor methodVisitor) {
super(ASM7, methodVisitor);
System.out.println("== AsmMethodVisitor");
}
#Override
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {
System.out.println("== visitLocalVariableAnnotation");
return super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible);
}
}
This is expected, as per JLS 9.6.4.2 this information is simply not retained in the class file:
9.6.4.2. #Retention
Annotations may be present only in source code, or they may be present in the binary form of a class or interface. An annotation that is present in the binary form may or may not be available at run time via the reflection libraries of the Java SE Platform. The annotation type java.lang.annotation.Retention is used to choose among these possibilities.
If an annotation a corresponds to a type T, and T has a (meta-)annotation m that corresponds to java.lang.annotation.Retention, then:
If m has an element whose value is java.lang.annotation.RetentionPolicy.SOURCE, then a Java compiler must ensure that a is not present in the binary representation of the class or interface in which a appears.
If m has an element whose value is java.lang.annotation.RetentionPolicy.CLASS or java.lang.annotation.RetentionPolicy.RUNTIME, then a Java compiler must ensure that a is represented in the binary representation of the class or interface in which a appears, unless a annotates a local variable declaration or a annotates a formal parameter declaration of a lambda expression.
An annotation on the declaration of a local variable, or on the declaration of a formal parameter of a lambda expression, is never retained in the binary representation. In contrast, an annotation on the type of a local variable, or on the type of a formal parameter of a lambda expression, is retained in the binary representation if the annotation type specifies a suitable retention policy.
Note that it is not illegal for an annotation type to be meta-annotated with #Target(java.lang.annotation.ElementType.LOCAL_VARIABLE) and #Retention(java.lang.annotation.RetentionPolicy.CLASS) or #Retention(java.lang.annotation.RetentionPolicy.RUNTIME).
If m has an element whose value is java.lang.annotation.RetentionPolicy.RUNTIME, the reflection libraries of the Java SE Platform must make a available at run time.
If T does not have a (meta-)annotation m that corresponds to java.lang.annotation.Retention, then a Java compiler must treat T as if it does have such a meta-annotation m with an element whose value is java.lang.annotation.RetentionPolicy.CLASS.
(emphasis mine)
ASM can simply not emit events for information that is not present.

Passing TYPE annotations to methods instead of marker interfaces

[ANSWER EDIT]: Short answer is that what I'm looking to do isn't possible. My question is a little misleading. I learnt that the Marker Interface pattern is actually what I called the Marked Annotations in my question (since the annotation you're creating is actually an interface). And checks on that can only be made at runtime. So if you're looking to make a compile time check with annotations well it's just not possible. An empty interface is the only option. Check answer to see how to do it at runtime.
I'm trying to avoid using marker interfaces in favor of marked annotations. Basically I want a bunch of classes be marked with this annotation, and pass instances of those classes to methods that accept that type. Here is my code:
MARKER ANNOTATION:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Message {
}
CLASS:
#Message
public class MessageTypeA {
}
METHOD:
public class DatabaseWriter {
public void save(Message msg) {
//some code
}
}
CALLING CODE:
MessageTypeA msgA = new MessageTypeA();
DatabaseWriter writer = new DatabaseWriter();
writer.save(msgA);
However I get Error:(78, 23) java: incompatible types: MessageTypeA cannot be converted to Message
I'm not sure if what I'm doing is possible, but I read that marker interfaces can be replaced with marker annotations. Is it not possible in this case?
Thanks
The marker interface pattern is a way of adding metadata to your program types or obbjects that is readable in runtime.
See for example hibernate implementation of this pattern. Their insert method accepts a plain java.lang.Object, and is inside that method where the metadata from the various annotations is used.
So, following your example implementation, I'd go with something like this
public class DatabaseWriter {
public void save(Object msg) {
if (msg.getClass().isAnnotationPresent(Message.class)) {
//some code
}
}
}
In your example, MessageTypeA and Message are unrelated in the class hierarchy. A method call is legal only if the expression's type is a subtype of the formal parameter's type.
One way to establish a subtyping relationship is with an interface, as you already noted.
Another way to establish a subtyping relationship is with type qualifiers (expressed as type annotations).
TYPE QUALIFIER HIERARCHY:
#Message
|
#MessageTypeA
where #MessageTypeA is a subtype of #Message, and #Message means an unknown type of message. #Message is the default if no type annotation is written.
LIBRARY
public class DatabaseWriter {
public void save(Object msg) {
// some code that can run on any old message
}
public void saveA(#MessageTypeA Object msg) {
// some code that is specific to MessageTypeA
}
}
CLIENT
Object msg = ...;
#MessageTypeA Object msgA = ...;
DatabaseWriter writer = new DatabaseWriter();
writer.save(msg); // legal
writer.save(msgA); // legal
writer.saveA(msg); // compile-time error
writer.save(msgA); // legal
There is no run-time overhead or representation: the enforcement is done at compile time.
A tool that enables you to build pluggable type-checkers that enforce correct usage is the Checker Framework. (Disclaimer: I am a maintainer of the tool, but it is a regular part of the development toolchain at Amazon, Google, Uber, etc.)
You can define your own type system in a few lines of code. However, still consider using Java subtypes rather than type qualifiers.

Is it legal to put annotation after access modifier in Java 7? Or Java 8?

This is usual code:
#Autowire
private Service service;
But with Java 7 this also works (and shorter):
private #Autowire Service service;
Is that legal in Java 8 (have same semantic)? Is that bad coding practice?
According to documentation
In Java 7 :
Annotations can be applied to declarations: declarations of classes,
fields, methods, and other program elements. When used on a
declaration, each annotation often appears, by convention, on its own
line.
As of the Java SE 8 release, annotations can also be applied to the use of types. :
Class instance creation expression:
new #Interned MyObject();
Type cast:
myString = (#NonNull String) str;
implements clause:
class UnmodifiableList<T> implements
#Readonly List<#Readonly T> { ... }
Thrown exception declaration:
void monitorTemperature() throws
#Critical TemperatureException { ... }
According to official Java 7 grammar this is legal:
Modifier:
Annotation
public
protected
private
static
abstract
final
native
synchronized
transient
volatile
strictfp
ClassOrInterfaceDeclaration:
{Modifier} (ClassDeclaration | InterfaceDeclaration)
...
Grammar for Java 8 also seems to allow free mixing of modifiers:
MethodDeclaration:
{MethodModifier} MethodHeader MethodBody
MethodModifier:
Annotation public protected private
abstract static final synchronized native strictfp

Why java annotation attributes have restrictions?

I noticed that if I create an annotation:
public #interface NullableTypeOverride {
NullableType hibernateTypeOverride();
}
I have limited options for annotation attributes. The above code will not work because annotations only take primitive, String or Class types for their attributes.
So in this case I can't use this annotation like this:
#NullableTypeOverride(hibernateTypeOverride = Hibernate.INTEGER)
private Long distance;
My guess is that it has something to do with compile time vs. runtime but I'm not entirely sure. So what is the reason for this limitation and how can I work around it?
The JLS states
It is a compile-time error if the return type of a method declared in
an annotation type is not one of the following: a primitive type,
String, Class, any parameterized invocation of Class, an enum type
(§8.9), an annotation type, or an array type (§10) whose element type
is one of the preceding types.
The reason for this is that annotations must have a constant value. If you provide a reference to an object that may change, you'll have problems. This is only relevant if the annotation's Retention is RUNTIME.
public class Person {
public String name;
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
Person person();
}
#MyAnnotation(person = ???) // how to guarantee it won't change at runtime?
public void method1() {...}
What's that value supposed to be? And how can reflection libs cache it?
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
annotation.person(); // should be the same value every time
Remember, annotations are supposed to be metadata.

Categories

Resources