<commit_before>package com.github.wrdlbrnft.gluemeister.modules.factories;

import com.github.wrdlbrnft.codebuilder.annotations.Annotations;
import com.github.wrdlbrnft.codebuilder.code.Block;
import com.github.wrdlbrnft.codebuilder.code.CodeElement;
import com.github.wrdlbrnft.codebuilder.executables.Constructor;
import com.github.wrdlbrnft.codebuilder.executables.ExecutableBuilder;
import com.github.wrdlbrnft.codebuilder.executables.Method;
import com.github.wrdlbrnft.codebuilder.executables.Methods;
import com.github.wrdlbrnft.codebuilder.implementations.Implementation;
import com.github.wrdlbrnft.codebuilder.types.Type;
import com.github.wrdlbrnft.codebuilder.types.Types;
import com.github.wrdlbrnft.codebuilder.util.Utils;
import com.github.wrdlbrnft.codebuilder.variables.Field;
import com.github.wrdlbrnft.codebuilder.variables.Variable;
import com.github.wrdlbrnft.codebuilder.variables.Variables;
import com.github.wrdlbrnft.gluemeister.GlueCollect;
import com.github.wrdlbrnft.gluemeister.GlueFactory;
import com.github.wrdlbrnft.gluemeister.GlueInject;
import com.github.wrdlbrnft.gluemeister.GlueMeisterException;
import com.github.wrdlbrnft.gluemeister.glueable.GlueableInfo;
import com.github.wrdlbrnft.gluemeister.modules.exceptions.GlueModuleConstructorNotSatisfiedException;
import com.github.wrdlbrnft.gluemeister.modules.exceptions.GlueModuleFactoryException;
import com.github.wrdlbrnft.gluemeister.utils.ElementUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;

class ClassResolver {

    private static final Type TYPE_ARRAYS = Types.of("java.util", "Arrays");
    private static final Method METHOD_AS_LIST = Methods.stub("asList");
    private static final Method METHOD_EMPTY_LIST = Methods.stub("emptyList");

    private final Map<GlueableInfo, CodeElement> mResolvedElementsMap;

    private final Map<String, GlueableInfo> mTypeParameterMap = new HashMap<>();

    private final List<FieldInfo> mNestedClassFields = new ArrayList<>();
    private Implementation.Builder mNestedClassBuilder;

    private final List<FieldInfo> mFactoryFields;

    private final ProcessingEnvironment mProcessingEnvironment;
    private final Implementation.Builder mFactoryBuilder;

    private final TypeElement mClassElement;

    private final TypeElement mListTypeElement;

    private DeclaredType mDeclaredType;


    ClassResolver(ProcessingEnvironment processingEnvironment, Map<GlueableInfo, CodeElement> resolvedElementsMap, List<FieldInfo> factoryFields, Implementation.Builder factoryBuilder, TypeElement classElement) {
        mResolvedElementsMap = resolvedElementsMap;
        mProcessingEnvironment = processingEnvironment;
        mFactoryFields = factoryFields;
        mFactoryBuilder = factoryBuilder;
        mClassElement = classElement;
        mListTypeElement = mProcessingEnvironment.getElementUtils().getTypeElement("java.util.List");
    }

