package org.hildan.livedoc.core.scanner.types;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * A utility class allowing to extract all classes involved in a generic type declaration.
 */
class GenericTypeExplorer {

    /**
     * Returns all classes/interfaces appearing in the given type declaration. This method recursively explores type
     * parameters and array components to find the involved types.
     * <p>
     * For instance, for the type {@code Map<Custom, List<Integer>[]>}, we get [Map, Custom, List, Integer].
     *
     * @param type
     *         the type to explore
     *
     * @return a non-null set of all classes involved in the given type declaration
     */
    static Set<Class<?>> getClassesInDeclaration(Type type) {
        if (type == null) {
            throw new IllegalArgumentException("Type declaration may not be null");
        }
        if (type instanceof WildcardType) {
            return Collections.emptySet();
        }
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            return getClassesInDeclaration(componentType);
        }
        if (type instanceof TypeVariable) {
            return getTypesFromTypeVariable((TypeVariable) type);
        }
        if (type instanceof ParameterizedType) {
            return getTypesFromParameterizedType(((ParameterizedType) type));
        }
        assert type instanceof Class : "Unknown type category " + type.getClass();
        return getTypesFromClassOrArray((Class<?>) type);
    }

    private static Set<Class<?>> getTypesFromClassOrArray(Class<?> clazz) {
        if (clazz.isArray()) {
            return getClassesInDeclaration(clazz.getComponentType());
        }
        return Collections.singleton(clazz);
    }

    private static Set<Class<?>> getTypesFromParameterizedType(ParameterizedType type) {
        Class<?> rawType = (Class<?>) type.getRawType();
        Type[] typeParams = type.getActualTypeArguments();

        Set<Class<?>> candidates = new HashSet<>();
        candidates.add(rawType);
        candidates.addAll(getTypesInDeclarations(typeParams));
        return candidates;
    }

    private static Set<Class<?>> getTypesFromTypeVariable(TypeVariable type) {
        return getTypesInDeclarations(type.getBounds());
    }

    private static Set<Class<?>> getTypesInDeclarations(Type... types) {
        Set<Class<?>> candidates = new HashSet<>();
        for (Type typeParam : types) {
            candidates.addAll(getClassesInDeclaration(typeParam));
        }
        return candidates;
    }
}

@@ -233,24 +233,49 @@
 ldcardType;%0A
+import java.util.Arrays;%0A
 import java.
@@ -339,16 +339,52 @@
 til.Set;
+%0Aimport java.util.stream.Collectors;
 %0A%0A/**%0A *
@@ -700,26 +700,12 @@
 pes.
-%0A     * %3Cp%3E%0A     *
+ %3Cp%3E
  For
@@ -742,24 +742,31 @@
  Map%3CCustom,
+%0A     *
  List%3CIntege
@@ -2557,32 +2557,117 @@
 ariable type) %7B%0A
+        if (!hasBounds(type)) %7B%0A            return Collections.emptySet();%0A        %7D%0A
         return g
@@ -2737,58 +2737,43 @@
 tic 
-Set%3CClass%3C?%3E%3E getTypesInDeclarations(Type...
+boolean hasBounds(TypeVariable
  type
-s
 ) %7B%0A
@@ -2784,129 +2784,219 @@
     
-Set%3CClass%3C?%3E%3E candidates = new HashSet%3C%3E();%0A        for (Type typeParam : types) %7B%0A            candidates.addAll(g
+Type%5B%5D bounds = type.getBounds();%0A        // unbounded variables have one bound of type Object%0A        return bounds.length != 1 %7C%7C !bounds%5B0%5D.equals(Object.class);%0A    %7D%0A%0A    private static S
 et
+%3C
 Class
+%3C?%3E%3E getTyp
 esIn
@@ -3010,57 +3010,140 @@
 tion
-(typeParam));%0A        %7D%0A        return candidates
+s(Type... types) %7B%0A        return Arrays.stream(types).flatMap(t -%3E getClassesInDeclaration(t).stream()).collect(Collectors.toSet())
 ;%0A  

Fix TypeVariable bound exploration