<commit_before>package ru.vyarus.java.generics.resolver.util;

import ru.vyarus.java.generics.resolver.error.GenericsResolutionException;
import ru.vyarus.java.generics.resolver.error.IncompatibleTypesException;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Generics analysis utilities.
 *
 * @author <NAME>
 * @since 11.05.2018
 */
// LinkedHashMap used instead of usual map to avoid accidental simple map usage (order is important!)
@SuppressWarnings({"PMD.LooseCoupling", "PMD.GodClass"})
public final class GenericsResolutionUtils {

    private static final LinkedHashMap<String, Type> EMPTY_MAP = new LinkedHashMap<String, Type>(0);
    private static final String GROOVY_OBJECT = "GroovyObject";

    private GenericsResolutionUtils() {
    }

    /**
     * Analyze class hierarchy and resolve actual generic values for all composing types.
     *
     * @param type          class to analyze
     * @param rootGenerics  resolved root type generics (including owner type generics)
     * @param knownGenerics type generics known before analysis (some middle class generics are known) and
     *                      could contain possible outer generics (types for sure not included in resolving type
     *                      hierarchy)
     * @param ignoreClasses classes to ignore during analysis
     * @return resolved generics for all types in class hierarchy
     */
    public static Map<Class<?>, LinkedHashMap<String, Type>> resolve(
            final Class<?> type,
            final LinkedHashMap<String, Type> rootGenerics,
            final Map<Class<?>, LinkedHashMap<String, Type>> knownGenerics,
            final List<Class<?>> ignoreClasses) {
        final Map<Class<?>, LinkedHashMap<String, Type>> generics =
                new HashMap<Class<?>, LinkedHashMap<String, Type>>();
        generics.put(type, rootGenerics);
        try {
            analyzeType(generics, type, knownGenerics, ignoreClasses);
        } catch (Exception ex) {
            throw new GenericsResolutionException(type, rootGenerics, knownGenerics, ex);
        }
        return generics;
    }

    /**
     * Resolve declared generics for type (actually declared generics in context of some type).
     * If provided class is inner class - resolves outer class generics as upper bound
     * <p>
     * If type is not {@link ParameterizedType} and so does not contains actual generics info, resolve
     * generics from type declaration ({@link #resolveRawGenerics(Class)}),
     *
     * @param type     type to resolve generics for
     * @param generics generics of context class
     * @return resolved generics of parameterized type or empty map
     */
    public static LinkedHashMap<String, Type> resolveGenerics(final Type type,
                                                              final Map<String, Type> generics) {
        final LinkedHashMap<String, Type> res;
        if (type instanceof ParameterizedType) {
            final ParameterizedType actualType = (ParameterizedType) type;
            final Type[] genericTypes = actualType.getActualTypeArguments();
            final Class target = (Class) actualType.getRawType();
            final TypeVariable[] genericNames = target.getTypeParameters();

            // inner class can use outer class generics
            res = fillOuterGenerics(target, new LinkedHashMap<String, Type>(), null);

            final int cnt = genericNames.length;
            for (int i = 0; i < cnt; i++) {
                final Type resolvedGenericType = GenericsUtils.resolveTypeVariables(genericTypes[i], generics);
                res.put(genericNames[i].getName(), resolvedGenericType);
            }
        } else {
            res = resolveRawGenerics(GenericsUtils.resolveClass(type, generics));
        }
        return res;
    }

    /**
     * Resolve type generics by declaration (as lower bound). Used for cases when actual generic definition is not
     * available (so actual generics are unknown). In most cases such generics resolved as Object
     * (for example, {@code Some<T>}).
     * <p>
     * If class is inner class, resolve outer class generics (which may be used in class)
     *
     * @param type class to analyze generics for
     * @return resolved generics or empty map if not generics used
     */
    public static LinkedHashMap<String, Type> resolveRawGenerics(final Class<?> type) {
        final TypeVariable[] declaredGenerics = type.getTypeParameters();
        // inner class can use outer class generics
        final LinkedHashMap<String, Type> res =
                fillOuterGenerics(type, new LinkedHashMap<String, Type>(), null);

        for (TypeVariable variable : declaredGenerics) {
            res.put(variable.getName(), GenericsUtils.resolveTypeVariables(variable.getBounds()[0], res));
        }
        return res;
    }

