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
}
Related
I'm making a Java class where I can apply a filter (searchterms) on a List with DTO's. The filter looks like:
[{ field: "price", value: "10.0" }, { field: "name", value: "%phone%" }]
In my class I have the following method, to apply all the filters to the list:
public List<T> applyFilters(List<T> input, ArrayList<LinkedHashMap<String, String>> searchTerms) {
for (LinkedHashMap<String, String> searchTerm : searchTerms) {
input = input.stream()
.filter(row -> {
try {
return applySingleFilter(row, searchTerm);
} catch (Exception e) {
throw new CustomGraphQLException(400, "The filter field is not a valid field in this type");
}
})
.collect(Collectors.toList());
}
return input;
}
But the applySingleFilter has different implementations based on the type of a field. Like for Strings I create a Regex:
private boolean applySingleStringFilter (T category, LinkedHashMap<String, String> searchTerm) throws Exception {
String patternString = createCompareRegex(searchTerm.get("value"));
String propertyValue = (String) PropertyUtils.getProperty(category, searchTerm.get("field"));
return propertyValue.matches(patternString);
}
But for like a Float I want another comparison, I don't want to apply the Regex to a float. What is the best way to make sure the correct method is called based on the type of the field?
Well, you'd first need to know the type of the field and once you have that information you could maintain a repository of filters that apply to the type (some casting might be required though).
We had a similar system in place which in essence looked like this:
interface Filter<T> {
Class<T> getHandledType();
boolean apply(T element);
}
class DoubleFilter implements Filter<Double> {
public Class<Double> getHandledType() { return Double.class; }
boolean apply(Double element) {
//filter here
}
}
The repository was basically a Map<Class<?>, Filter<?>> and using it was like:
Object fieldValue = //get the field value;
Class<?> fieldType = fieldValue.getClass(); //ofc, check for null first
Filter<?> filter = repo.get(fieldType);
if( filter != null ) {
//nasty cast to a raw type to tell the compiler to allow the call
((Filter)filter).apply(fieldValue);
}
I'm creating an AnnotationProcessor and I need to check if the annotated type is a subtype of an specified class.
The annotation:
public #interface Component {
Class<?> supertype();
}
Example: (correct)
#Component(supertype = MyInterface.class)
public class MyClass implements MyInterface {
// ...
}
Example: (incorrect, this must not compile because MyClass isn't a subtype of String)
#Component(supertype = String.class)
public class MyClass implements MyInterface {
}
I know that I cannot get the annotated Class because it isn't compiled yet.
This version does not rely on exceptions, it uses Element.getAnnotationMirrors which is probably there for this type of use. It's a lot more verbose though:
#Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(Component.class)) {
// TODO: Ensure element is a class before continuing
TypeMirror superType = null;
boolean found = false;
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
TypeElement annotationElement = (TypeElement) mirror.getAnnotationType().asElement();
if (annotationElement.getQualifiedName().contentEquals(Component.class.getName())) {
found = true;
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> kv :
mirror.getElementValues().entrySet()) {
ExecutableElement member = kv.getKey();
if (member.getKind() == ElementKind.METHOD &&
member.getSimpleName().contentEquals("supertype")) {
superType = (TypeMirror) kv.getValue().getValue();
break;
}
}
}
if (found) {
break;
}
}
// TODO: superType null check
TypeElement typeElement = (TypeElement) element;
Set<TypeMirror> validTypes = Sets.newHashSet(typeElement.getInterfaces());
validTypes.add(typeElement.getSuperclass());
if (!validTypes.contains(superType)) {
// TODO: throw something better
throw new IllegalArgumentException(typeElement.toString() + " does not implement or inherit from "
+ superType.toString() + " declared in #Component annotation");
}
}
return true;
}
You don't mention it in your question, but I assume you're running into TypeMirror issues.
Here's a very old and hacky solution to get to the TypeMirror of your supertype field, and compare it against the type's base class:
#Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(Component.class)) {
// TODO: Ensure element is a class before continuing
TypeElement typeElement = (TypeElement) element;
Component componentAnnotation = typeElement.getAnnotation(Component.class);
TypeMirror superType = null;
try {
// Hack to quickly get to TypeMirror of the annotation property
componentAnnotation.supertype();
} catch (MirroredTypeException mte) {
superType = mte.getTypeMirror();
}
// TODO: superType null check
Set<TypeMirror> validTypes = Sets.newHashSet(typeElement.getInterfaces());
validTypes.add(typeElement.getSuperclass());
if (!validTypes.contains(superType)) {
// TODO: throw something better
throw new IllegalArgumentException(typeElement.toString() + " does not implement or inherit from "
+ superType.toString() + " declared in #Component annotation");
}
}
return true;
}
If I were you I'd look into this a bit more to see if there are better options these days, but if not this will solve your problem.
Use the following code to get the TypeMirror of the class value in the annotation
public Optional<TypeMirror> getClassValueFromAnnotation(Element element, Class<? extends Annotation> annotation, String paramName) {
for (AnnotationMirror am : element.getAnnotationMirrors()) {
if (types.isSameType(am.getAnnotationType(), elements.getTypeElement(annotation.getCanonicalName()).asType())) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet()) {
if (paramName.equals(entry.getKey().getSimpleName().toString())) {
AnnotationValue annotationValue = entry.getValue();
return Optional.of((DeclaredType) annotationValue.getValue());
}
}
}
}
return Optional.empty();
}
then use this code to check if it is a super type of a specific class
/**
* A wrapper over {#link Types#isAssignable(TypeMirror, TypeMirror)} which will apply type erasure on the targetClass before calling the wrapped method.
*
* #param typeMirror a {#link javax.lang.model.type.TypeMirror} object.
* #param targetClass a {#link java.lang.Class} object.
* #return a boolean.
*/
public boolean isAssignableFrom(TypeMirror typeMirror, Class<?> targetClass) {
return types.isAssignable(typeMirror, types.erasure(elements.getTypeElement(targetClass.getCanonicalName()).asType()));
}
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());
}
}
I've been using modelmapper and java 8 Optionals all around the application which was working fine because they were primitive types; until I changed one of my model objects' field to Optional type. Then all hell broke loose. Turns out many libraries cannot handle generics very well.
Here is the structure
public class MyObjectDto
{
private Optional<MySubObjectDto> mySubObject;
}
public MyObject
{
privae Optional<MySubjObject> mySubObject;
}
When I attempt to map MyObjectDto to MyObject, modelmapper calls
public void setMySubObject(Optional<MySubObject> mySubObject){
this.mySubObject = mySubObject;
}
with Optional<MySubObjectDto>, which I don't understand how that's even possible (there is no inheritance between them). Of course that crashes fast. For now I've changed my setters to accept Dto type just to survive the day but that's not going to work on the long run. Is there a better way to get around this, or shall I create an issue?
So I digged into the modelmapper code and have done this looking at some generic implementations:
modelMapper.createTypeMap(Optional.class, Optional.class).setConverter(new OptionalConverter());
public class OptionalConverter implements ConditionalConverter<Optional, Optional> {
public MatchResult match(Class<?> sourceType, Class<?> destinationType) {
if (Optional.class.isAssignableFrom(destinationType)) {
return MatchResult.FULL;
} else {
return MatchResult.NONE;
}
}
private Class<?> getElementType(MappingContext<Optional, Optional> context) {
Mapping mapping = context.getMapping();
if (mapping instanceof PropertyMapping) {
PropertyInfo destInfo = ((PropertyMapping) mapping).getLastDestinationProperty();
Class<?> elementType = TypeResolver.resolveArgument(destInfo.getGenericType(),
destInfo.getInitialType());
return elementType == TypeResolver.Unknown.class ? Object.class : elementType;
} else if (context.getGenericDestinationType() instanceof ParameterizedType) {
return Types.rawTypeFor(((ParameterizedType) context.getGenericDestinationType()).getActualTypeArguments()[0]);
}
return Object.class;
}
public Optional<?> convert(MappingContext<Optional, Optional> context) {
Class<?> optionalType = getElementType(context);
Optional source = context.getSource();
Object dest = null;
if (source != null && source.isPresent()) {
MappingContext<?, ?> optionalContext = context.create(source.get(), optionalType);
dest = context.getMappingEngine().map(optionalContext);
}
return Optional.ofNullable(dest);
}
}
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.