    ResolveResult resolveClass(List<GlueableInfo> glueables) {
        final List<? extends TypeParameterElement> typeParameters = mClassElement.getTypeParameters();
        final TypeMirror[] args = new TypeMirror[typeParameters.size()];
        for (int i = 0, size = typeParameters.size(); i < size; i++) {
            final TypeParameterElement typeParameter = typeParameters.get(i);
            final GlueableInfo info = findGlueableForTypeParameter(typeParameter, glueables);
            args[i] = getTypeMirrorOfGlueable(info);
            mTypeParameterMap.put(typeParameter.getSimpleName().toString(), info);
        }

        mDeclaredType = mProcessingEnvironment.getTypeUtils().getDeclaredType(mClassElement, args);

        final List<ExecutableElement> abstractMethods = ElementUtils.determineAbstractMethods(mProcessingEnvironment, mClassElement);
        final Type classType = typeParameters.isEmpty()
                ? Types.of(mClassElement)
                : Types.generic(Types.of(mClassElement), resolveTypeParameters(typeParameters));

        if (abstractMethods.isEmpty()) {
            return new ResolveResultImpl(
                    Types.of(mDeclaredType),
                    mDeclaredType,
                    classType.newInstance(resolveTypeConstructorParameters(mClassElement, glueables))
            );
        }

        return resolveAbstractClass(mClassElement, glueables, abstractMethods, classType);
    }

    private ResolveResult resolveAbstractClass(TypeElement classElement, List<GlueableInfo> glueables, List<ExecutableElement> abstractMethods, Type classType) {
        mNestedClassBuilder = new Implementation.Builder();
        mNestedClassBuilder.setModifiers(EnumSet.of(Modifier.PRIVATE, Modifier.STATIC));
        if (classElement.getKind() == ElementKind.INTERFACE) {
            mNestedClassBuilder.addImplementedType(classType);
        } else if (classElement.getKind() == ElementKind.CLASS) {
            mNestedClassBuilder.setExtendedType(classType);

            final List<Constructor> constructors = createConstructorsForSubclass(classElement);
            constructors.forEach(mNestedClassBuilder::addConstructor);
        }

        final List<Method> missingMethods = implementAbstractMethods(abstractMethods, glueables);
        missingMethods.forEach(mNestedClassBuilder::addMethod);

        final Implementation implementation = mNestedClassBuilder.build();
        mFactoryBuilder.addNestedImplementation(implementation);

        return new ResolveResultImpl(
                Types.of(mDeclaredType),
                mDeclaredType,
                implementation.newInstance(resolveTypeConstructorParameters(classElement, glueables))
        );
    }

    private List<Method> implementAbstractMethods(List<ExecutableElement> abstractMethods, List<GlueableInfo> glueables) {
        return abstractMethods.stream()
                .map(method -> {
                    final ExecutableType executableType = (ExecutableType) mProcessingEnvironment.getTypeUtils().asMemberOf(mDeclaredType, method);
                    final CodeElement resolvedMethodValue = resolveMethodReturnValue(glueables, method, executableType);

                    return new Method.Builder()
                            .setName(method.getSimpleName().toString())
                            .addAnnotation(Annotations.forType(Override.class))
                            .setReturnType(Types.of(executableType.getReturnType()))
                            .setModifiers(method.getModifiers().stream()
                                    .filter(modifier -> modifier != Modifier.ABSTRACT)
                                    .collect(Collectors.toSet()))
                            .setCode(new ExecutableBuilder() {
                                @Override
                                protected List<Variable> createParameters() {
                                    return Collections.emptyList();
                                }

                                @Override
                                protected void write(Block block) {
                                    block.append("return ").append(resolvedMethodValue).append(";");
                                }
                            })
                            .build();
                })
                .collect(Collectors.toList());
    }

