<commit_before>package eu.mikroskeem.shuriken.instrumentation.methodreflector;

import eu.mikroskeem.shuriken.common.Ensure;
import eu.mikroskeem.shuriken.reflect.ClassWrapper;
import eu.mikroskeem.shuriken.reflect.Reflect;
import eu.mikroskeem.shuriken.reflect.wrappers.TypeWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static eu.mikroskeem.shuriken.instrumentation.ClassTools.unqualifyName;
import static eu.mikroskeem.shuriken.instrumentation.methodreflector.MethodGenerator.*;
import static org.objectweb.asm.Opcodes.*;


/**
 * @author <NAME>
 */
final class MethodReflectorFactory {
    private static final Map<Class<?>, AtomicInteger> COUNTER = new WeakHashMap<>();
    private final GeneratedClassLoader GCL = new GeneratedClassLoader(MethodReflectorFactory.class.getClassLoader());
    private final MethodHandles.Lookup mhLookup = MethodHandles.lookup();

    /* Error messages */
    private final static String NO_CLASS_INSTANCE_PRESET = "Interface targets instance methods, but class instance " +
            "is not present in ClassWrapper!";
    private final static String ANNOTATION_ERROR = "Interface method can only have one target or field annotation! ";
    private final static String FIELD_NAME_IS_NULL = "Field name shouldn't be null or empty!";
    private final static String FAILED_TO_UNREFLECT = "Failed to unreflect target: ";
    private final static String FAILED_TO_FIND_FIELD = "Could not find target field for interface method: ";
    private final static String FAILED_TO_FIND_METHOD = "Could not find target method for interface method: ";
    private final static String SETTER_WRONG_RETURN_TYPE = "Setters can only return void type! ";
    private final static String SETTER_WRONG_PARAM_COUNT = "Setters can only take one argument! ";
    private final static String GETTER_WRONG_RETURN_TYPE = "Getters can't return void type! ";
    private final static String GETTER_WRONG_PARAM_COUNT = "Getters can't take any arguments! ";
    private final static String CTOR_INVOKER_WRONG_RETURN_TYPE = "Constructor invoker's return type must match interface " +
            "method's return type or return java.lang.Object! ";
    private final static String CTOR_TO_USE_OBJECT_PLEASE_OVERRIDE = "Please override constructor descriptor in " +
            "annotation, or change method return type: ";

    @SuppressWarnings("unchecked")
    <T> T generateReflector(ClassWrapper<?> target, Class<T> intf) {
        List<MethodHandle> methodHandles = new ArrayList<>();
        boolean isTargetPublic = Modifier.isPublic(target.getWrappedClass().getModifiers());
        boolean classMustUseInstance = false;
        boolean classMustUseMH = false;

        /* Proxy class info */
        String proxyClassName = generateName(target, intf);
        Type proxyClassType = Type.getObjectType(unqualifyName(proxyClassName));

        /* Interface class info */
        Type interfaceClassType = Type.getType(intf);

        /* Target class info */
        Class<?> targetClass = target.getWrappedClass();
        Type targetClassType = Type.getType(targetClass);

        /* Set up class writer */
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        ClassVisitor classWriter = cw;
        if(MethodReflector.DEBUG) {
            classWriter = new CheckClassAdapter(new TraceClassVisitor(classWriter, new PrintWriter(System.out)), false);
        }

        /* Start generating new class */
        classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER,
                proxyClassType.getInternalName(),
                null,
                OBJECT.getInternalName(),
                new String[]{interfaceClassType.getInternalName()}
        );

