Java - How to edit annotation fields at runtime? - java

I wrote this simple annotation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
public #interface CSVColumn {
String name();
int order();
}
which I'm currently using to annotate some getter methods of a class.
I'm able to get the values of this annotation at runtime:
Class classType = objects[0].getClass();
Method[] methodsArray = classType.getDeclaredMethods();
List<Method> methods = Arrays.asList(methodsArray);
//FIlter annotated methods
methods = methods.stream().filter(method -> method.getAnnotation(CSVColumn.class) != null).collect(Collectors.toList());
Iterator<Method> methodIterator = methods.iterator();
while (methodIterator.hasNext()) {
Method method = methodIterator.next();
CSVColumn csvColumn = method.getAnnotation(CSVColumn.class);
String header = csvColumn.name();
}
but how could I set the field name of the annotation to another value?
Should/could I declare some setter methods in the annotation class?

Use the utility class AnnotationUtil
You can do changeAnnotation(csvColumn, "name", "newName")

Related

Can we set Annotation id at runtime

Is there a way by which I can set the id inside annotated method...
Annotation class:
import java.lang.annotation.*;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public
#interface MyAnnotation {
int id();
}
//Set id at runtime
public class A {
#MyAnnotation(id = ? )
public void method1() {
// I want to set the id here for my annotation...
}
}
Yes, but it's a bit unintuitive. You'll have to edit its bytecode using a tool like JavaAssist.
Here is an article describing what you're after.

How to give access inside Groovy script to a field by adding getter for a field through an annotation

I have a model class with fields inside:
public class Model1 {
#Bind private int LL;
#Bind private double[] twKI;
#Bind private double[] twKS;
#Bind private double[] twINW;
#Bind private double[] twEKS;
}
I created an annotation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Bind {
}
Is it possible to define getter and setters for fields inside Model1 class without modyfing it so later on they will by available in groovy script?
Well you have multiple choices and you can choose whichever suits you better:
We have a model object: Model model = new Model()
1. Using getter and setters:
Create getters and setter methods and then call the setter method: model.setLL(10)
2. Without getter and setters:
Well in groovy/grails scope variables doesn't make much difference until you are overriding them for some specific purpose. So you can directly set the value using model.LL = 10
3. Using setProperty: model.setProperty('LL', 10)
4. Reflection way: before setting the field value, mark it as accessible.
Field field = Model.getDeclaredField("LL")
field.setAccessible(true)
field.set(model, 10)

Java custom annotation

I want to write custome annonation on method .my usecase is - I have user which has role so when user try to access some method of class then i want to check his roles. so roles i want pass in annotation.
my code is here
package com.samples;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Roles {
public String roles();
}
package com.samples;
public class AnnotationExample {
#Roles(roles="admin")
public void createAction(){
String userrole = "admin"; // this hardcoded value
// a
// here i want to compare annotaiton value with user role
}
}
How can i do this ?
It is possible to declare annotation value as object value. like
#Roles(Role.AADMIN)
To check the annotations you need to use reflection.
Roles r = AnnotationExample.class.getMethod("createAction").getAnnotation(Roles.class);
if ("admin".equals(r.roles())) //has admin roles
It is possible to declare annotation value as object value. like #Roles(Role.AADMIN)
I think what you want is a Enum.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Roles {
public Role roles();
}
enum Role { ADMIN, USER };
public class AnnotationExample {
#Roles(roles=Role.ADMIN)
public void createAction(){
Role userrole = Role.ADMIN;
}
}
If you really need to reinvent security annotations (if you don't want to use already available options with declarative security like EJB or Spring) you may at least want to see how they implement it. Here for example for one of EJB implementations.
But maybe you would reconsider?

How to create custom annotation in java?

I want to create custom annotation in java for DirtyChecking. Like I want to compare two string values using this annotation and after comparing it will return a boolean value.
For instance: I will put #DirtyCheck("newValue","oldValue") over properties.
Suppose I made an interface:
public #interface DirtyCheck {
String newValue();
String oldValue();
}
My Questions are:
Where I make a class to create a method for comparison for two string values? I mean, how this annotation notifies that this method I have to call?
How to retreive returning values of this method ?
First you need to mark if annotation is for class, field or method. Let's say it is for method: so you write this in your annotation definition:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface DirtyCheck {
String newValue();
String oldValue();
}
Next you have to write let's say DirtyChecker class which will use reflection to check if method has annotation and do some job for example say if oldValue and newValue are equal:
final class DirtyChecker {
public boolean process(Object instance) {
Class<?> clazz = instance.getClass();
for (Method m : clazz.getDeclaredMethods()) {
if (m.isAnnotationPresent(DirtyCheck.class)) {
DirtyCheck annotation = m.getAnnotation(DirtyCheck.class);
String newVal = annotation.newValue();
String oldVal = annotation.oldValue();
return newVal.equals(oldVal);
}
}
return false;
}
}
Cheers,
Michal
To answer your second question: your annotation can't return a value. The class which processes your annotation can do something with your object. This is commonly used for logging for example.
I'm not sure if using an annotation for checking if an object is dirty makes sense except you want to throw an exception in this case or inform some kind of DirtyHandler.
For your first question: you could really spent some effort in finding this yourself. There are enough information here on stackoverflow and the web.
CustomAnnotation.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomAnnotation {
int studentAge() default 21;
String studentName();
String stuAddress();
String stuStream() default "CS";
}
How to use the field of Annotation in Java?
TestCustomAnnotation.java
package annotations;
import java.lang.reflect.Method;
public class TestCustomAnnotation {
public static void main(String[] args) {
new TestCustomAnnotation().testAnnotation();
}
#CustomAnnotation(
studentName="Rajesh",
stuAddress="Mathura, India"
)
public void testAnnotation() {
try {
Class<? extends TestCustomAnnotation> cls = this.getClass();
Method method = cls.getMethod("testAnnotation");
CustomAnnotation myAnno = method.getAnnotation(CustomAnnotation.class);
System.out.println("Name: "+myAnno.studentName());
System.out.println("Address: "+myAnno.stuAddress());
System.out.println("Age: "+myAnno.studentAge());
System.out.println("Stream: "+myAnno.stuStream());
} catch (NoSuchMethodException e) {
}
}
}
Output:
Name: Rajesh
Address: Mathura, India
Age: 21
Stream: CS
Reference

