Java 8 introduces both Lambda Expressions and Type Annotations.
With type annotations, it is possible to define Java annotations like the following:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE_USE)
public #interface MyTypeAnnotation {
public String value();
}
One can then use this annotation on any type reference like e.g.:
Consumer<String> consumer = new #MyTypeAnnotation("Hello ") Consumer<String>() {
#Override
public void accept(String str) {
System.out.println(str);
}
};
Here is a complete example, that uses this annotation to print "Hello World":
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Java8Example {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE_USE)
public #interface MyTypeAnnotation {
public String value();
}
public static void main(String[] args) {
List<String> list = Arrays.asList("World!", "Type Annotations!");
testTypeAnnotation(list, new #MyTypeAnnotation("Hello ") Consumer<String>() {
#Override
public void accept(String str) {
System.out.println(str);
}
});
}
public static void testTypeAnnotation(List<String> list, Consumer<String> consumer){
MyTypeAnnotation annotation = null;
for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) {
annotation = t.getAnnotation(MyTypeAnnotation.class);
if (annotation != null) {
break;
}
}
for (String str : list) {
if (annotation != null) {
System.out.print(annotation.value());
}
consumer.accept(str);
}
}
}
The output will be:
Hello World!
Hello Type Annotations!
In Java 8 one can also replace the anonymous class in this example with a lambda expression:
public static void main(String[] args) {
List<String> list = Arrays.asList("World!", "Type Annotations!");
testTypeAnnotation(list, p -> System.out.println(p));
}
But since the compiler infers the Consumer type argument for the lambda expression, one is no longer able to annotate the created Consumer instance:
testTypeAnnotation(list, #MyTypeAnnotation("Hello ") (p -> System.out.println(p))); // Illegal!
One could cast the lambda expression into a Consumer and then annotate the type reference of the cast expression:
testTypeAnnotation(list,(#MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p))); // Legal!
But this will not produce the desired result, because the created Consumer class will not be annotated with the annotation of the cast expression. Output:
World!
Type Annotations!
Two questions:
Is there any way to annotate a lambda expression similar to annotating a corresponding anonymous class, so one gets the expected "Hello World" output in the example above?
In the example, where I did cast the lambda expression and annotated the casted type: Is there any way to receive this annotation instance at runtime, or is such an annotation always implicitly restricted to RetentionPolicy.SOURCE?
The examples have been tested with javac and the Eclipse compiler.
Update
I tried the suggestion from #assylias, to annotate the parameter instead, which produced an interesting result. Here is the updated test method:
public static void testTypeAnnotation(List<String> list, Consumer<String> consumer){
MyTypeAnnotation annotation = null;
for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) {
annotation = t.getAnnotation(MyTypeAnnotation.class);
if (annotation != null) {
break;
}
}
if (annotation == null) {
// search for annotated parameter instead
loop: for (Method method : consumer.getClass().getMethods()) {
for (AnnotatedType t : method.getAnnotatedParameterTypes()) {
annotation = t.getAnnotation(MyTypeAnnotation.class);
if (annotation != null) {
break loop;
}
}
}
}
for (String str : list) {
if (annotation != null) {
System.out.print(annotation.value());
}
consumer.accept(str);
}
}
Now, one can also produce the "Hello World" result, when annotating the parameter of an anonymous class:
public static void main(String[] args) {
List<String> list = Arrays.asList("World!", "Type Annotations!");
testTypeAnnotation(list, new Consumer<String>() {
#Override
public void accept(#MyTypeAnnotation("Hello ") String str) {
System.out.println(str);
}
});
}
But annotating the parameter does not work for lambda expressions:
public static void main(String[] args) {
List<String> list = Arrays.asList("World!", "Type Annotations!");
testTypeAnnotation(list, (#MyTypeAnnotation("Hello ") String str) -> System.out.println(str));
}
Interestingly, it is also not possible to receive the name of the parameter (when compiling with javac -parameter), when using a lambda expression. I'm not sure though, if this behavior is intended, if parameter annotations of lambdas have not yet been implemented, or if this should be considered a bug of the compiler.
After digging into the Java SE 8 Final Specification I'm able to answer my questions.
(1) In response to my first question
Is there any way to annotate a lambda expression similar to annotating
a corresponding anonymous class, so one gets the expected "Hello
World" output in the example above?
No.
When annotating the Class Instance Creation Expression (§15.9) of an anonymous type, then the annotation will be stored in the class file either for the extending interface or the extending class of the anonymous type.
For the following anonymous interface annotation
Consumer<String> c = new #MyTypeAnnotation("Hello ") Consumer<String>() {
#Override
public void accept(String str) {
System.out.println(str);
}
};
the type annotation can then be accessed at runtime by calling Class#getAnnotatedInterfaces():
MyTypeAnnotation a = c.getClass().getAnnotatedInterfaces()[0].getAnnotation(MyTypeAnnotation.class);
If creating an anonymous class with an empty body like this:
class MyClass implements Consumer<String>{
#Override
public void accept(String str) {
System.out.println(str);
}
}
Consumer<String> c = new #MyTypeAnnotation("Hello ") MyClass(){/*empty body!*/};
the type annotation can also be accessed at runtime by calling Class#getAnnotatedSuperclass():
MyTypeAnnotation a = c.getClass().getAnnotatedSuperclass().getAnnotation(MyTypeAnnotation.class);
This kind of type annotation is not possible for lambda expressions.
On a side note, this kind of annotation is also not possible for normal class instance creation expressions like this:
Consumer<String> c = new #MyTypeAnnotation("Hello ") MyClass();
In this case, the type annotation will be stored in the method_info structure of the method, where the expression occurred and not as an annotation of the type itself (or any of its super types).
This difference is important, because annotations stored in the method_info will not be accessible at runtime by the Java reflection API. When looking at the generated byte code with ASM, the difference looks like this:
Type Annotation on an anonymous interface instance creation:
#Java8Example$MyTypeAnnotation(value="Hello ") : CLASS_EXTENDS 0, null
// access flags 0x0
INNERCLASS Java8Example$1
Type Annotation on a normal class instance creation:
NEW Java8Example$MyClass
#Java8Example$MyTypeAnnotation(value="Hello ") : NEW, null
While in the first case, the annotation is associated with the inner class, in the second case, the annotation is associated with the instance creation expression inside the methods byte code.
(2) In response to the comment from #assylias
You can also try (#MyTypeAnnotation("Hello ") String s) ->
System.out.println(s) although I have not managed to access the
annotation value...
Yes, this is actually possible according to the Java 8 specification. But it is not currently possible to receive the type annotations of the formal parameters of lambda expressions through the Java reflection API, which is most likely related to this JDK bug: Type Annotations Cleanup. Also the Eclipse Compiler does not yet store the relevant Runtime[In]VisibleTypeAnnotations attribute in the class file - the corresponding bug is found here: Lambda parameter names and annotations don't make it to class files.
(3) In response to my second question
In the example, where I did cast the lambda expression and annotated
the casted type: Is there any way to receive this annotation instance
at runtime, or is such an annotation always implicitly restricted to
RetentionPolicy.SOURCE?
When annotating the type of a cast expression, this information also gets stored in the method_info structure of the class file. The same is true for other possible locations of type annotations inside the code of a method like e.g. if(c instanceof #MyTypeAnnotation Consumer). There is currently no public Java reflection API to access these code annotations. But since they are stored in the class file, it is at least potentially possible to access them at runtime - e.g. by reading the byte code of a class with an external library like ASM.
Actually, I managed to get my "Hello World" example working with a cast expression like
testTypeAnnotation(list,(#MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p)));
by parsing the calling methods byte code using ASM. But the code is very hacky and inefficient, and one should probably never do something like this in production code. Anyway, just for completeness, here is the complete working "Hello World" example:
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.TypeReference;
public class Java8Example {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE_USE)
public #interface MyTypeAnnotation {
public String value();
}
public static void main(String[] args) {
List<String> list = Arrays.asList("World!", "Type Annotations!");
testTypeAnnotation(list, new #MyTypeAnnotation("Hello ") Consumer<String>() {
#Override
public void accept(String str) {
System.out.println(str);
}
});
list = Arrays.asList("Type-Cast Annotations!");
testTypeAnnotation(list,(#MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p)));
}
public static void testTypeAnnotation(List<String> list, Consumer<String> consumer){
MyTypeAnnotation annotation = null;
for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) {
annotation = t.getAnnotation(MyTypeAnnotation.class);
if (annotation != null) {
break;
}
}
if (annotation == null) {
// search for annotated parameter instead
loop: for (Method method : consumer.getClass().getMethods()) {
for (AnnotatedType t : method.getAnnotatedParameterTypes()) {
annotation = t.getAnnotation(MyTypeAnnotation.class);
if (annotation != null) {
break loop;
}
}
}
}
if (annotation == null) {
annotation = findCastAnnotation();
}
for (String str : list) {
if (annotation != null) {
System.out.print(annotation.value());
}
consumer.accept(str);
}
}
private static MyTypeAnnotation findCastAnnotation() {
// foundException gets thrown, when the cast annotation is found or the search ends.
// The found annotation will then be stored at foundAnnotation[0]
final RuntimeException foundException = new RuntimeException();
MyTypeAnnotation[] foundAnnotation = new MyTypeAnnotation[1];
try {
// (1) find the calling method
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement previous = null;
for (int i = 0; i < stackTraceElements.length; i++) {
if (stackTraceElements[i].getMethodName().equals("testTypeAnnotation")) {
previous = stackTraceElements[i+1];
}
}
if (previous == null) {
// shouldn't happen
return null;
}
final String callingClassName = previous.getClassName();
final String callingMethodName = previous.getMethodName();
final int callingLineNumber = previous.getLineNumber();
// (2) read and visit the calling class
ClassReader cr = new ClassReader(callingClassName);
cr.accept(new ClassVisitor(Opcodes.ASM5) {
#Override
public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) {
if (name.equals(callingMethodName)) {
// (3) visit the calling method
return new MethodVisitor(Opcodes.ASM5) {
int lineNumber;
String type;
public void visitLineNumber(int line, Label start) {
this.lineNumber = line;
};
public void visitTypeInsn(int opcode, String type) {
if (opcode == Opcodes.CHECKCAST) {
this.type = type;
} else{
this.type = null;
}
};
public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
if (lineNumber == callingLineNumber) {
// (4) visit the annotation, if this is the calling line number AND the annotation is
// of type MyTypeAnnotation AND it was a cast expression to "java.util.function.Consumer"
if (desc.endsWith("Java8Example$MyTypeAnnotation;") && this.type != null && this.type.equals("java/util/function/Consumer")) {
TypeReference reference = new TypeReference(typeRef);
if (reference.getSort() == TypeReference.CAST) {
return new AnnotationVisitor(Opcodes.ASM5) {
public void visit(String name, final Object value) {
if (name.equals("value")) {
// Heureka! - we found the Cast Annotation
foundAnnotation[0] = new MyTypeAnnotation() {
#Override
public Class<? extends Annotation> annotationType() {
return MyTypeAnnotation.class;
}
#Override
public String value() {
return value.toString();
}
};
// stop search (Annotation found)
throw foundException;
}
};
};
}
}
} else if (lineNumber > callingLineNumber) {
// stop search (Annotation not found)
throw foundException;
}
return null;
};
};
}
return null;
}
}, 0);
} catch (Exception e) {
if (foundException == e) {
return foundAnnotation[0];
} else{
e.printStackTrace();
}
}
return null;
}
}
One possible work around that might be of use is to define empty interfaces that extend the interface that the lambda is going to implement and then cast to this empty interface just to use the annotation. Like so:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Consumer;
public class Main
{
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE_USE)
public #interface MyAnnotation {
public String value();
}
#MyAnnotation("Get this")
interface AnnotatedConsumer<T> extends Consumer<T>{};
public static void main( String[] args )
{
printMyAnnotationValue( (AnnotatedConsumer<?>)value->{} );
}
public static void printMyAnnotationValue( Consumer<?> consumer )
{
Class<?> clas = consumer.getClass();
MyAnnotation annotation = clas.getAnnotation( MyAnnotation.class );
for( Class<?> infClass : clas.getInterfaces() ){
annotation = infClass.getAnnotation( MyAnnotation.class );
System.out.println( "MyAnnotation value: " + annotation.value() );
}
}
}
The annotation is then available on the interfaces implemented by the class and is reusable if you want the same annotation elsewhere.
Related
Even though my question origins from annotation processing, my question is more Java annotation related.
I was writing some code until I realised I didn't know a good way to implement something.
The program uses annotation-processing, I'm trying to get the value of multiple JAX-RS annotations, let's take #PathParam and #QueryParam as example. Both annotations have the abstract method called value()
The following piece of code is an example of how I do not want to write it. I'd have to do this for each JAX-RS annotation.
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for(Element element : roundEnv.getElementsAnnotatedWith(PathParam.class)) {
PathParam parameter = element.getAnnotation(PathParam.class);
String value = parameter.value();
// Process data & more program code.
}
for(Element element : roundEnv.getElementsAnnotatedWith(QueryParam.class)) {
QueryParam parameter = element.getAnnotation(QueryParam.class);
String value = parameter.value();
// Process data & more program code.
}
// Etc... do the same for other JAX-RS annotations.
return true;
}
I know with abstract classes you can do the following:
abstract class Animal {
abstract String name();
}
class Dog extends Animal {
public String name() {
return "Dog";
}
}
class Cat extends Animal {
public String name() {
return "Cat";
}
}
Animal animal = new Cat();
System.out.println(animal.name()); // Prints 'Cat'
animal = new Dog();
System.out.println(animal.name()); // Prints 'Dog'
But I'm not sure how to accomplish a similar thing using annotation since there is no superclass which it can be cast to.
I'm imagining it should be something along the lines of this:
ArrayList<Class<? extends Annotation>> annotationsToCheck =
new ArrayList<>(Arrays.asList(PathParam.class, QueryParam.class));
for(Class<? extends Annotation> annotationToCheck : annotationsToCheck) {
for(Element element : roundEnv.getElementsAnnotatedWith(annotationToCheck)) {
// Somehow cast it to something so that the value() method can be accessed
// Process data & more program code.
}
}
I feel like I'm close but I just can't quite put my finger on it. Is there a good way to solve my problem?
In Java 9+, no casting is needed:
for (Element element : roundEnv.getElementsAnnotatedWithAny​(Set.of(PathParam.class,
QueryParam.class))) {
PathParam pathParam = element.getAnnotation(PathParam.class);
if (pathParam != null) {
String value = pathParam.value();
// Process #PathParam value
}
QueryParam queryParam = element.getAnnotation(QueryParam.class);
if (queryParam != null) {
String value = queryParam.value();
// Process #QueryParam value
}
}
Or, if you only expect one of them:
for (Element element : roundEnv.getElementsAnnotatedWithAny​(Set.of(PathParam.class,
QueryParam.class))) {
String value = null;
PathParam pathParam = null;
QueryParam queryParam = null;
if ((pathParam = element.getAnnotation(PathParam.class)) != null) {
value = pathParam.value();
} else if ((queryParam = element.getAnnotation(QueryParam.class)) != null) {
value = queryParam.value();
}
// Process value. Exactly one of pathParam and queryParam will be non-null
}
Would be grateful if you could help me with the following.
Suppose I have the following classes and interface:
public interface BaseType {
public void method();
}
#Component
#Scope(SCOPE_PROTOTYPE)
public class BaseTypeImpl implements BaseType {
private int num;
public BaseTypeImpl(int num) {
this.num = num;
}
#Override
public void method() {
System.out.println(num);
}
}
#Component
#Scope(SCOPE_PROTOTYPE)
public class ChildBaseTypeImpl extends BaseTypeImpl {
String mes;
public ChildBaseTypeImpl(int num, String mes) {
super(num);
this.mes = mes;
}
#Override
public void method() {
super.method();
System.out.println(mes);
}
}
#Component
#Scope(SCOPE_PROTOTYPE)
public class SecondaryTypeImpl implements BaseType {
private String str;
public SecondaryTypeImpl(String str) {
this.str = str;
}
#Override
public void method() {
System.out.println(str);
}
}
In result, I have 3 classes implementing 1 interface.
All the classes have non-default constructor with a different parameters.
Is there a way to make Spring lookup for a correct bean by BaseType interface based on the constructor parameters?
I want to do this:
ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml");
ctx.getBean(BaseType.class, 11); //Should return instance of BaseTypeImpl class object, since it has constructor taking int
or like this:
ctx.getBean(BaseType.class, 11, "hello!"); //Should return instance of ChildBaseTypeImpl
Trying to do this will result in exception:
Exception in thread "main"
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type 'springtest.BaseType' available: expected
single matching bean but found 3:
baseTypeImpl,childBaseTypeImpl,secondaryTypeImpl
It seems like there is no straightforward way to do this.
But maybe is it possible to get all classes, that are assignable from a BaseType, find appropriate constructors among them and then call getBean method with the *Impl class as a first parameter?
UPDATED
Thanks everyone for answering!
As pvpkiran mentioned, there is a way to get class of bean by bean name.
This opens the door for reflection, which solves the problem, here's the sample code:
public class App {
static ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml");
public static void main(String[] args) {
BaseType beanByInterface = getBeanByInterface(BaseType.class, "Hello!");
System.out.println(beanByInterface.getClass());
}
public static <T> T getBeanByInterface(Class<T> interf, Object... params) {
BeanDefinitionRegistry bdr = new SimpleBeanDefinitionRegistry();
ClassPathBeanDefinitionScanner s = new ClassPathBeanDefinitionScanner(bdr);
TypeFilter tf = new AssignableTypeFilter(interf);
s.addIncludeFilter(tf);
s.scan("springtest"); //Sample project package name
String[] beans = bdr.getBeanDefinitionNames();
for(String b : beans) {
Class<?> type = ctx.getType(b);
MAIN: for(Constructor cons : type.getConstructors()) {
if (cons.getParameterCount() == params.length) {
for (int i = 0; i < cons.getParameterCount(); i++) {
if (!params[i].getClass().equals(cons.getParameterTypes()[i])) { //Will fail comparing primitive and boxed types, just leaving like this for simplicity
continue MAIN;
}
}
return (T) ctx.getBean(type, params);
}
}
}
return null;
}
}
The code is buggy, but this is just to show the concept.
Having the beans classes I can get the right bean, that has the needed constructor.
This sample outputs "class springtest.SecondaryTypeImpl"
But I believe this should be the common problem that is covered in some utils.
I don't want to invent the bicycle, but still can't find the solution.
UPDATED 2
Seems like there is no such solution in existing libs, since it's not the best practice.
Anyway, here's the updated method, maybe someone will find it useful.
public static <T> T getBeanByInterface(Class<T> interf, Object... params) {
String[] beans = ctx.getBeanNamesForType(interf);
for(String beanName : ctx.getBeanNamesForType(interf)) {
Class<?> type = ctx.getType(beanName);
Class<?>[] paramTypes = new Class[params.length];
//Getting params types
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i].getClass();
}
if (ConstructorUtils.getMatchingAccessibleConstructor(type, paramTypes) != null) {
return (T) ctx.getBean(type, params);
}
}
return null;
}
BeanFactory has following getBean() declaration
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException
As you can see here, there is no declaration for class type, bean name and arguments(which is what you are looking for).
But you can get the bean using bean name and arguments.
ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml");
ChildBaseTypeImpl childBaseTypeImpl = (ChildBaseTypeImpl) ctx.getBean("childBaseTypeImpl", 11, "hello");
BaseTypeImpl basetypeImpl = (BaseTypeImpl) ctx.getBean("baseTypeImpl", 11, );
Well, you can't use constructor arguments to differentiate your beans, as #alexlys noted. But you could do something different with the same result. Both approaches assumes that you could define what bean you need not in runtime but when you write the code.
You could create beans with unique names and then get desired bean by
name, not by interface
You could add some marker interfaces like
interface SecondaryType extends BaseType {//empty}. Each bean will implement only one specific interface so you could instaniate beans using such iterfaces.
I want to explain my issue with a simple example:
Foo:
#SomeXMLAnnotations
public class Foo {
// Bar is just a random class with its own XML annotations
#XmlElement(required = true)
Bar someBarObj;
boolean chosen = true;
boolean required = true;
public Foo(){
chosen = false;
}
public Foo(Bar someBarObj){
this.someBarObj = someBarObj;
}
}
MyClass:
#SomeXMLAnnotations
public class MyClass {
#XmlElement(required = false)
Foo anyFooObj;
#XmlElement(required = true)
Foo anyFooObjRequired;
public MyClass (){ }
public MyClass (Foo anyFooObj, Foo anyFooObjRequired){
this.anyFooObj = anyFooObj;
if(anyFooObj == null)
this.anyFooObj = new Foo();
/*
* This is the reason why i can't let 'anyFooObj' be 'null'.
* So 'anyFooObj' MUST be initialized somehow.
* It's needed for some internal logic, not JAXB.
*/
anyFooObj.required = false;
this.anyFooObjRequired = anyFooObjRequired;
}
}
Example Objects:
Foo fooRequired = new Foo(new Bar());
MyClass myObj = new MyClass(null, fooRequired);
When i try to marshal myObj now, it throws an exception like this:
org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException;
cvc-complex-type.2.4.b: The content of element 'n0:anyFooObj ' is not complete.
One of '{"AnyNamespace":someBarObj}' is expected.
This happens because anyFooObj is initialized but it's required, member
someBarObj isn't.
Possible Solution:
I know i could add this method to MyClass:
void beforeMarshal(Marshaller m){
if(! anyFooObj.chosen)
anyFooObj= null;
}
}
But I have a lot of classes and those classes have a lot of not required fields.
So this solution would take ages and doesn't look like a proper solution as well.
My Question:
Is there a way to tell JAXB that it should treat empty objects like they were null? Or that it should ignore an element when it's not properly set. Something like this for example:
#XmlElement(required = false, ingnoreWhenNotMarshallable = true)
Foo anyFooObj;
NOTE:
I'm NOT the developer of the code. I just have to add JAXB to the project and make everything compatible with a given XSD file. I'm NOT allowed to change the relation between classes.
I think you're trying to make the JAXB marshaller do something it's really not designed to do, so I'd say you're into hack territory here. I'd recommend pushing back on the requirements to try and avoid having this problem in the first place.
That said, if you have to do it then given your requirement to avoid writing code for each class/field, I think you'll want to use reflection for this - I've included an example below that reflectively inspects the values of all fields.
Useful extensions would be:
Have it consider getter methods too
Make the null-setting behaviour opt-in by requiring the field has an additional annotation - you could name it #JAXBNullIfEmpty
Example.java:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringWriter;
import java.lang.reflect.Field;
public class Example
{
public abstract static class JAXBAutoNullifierForEmptyOptionalFields
{
void beforeMarshal(Marshaller x)
{
try
{
for (Field field : this.getClass().getFields())
{
final XmlElement el = field.getAnnotation(XmlElement.class);
// If this is an optional field, it has a value & it has no fields populated then we should replace it with null
if (!el.required())
{
if (JAXBAutoNullifierForEmptyOptionalFields.class.isAssignableFrom(field.getType()))
{
final JAXBAutoNullifierForEmptyOptionalFields val = (JAXBAutoNullifierForEmptyOptionalFields) field.get(
this);
if (val != null && !val.hasAnyElementFieldsPopulated())
field.set(this, null); // No fields populated, replace with null
}
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has all required fields: " + this, e);
}
}
boolean hasAnyElementFieldsPopulated()
{
for (Field field : this.getClass().getFields())
{
try
{
if (field.isAnnotationPresent(XmlElement.class))
{
// Retrieve value
final Object val = field.get(this);
// If the value is non-null then at least one field has been populated
if (val != null)
{
return true;
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has any populated JAXB fields: " + this, e);
}
}
// There were no fields with a non-null value
return false;
}
}
#XmlRootElement
public static class MyJAXBType extends JAXBAutoNullifierForEmptyOptionalFields
{
#XmlElement
public String someField;
#XmlElement
public MyJAXBType someOtherField;
public MyJAXBType()
{
}
public MyJAXBType(final String someField, MyJAXBType someOtherField)
{
this.someField = someField;
this.someOtherField = someOtherField;
}
}
public static void main(String[] args) throws Exception
{
final Marshaller marshaller = JAXBContext.newInstance(MyJAXBType.class).createMarshaller();
MyJAXBType innerValue = new MyJAXBType(); // Unpopulated inner value
MyJAXBType value = new MyJAXBType("some text value", innerValue);
final StringWriter sw = new StringWriter();
marshaller.marshal(value, sw); // Omits "someOtherField"
System.out.println(sw.toString());
}
}
How to read annotation which is declared over an object.
For e.g
Annotation :
AuthorInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AuthorInfo {
String author() default "Dushyant Kumar";
String login() default "dushyantashu";
}
What I am trying to do :
Book.java
#Data
public class Book {
private int serialNo;
private String review;
}
Main.java
public class Main {
#AuthorInfo (
author = "Barry Allen",
login = "theflash"
)
private static Book book = new Book();
public static void main(String[] args) {
showAnnotation(book);
}
private static void showAnnotation(Object object) {
// How to get values of annotation declared over this object.
}
}
My usecase is to generate this generic showAnnotation() method, that's why param is Object. How to achieve this? From what I explored, I only got ways to read annotation if it's declared over a class, or declared over a member of a class. Isn't there a way where given an object, if some annotation is present over it can be read?
Thanks
You can give a try with generics and reflection. Assume the Book class is annotated with AuthorInfo like below:
#AuthorInfo(author = "Ram", login = "ram")
public class Book {
}
Suppose if you want to know whether AuthorInfo is present in the object of Book, you can do like below. This is straight forward solution to know whether specific annotation is present in an object.
public class TestAnnotation {
public static void main(String[] args) {
Book book = new Book();
showAnnotation(book);
}
private static <T> void showAnnotation(T t) {
Class<? extends Object> aClass = t.getClass();
if (aClass.isAnnotationPresent(AuthorInfo.class)) {
System.out.println("AuthorInfo annotation present");
AuthorInfo authorInfo = aClass.getAnnotation(AuthorInfo.class);
System.out.println(authorInfo.author());
System.out.println(authorInfo.login());
}
}
}
Suppose, if you want to know all annotations on that object, something like below helps:
private static <T> void showAnnotation(T t) {
Class<? extends Object> aClass = t.getClass();
for (Annotation annotation : aClass.getAnnotations()) {
System.out.println(annotation.toString());
}
}
You can retrieve the object class and then explore it. Via Reflection you could get its fields and methods also, and check if any has annotations on it.
Annotations can be read using Reflection API. Like
Class<Main> clazz = Main.class;
Method[] methods = clazz.getDeclaredMethods();
Field[] fields = clazz.getDeclaredFields();
for (Method method : methods) {
Annotation[] annotation = method.getDeclaredAnnotations();
}
for (Field field : fields) {
//This will get #AuthorInfo annotation on book
Annotation[] annotation = field.getDeclaredAnnotations();
//This will get #Data annotation on Book class
Annotation[] annotationsOnFieldClass = field.getClass().getDeclaredAnnotations();
}
clazz.getDeclaredAnnotations();
Is is possible to change field annotation values at runtime?
I can access the values, but can't find a way to change them.
Access is possible with:
Article.class.declaredFields.find {it.name="annotatedField"}.declaredAnnotations
I think it would be best to keep a reference to an Annotation object in addition to your Field (or Object), and update the Annotation reference as you change its values. This way, when the implementation of annotations in Class.java changes, your code is less likely to break.
The answer linked in the question comments is useful for dealing with annotations containing a single element, but if you have multiple elements that you need to set, here is a more general solution that makes use of a proxy:
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
Foo foo = new Foo();
Field field = foo.getClass().getDeclaredFields()[0];
Anno anno = field.getAnnotation(Anno.class);
System.out.println(String.format("Old properties: %s, %s, %s", anno.value(), anno.bar(), anno.barr()));
Anno anno2 = (Anno) setAttrValue(anno, Anno.class, "value", "new");
System.out.println(String.format("New properties: %s, %s, %s", anno2.value(), anno2.bar(), anno2.barr()));
Anno anno3 = (Anno) setAttrValue(anno2, Anno.class, "bar", "new bar");
System.out.println(String.format("New properties: %s, %s, %s", anno3.value(), anno3.bar(), anno3.barr()));
}
public static Annotation setAttrValue(Annotation anno, Class<? extends Annotation> type, String attrName, Object newValue) throws Exception {
InvocationHandler handler = new AnnotationInvocationHandler(anno, attrName, newValue);
Annotation proxy = (Annotation) Proxy.newProxyInstance(anno.getClass().getClassLoader(), new Class[]{type}, handler);
return proxy;
}
}
class AnnotationInvocationHandler implements InvocationHandler {
private Annotation orig;
private String attrName;
private Object newValue;
public AnnotationInvocationHandler(Annotation orig, String attrName, Object newValue) throws Exception {
this.orig = orig;
this.attrName = attrName;
this.newValue = newValue;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// "override" the return value for the property we want
if (method.getName().equals(attrName) && args == null)
return newValue;
// keep other properties and methods we want like equals() and hashCode()
else {
Class<?>[] paramTypes = toClassArray(args);
return orig.getClass().getMethod(method.getName(), paramTypes).invoke(orig, args);
}
}
private static Class<?>[] toClassArray(Object[] arr) {
if (arr == null)
return null;
Class<?>[] classArr = new Class[arr.length];
for (int i=0; i<arr.length; i++)
classArr[i] = arr[i].getClass();
return classArr;
}
}
class Foo {
#Anno(value="old", bar="bar", barr="barr")
public Object field1;
}
#Retention(RetentionPolicy.RUNTIME)
#interface Anno {
String value();
String bar();
String barr();
}
Program output:
Old properties: old, bar, barr
New properties: new, bar, barr
New properties: new, new bar, barr