    /**
     * Inner class could reference outer class generics and so this generics must be included into class context.
     * Outer class generics resolved as lower bound (by declaration). Class may declare the same generic name
     * and, in this case, outer generic will be ignored (as not visible).
     * <p>
     * During inlying context resolution we know outer context, and, if outer class is in outer context hierarchy,
     * we could assume that it's actual parent class and so we could use more specific generics. This will be true for
     * most sane cases (often inner class is used inside owner), but other cases are still possible (anyway,
     * the chance that inner class will appear in two different hierarchies of outer class is quite small).
     * <p>
     * It is very important to use returned map instead of passed in map because, incoming empty map is always replaced
     * to avoid modifications of shared empty maps.
     *
     * @param type          context type
     * @param generics      resolved type generics
     * @param knownGenerics map of known middle generics and possibly known outer context (outer context may contain
     *                      outer class generics declarations). May be null.
     * @return actual generics map (incoming map may be default empty map and in this case it must be replaced)
     */
    public static LinkedHashMap<String, Type> fillOuterGenerics(final Class<?> type,
                                         final LinkedHashMap<String, Type> generics,
                                         final Map<Class<?>, LinkedHashMap<String, Type>> knownGenerics) {
        final Class<?> outer = TypeUtils.getOuter(type);
        if (outer == null) {
            // not inner class
            return generics;
        }
        final LinkedHashMap<String, Type> outerGenerics = knownGenerics != null && knownGenerics.containsKey(outer)
                ? new LinkedHashMap<String, Type>(knownGenerics.get(outer))
                : resolveRawGenerics(outer);
        // class may declare generics with the same name and they must not be overridden
        for (TypeVariable var : type.getTypeParameters()) {
            outerGenerics.remove(var.getName());
        }
        if (generics.isEmpty()) {
            // empty generics map almost sure means that passed map is shared empty map which must not be modified
            return outerGenerics;
        } else {
            generics.putAll(outerGenerics);
            return generics;
        }
    }