How to use an array constant in an annotation

I would like to use constants for annotation values.
interface Client {
#Retention(RUNTIME)
#Target(METHOD)
#interface SomeAnnotation { String[] values(); }
interface Info {
String A = "a";
String B = "b";
String[] AB = new String[] { A, B };
}
#SomeAnnotation(values = { Info.A, Info.B })
void works();
#SomeAnnotation(values = Info.AB)
void doesNotWork();
}
The constants Info.A and Info.B can be used in the annotation but not the array Info.AB as it has to be an array initializer in this place. Annotation values are restricted to values that could be inlined into the byte code of a class. This is not possible for the array constant as it has to be constructed when Info is loaded. Is there a workaround for this problem?
No, there is no workaround.
Why not make the annotation values an enum, which are keys to the actual data values you want?
e.g.
enum InfoKeys
{
A("a"),
B("b"),
AB(new String[] { "a", "b" }),
InfoKeys(Object data) { this.data = data; }
private Object data;
}
#SomeAnnotation (values = InfoKeys.AB)
This could be improved for type safety, but you get the idea.
It is because arrays' elements can be changed at runtime (Info.AB[0] = "c";) while the annotation values are constant after compile time.
With that in mind someone will inevitably be confused when they try to change an element of Info.AB and expect the annotation's value to change (it won't). And if the annotation value were allowed to change at runtime it would differ than the one used at compile time. Imagine the confusion then!
(Where confusion here means that there is a bug that someone may find and spend hours debugging.)
While there is no way to pass an array directly as an annotation parameter value, there is a way to effectively get similar behavior (depending on how you plan on using your annotations, this may not work for every use case).
Here's an example -- let's say we have a class InternetServer and it has a hostname property. We'd like to use regular Java Validation to ensure that no object has a "reserved" hostname. We can (somewhat elaborately) pass an array of reserved hostnames to the annotation that handles hostname validation.
caveat- with Java Validation, it would be more customary to use the "payload" to pass in this kind of data. I wanted this example to be a bit more generic so I used a custom interface class.
// InternetServer.java -- an example class that passes an array as an annotation value
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Pattern;
public class InternetServer {
// These are reserved names, we don't want anyone naming their InternetServer one of these
private static final String[] RESERVED_NAMES = {
"www", "wwws", "http", "https",
};
public class ReservedHostnames implements ReservedWords {
// We return a constant here but could do a DB lookup, some calculation, or whatever
// and decide what to return at run-time when the annotation is processed.
// Beware: if this method bombs, you're going to get nasty exceptions that will
// kill any threads that try to load any code with annotations that reference this.
#Override public String[] getReservedWords() { return RESERVED_NAMES; }
}
#Pattern(regexp = "[A-Za-z0-9]{3,}", message = "error.hostname.invalid")
#NotReservedWord(reserved=ReservedHostnames.class, message="error.hostname.reserved")
#Getter #Setter private String hostname;
}
// NotReservedWord.java -- the annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({FIELD, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy=ReservedWordValidator.class)
#Documented
public #interface NotReservedWord {
Class<? extends ReservedWords> reserved ();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String message() default "{err.reservedWord}";
}
// ReservedWords.java -- the interface referenced in the annotation class
public interface ReservedWords {
public String[] getReservedWords ();
}
// ReservedWordValidator.java -- implements the validation logic
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ReservedWordValidator implements ConstraintValidator<NotReservedWord, Object> {
private Class<? extends ReservedWords> reserved;
#Override
public void initialize(NotReservedWord constraintAnnotation) {
reserved = constraintAnnotation.reserved();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) return true;
final String[] words = getReservedWords();
for (String word : words) {
if (value.equals(word)) return false;
}
return true;
}
private Map<Class, String[]> cache = new ConcurrentHashMap<>();
private String[] getReservedWords() {
String[] words = cache.get(reserved);
if (words == null) {
try {
words = reserved.newInstance().getReservedWords();
} catch (Exception e) {
throw new IllegalStateException("Error instantiating ReservedWords class ("+reserved.getName()+"): "+e, e);
}
cache.put(reserved, words);
}
return words;
}
}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Handler {
enum MessageType { MESSAGE, OBJECT };
String value() default "";
MessageType type() default MessageType.MESSAGE;
}
As already was mentioned in previous posts, annotation vales are compile-time constants and there is no way to use an array value as a parameter.
I solved this problem a bit differently.
If you're owning the processing logic, take advantage of it.
For example, give an additional parameter to your annotation:
#Retention(RUNTIME)
#Target(METHOD)
#interface SomeAnnotation {
String[] values();
boolean defaultInit() default false;
}
Use this parameter:
#SomeAnnotation(defaultInit = true)
void willWork();
And this will be a marker to the AnnotationProcessor, which can do anything - initialize it with an array, use String[], or use Enums like Enum.values() and map them to String[].
Hope this will guide someone who has the similar situation in the right direction.

Categories