I am writing my first Annotations processor and having trouble with something that seems trivial but I cannot find any information about it.
I have a element annotated with my annotation
#MyAnnotation String property;
When I get this property as a element in my processor I can not seem to get the type of the element in any way. In this case a would want to get a Class or TypeElement instance representing String.
I tried instantiating a class object of the container type with Class.forName() but it threw a ClassNotFoundException. I think this is because I do not have access to the class loader containing the class?
When running your annotation processor, you don't have access to the compiled classes. The point of annotation processing is that it happens pre-compile.
Instead, you need to create an annotation processor that specifically handles your annotation type, then use the mirror API to access the field. For example:
#SupportedAnnotationTypes("com.example.MyAnnotation")
public class CompileTimeAnnotationProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// Only one annotation, so just use annotations.iterator().next();
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(
annotations.iterator().next());
Set<VariableElement> fields = ElementFilter.fieldsIn(elements);
for (VariableElement field : fields) {
TypeMirror fieldType = field.asType();
String fullTypeClassName = fieldType.toString();
// Validate fullTypeClassName
}
return true;
}
}
For the validation, you cannot use any classes which have yet to be compiled (including those that are about to be compiled with the annotation) using something like MyType.class. For these, you must use strings only. That is because annotation processing occurs during a pre-compiling phase known as "source generation", which is what allows you to generate source code before the compiler runs using annotations.
An example validation verifying that the field type is java.lang.String (which is already compiled):
for (VariableElement field : fields) {
TypeMirror fieldType = field.asType();
String fullTypeClassName = fieldType.toString();
if (!String.class.getName().equals(fullTypeClassName)) {
processingEnv.getMessager().printMessage(
Kind.ERROR, "Field type must be java.lang.String", field);
}
}
Resources
Main APT Page
Mirror API Javadocs (Java 7 and older)
Edit: Mirror API Javadocs (Java 8)
Note that the mirror API is now standardized in Java 8 under javax.lang.model and the old API is deprecated. See this blog post for more information. If you've been using the javax classes, then you don't need to worry.
Edit:
I want to get the field type to get annotations on that type. But this does not seem like it will be possible?
Indeed it is possible! This can be done using more methods on the TypeMirror:
if (fieldType.getKind() != TypeKind.DECLARED) {
processingEnv.getMessager().printMessage(
Kind.ERROR, "Field cannot be a generic type.", field);
}
DeclaredType declaredFieldType = (DeclaredType) fieldType;
TypeElement fieldTypeElement = (TypeElement) declaredFieldType.asElement();
From here, you have two choices:
If the annotation you're trying to find is already compiled (i.e. it's from another library) then you can reference the class directly to get the annotation.
If the annotation you're trying to find is not compiled (i.e. it's being compiled in the current call to javac that's running the APT) then you can reference it via AnnotationMirror instances.
Already Compiled
DifferentAnnotation diffAnn = fieldTypeElement.getAnnotation(
DifferentAnnotation.class);
// Process diffAnn
Very straight-forward, this gives you direct access to the annotation itself.
Not Compiled
Note that this solution will work regardless of whether or not the annotation is compiled, it's just not as clean as the code above.
Here are a couple methods I wrote once to extract a certain value from an annotation mirror by its class name:
private static <T> T findAnnotationValue(Element element, String annotationClass,
String valueName, Class<T> expectedType) {
T ret = null;
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
DeclaredType annotationType = annotationMirror.getAnnotationType();
TypeElement annotationElement = (TypeElement) annotationType
.asElement();
if (annotationElement.getQualifiedName().contentEquals(
annotationClass)) {
ret = extractValue(annotationMirror, valueName, expectedType);
break;
}
}
return ret;
}
private static <T> T extractValue(AnnotationMirror annotationMirror,
String valueName, Class<T> expectedType) {
Map<ExecutableElement, AnnotationValue> elementValues = new HashMap<ExecutableElement, AnnotationValue>(
annotationMirror.getElementValues());
for (Entry<ExecutableElement, AnnotationValue> entry : elementValues
.entrySet()) {
if (entry.getKey().getSimpleName().contentEquals(valueName)) {
Object value = entry.getValue().getValue();
return expectedType.cast(value);
}
}
return null;
}
Let's say that you're looking for the DifferentAnnotation annotation and your source code looks like this:
#DifferentAnnotation(name = "My Class")
public class MyClass {
#MyAnnotation
private String field;
// ...
}
This code will print My Class:
String diffAnnotationName = findAnnotationValue(fieldTypeElement,
"com.example.DifferentAnnotation", "name", String.class);
System.out.println(diffAnnotationName);
Related
I am writing an annotation processor that generates JSON serialization code. Here's my annotation that I use to identify the POJOs that need a serializer
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.SOURCE)
public #interface JsonSerialize {
}
And here's the base interface of my serializer
public interface JsonSerializer<T> {
String serialize(T t);
}
Here's the annotation processor code that looks for that annotation and generates the serializer code
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(JsonSerialize.class)) {
if (element.getKind() == ElementKind.CLASS) {
MethodSpec serializeMethod = MethodSpec
.methodBuilder("serialize")
.addModifiers(Modifier.PUBLIC)
.addParameter(ParameterSpec.builder(TypeName.get(element.asType()), "obj", Modifier.FINAL).build())
.returns(String.class)
.addStatement("return \"dummy string\"")
.build();
TypeSpec serializer = TypeSpec
.classBuilder(element.getSimpleName().toString() + "JsonSerializer")
.addSuperinterface(JsonSerializer.class) // THIS LINE IS WRONG
.addModifiers(Modifier.PUBLIC)
.addMethod(serializeMethod)
.build();
try {
JavaFile.builder(processingEnv.getElementUtils().getPackageOf(element).toString(), serializer)
.build()
.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
But I get a compile error, because my generated class is not specifying the generic parameter in it's inheritance. How can I specify that?
Instead of passing a java.lang.Class to the addSuperinterface method, you'll need to pass something with the specific type details you have in mind. This method has two overloads - one which takes java.lang.reflect.Type (and Class is a subtype of this), and another which one which takes com.squareup.javapoet.TypeName). Technically either works, though since you are already using JavaPoet, I'd encourage trying to create the TypeName instance.
TypeName has a number of subclasses, ClassName, ParameterizedTypeName are probably the main ones to focus on here. In an annotation processor, they have some big advantages over using a Class instance - mostly that you don't need to actually be able to load or reference the class you are talking about - kind of like how you are using element.getSimpleName().toString() elsewhere in your code.
These classes have static methods to create them, which can be based on a variety of things. The one we're interested in here is this:
/** Returns a parameterized type, applying {#code typeArguments} to {#code rawType}. */
public static ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments)
In you code, you would use it roughly like this:
...
.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(JsonSerializer.class),//rawType
ClassName.get(whateverTShouldBe) //the value for T
))
...
Chance are excellent that T could eventually be generic here too, like List<String>, so you should take care to properly build the type which is passed in there - it might itself be a ParameterizedTypeName. Keep an eye on the various methods in TypeName for this too - the TypeName.get(TypeMirror) overload for example will take an already-parameterized declared type mirror and give you the expected ParameterizedTypeName back again.
With that said, according to your other code, T today cannot be generic - you look for the #JsonSerialize annotation on an Element, which means it would be the equivelent of List<T> rather than the usage of it, List<String>. Then, in this line, you make the Element into a TypeMirror to build the type name as I've described above:
.addParameter(ParameterSpec.builder(TypeName.get(element.asType()), "obj", Modifier.FINAL).build())
This means the final code would probably be
...
.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(JsonSerializer.class),//rawType
TypeName.get(element.asType()) //the value for T
))
...
I have to implement an interceptor that can be used for dynamically specified fields regardless of the field name.
On the comment for the answer here
https://stackoverflow.com/a/35113359/11390192
I've read
you can really just use reflection on a #This object. As long as you
cache the Field instances, this has no relevance to performance.
However I doubt the following interceptor implementation is an effecient one (if I understood the comment right).
public static class DynamicFieldInterceptor {
private final String fieldName;
public DynamicFieldInterceptor(String fieldName) {
this.fieldName = fieldName;
}
public void intercept(#This Object thiz) throws NoSuchFieldException, IllegalAccessException {
Field field = thiz.getClass().getDeclaredField(fieldName);
boolean oldAccessible = field.isAccessible();
field.setAccessible(true);
Long fieldValue = (Long)field.get(thiz);
field.set(thiz, fieldValue + 1L); // !< Instead of my business logic
field.setAccessible(oldAccessible);
}
}
I've also tried the following idea: to generate interceptor classes for each field with the different annotations on the #FieldProxy argument. Than use the generated class as an interceptor to the target class.
public interface Metaclass {
void intercept(GetterAndSetter field);
}
public static class MetaclassInterceptor implements Metaclass{
#Override
public void intercept(GetterAndSetter field) {
field.set((Long)field.get() + 1L);
}
}
public static Class<?> annotateInterceptorClass(final String annotation)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return new ByteBuddy()
.subclass(MetaclassInterceptor.class)
.topLevelType()
.name("ClassForIntercepting_" + annotation + "_Field")
.modifiers(Visibility.PUBLIC, Ownership.STATIC)
.defineMethod("intercept", void.class, Visibility.PUBLIC)
.withParameter(GetterAndSetter.class, "intercept")
.annotateParameter(AnnotationDescription.Builder.ofType(FieldProxy.class)
.define("value", annotation).build())
.intercept(SuperMethodCall.INSTANCE)
.make()
.load(MetaclassInterceptor.class.getClassLoader())
.getLoaded();
}
The class seems to be generated well. The method in the generated class exists and the parameter is annotated with the expected annotation.
However when I tried to use the generated class as an interceptor, I've got an exception.
Class<?> klass = new ByteBuddy()
.subclass(Object.class)
.defineProperty("index0", Long.class, false)
.defineProperty("index1", Long.class, false)
.defineMethod("doSomeActions", void.class, Visibility.PUBLIC)
.intercept(
MethodDelegation
.withDefaultConfiguration()
.withBinders(FieldProxy.Binder.install(GetterAndSetter.class))
// Use dynamically generated interceptor, see abode
.to(annotateInterceptor("index0"))
.andThen(
MethodDelegation
.withDefaultConfiguration()
.withBinders(FieldProxy.Binder.install(GetterAndSetter.class))
// Use dynamically generated interceptor, see abode
.to(annotateInterceptor("index1"))
)
)
.make()
.load(MetaclassInterceptor.class.getClassLoader())
.getLoaded();
Exception in thread "main" java.lang.NoClassDefFoundError: LClassForIntercepting_index0_Field;
at java.base/java.lang.Class.getDeclaredFields0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3062)
at java.base/java.lang.Class.getDeclaredField(Class.java:2410)
at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:120)
at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:187)
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:102)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:5662)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:5651)
at MainClass4.main(MainClass4.java:107)
Even if I succeeded with dynamic implementation of interceptors, I'd be sure that it's not the perfect way. I think it has to be the possibility to make it in the easier way. Really, #FieldProxy annotation can get the field from both explicitly specified name and bean property if the field name in the annotation is not specified, so I think it is the technical opportunity to map it to any other field.
When you load a class using load(MetaclassInterceptor.class.getClassLoader()), you are creating a new class loader that does not become visible to any other classes on other loaders unless you reuse it.
You can:
a) Combine the two DynamicTypes that are created by the make step and load them together. This way, they will end up in the same class loader.
b) Take the class loader of the first generated class and cast it to an InjectionClassLoader. You will also need to specify the ClassLoadingStrategy.WRAPPER.opened() and use it together with InjectionClassLoader.Strategy.INSTANCE. Note that this will allow anybody with a reference to an instance of your generated class to define classes in the same package.
c) Use ClassLoadingStrategy.Default.INJECTION what defines classes in the original class loader without creating a wrapper. Not that this strategy relies on Unsafe API.
When writing a type handler for a repository (such as a web service or a database), I need to instantiate the type after the value is loaded from the repository.
Let's say I get a String value from the repository and there is a constructor with one String argument that I can use. If the return type has a type parameter, what else can I do besides instantiating the raw type? It seems raw types exist only for compatibility with legacy code so I would prefer not to use them.
Normally ? can be used as type parameter (if you know the type will be correct at runtime), but not in this case because you can't instantiate classes with wildcards as type parameter.
EDIT: some example code:
Let's say I have a PrimaryKey class like this:
public class PrimaryKey<R extends RepositoryObject<R>> {
private String value;
public PrimaryKey(String value) {
this.value = value;
}
}
And a set of classes that extend RepositoryObject, which is defined like this:
public class RepositoryObject<R extends RepositoryObject<R>> {
private PrimaryKey<R> pk;
public RepositoryObject(PrimaryKey<R> pk) {
this.pk = pk;
}
PrimaryKey<R> getPrimaryKey() {
return pk;
}
}
Example of a subclass:
public class User extends RepositoryObject<User> {
public User(PrimaryKey<User> userId) {
super(userId);
}
}
Now the type handling method for class PrimaryKey will look something like this:
public PrimaryKey<?> getValue(String stringValue) {
return new PrimaryKey<>(stringValue);
}
But this results in a compiler error (in the Maven build, not in Eclipse IDE strangely enough) even though I'm using the diamond operator instead of when instantiating. Maybe for some reason type inference doesn't work well because of the recursion in the type parameters.
In Java 7 you can typically use the diamond operator to get around this limitation:
Container<?> c = new Container<>(arg);
Otherwise you can use a helper factory method:
<T> Container<T> makeContainer(String arg) {
return new Container<T>(arg);
}
...
Container<?> c = makeContainer(arg);
EDIT:
Following your update, I can see you're using a recursive type parameter <R extends RepositoryObject<R>>. This compile error is due to limitations of javac when it comes to wildcard capture and recursive type parameters. See this related post for example: Java CRTP and Wildcards: Code compiles in Eclipse but not `javac`
Unfortunately, using a raw type is necessary as a workaround, but it can be hidden as an implementation detail:
public PrimaryKey<?> getValue(String stringValue) {
#SuppressWarnings("rawtypes") //a raw type is necessary to placate javac
final PrimaryKey<?> pk = new PrimaryKey(stringValue);
return pk;
}
class SomeBogusClass extends RepositoryObject<SomeBogusClass> { }
return new PrimaryKey<SomeBogusClass>(stringValue);
seriously, you can put anything there that satisfies the bounds, even some bogus class that has nothing to do with your code.
I am using Scannotation to scan classfiles and get all classes with annotations present on any element of that class. Using reflection i've been able to find out all annotations on parameters in methods, but i need objects of those annotations so i can later get its parameters (or what do you call it).
this is fraction of my code, which will return annotations i want, but i can't work with them.
public Set<Class> getParametersAnnotatedBy(Class<? extends Annotation> annotation) {
for (String s : annotated) {
//annotated is set containing names of annotated classes
clazz = Class.forName(s);
for (Method m : clazz.getDeclaredMethods()) {
int i = 0;
Class[] params = m.getParameterTypes();
for (Annotation[] ann : m.getParameterAnnotations()) {
for (Annotation a : ann) {
if (annotation.getClass().isInstance(a.getClass())) {
parameters.add(a.getClass());
//here i add annotation to a set
}
}
}
}
}
}
i know i can work with it, if i know the annotation, like this:
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
public String name();
public int count();
}
// ... some code to get annotations
MyAnnotation ann = (MyAnnotation) someAnnotation;
System.out.println(ann.name());
System.out.println(ann.count());
but so far i was not able to do it this way, using reflection... I would very much appreciate any directions, thanks in advance.
PS.: is there any way to get object of parameters like Field for fields, Method for methods etc. ?
You need to use a.annotationType. When you call getClass on an annotation you are actually getting its Proxy Class. To get the real class that it is you need to call annotationType instead of getClass.
if (annotation.getClass() == a.annotationType()) {
parameters.add(a.annotationType());
// here i add annotation to a set
}
Is there any way to tell an ObjectOutputStream which fields of a serializable class should be serialized without using the keyword transient and without defining an serialPersistentFields-array?
Background: I need to use annotations to define which members of a class should be serialized (or better: not be serialized). The involved classes must implement the interface Serializable, but NOT Externalizable, so I don't want to implement the serialization/deserialization algorithm for each object but rather just use annotations for it. I can not use the transient keyword, because the annotation requires some further checks to determine whether a field should be serialized or not. These checks have to be done by the ObjectOutputStream (or in my own subclass of ObjectOutputStream). I also cannot define a serialPersistentFields-array in each class, because as explained previously, at compilation time it is not defined which fields should be serialized.
So the only thing that should be notet in the affected class is the annotation at field-level (#Target(ElementType.FIELD)).
I've tried quite a lot of approaches in the last few days, but haven't found one which is working:
The ObjectOutputStream has a method writeObjectOverride(Object) which can be used to define an own implementation of the serialization-process when extending ObjectOutputStream. This only works if the ObjectOutputStream is initialized with the no-argument-constructor because otherwise writeObjectOverride is never invoked. But this approach requires me to implement the whole serialization-process by myself and I don't want to do this, as it is quite complex and already implemented by the default ObjectOutputStream. I am looking for a way to just modify the default serialization implementation.
Another approach was extending ObjectOutputStream again and overriding writeObjectOverride(Object) (after calling enableReplaceObject(true)). In this method, I tried using some kind of SerializationProxy (see What is the Serialization Proxy Pattern?) to encapsulate the serialized object in a proxy which defines a List of Fields which should be serialized. But this approach also fails as writeObjectOverride then is also called for the List of fields (List<SerializedField> fields) in the Proxy resulting in an infinite loop.
Example:
public class AnnotationAwareObjectOutputStream extends ObjectOutputStream {
public AnnotationAwareObjectOutputStream(OutputStream out)
throws IOException {
super(out);
enableReplaceObject(true);
}
#Override
protected Object replaceObject(Object obj) throws IOException {
try {
return new SerializableProxy(obj);
} catch (Exception e) {
return new IOException(e);
}
}
private class SerializableProxy implements Serializable {
private Class<?> clazz;
private List<SerializedField> fields = new LinkedList<SerializedField>();
private SerializableProxy(Object obj) throws IllegalArgumentException,
IllegalAccessException {
clazz = obj.getClass();
for (Field field : getInheritedFields(obj.getClass())) {
// add all fields which don't have an DontSerialize-Annotation
if (!field.isAnnotationPresent(DontSerialize.class))
fields.add(new SerializedField(field.getType(), field
.get(obj)));
}
}
public Object readResolve() {
// TODO: reconstruct object of type clazz and set fields using
// reflection
return null;
}
}
private class SerializedField {
private Class<?> type;
private Object value;
public SerializedField(Class<?> type, Object value) {
this.type = type;
this.value = value;
}
}
/** return all fields including superclass-fields */
public static List<Field> getInheritedFields(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}
}
// I just use the annotation DontSerialize in this example for simlicity.
// Later on I want to parametrize the annotation and do some further checks
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface DontSerialize {
}
When I found out that it is possible to modify modifiers at runtime (see Change private static final field using Java reflection) I tried to set the transient-Modifier at runtime if the corresponding annotation was set.
Unfortunately this also does not work, because the approach used in the previous link seems to work only on static fields.
When trying it with non-static fields it runs without an exception but is not persisted because is looks like Field.class.getDeclaredField(...) returns new instances of the affected fields every time it is called:
public void setTransientTest() throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Class<MyClass> clazz = MyClass.class;
// anyField is defined as "private String anyField"
Field field = clazz.getDeclaredField("anyField");
System.out.println("1. is "
+ (Modifier.isTransient(field.getModifiers()) ? "" : "NOT ")
+ "transient");
Field modifiersField = Field.class.getDeclaredField("modifiers");
boolean wasAccessible = modifiersField.isAccessible();
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() | Modifier.TRANSIENT);
modifiersField.setAccessible(wasAccessible);
System.out.println("2. is "
+ (Modifier.isTransient(field.getModifiers()) ? "" : "NOT ")
+ "transient");
Field field2 = clazz.getDeclaredField("anyField");
System.out.println("3. is "
+ (Modifier.isTransient(field2.getModifiers()) ? "" : "NOT ")
+ "transient");
}
The output is:
1. is NOT transient
2. is transient
3. is NOT transient
So after calling getDeclaredField again (Field field2 = clazz.getDeclaredField("anyField");) it already lost the transient modifier.
Next approach:
Extend ObjectOutputStream and override ObjectOutputStream.PutField putFields() and define an own PutField-implementation. PutField lets you specify which (additional) fields are serialized but unfortunately the interface only has a lot of methodes of the form put(String name, <type> val) and when implementing these I cannot associate the method calls with the class field it is invoked from. For instance when serializing a field declared as private String test = "foo" the method put("test", "foo") is invoked, but I cannot associate the value of name (which is test) with the class containing the field test because no reference to the containing class is available and therefore it is impossible to read the annotation noted for the field test.
I also tried a few other approaches but as already mentioned I was not able to successfully serialize all fields except the ones with the annotation DontSerialize present.
One thing I also came across were ByteCode manipulators. Maybe it is possible with these but I have a requirement for not using any external tools - it needs to be pure Java (1.5 or 1.6).
Sorry for this really long post but I just wanted to show what I already tried and am hoping that someone can help me.
Thanks in advance.
I would reconsider if "Serialization" is really the thing you want to do. Given that the Serialization rules depends on some logic defined at runtime, the Deserialization process will be a nightmare to write.
Interesting problem, though.
Without rewriting much of Java Serialization, you will need to rewrite the bytecode. At runtime this can be done with Java Agents, but can also be done to class files during the build.