        /* Iterate through interface methods */
        for(Method interfaceMethod: intf.getMethods()) {
            /* Gather proxy method info */
            boolean interfaceHasDefault = interfaceMethod.isDefault();
            Type[] interfaceParameters = Type.getArgumentTypes(interfaceMethod);
            Type interfaceReturnType = Type.getReturnType(interfaceMethod);

            /* Gather proxy method annotations */
            boolean hasAnnotation = false;
            TargetFieldGetter fieldGetterInfo = interfaceMethod.getAnnotation(TargetFieldGetter.class);
            TargetFieldSetter fieldSetterInfo = interfaceMethod.getAnnotation(TargetFieldSetter.class);
            TargetMethod targetMethodInfo = interfaceMethod.getAnnotation(TargetMethod.class);
            TargetConstructor targetConstructorInfo = interfaceMethod.getAnnotation(TargetConstructor.class);
            if(fieldGetterInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }
            if(fieldSetterInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }
            if(targetMethodInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }
            if(targetConstructorInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }

            /* Check if annotation is present and create method with custom target */
            if(hasAnnotation && targetMethodInfo == null) {
                /* Field getter/setter */
                if(fieldGetterInfo != null || fieldSetterInfo != null) {
                    String fieldName = Ensure.notNull(either(fieldGetterInfo, fieldSetterInfo, "value"), FIELD_NAME_IS_NULL);
                    String annotationFieldType = either(fieldGetterInfo, fieldSetterInfo, "type");
                    boolean isSetter = fieldSetterInfo != null;
                    Type fieldType;

                    /*
                     * Force setters to return void and take only one parameter,
                     * force getters to return type and take no parameters
                     */
                    if(isSetter) {
                        Ensure.ensureCondition(interfaceReturnType.equals(Type.VOID_TYPE),
                                SETTER_WRONG_RETURN_TYPE + interfaceMethod);
                        Ensure.ensureCondition(interfaceParameters.length == 1,
                                SETTER_WRONG_PARAM_COUNT + interfaceMethod);
                        fieldType = Optional.ofNullable(annotationFieldType).map(Type::getType)
                                .orElse(interfaceParameters[0]);
                    } else {
                        Ensure.ensureCondition(!interfaceReturnType.equals(Type.VOID_TYPE),
                                GETTER_WRONG_RETURN_TYPE + interfaceMethod);
                        Ensure.ensureCondition(interfaceParameters.length == 0,
                                GETTER_WRONG_PARAM_COUNT + interfaceMethod);
                        fieldType = Optional.ofNullable(annotationFieldType).map(Type::getType)
                                .orElse(interfaceReturnType);
                    }

                    /* Try to find field */
                    Field targetField = findDeclaredField(targetClass, fieldName, fieldType);

                    /* Ensure target field is present */
                    Ensure.notNull(targetField, FAILED_TO_FIND_FIELD + interfaceMethod);

                     /* Get needed target field info */
                    boolean useInstance = false;
                    boolean useMH = false;

                    if(!Modifier.isStatic(targetField.getModifiers())) {
                        /* Proxy class must use target class instance */
                        if(!classMustUseInstance) classMustUseInstance = true;
                        useInstance = true;
                    }

                    if(!isTargetPublic) {
                        /* Must use MethodHandle */
                        if(!classMustUseMH) classMustUseMH = true;
                        useMH = true;
                    }

                    if(!Modifier.isPublic(targetField.getModifiers())) {
                        /* Proxy class must use method handles */
                        if(!classMustUseMH) classMustUseMH = true;
                        useMH = true;
                    }

                    if(!Modifier.isPublic(targetField.getType().getModifiers())) {
                        /* Must use MethodHandle, again */
                        if(!classMustUseMH) classMustUseMH = true;
                        fieldType = OBJECT;
                        useMH = true;
                    }

                    if(Modifier.isFinal(targetField.getModifiers())) {
                        /* Proxy class must use method handles */
                        if(!classMustUseMH) classMustUseMH = true;
                        useMH = true;

                        /* Remove final modifier */
                        int modifiers = targetField.getModifiers();
                        Reflect.wrapInstance(targetField).getField("modifiers", int.class)
                                .ifPresent(fw -> fw.write(modifiers & ~Modifier.FINAL));
                    }

                    /* Generate proxy method */
                    try {
                        int mhIndex = -1;
                        if(useMH) {
                            /* Try to look up method handle */
                            if(!targetField.isAccessible()) targetField.setAccessible(true);
                            MethodHandle methodHandle = isSetter? mhLookup.unreflectSetter(targetField) :
                                    mhLookup.unreflectGetter(targetField);

                            /* Add MethodHandle into MethodHandles array */
                            methodHandles.add(methodHandle);

                            /* Set up mhIndex */
                            mhIndex = methodHandles.size() - 1;
                        }

                        if(isSetter) {
                            /* Generate setter */
                            generateFieldWriteMethod(classWriter, interfaceMethod,
                                    proxyClassType, targetClassType, fieldType, fieldName,
                                    isTargetPublic, useInstance, useMH, mhIndex);
                        } else {
                            /* Generate getter */
                            generateFieldReadMethod(classWriter, interfaceMethod,
                                    proxyClassType, targetClassType, fieldType, fieldName,
                                    isTargetPublic, useInstance, useMH, mhIndex);
                        }
                    } catch (IllegalStateException e) {
                        generateFailedMethod(classWriter, interfaceMethod, e.getMessage());
                    } catch (IllegalAccessException e) {
                        generateFailedMethod(classWriter, interfaceMethod, FAILED_TO_UNREFLECT + targetField);
                    }
                    continue;
                }

                /* ** Constructor invoker */

                /* Ensure interface method returns given class type or Object */
                Ensure.ensureCondition(
                        interfaceReturnType.equals(targetClassType) ||
                        interfaceReturnType.equals(OBJECT),
                        CTOR_INVOKER_WRONG_RETURN_TYPE + interfaceMethod
                );
                Type[] targetParameters = interfaceParameters;
                Type targetReturnType = interfaceReturnType;

                /* Read custom descriptor, if present */
                if(!targetConstructorInfo.desc().isEmpty()) {
                    targetParameters = Type.getArgumentTypes(targetConstructorInfo.desc());
                    targetReturnType = Type.getReturnType(targetConstructorInfo.desc());
                }

                /* Constructor return type cannot be Object anymore */
                Ensure.ensureCondition(!targetReturnType.equals(OBJECT) &&
                                targetClass != Object.class, /* People like to do weird stuff */
                        CTOR_TO_USE_OBJECT_PLEASE_OVERRIDE + interfaceMethod);

                /* Try to find target constructor */
                Constructor<?> targetConstructor = findDeclaredConstructor(targetClass, targetParameters);

                /* Get needed target constructor info */
                boolean useMH = false;

                if(!Modifier.isPublic(targetConstructor.getModifiers())) {
                    /* Proxy class must use method handles */<commit_msg>Check if target constructor is null<commit_after>package eu.mikroskeem.shuriken.instrumentation.methodreflector;

import eu.mikroskeem.shuriken.common.Ensure;
import eu.mikroskeem.shuriken.reflect.ClassWrapper;
import eu.mikroskeem.shuriken.reflect.Reflect;
import eu.mikroskeem.shuriken.reflect.wrappers.TypeWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static eu.mikroskeem.shuriken.instrumentation.ClassTools.unqualifyName;
import static eu.mikroskeem.shuriken.instrumentation.methodreflector.MethodGenerator.*;
import static org.objectweb.asm.Opcodes.*;


/**
 * @author <NAME>
 */
final class MethodReflectorFactory {
    private static final Map<Class<?>, AtomicInteger> COUNTER = new WeakHashMap<>();
    private final GeneratedClassLoader GCL = new GeneratedClassLoader(MethodReflectorFactory.class.getClassLoader());
    private final MethodHandles.Lookup mhLookup = MethodHandles.lookup();

    /* Error messages */
    private final static String NO_CLASS_INSTANCE_PRESET = "Interface targets instance methods, but class instance " +
            "is not present in ClassWrapper!";
    private final static String ANNOTATION_ERROR = "Interface method can only have one target or field annotation! ";
    private final static String FIELD_NAME_IS_NULL = "Field name shouldn't be null or empty!";
    private final static String FAILED_TO_UNREFLECT = "Failed to unreflect target: ";
    private final static String FAILED_TO_FIND_FIELD = "Could not find target field for interface method: ";
    private final static String FAILED_TO_FIND_METHOD = "Could not find target method for interface method: ";
    private final static String FAILED_TO_FIND_CTOR = "Could not find target constructor for interface method: ";
    private final static String SETTER_WRONG_RETURN_TYPE = "Setters can only return void type! ";
    private final static String SETTER_WRONG_PARAM_COUNT = "Setters can only take one argument! ";
    private final static String GETTER_WRONG_RETURN_TYPE = "Getters can't return void type! ";
    private final static String GETTER_WRONG_PARAM_COUNT = "Getters can't take any arguments! ";
    private final static String CTOR_INVOKER_WRONG_RETURN_TYPE = "Constructor invoker's return type must match interface " +
            "method's return type or return java.lang.Object! ";
    private final static String CTOR_TO_USE_OBJECT_PLEASE_OVERRIDE = "Please override constructor descriptor in " +
            "annotation, or change method return type: ";

    @SuppressWarnings("unchecked")
    <T> T generateReflector(ClassWrapper<?> target, Class<T> intf) {
        List<MethodHandle> methodHandles = new ArrayList<>();
        boolean isTargetPublic = Modifier.isPublic(target.getWrappedClass().getModifiers());
        boolean classMustUseInstance = false;
        boolean classMustUseMH = false;

        /* Proxy class info */
        String proxyClassName = generateName(target, intf);
        Type proxyClassType = Type.getObjectType(unqualifyName(proxyClassName));

        /* Interface class info */
        Type interfaceClassType = Type.getType(intf);

        /* Target class info */
        Class<?> targetClass = target.getWrappedClass();
        Type targetClassType = Type.getType(targetClass);

        /* Set up class writer */
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        ClassVisitor classWriter = cw;
        if(MethodReflector.DEBUG) {
            classWriter = new CheckClassAdapter(new TraceClassVisitor(classWriter, new PrintWriter(System.out)), false);
        }

        /* Start generating new class */
        classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER,
                proxyClassType.getInternalName(),
                null,
                OBJECT.getInternalName(),
                new String[]{interfaceClassType.getInternalName()}
        );

        /* Iterate through interface methods */
        for(Method interfaceMethod: intf.getMethods()) {
            /* Gather proxy method info */
            boolean interfaceHasDefault = interfaceMethod.isDefault();
            Type[] interfaceParameters = Type.getArgumentTypes(interfaceMethod);
            Type interfaceReturnType = Type.getReturnType(interfaceMethod);

            /* Gather proxy method annotations */
            boolean hasAnnotation = false;
            TargetFieldGetter fieldGetterInfo = interfaceMethod.getAnnotation(TargetFieldGetter.class);
            TargetFieldSetter fieldSetterInfo = interfaceMethod.getAnnotation(TargetFieldSetter.class);
            TargetMethod targetMethodInfo = interfaceMethod.getAnnotation(TargetMethod.class);
            TargetConstructor targetConstructorInfo = interfaceMethod.getAnnotation(TargetConstructor.class);
            if(fieldGetterInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }
            if(fieldSetterInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }
            if(targetMethodInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }
            if(targetConstructorInfo != null) { Ensure.ensureCondition(!hasAnnotation, ANNOTATION_ERROR + interfaceMethod); hasAnnotation = true; }

            /* Check if annotation is present and create method with custom target */
            if(hasAnnotation && targetMethodInfo == null) {
                /* Field getter/setter */
                if(fieldGetterInfo != null || fieldSetterInfo != null) {
                    String fieldName = Ensure.notNull(either(fieldGetterInfo, fieldSetterInfo, "value"), FIELD_NAME_IS_NULL);
                    String annotationFieldType = either(fieldGetterInfo, fieldSetterInfo, "type");
                    boolean isSetter = fieldSetterInfo != null;
                    Type fieldType;

                    /*
                     * Force setters to return void and take only one parameter,
                     * force getters to return type and take no parameters
                     */
                    if(isSetter) {
                        Ensure.ensureCondition(interfaceReturnType.equals(Type.VOID_TYPE),
                                SETTER_WRONG_RETURN_TYPE + interfaceMethod);
                        Ensure.ensureCondition(interfaceParameters.length == 1,
                                SETTER_WRONG_PARAM_COUNT + interfaceMethod);
                        fieldType = Optional.ofNullable(annotationFieldType).map(Type::getType)
                                .orElse(interfaceParameters[0]);
                    } else {
                        Ensure.ensureCondition(!interfaceReturnType.equals(Type.VOID_TYPE),
                                GETTER_WRONG_RETURN_TYPE + interfaceMethod);
                        Ensure.ensureCondition(interfaceParameters.length == 0,
                                GETTER_WRONG_PARAM_COUNT + interfaceMethod);
                        fieldType = Optional.ofNullable(annotationFieldType).map(Type::getType)
                                .orElse(interfaceReturnType);
                    }

                    /* Try to find field */
                    Field targetField = findDeclaredField(targetClass, fieldName, fieldType);

                    /* Ensure target field is present */
                    Ensure.notNull(targetField, FAILED_TO_FIND_FIELD + interfaceMethod);

                     /* Get needed target field info */
                    boolean useInstance = false;
                    boolean useMH = false;

                    if(!Modifier.isStatic(targetField.getModifiers())) {
                        /* Proxy class must use target class instance */
                        if(!classMustUseInstance) classMustUseInstance = true;
                        useInstance = true;
                    }

                    if(!isTargetPublic) {
                        /* Must use MethodHandle */
                        if(!classMustUseMH) classMustUseMH = true;
                        useMH = true;
                    }

                    if(!Modifier.isPublic(targetField.getModifiers())) {
                        /* Proxy class must use method handles */
                        if(!classMustUseMH) classMustUseMH = true;
                        useMH = true;
                    }

                    if(!Modifier.isPublic(targetField.getType().getModifiers())) {
                        /* Must use MethodHandle, again */
                        if(!classMustUseMH) classMustUseMH = true;
                        fieldType = OBJECT;
                        useMH = true;
                    }

                    if(Modifier.isFinal(targetField.getModifiers())) {
                        /* Proxy class must use method handles */
                        if(!classMustUseMH) classMustUseMH = true;
                        useMH = true;

                        /* Remove final modifier */
                        int modifiers = targetField.getModifiers();
                        Reflect.wrapInstance(targetField).getField("modifiers", int.class)
                                .ifPresent(fw -> fw.write(modifiers & ~Modifier.FINAL));
                    }

                    /* Generate proxy method */
                    try {
                        int mhIndex = -1;
                        if(useMH) {
                            /* Try to look up method handle */
                            if(!targetField.isAccessible()) targetField.setAccessible(true);
                            MethodHandle methodHandle = isSetter? mhLookup.unreflectSetter(targetField) :
                                    mhLookup.unreflectGetter(targetField);

                            /* Add MethodHandle into MethodHandles array */
                            methodHandles.add(methodHandle);

                            /* Set up mhIndex */
                            mhIndex = methodHandles.size() - 1;
                        }

                        if(isSetter) {
                            /* Generate setter */
                            generateFieldWriteMethod(classWriter, interfaceMethod,
                                    proxyClassType, targetClassType, fieldType, fieldName,
                                    isTargetPublic, useInstance, useMH, mhIndex);
                        } else {
                            /* Generate getter */
                            generateFieldReadMethod(classWriter, interfaceMethod,
                                    proxyClassType, targetClassType, fieldType, fieldName,
                                    isTargetPublic, useInstance, useMH, mhIndex);
                        }
                    } catch (IllegalStateException e) {
                        generateFailedMethod(classWriter, interfaceMethod, e.getMessage());
                    } catch (IllegalAccessException e) {
                        generateFailedMethod(classWriter, interfaceMethod, FAILED_TO_UNREFLECT + targetField);
                    }
                    continue;
                }

                /* ** Constructor invoker */

                /* Ensure interface method returns given class type or Object */
                Ensure.ensureCondition(
                        interfaceReturnType.equals(targetClassType) ||
                        interfaceReturnType.equals(OBJECT),
                        CTOR_INVOKER_WRONG_RETURN_TYPE + interfaceMethod
                );
                Type[] targetParameters = interfaceParameters;
                Type targetReturnType = interfaceReturnType;

                /* Read custom descriptor, if present */
                if(!targetConstructorInfo.desc().isEmpty()) {
                    targetParameters = Type.getArgumentTypes(targetConstructorInfo.desc());
                    targetReturnType = Type.getReturnType(targetConstructorInfo.desc());
                }

                /* Constructor return type cannot be Object anymore */
                Ensure.ensureCondition(!targetReturnType.equals(OBJECT) &&
                                targetClass != Object.class, /* People like to do weird stuff */
                        CTOR_TO_USE_OBJECT_PLEASE_OVERRIDE + interfaceMethod);

                /* Try to find target constructor */
                Constructor<?> targetConstructor = findDeclaredConstructor(targetClass, targetParameters);
                Ensure.notNull(targetConstructor, FAILED_TO_FIND_CTOR + interfaceMethod);

                /* Get needed target constructor info */
                boolean useMH = false;

                if(!Modifier.isPublic(targetConstructor.getModifiers())) {
                    /* Proxy class must use method handles */
{'fraction_non_alphanumeric': 0.07779205077829832, 'fraction_numerical': 0.0005289406075260693, 'mean_word_length': 1.9521525763997323, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3147048', 'n_tokens_mistral': 6271, 'n_tokens_neox': 5971, 'n_words': 1419}