I'm trying to create annotations from inner string which contains other annotations.
This is SimpleAnnotation that should be processed:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.SOURCE)
public #interface SimpleAnnotation {
String[] value() default {};
}
This is annotated class
#SimpleAnnotation({
"#com.demo.annotations.Entity(name = \"simple_name\")",
"#com.demo.annotations.CustomAnnotation"
})
public class Simple {
}
The compilation result of annotated class should be
#com.demo.annotations.Entity(name = "simple_name")
#com.demo.annotations.CustomAnnotation
public class Simple {
}
I've tried to use custom annotation processor
that processes class declaration. It gets class modifiers with annotations and analyzes derived annotation as tree
public class SimpleAnnotationProcessor extends AbstractProcessor {
private Messager messager;
private Trees trees;
private ChangeTranslator visitor;
#Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(SimpleAnnotation.class.getCanonicalName());
}
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
............
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(SimpleAnnotation.class);
for (Element element : elementsAnnotatedWith) {
Name simpleName = element.getSimpleName();
System.out.println(simpleName);
messager.printMessage(Diagnostic.Kind.NOTE, "found with annotation " + simpleName);
JCTree tree = (JCTree) trees.getTree(element);
visitor.setElement(element);
tree.accept(visitor);
}
return true;
}
public class ChangeTranslator extends TreeTranslator {
private JavacProcessingEnvironment javacProcessingEnvironment;
private TreeMaker treeMaker;
private Messager messager;
public ChangeTranslator(JavacProcessingEnvironment javacProcessingEnvironment, TreeMaker treeMaker, Messager messager) {
this.javacProcessingEnvironment = javacProcessingEnvironment;
this.treeMaker = treeMaker;
this.messager = messager;
}
#Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
super.visitClassDef(jcClassDecl);
if (isNeedProcessing(jcClassDecl)) {
JCTree.JCModifiers modifiers = jcClassDecl.getModifiers();
List<JCTree.JCAnnotation> annotations = modifiers.getAnnotations();
List<JCTree.JCAnnotation> jcAnnotations = List.nil();
for (JCTree.JCAnnotation a : annotations) {
if (a.getAnnotationType().toString().contains(SimpleAnnotation.class.getSimpleName())) {
List<JCTree.JCExpression> arguments = a.getArguments();
for (JCTree.JCExpression arg : arguments) {
JCTree.JCNewArray expressions = (JCTree.JCNewArray) ((JCTree.JCAssign) arg).getExpression();
List<JCTree.JCExpression> elems = expressions.elems;
for (JCTree.JCExpression expression : elems) {
// parse annotation from string
String value = (String) ((JCTree.JCLiteral) expression).getValue();
// e.g com.demo.annotations.Entity
String substringName = value.trim().substring(1, 28);
Class<? extends Class> aClass = null;
try {
aClass = Class.forName(substringName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 1 - attribute to create annotation from
Attribute attribute = new Attribute.Compound(aClass, null);
// 2 - place where annotation should be created
treeMaker.Annotation(attribute);
}
}
}
}
modifiers.annotations = jcAnnotations;
System.out.println(result);
}
}
private boolean isNeedProcessing(JCTree.JCClassDecl jcClassDecl) {
return jcClassDecl.getModifiers().toString().contains("#SimpleAnnotation");
}
}
}
The issue is to get information from Class type to create com.sun.tools.javac.code.Type.ClassType which is used to create JCAnnotation.
Any help is appreciated.
public class SimpleAnnotationProcessor extends AbstractProcessor {
...
#Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
...
ListBuffer<JCTree.JCExpression> params = new ListBuffer<JCTree.JCExpression>();
params.append(treeMaker.Assign(treeMaker.Ident(names.fromString("name")), treeMaker.Literal("simple_name")));
JCTree.JCAnnotation entity = treeMaker.Annotation(select("com.demo.annotations.Entity"), params.toList());
JCTree.JCAnnotation customAnnotation = treeMaker.Annotation(select("com.demo.annotations.CustomAnnotation"), List.nil());
// then append annotation to modifiers of you want
// NOTE: List<A>.append() method will return a new List in javac
...
}
JCTree.JCExpression select(String path) {
JCTree.JCExpression expression = null;
int i = 0;
for (String split : path.split("\\.")) {
if (i == 0)
expression = treeMaker.Ident(names.fromString(split));
else {
expression = treeMaker.Select(expression, names.fromString(split));
}
i++;
}
return expression;
}
}
Hope it helps those who have the same problem
Related
Imagine there is a class:
#Something(someProperty = "some value")
public class Foobar {
//...
}
Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".
Is this possible? If so, how?
Warning: Not tested on OSX - see comment from #Marcel
Tested on OSX. Works fine.
Since I also had the need to change annotation values at runtime, I revisited this question.
Here is a modified version of #assylias approach (many thanks for the inspiration).
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Usage example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ClassAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FieldAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MethodAnnotation {
String value() default "";
}
#ClassAnnotation("class test")
public static class TestClass{
#FieldAnnotation("field test")
public Object field;
#MethodAnnotation("method test")
public void method(){
}
}
public static void main(String[] args) throws Exception {
final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
System.out.println("old ClassAnnotation = " + classAnnotation.value());
changeAnnotationValue(classAnnotation, "value", "another class annotation value");
System.out.println("modified ClassAnnotation = " + classAnnotation.value());
Field field = TestClass.class.getField("field");
final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
Method method = TestClass.class.getMethod("method");
final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
System.out.println("old MethodAnnotation = " + methodAnnotation.value());
changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}
The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.
Tested with Java 8.
This code does more or less what you ask for - it is a simple proof of concept:
a proper implementation needs to also deal with the declaredAnnotations
if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
I have no idea if there are side effects...
Output:
oldAnnotation = some value
modifiedAnnotation = another value
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Field field = Class.class.getDeclaredField("annotations");
field.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation #JsonIgnoreProperties(ignoreUnknown = true) from true to false.
final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());
final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
#Override public Class<? extends Annotation> annotationType() {
return matchedAnnotation.get(0).annotationType();
} #Override public String[] value() {
return new String[0];
} #Override public boolean ignoreUnknown() {
return false;
} #Override public boolean allowGetters() {
return false;
} #Override public boolean allowSetters() {
return false;
}
};
final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
SPRING can do this job very easily , might be useful for spring developer .
follow these steps :-
First Solution :-
1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with #Value annotation from DB or property file :-
#Value("${config.somePropertyValue}")
private String somePropertyValue;
#Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{#somePropertyValue}")
public class Foobar {
//...
}
Second solution :-
1) create getter setter in bean :-
#Component
public class config{
#Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
Try this solution for Java 8
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Method method = Class.class.getDeclaredMethod("annotationData", null);
method.setAccessible(true);
Object annotationData = method.invoke(getClass(), null);
Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
declaredAnnotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,
try {
Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
annotationDataField.setAccessible(true);
Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?
I'm developing a library with CDI. This works fine. But when I try to use this library as an included Jar in a project, CDI complains that it can't resolve dependencies on managed Beans defined in the parent Project.
MyProject
- MyLib.jar
- MyManagedBean
So MyLib needs to inject a bean annotated with a stereotype #foo. This stereotype is applied on MyManagedBean.
#foo
public class MyManagedBean {
//...
}
I added beans.xml to my parent project too. But it's like there is 2 different CDI Container and MyLib can't access MyProject.
Any suggestion?
I was able to access the parent class file by using the CDI extension. Documentation and reflection.
This is the code i use :
public class ConfigExtension implements Extension {
void afterBeanDiscovery(#Observes AfterBeanDiscovery abd, BeanManager bm) {
Reflections reflections = new Reflections("");
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Tester.class);
types.remove(info.scandi.fusion.cucumber.Tester.class);
types.addAll(reflections.getTypesAnnotatedWith(Driver.class));
types.addAll(reflections.getTypesAnnotatedWith(Worker.class));
types.forEach(type -> {
abd.addBean(new FusionBean((Class<T>) type, bm));
});
}
}
public class FusionBean<T> implements Bean<T>, Serializable, PassivationCapable {
/**
*
*/
private static final long serialVersionUID = 1L;
private InjectionTarget<T> it;
private Class<T> bean;
private BeanManager bm;
public FusionBean(Class<T> workerClass, BeanManager bm) {
this.bm = bm;
bean = workerClass;
AnnotatedType<T> at = bm.createAnnotatedType(bean);
// use this to instantiate the class and inject dependencies
it = bm.createInjectionTarget(at);
}
#Override
public T create(CreationalContext<T> creationalContext) {
T instance = it.produce(creationalContext);
it.inject(instance, creationalContext);
it.postConstruct(instance);
return instance;
}
#Override
public void destroy(T instance, CreationalContext<T> creationalContext) {
it.preDestroy(instance);
it.dispose(instance);
creationalContext.release();
}
#Override
public Set<Type> getTypes() {
Set<Type> types = new HashSet<>();
//Use Apache Common Lang to get all Interfaces and Superclasses
types.addAll(ClassUtils.getAllInterfaces(bean));
types.addAll(ClassUtils.getAllSuperclasses(bean));
return types;
}
#Override
public Set<Annotation> getQualifiers() {
Set<Annotation> annotations = new HashSet<>();
for (int i = 0; i < bean.getAnnotations().length; i++) {
Class<? extends Annotation> possibleQualifier = bean.getAnnotations()[i].annotationType();
if (bm.isQualifier(possibleQualifier)) {
annotations.add(bean.getAnnotations()[i]);
}
}
return annotations;
}
#Override
public Class<? extends Annotation> getScope() {
for (int i = 0; i < bean.getAnnotations().length; i++) {
Class<? extends Annotation> possibleScope = bean.getAnnotations()[i].annotationType();
if (bm.isStereotype(possibleScope)) {
for (Annotation annotation : possibleScope.getAnnotations()) {
if (bm.isScope(annotation.annotationType())) {
return annotation.annotationType();
}
}
}
}
return null;
}
#Override
public String getName() {
return bean.getName();
}
#Override
public Set<Class<? extends Annotation>> getStereotypes() {
Set<Class<? extends Annotation>> stereotypes = new HashSet<>();
for (int i = 0; i < bean.getAnnotations().length; i++) {
Class<? extends Annotation> possibleStereotype = bean.getAnnotations()[i].annotationType();
if (bm.isStereotype(possibleStereotype)) {
stereotypes.add(possibleStereotype);
}
}
return stereotypes;
}
#Override
public boolean isAlternative() {
for (int i = 0; i < bean.getAnnotations().length; i++) {
if (bean.getAnnotations()[i].equals(Alternative.class)) {
return true;
}
}
return false;
}
#Override
public Class<?> getBeanClass() {
return bean.getClass();
}
#Override
public Set<InjectionPoint> getInjectionPoints() {
return it.getInjectionPoints();
}
#Override
public boolean isNullable() {
return false;
}
#Override
public String getId() {
return UUID.randomUUID().toString();
}
}
The trick is CDI Extension has access to Parent Classpath. Here i use the Reflection api to get Tester, Driver et Worker classes that the user using my framework could specified.
Imagine there is a class:
#Something(someProperty = "some value")
public class Foobar {
//...
}
Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".
Is this possible? If so, how?
Warning: Not tested on OSX - see comment from #Marcel
Tested on OSX. Works fine.
Since I also had the need to change annotation values at runtime, I revisited this question.
Here is a modified version of #assylias approach (many thanks for the inspiration).
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Usage example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ClassAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FieldAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MethodAnnotation {
String value() default "";
}
#ClassAnnotation("class test")
public static class TestClass{
#FieldAnnotation("field test")
public Object field;
#MethodAnnotation("method test")
public void method(){
}
}
public static void main(String[] args) throws Exception {
final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
System.out.println("old ClassAnnotation = " + classAnnotation.value());
changeAnnotationValue(classAnnotation, "value", "another class annotation value");
System.out.println("modified ClassAnnotation = " + classAnnotation.value());
Field field = TestClass.class.getField("field");
final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
Method method = TestClass.class.getMethod("method");
final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
System.out.println("old MethodAnnotation = " + methodAnnotation.value());
changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}
The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.
Tested with Java 8.
This code does more or less what you ask for - it is a simple proof of concept:
a proper implementation needs to also deal with the declaredAnnotations
if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
I have no idea if there are side effects...
Output:
oldAnnotation = some value
modifiedAnnotation = another value
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Field field = Class.class.getDeclaredField("annotations");
field.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation #JsonIgnoreProperties(ignoreUnknown = true) from true to false.
final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());
final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
#Override public Class<? extends Annotation> annotationType() {
return matchedAnnotation.get(0).annotationType();
} #Override public String[] value() {
return new String[0];
} #Override public boolean ignoreUnknown() {
return false;
} #Override public boolean allowGetters() {
return false;
} #Override public boolean allowSetters() {
return false;
}
};
final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
SPRING can do this job very easily , might be useful for spring developer .
follow these steps :-
First Solution :-
1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with #Value annotation from DB or property file :-
#Value("${config.somePropertyValue}")
private String somePropertyValue;
#Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{#somePropertyValue}")
public class Foobar {
//...
}
Second solution :-
1) create getter setter in bean :-
#Component
public class config{
#Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
Try this solution for Java 8
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Method method = Class.class.getDeclaredMethod("annotationData", null);
method.setAccessible(true);
Object annotationData = method.invoke(getClass(), null);
Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
declaredAnnotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,
try {
Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
annotationDataField.setAccessible(true);
Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?
I am developing a small code generator using JDK 6's Annotation Processing API and am stuck trying to get the actual generic type of a field in the class. To be clearer, let's say I have a class like this:
#MyAnnotation
public class User {
private String id;
private String username;
private String password;
private Set<Role> roles = new HashSet<Role>();
private UserProfile profile;
}
and here is my annotation processor class:
#SupportedAnnotationTypes({ "xxx.MyAnnotation" })
#SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MongoDocumentAnnotationProcessor extends AbstractProcessor {
private Types typeUtils = null;
private Elements elementUtils = null;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
debug("Running " + getClass().getSimpleName());
if (roundEnv.processingOver() || annotations.size() == 0) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() == ElementKind.CLASS && isAnnotatedWithMongoDocument(element)) {
for (VariableElement variableElement : ElementFilter.fieldsIn(element.getEnclosedElements())) {
String fieldName = variableElement.getSimpleName().toString();
Element innerElement = typeUtils.asElement(variableElement.asType());
String fieldClass = "";
if (innerElement == null) { // Primitive type
PrimitiveType primitiveType = (PrimitiveType) variableElement.asType();
fieldClass = typeUtils.boxedClass(primitiveType).getQualifiedName().toString();
} else {
if (innerElement instanceof TypeElement) {
TypeElement typeElement = (TypeElement) innerElement;
fieldClass = typeElement.getQualifiedName().toString();
TypeElement collectionType = elementUtils.getTypeElement("java.util.Collection");
if (typeUtils.isAssignable(typeElement.asType(), collectionType.asType())) {
TypeVariable typeMirror = (TypeVariable)((DeclaredType)typeElement.asType()).getTypeArguments().get(0);
TypeParameterElement typeParameterElement = (TypeParameterElement) typeUtils.asElement(typeMirror);
// I am stuck here. I don't know how to get the
// full qualified class name of the generic type of
// property 'roles' when the code processes the User
// class as above. What I want to retrieve is the
// 'my.package.Role' value
}
}
}
}
}
}
return false;
}
private boolean isAnnotated(Element element) {
List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
if (annotationMirrors == null || annotationMirrors.size() == 0) return false;
for (AnnotationMirror annotationMirror : annotationMirrors) {
String qualifiedName = ((TypeElement)annotationMirror.getAnnotationType().asElement()).getQualifiedName().toString();
if ("xxx.MyAnnotation".equals(qualifiedName)) return true;
}
return false;
}
}
Any hint would be really appreciated!
Copy-paste of my original answer:
This seems to be a common question so, for those arriving from Google: there is hope.
The Dagger DI project is licensed under the Apache 2.0 License and contains some utility methods for working with types in an annotation processor.
In particular, the Util class can be viewed in full on GitHub (Util.java) and defines a method public static String typeToString(TypeMirror type). It uses a TypeVisitor and some recursive calls to build up a string representation of a type. Here is a snippet for reference:
public static void typeToString(final TypeMirror type, final StringBuilder result, final char innerClassSeparator)
{
type.accept(new SimpleTypeVisitor6<Void, Void>()
{
#Override
public Void visitDeclared(DeclaredType declaredType, Void v)
{
TypeElement typeElement = (TypeElement) declaredType.asElement();
rawTypeToString(result, typeElement, innerClassSeparator);
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty())
{
result.append("<");
for (int i = 0; i < typeArguments.size(); i++)
{
if (i != 0)
{
result.append(", ");
}
// NOTE: Recursively resolve the types
typeToString(typeArguments.get(i), result, innerClassSeparator);
}
result.append(">");
}
return null;
}
#Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v) { ... }
#Override
public Void visitArray(ArrayType arrayType, Void v) { ... }
#Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v)
{
result.append(typeVariable.asElement().getSimpleName());
return null;
}
#Override
public Void visitError(ErrorType errorType, Void v) { ... }
#Override
protected Void defaultAction(TypeMirror typeMirror, Void v) { ... }
}, null);
}
I am busy with my own project which generates class extensions. The Dagger method works for complex situations, including generic inner classes. I have the following results:
My test class with field to extend:
public class AnnotationTest
{
...
public static class A
{
#MyAnnotation
private Set<B<Integer>> _bs;
}
public static class B<T>
{
private T _value;
}
}
Calling the Dagger method on the Element the processor provides for the _bs field:
accessor.type = DaggerUtils.typeToString(element.asType());
The generated source (custom, of course). Note the awesome nested generic types.
public java.util.Set<AnnotationTest.B<java.lang.Integer>> AnnotationTest.A.getBsGenerated()
{
return this._bs;
}
EDIT: adapting the concept to extract a TypeMirror of the first generic argument, null otherwise:
public static TypeMirror getGenericType(final TypeMirror type)
{
final TypeMirror[] result = { null };
type.accept(new SimpleTypeVisitor6<Void, Void>()
{
#Override
public Void visitDeclared(DeclaredType declaredType, Void v)
{
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty())
{
result[0] = typeArguments.get(0);
}
return null;
}
#Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v)
{
return null;
}
#Override
public Void visitArray(ArrayType arrayType, Void v)
{
return null;
}
#Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v)
{
return null;
}
#Override
public Void visitError(ErrorType errorType, Void v)
{
return null;
}
#Override
protected Void defaultAction(TypeMirror typeMirror, Void v)
{
throw new UnsupportedOperationException();
}
}, null);
return result[0];
}
Looks like there are a couple of problems. One, the isAssignable() isnt working as expected. Second, in the above code you are trying to get the generic parameters of the Set type (T), rather than the variable declaration (Role).
Nevertheless, the following code should demonstrate what you need:
#SupportedAnnotationTypes({ "xxx.MyAnnotation" })
#SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MongoDocumentAnnotationProcessor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver() || annotations.size() == 0) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() == ElementKind.CLASS && isAnnotatedWithMongoDocument(element)) {
System.out.println("Running " + getClass().getSimpleName());
for (VariableElement variableElement : ElementFilter.fieldsIn(element.getEnclosedElements())) {
if(variableElement.asType() instanceof DeclaredType){
DeclaredType declaredType = (DeclaredType) variableElement.asType();
for (TypeMirror typeMirror : declaredType.getTypeArguments()) {
System.out.println(typeMirror.toString());
}
}
}
}
}
return true; //processed
}
private boolean isAnnotatedWithMongoDocument(Element element) {
return element.getAnnotation(MyAnnotation.class) != null;
}
}
This code should output:
xxx.Role
All the other answers, while having lots of good points. Don't really show you the problem you have and it's solution.
The problem in your code is here
TypeElement collectionType = elementUtils.getTypeElement("java.util.Collection");
if (typeUtils.isAssignable(typeElement.asType(), collectionType.asType())) {
...
Your type is not extending java.util.Collection but rather java.util.Collection<*>. Let's rewrite the above block to reflect this:
WildcardType WILDCARD_TYPE_NULL = this.typeUtils.getWildcardType(null, null);
final TypeElement collectionTypeElement = this.elementUtils.getTypeElement(Collection.class.getName());
TypeMirror[] typex = {WILDCARD_TYPE_NULL};
DeclaredType collectionType=this.typeUtils.getDeclaredType(collectionTypeElement, typex);
if (typeUtils.isAssignable(typeElement.asType(), collectionType)){
...
That should make it work
Using Java 11 you can cast your TypeMirror to Type.ClassType
This code
// classToIntrospect is a TypeMirror of java.util.List<it.firegloves.sragen.Dog>
(ClassType)classToIntrospect
will be evaluated in
I have a lot of classes UNO,HAV,MAS,KOS
I want to create a factory pattern.
validator.load("UNO").validate();
I need dynamically load classes into validator class and return an instance.
(dynamically set name of the class and return an instance)
My problem is: how can I return the instance of a class, if I have incompatible types?
I don't know what to write in return type of method.
The main problem in the Validator CLASS.
public SegmentAbstract load(String str) {
AND
return SegmentAbsClass.forName(identify);
Main class
try{
validator.load("UNO").validate();
}catch(Exception e){
System.out.print("No class ");
}
Abstract Class (SegmentAbstract)
public abstract class SegmentAbstract {
public abstract Boolean validate();
}
Class UNO
public class UNA extends SegmentAbstract{
public Boolean validate() {
System.out.print("UNO!!");
return true;
}
}
Class Validator
public class Validator {
public SegmentAbstract load(String str) {
String identify = str.substring(0, 3);
try {
return SegmentAbsClass.forName(identify);
}
catch(Exception e) {
return this;
}
}
}
Try this :
public interface Validator {
boolean validate(Object obj);
}
public final class ValidatorFactory {
private ValidatorFactory(){}
public static Validator load(String type){
try {
Class<?> clazz = Class.forName(type);
if (Arrays.asList(clazz.getInterfaces()).contains(Validator.class)){
return (Validator) clazz.newInstance();
}
throw new IllegalArgumentException("Provided class doesn't implement Validator interface");
} catch (Exception e) {
throw new IllegalArgumentException("Wrong class provided", e);
}
}
}
Maybe this will help???
I will do something like that:
// ISegment.java
public interface ISegment {
Boolean validate();
}
// Uno.java
public class Uno implements ISegment {
public Boolean validate() {
System.out.print("UNO!!");
return true;
}
}
// SegmentFactory.java
public final class SegmentFactory {
public static enum Supported {
UNO("uno", Uno.class), /* ... */, HAV("hav", Hav.class);
private final Class<?> clazz;
private final String name;
private Supported(final String name, final Class<?> clazz) {
this.name = name;
this.clazz = clazz;
}
public Class<?> getClazz() {
return clazz;
}
public static Supported for(final String name) {
for (final Supported s : values()) {
if (s.name.equals(name) {
return s;
}
}
return null; // a default one
}
}
public static ISegment create(final Supported supp) {
if (supp == null) {
return null;
}
return supp.getClazz.newInstance();
}
private SegmentFactory() {
// avoid instantiation
}
}
usage:
final ISegment sa = SegmentFactory.create(SegmentFactory.Supported.for("uno"));
sa.validate();
Not tested!!
Take a look here. Briefly, the idea is to create a map in your factory class (Map<String,String>, key is identifier, value is fully qualified class name), and add supported classes during initialization. Then you use reflection to instantiate an object in your factory method. Also, you can avoid reflection by using Map<String, SegmentAbstract> instead of Map<String,String> and adding public abstract getNewSegment() to your SegmentAbstract class.