Reputation: 18990
Let there be a type extending java.util.Map
:
public interface IdentifiableMap<ID, KEY, VALUE> extends Map<KEY, VALUE>{
ID getId();
}
Also let there be a field of that type which declares an annotation on one of the type parameters (as it's possible as of Java 8):
private IdentifiableMap<String, @Size(min=1) String, Integer> myMap = ...;
Using the Java reflection API, how can I find out from this declaration which annotations have been given for the type parameters from the Map
interface? In other words, I'd like to deduct that the @Size
annotation has been given for the K
type parameter of java.util.Map
.
I know how to obtain the annotations for the type parameters from the field's AnnotatedParameterizedType
, but I'm missing how to correlate this to the type parameters of the super type Map
.
Upvotes: 2
Views: 214
Reputation: 298233
Unfortunately, this is far away from being easy. The following code assumes that you want to lookup the annotations for a type that is directly implementing or extending the type, otherwise the code would become even more complicated or get ambiguity problems for interfaces indirectly implemented multiple times.
static Annotation[][] getActualAnnotations(AnnotatedType at, Class<?> target) {
Type[] typeParameters = target.getTypeParameters();
if(typeParameters.length==0) return new Annotation[0][];
Type t=at.getType();
Map<Type,Annotation[]> atArgAnnos;
Class<?> raw;
if(t instanceof Class) {
atArgAnnos=Collections.emptyMap();
raw=(Class<?>)t;
if(raw==target) return new Annotation[typeParameters.length][0];
}
else if(t instanceof ParameterizedType) {
ParameterizedType pt=(ParameterizedType)t;
raw=(Class<?>)pt.getRawType();
Type[] param=raw.getTypeParameters();
Annotation[][] a = Arrays
.stream(((AnnotatedParameterizedType)at).getAnnotatedActualTypeArguments())
.map(AnnotatedType::getAnnotations)
.toArray(Annotation[][]::new);
if(raw==target) return a;
atArgAnnos=new HashMap<>(a.length);
for(int ix = 0; ix < a.length; ix++)
atArgAnnos.put(param[ix], a[ix]);
}
else throw new UnsupportedOperationException(
"type variables, wildcard or arrays are not supported");
raw.asSubclass(target);// throws if not assignable
for(AnnotatedType aift: target.isInterface()? raw.getAnnotatedInterfaces():
new AnnotatedType[]{raw.getAnnotatedSuperclass()}) {
Type ift=aift.getType();
if(ift==target) return new Annotation[typeParameters.length][0]; // raw
else {
AnnotatedParameterizedType ifpt = (AnnotatedParameterizedType)aift;
if(((ParameterizedType)ifpt.getType()).getRawType()!=target) continue;
return Arrays.stream(ifpt.getAnnotatedActualTypeArguments())
.map(ta -> atArgAnnos.getOrDefault(ta.getType(), ta.getAnnotations()))
.toArray(Annotation[][]::new);
}
}
throw new UnsupportedOperationException(
t.getTypeName()+" does not (directly) extend or implement "+target);
}
It also doesn’t handle type variables, wildcard types or arrays; it’s already quite complicated. But it handle the example of your question and also the case that either or both of Map
’s type parameters doen’t appear in the declaration of the field’s type, e.g.
interface AnotherExample<ID, KEY> extends Map<KEY, @Size(min=100) String> {
ID getId();
}
and
AnotherExample<String, @Size(min=42) String> field;
It also works when having a type declaration like interface I<X> extends Map<X,X> {}
and a field declaration I<@Size(min=10) String> field;
, so the annotation applies to both, key and value type.
The returned annotation correspond to the type parameters of the specified type; it can be used like:
System.out.print(field.getName()+"\t");
Annotation[][] a=getActualAnnotations(field.getAnnotatedType(), Map.class);
System.out.println("key: "+Arrays.toString(a[0])+", value: "+Arrays.toString(a[1]));
Upvotes: 2