    private CodeElement resolveMethodReturnValue(List<GlueableInfo> glueables, ExecutableElement method, ExecutableType executableType) {
        final GlueCollect glueCollectAnnotation = method.getAnnotation(GlueCollect.class);
        final GlueFactory glueFactoryAnnotation = method.getAnnotation(GlueFactory.class);
        final boolean isFactory = glueFactoryAnnotation != null;
        if (glueCollectAnnotation != null) {
            final TypeMirror returnType = executableType.getReturnType();
            final TypeMirror erasedReturnType = mProcessingEnvironment.getTypeUtils().erasure(returnType);
            if (!mProcessingEnvironment.getTypeUtils().isAssignable(erasedReturnType, mListTypeElement.asType())) {
                throw new GlueModuleFactoryException("You can use GlueCollect only on methods which return a List! " + method + " does not.", method);
            }

            final List<TypeMirror> typeParameters = Utils.getTypeParameters(returnType);
            System.out.println("LOL: " + typeParameters);
            final TypeElement typeParameterElement = (TypeElement) mProcessingEnvironment.getTypeUtils().asElement(typeParameters.get(0));
            final List<GlueableInfo> matchingGlueables = findAllGlueablesForElement(typeParameterElement, glueables);
            System.out.println("LEL: " + matchingGlueables);

            if (matchingGlueables.isEmpty()) {
                return METHOD_EMPTY_LIST.callOnTarget(Types.COLLECTIONS);
            }

            final CodeElement[] resolvedGlueables = matchingGlueables.stream()
                    .map(info -> resolveGlueableCodeElement(info, glueables, isFactory))
                    .toArray(CodeElement[]::new);
            System.out.println("LIL: " + Arrays.toString(resolvedGlueables));
            return METHOD_AS_LIST.callOnTarget(TYPE_ARRAYS, resolvedGlueables);
        }

        final Element returnTypeElement = mProcessingEnvironment.getTypeUtils().asElement(executableType.getReturnType());
        final List<GlueableInfo> matchingGlueables = findAllGlueablesForElement(returnTypeElement, glueables);
        for (GlueableInfo glueableInfo : matchingGlueables) {
            try {
                return resolveGlueableCodeElement(glueableInfo, glueables, isFactory);
            } catch (GlueModuleConstructorNotSatisfiedException e) {
                mProcessingEnvironment.getMessager().printMessage(
                        Diagnostic.Kind.NOTE,
                        e.getMessage(),
                        e.getElement()
                );
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        throw new GlueModuleConstructorNotSatisfiedException("Return type of method " + method + " cannot be injected by GlueMeister. Make sure there is an @Glueable component which can be used to satisfy it.", method);
    }

    private List<Constructor> createConstructorsForSubclass(TypeElement classElement) {
        return classElement.getEnclosedElements().stream()
                .filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
                .map(ExecutableElement.class::cast)
                .map(constructor -> {
                    final List<? extends VariableElement> constructorParameters = constructor.getParameters();
                    final List<Variable> parameters = constructorParameters.stream()
                            .map(p -> Variables.of(Types.of(resolveTypeMirror(p.asType()))))
                            .collect(Collectors.toList());
                    return new Constructor.Builder()
                            .setModifiers(constructor.getModifiers())
                            .setCode(new ExecutableBuilder() {
                                @Override
                                protected List<Variable> createParameters() {
                                    return parameters;
                                }

                                @Override
                                protected void write(Block block) {
                                    block.append(Methods.SUPER.call(parameters.toArray(new CodeElement[parameters.size()]))).append(";");
                                }
                            })
                            .build();
                })
                .collect(Collectors.toList());
    }

    private Type[] resolveTypeParameters(List<? extends TypeParameterElement> typeParameters) {
        return typeParameters.stream()
                .map(Element::asType)
                .map(this::resolveTypeMirror)
                .map(Types::of)
                .toArray(Type[]::new);
    }

    private TypeMirror resolveTypeMirror(TypeMirror mirror) {
        switch (mirror.getKind()) {

            case TYPEVAR:
                final GlueableInfo typeVarInfo = mTypeParameterMap.get(mirror.toString());
                if (typeVarInfo == null) {
                    throw new GlueModuleFactoryException("Failed to resolve Type Parameter: " + mirror, null);
                }
                return resolveTypeMirror(typeVarInfo.getElement().asType());

            default:
                return mirror;
        }
    }

    private CodeElement[] resolveTypeConstructorParameters(TypeElement entityElement, List<GlueableInfo> glueables) {
        if (entityElement.getKind() == ElementKind.INTERFACE) {
            return new CodeElement[0];
        }

        final List<ExecutableElement> constructors = entityElement.getEnclosedElements().stream()
                .filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
                .map(ExecutableElement.class::cast)
                .sorted((a, b) -> Integer.signum(b.getParameters().size() - a.getParameters().size()))
                .collect(Collectors.toList());

        for (ExecutableElement constructor : constructors) {
            if (!ElementUtils.hasPublicOrPackageLocalVisibility(constructor)) {
                continue;
            }

            try {
                return constructor.getParameters().stream()
                        .map(parameter -> {
                            final List<GlueableInfo> matchingGlueables = findAllGlueablesForElement(parameter, glueables);
                            for (GlueableInfo matchingGlueable : matchingGlueables) {
                                try {
                                    return resolveGlueableCodeElement(matchingGlueable, glueables, false);
                                } catch (GlueModuleConstructorNotSatisfiedException e) {
                                    mProcessingEnvironment.getMessager().printMessage(
                                            Diagnostic.Kind.NOTE,
                                            e.getMessage(),
                                            e.getElement()
                                    );
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }

                            throw new GlueModuleConstructorNotSatisfiedException("Parameter " + parameter.getSimpleName() + " cannot be injected by GlueMeister. Make sure there is an @Glueable component which can be used to satisfy it.", parameter);
                        })
                        .toArray(CodeElement[]::new);
            } catch (GlueModuleConstructorNotSatisfiedException e) {
                mProcessingEnvironment.getMessager().printMessage(
                        Diagnostic.Kind.NOTE,
                        e.getMessage(),
                        e.getElement()
                );<commit_msg>Update ClassResolver.java<commit_after>package com.github.wrdlbrnft.gluemeister.modules.factories;

import com.github.wrdlbrnft.codebuilder.annotations.Annotations;
import com.github.wrdlbrnft.codebuilder.code.Block;
import com.github.wrdlbrnft.codebuilder.code.CodeElement;
import com.github.wrdlbrnft.codebuilder.executables.Constructor;
import com.github.wrdlbrnft.codebuilder.executables.ExecutableBuilder;
import com.github.wrdlbrnft.codebuilder.executables.Method;
import com.github.wrdlbrnft.codebuilder.executables.Methods;
import com.github.wrdlbrnft.codebuilder.implementations.Implementation;
import com.github.wrdlbrnft.codebuilder.types.Type;
import com.github.wrdlbrnft.codebuilder.types.Types;
import com.github.wrdlbrnft.codebuilder.util.Utils;
import com.github.wrdlbrnft.codebuilder.variables.Field;
import com.github.wrdlbrnft.codebuilder.variables.Variable;
import com.github.wrdlbrnft.codebuilder.variables.Variables;
import com.github.wrdlbrnft.gluemeister.GlueCollect;
import com.github.wrdlbrnft.gluemeister.GlueFactory;
import com.github.wrdlbrnft.gluemeister.GlueInject;
import com.github.wrdlbrnft.gluemeister.GlueMeisterException;
import com.github.wrdlbrnft.gluemeister.glueable.GlueableInfo;
import com.github.wrdlbrnft.gluemeister.modules.exceptions.GlueModuleConstructorNotSatisfiedException;
import com.github.wrdlbrnft.gluemeister.modules.exceptions.GlueModuleFactoryException;
import com.github.wrdlbrnft.gluemeister.utils.ElementUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;

class ClassResolver {

    private static final Type TYPE_ARRAYS = Types.of("java.util", "Arrays");
    private static final Method METHOD_AS_LIST = Methods.stub("asList");
    private static final Method METHOD_EMPTY_LIST = Methods.stub("emptyList");

    private final Map<GlueableInfo, CodeElement> mResolvedElementsMap;

    private final Map<String, GlueableInfo> mTypeParameterMap = new HashMap<>();

    private final List<FieldInfo> mNestedClassFields = new ArrayList<>();
    private Implementation.Builder mNestedClassBuilder;

    private final List<FieldInfo> mFactoryFields;

    private final ProcessingEnvironment mProcessingEnvironment;
    private final Implementation.Builder mFactoryBuilder;

    private final TypeElement mClassElement;

    private final TypeElement mListTypeElement;

    private DeclaredType mDeclaredType;


    ClassResolver(ProcessingEnvironment processingEnvironment, Map<GlueableInfo, CodeElement> resolvedElementsMap, List<FieldInfo> factoryFields, Implementation.Builder factoryBuilder, TypeElement classElement) {
        mResolvedElementsMap = resolvedElementsMap;
        mProcessingEnvironment = processingEnvironment;
        mFactoryFields = factoryFields;
        mFactoryBuilder = factoryBuilder;
        mClassElement = classElement;
        mListTypeElement = mProcessingEnvironment.getElementUtils().getTypeElement("java.util.List");
    }

    ResolveResult resolveClass(List<GlueableInfo> glueables) {
        final List<? extends TypeParameterElement> typeParameters = mClassElement.getTypeParameters();
        final TypeMirror[] args = new TypeMirror[typeParameters.size()];
        for (int i = 0, size = typeParameters.size(); i < size; i++) {
            final TypeParameterElement typeParameter = typeParameters.get(i);
            final GlueableInfo info = findGlueableForTypeParameter(typeParameter, glueables);
            args[i] = getTypeMirrorOfGlueable(info);
            mTypeParameterMap.put(typeParameter.getSimpleName().toString(), info);
        }

        mDeclaredType = mProcessingEnvironment.getTypeUtils().getDeclaredType(mClassElement, args);

        final List<ExecutableElement> abstractMethods = ElementUtils.determineAbstractMethods(mProcessingEnvironment, mClassElement);
        final Type classType = typeParameters.isEmpty()
                ? Types.of(mClassElement)
                : Types.generic(Types.of(mClassElement), resolveTypeParameters(typeParameters));

        if (abstractMethods.isEmpty()) {
            return new ResolveResultImpl(
                    Types.of(mDeclaredType),
                    mDeclaredType,
                    classType.newInstance(resolveTypeConstructorParameters(mClassElement, glueables))
            );
        }

        return resolveAbstractClass(mClassElement, glueables, abstractMethods, classType);
    }

    private ResolveResult resolveAbstractClass(TypeElement classElement, List<GlueableInfo> glueables, List<ExecutableElement> abstractMethods, Type classType) {
        mNestedClassBuilder = new Implementation.Builder();
        mNestedClassBuilder.setModifiers(EnumSet.of(Modifier.PRIVATE, Modifier.STATIC));
        if (classElement.getKind() == ElementKind.INTERFACE) {
            mNestedClassBuilder.addImplementedType(classType);
        } else if (classElement.getKind() == ElementKind.CLASS) {
            mNestedClassBuilder.setExtendedType(classType);

            final List<Constructor> constructors = createConstructorsForSubclass(classElement);
            constructors.forEach(mNestedClassBuilder::addConstructor);
        }

        final List<Method> missingMethods = implementAbstractMethods(abstractMethods, glueables);
        missingMethods.forEach(mNestedClassBuilder::addMethod);

        final Implementation implementation = mNestedClassBuilder.build();
        mFactoryBuilder.addNestedImplementation(implementation);

        return new ResolveResultImpl(
                Types.of(mDeclaredType),
                mDeclaredType,
                implementation.newInstance(resolveTypeConstructorParameters(classElement, glueables))
        );
    }

    private List<Method> implementAbstractMethods(List<ExecutableElement> abstractMethods, List<GlueableInfo> glueables) {
        return abstractMethods.stream()
                .map(method -> {
                    final ExecutableType executableType = (ExecutableType) mProcessingEnvironment.getTypeUtils().asMemberOf(mDeclaredType, method);
                    final CodeElement resolvedMethodValue = resolveMethodReturnValue(glueables, method, executableType);

                    return new Method.Builder()
                            .setName(method.getSimpleName().toString())
                            .addAnnotation(Annotations.forType(Override.class))
                            .setReturnType(Types.of(executableType.getReturnType()))
                            .setModifiers(method.getModifiers().stream()
                                    .filter(modifier -> modifier != Modifier.ABSTRACT)
                                    .collect(Collectors.toSet()))
                            .setCode(new ExecutableBuilder() {
                                @Override
                                protected List<Variable> createParameters() {
                                    return Collections.emptyList();
                                }

                                @Override
                                protected void write(Block block) {
                                    block.append("return ").append(resolvedMethodValue).append(";");
                                }
                            })
                            .build();
                })
                .collect(Collectors.toList());
    }

    private CodeElement resolveMethodReturnValue(List<GlueableInfo> glueables, ExecutableElement method, ExecutableType executableType) {
        final GlueCollect glueCollectAnnotation = method.getAnnotation(GlueCollect.class);
        final GlueFactory glueFactoryAnnotation = method.getAnnotation(GlueFactory.class);
        final boolean isFactory = glueFactoryAnnotation != null;
        if (glueCollectAnnotation != null) {
            final TypeMirror returnType = executableType.getReturnType();
            final TypeMirror erasedReturnType = mProcessingEnvironment.getTypeUtils().erasure(returnType);
            if (!mProcessingEnvironment.getTypeUtils().isAssignable(erasedReturnType, mListTypeElement.asType())) {
                throw new GlueModuleFactoryException("You can use GlueCollect only on methods which return a List! " + method + " does not.", method);
            }

            final List<TypeMirror> typeParameters = Utils.getTypeParameters(returnType);
            System.out.println("LOL: " + typeParameters);
            final TypeElement typeParameterElement = (TypeElement) mProcessingEnvironment.getTypeUtils().asElement(typeParameters.get(0));
            final List<GlueableInfo> matchingGlueables = findAllGlueablesForElement(typeParameterElement, glueables);
            System.out.println("LEL: " + matchingGlueables);

            if (matchingGlueables.isEmpty()) {
                return METHOD_EMPTY_LIST.callOnTarget(Types.COLLECTIONS);
            }

            final CodeElement[] resolvedGlueables = matchingGlueables.stream()
                    .map(info -> resolveGlueableCodeElement(info, glueables, isFactory))
                    .toArray(CodeElement[]::new);
            System.out.println("LIL: " + Arrays.toString(resolvedGlueables));
            return METHOD_AS_LIST.callOnTarget(TYPE_ARRAYS, resolvedGlueables);
        }

        final Element returnTypeElement = mProcessingEnvironment.getTypeUtils().asElement(executableType.getReturnType());
        final List<GlueableInfo> matchingGlueables = findAllGlueablesForElement(returnTypeElement, glueables);
        for (GlueableInfo glueableInfo : matchingGlueables) {
            try {
                return resolveGlueableCodeElement(glueableInfo, glueables, isFactory);
            } catch (GlueModuleConstructorNotSatisfiedException e) {
                mProcessingEnvironment.getMessager().printMessage(
                        Diagnostic.Kind.NOTE,
                        e.getMessage(),
                        e.getElement()
                );
            } catch (Exception e) {
                mProcessingEnvironment.getMessager().printMessage(
                        Diagnostic.Kind.NOTE,
                        "Ran into this issue while following a resolution path: " + e.getMessage(),
                        null
                );
            }
        }

        throw new GlueModuleConstructorNotSatisfiedException("Return type of method " + method + " cannot be injected by GlueMeister. Make sure there is an @Glueable component which can be used to satisfy it.", method);
    }

    private List<Constructor> createConstructorsForSubclass(TypeElement classElement) {
        return classElement.getEnclosedElements().stream()
                .filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
                .map(ExecutableElement.class::cast)
                .map(constructor -> {
                    final List<? extends VariableElement> constructorParameters = constructor.getParameters();
                    final List<Variable> parameters = constructorParameters.stream()
                            .map(p -> Variables.of(Types.of(resolveTypeMirror(p.asType()))))
                            .collect(Collectors.toList());
                    return new Constructor.Builder()
                            .setModifiers(constructor.getModifiers())
                            .setCode(new ExecutableBuilder() {
                                @Override
                                protected List<Variable> createParameters() {
                                    return parameters;
                                }

                                @Override
                                protected void write(Block block) {
                                    block.append(Methods.SUPER.call(parameters.toArray(new CodeElement[parameters.size()]))).append(";");
                                }
                            })
                            .build();
                })
                .collect(Collectors.toList());
    }

    private Type[] resolveTypeParameters(List<? extends TypeParameterElement> typeParameters) {
        return typeParameters.stream()
                .map(Element::asType)
                .map(this::resolveTypeMirror)
                .map(Types::of)
                .toArray(Type[]::new);
    }

    private TypeMirror resolveTypeMirror(TypeMirror mirror) {
        switch (mirror.getKind()) {

            case TYPEVAR:
                final GlueableInfo typeVarInfo = mTypeParameterMap.get(mirror.toString());
                if (typeVarInfo == null) {
                    throw new GlueModuleFactoryException("Failed to resolve Type Parameter: " + mirror, null);
                }
                return resolveTypeMirror(typeVarInfo.getElement().asType());

            default:
                return mirror;
        }
    }

    private CodeElement[] resolveTypeConstructorParameters(TypeElement entityElement, List<GlueableInfo> glueables) {
        if (entityElement.getKind() == ElementKind.INTERFACE) {
            return new CodeElement[0];
        }

        final List<ExecutableElement> constructors = entityElement.getEnclosedElements().stream()
                .filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
                .map(ExecutableElement.class::cast)
                .sorted((a, b) -> Integer.signum(b.getParameters().size() - a.getParameters().size()))
                .collect(Collectors.toList());

        for (ExecutableElement constructor : constructors) {
            if (!ElementUtils.hasPublicOrPackageLocalVisibility(constructor)) {
                continue;
            }

            try {
                return constructor.getParameters().stream()
                        .map(parameter -> {
                            final List<GlueableInfo> matchingGlueables = findAllGlueablesForElement(parameter, glueables);
                            for (GlueableInfo matchingGlueable : matchingGlueables) {
                                try {
                                    return resolveGlueableCodeElement(matchingGlueable, glueables, false);
                                } catch (GlueModuleConstructorNotSatisfiedException e) {
                                    mProcessingEnvironment.getMessager().printMessage(
                                            Diagnostic.Kind.NOTE,
                                            e.getMessage(),
                                            e.getElement()
                                    );
                                } catch (Exception e) {
                                    mProcessingEnvironment.getMessager().printMessage(
                                            Diagnostic.Kind.NOTE,
                                            "Ran into this issue while following a resolution path: " + e.getMessage(),
                                            null
                                    );
                                }
                            }

                            throw new GlueModuleConstructorNotSatisfiedException("Parameter " + parameter.getSimpleName() + " cannot be injected by GlueMeister. Make sure there is an @Glueable component which can be used to satisfy it.", parameter);
                        })
                        .toArray(CodeElement[]::new);
            } catch (GlueModuleConstructorNotSatisfiedException e) {
                mProcessingEnvironment.getMessager().printMessage(
                        Diagnostic.Kind.NOTE,
                        e.getMessage(),
                        e.getElement()
                );
{'fraction_non_alphanumeric': 0.08485851708414018, 'fraction_numerical': 0.00018739459054281966, 'mean_word_length': 2.3149394347240917, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 43, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3025852', 'n_tokens_mistral': 7892, 'n_tokens_neox': 7535, 'n_words': 1353}