    /**
     * Analyze type hierarchy (all subclasses and interfaces).
     *
     * @param generics      resolved generics of already analyzed types
     * @param knownGenerics type generics known before analysis (some middle class generics are known) and
     *                      possible owner types (types not present in analyzed type hierarchy)
     * @param type          class to analyze
     * @param ignoreClasses classes to ignore during analysis
     */
    private static void analyzeType(final Map<Class<?>, LinkedHashMap<String, Type>> generics,
                                    final Class<?> type,<commit_msg>fix style violations<commit_after>package ru.vyarus.java.generics.resolver.util;

import ru.vyarus.java.generics.resolver.error.GenericsResolutionException;
import ru.vyarus.java.generics.resolver.error.IncompatibleTypesException;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Generics analysis utilities.
 *
 * @author <NAME>
 * @since 11.05.2018
 */
// LinkedHashMap used instead of usual map to avoid accidental simple map usage (order is important!)
@SuppressWarnings({"PMD.LooseCoupling", "PMD.GodClass"})
public final class GenericsResolutionUtils {

    private static final LinkedHashMap<String, Type> EMPTY_MAP = new LinkedHashMap<String, Type>(0);
    private static final String GROOVY_OBJECT = "GroovyObject";

    private GenericsResolutionUtils() {
    }

    /**
     * Analyze class hierarchy and resolve actual generic values for all composing types.
     *
     * @param type          class to analyze
     * @param rootGenerics  resolved root type generics (including owner type generics)
     * @param knownGenerics type generics known before analysis (some middle class generics are known) and
     *                      could contain possible outer generics (types for sure not included in resolving type
     *                      hierarchy)
     * @param ignoreClasses classes to ignore during analysis
     * @return resolved generics for all types in class hierarchy
     */
    public static Map<Class<?>, LinkedHashMap<String, Type>> resolve(
            final Class<?> type,
            final LinkedHashMap<String, Type> rootGenerics,
            final Map<Class<?>, LinkedHashMap<String, Type>> knownGenerics,
            final List<Class<?>> ignoreClasses) {
        final Map<Class<?>, LinkedHashMap<String, Type>> generics =
                new HashMap<Class<?>, LinkedHashMap<String, Type>>();
        generics.put(type, rootGenerics);
        try {
            analyzeType(generics, type, knownGenerics, ignoreClasses);
        } catch (Exception ex) {
            throw new GenericsResolutionException(type, rootGenerics, knownGenerics, ex);
        }
        return generics;
    }

    /**
     * Resolve declared generics for type (actually declared generics in context of some type).
     * If provided class is inner class - resolves outer class generics as upper bound
     * <p>
     * If type is not {@link ParameterizedType} and so does not contains actual generics info, resolve
     * generics from type declaration ({@link #resolveRawGenerics(Class)}),
     *
     * @param type     type to resolve generics for
     * @param generics generics of context class
     * @return resolved generics of parameterized type or empty map
     */
    public static LinkedHashMap<String, Type> resolveGenerics(final Type type,
                                                              final Map<String, Type> generics) {
        final LinkedHashMap<String, Type> res;
        if (type instanceof ParameterizedType) {
            final ParameterizedType actualType = (ParameterizedType) type;
            final Type[] genericTypes = actualType.getActualTypeArguments();
            final Class target = (Class) actualType.getRawType();
            final TypeVariable[] genericNames = target.getTypeParameters();

            // inner class can use outer class generics
            res = fillOuterGenerics(target, new LinkedHashMap<String, Type>(), null);

            final int cnt = genericNames.length;
            for (int i = 0; i < cnt; i++) {
                final Type resolvedGenericType = GenericsUtils.resolveTypeVariables(genericTypes[i], generics);
                res.put(genericNames[i].getName(), resolvedGenericType);
            }
        } else {
            res = resolveRawGenerics(GenericsUtils.resolveClass(type, generics));
        }
        return res;
    }

    /**
     * Resolve type generics by declaration (as lower bound). Used for cases when actual generic definition is not
     * available (so actual generics are unknown). In most cases such generics resolved as Object
     * (for example, {@code Some<T>}).
     * <p>
     * If class is inner class, resolve outer class generics (which may be used in class)
     *
     * @param type class to analyze generics for
     * @return resolved generics or empty map if not generics used
     */
    public static LinkedHashMap<String, Type> resolveRawGenerics(final Class<?> type) {
        final TypeVariable[] declaredGenerics = type.getTypeParameters();
        // inner class can use outer class generics
        final LinkedHashMap<String, Type> res =
                fillOuterGenerics(type, new LinkedHashMap<String, Type>(), null);

        for (TypeVariable variable : declaredGenerics) {
            res.put(variable.getName(), GenericsUtils.resolveTypeVariables(variable.getBounds()[0], res));
        }
        return res;
    }

    /**
     * Inner class could reference outer class generics and so this generics must be included into class context.
     * Outer class generics resolved as lower bound (by declaration). Class may declare the same generic name
     * and, in this case, outer generic will be ignored (as not visible).
     * <p>
     * During inlying context resolution we know outer context, and, if outer class is in outer context hierarchy,
     * we could assume that it's actual parent class and so we could use more specific generics. This will be true for
     * most sane cases (often inner class is used inside owner), but other cases are still possible (anyway,
     * the chance that inner class will appear in two different hierarchies of outer class is quite small).
     * <p>
     * It is very important to use returned map instead of passed in map because, incoming empty map is always replaced
     * to avoid modifications of shared empty maps.
     *
     * @param type          context type
     * @param generics      resolved type generics
     * @param knownGenerics map of known middle generics and possibly known outer context (outer context may contain
     *                      outer class generics declarations). May be null.
     * @return actual generics map (incoming map may be default empty map and in this case it must be replaced)
     */
    public static LinkedHashMap<String, Type> fillOuterGenerics(
            final Class<?> type,
            final LinkedHashMap<String, Type> generics,
            final Map<Class<?>, LinkedHashMap<String, Type>> knownGenerics) {
        final LinkedHashMap<String, Type> res;
        final Class<?> outer = TypeUtils.getOuter(type);
        if (outer == null) {
            // not inner class
            res = generics;
        } else {
            final LinkedHashMap<String, Type> outerGenerics = knownGenerics != null && knownGenerics.containsKey(outer)
                    ? new LinkedHashMap<String, Type>(knownGenerics.get(outer))
                    : resolveRawGenerics(outer);
            // class may declare generics with the same name and they must not be overridden
            for (TypeVariable var : type.getTypeParameters()) {
                outerGenerics.remove(var.getName());
            }

            if (generics.isEmpty()) {
                // empty generics map almost sure means that passed map is shared empty map which must not be modified
                res = outerGenerics;
            } else {
                generics.putAll(outerGenerics);
                res = generics;
            }
        }
        return res;
    }

    /**
     * Analyze type hierarchy (all subclasses and interfaces).
     *
     * @param generics      resolved generics of already analyzed types
     * @param knownGenerics type generics known before analysis (some middle class generics are known) and
     *                      possible owner types (types not present in analyzed type hierarchy)
     * @param type          class to analyze
     * @param ignoreClasses classes to ignore during analysis
     */
    private static void analyzeType(final Map<Class<?>, LinkedHashMap<String, Type>> generics,
                                    final Class<?> type,
{'fraction_non_alphanumeric': 0.07696042728817674, 'fraction_numerical': 0.001335275552318524, 'mean_word_length': 2.58429410485099, 'pattern_counts': {'":': 0, '<': 96, '<?xml version=': 0, '>': 94, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7253156', 'n_tokens_mistral': 4037, 'n_tokens_neox': 4004, 'n_words': 1559}