Gunnar
Gunnar

Reputation: 18990

Link annotations on type parameters in subtypes to declaring type parameters in supertype

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

Answers (1)

Holger
Holger

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

Related Questions