All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jobrunr.jobs.details.JobDetailsAsmGenerator Maven / Gradle / Ivy

Go to download

An easy way to perform background processing on the JVM. Backed by persistent storage. Open and free for commercial use.

There is a newer version: 7.3.1
Show newest version
package org.jobrunr.jobs.details;

import org.jobrunr.jobs.JobDetails;
import org.jobrunr.jobs.details.instructions.*;
import org.jobrunr.jobs.lambdas.*;
import org.jobrunr.utils.reflection.ReflectionUtils;
import org.objectweb.asm.*;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.util.Optional;

import static java.util.Arrays.stream;
import static org.jobrunr.JobRunrException.shouldNotHappenException;
import static org.jobrunr.jobs.details.JobDetailsGeneratorUtils.getJavaClassContainingLambdaAsInputStream;
import static org.jobrunr.jobs.details.JobDetailsGeneratorUtils.getKotlinClassContainingLambdaAsInputStream;
import static org.jobrunr.jobs.details.SerializedLambdaConverter.toSerializedLambda;
import static org.jobrunr.utils.reflection.ReflectionUtils.cast;
import static org.jobrunr.utils.reflection.ReflectionUtils.getValueFromField;

public class JobDetailsAsmGenerator implements JobDetailsGenerator {

    @Override
    public JobDetails toJobDetails(JobLambda lambda) {
        if (isKotlinLambda(lambda)) {
            return new KotlinJobDetailsFinder(lambda).getJobDetails();
        } else {
            return new JavaJobDetailsFinder(lambda, toSerializedLambda(lambda)).getJobDetails();
        }
    }

    @Override
    public JobDetails toJobDetails(IocJobLambda lambda) {
        if (isKotlinLambda(lambda)) {
            return new KotlinJobDetailsFinder(lambda, new Object()).getJobDetails();
        } else {
            return new JavaJobDetailsFinder(lambda, toSerializedLambda(lambda)).getJobDetails();
        }
    }

    @Override
    public  JobDetails toJobDetails(T itemFromStream, JobLambdaFromStream lambda) {
        if (isKotlinLambda(lambda)) {
            return new KotlinJobDetailsFinder(lambda, itemFromStream).getJobDetails();
        } else {
            return new JavaJobDetailsFinder(lambda, toSerializedLambda(lambda), itemFromStream).getJobDetails();
        }
    }

    @Override
    public  JobDetails toJobDetails(T itemFromStream, IocJobLambdaFromStream lambda) {
        if (isKotlinLambda(lambda)) {
            // why new Object(): it represents the item injected when we run the IocJobLambdaFromStream function
            return new KotlinJobDetailsFinder(lambda, new Object(), itemFromStream).getJobDetails();
        } else {
            // why null: it represents the item injected when we run the IocJobLambdaFromStream function
            return new JavaJobDetailsFinder(lambda, toSerializedLambda(lambda), null, itemFromStream).getJobDetails();
        }
    }

    private  boolean isKotlinLambda(T lambda) {
        return !lambda.getClass().isSynthetic() && stream(lambda.getClass().getAnnotations()).map(Annotation::annotationType).anyMatch(annotationType -> annotationType.getName().equals("kotlin.Metadata"));
    }

    private static class JavaJobDetailsFinder extends JobDetailsFinder {

        private final JobRunrJob jobRunrJob;
        private final SerializedLambda serializedLambda;

        private JavaJobDetailsFinder(JobRunrJob jobRunrJob, SerializedLambda serializedLambda, Object... params) {
            super(new JavaJobDetailsFinderContext(serializedLambda, params));
            this.jobRunrJob = jobRunrJob;
            this.serializedLambda = serializedLambda;
            parse(getClassContainingLambdaAsInputStream());
        }

        @Override
        protected boolean isLambdaContainingJobDetails(String name) {
            return serializedLambda.getImplMethodName().startsWith("lambda$") && name.equals(serializedLambda.getImplMethodName());
        }

        @Override
        protected InputStream getClassContainingLambdaAsInputStream() {
            return getJavaClassContainingLambdaAsInputStream(jobRunrJob);
        }
    }

    private static class KotlinJobDetailsFinder extends JobDetailsFinder {

        private int methodCounter = 0;
        private JobRunrJob jobRunrJob;

        private String nestedKotlinClassWithMethodReference;

        private KotlinJobDetailsFinder(JobRunrJob jobRunrJob, Object... params) {
            super(new KotlinJobDetailsFinderContext(jobRunrJob, params));
            this.jobRunrJob = jobRunrJob;
            parse(getClassContainingLambdaAsInputStream());
        }

        @Override
        protected boolean isLambdaContainingJobDetails(String name) {
            if (name.equals("accept") || name.equals("invoke")) {
                methodCounter++;
            }
            return name.equals("run") || ((name.equals("accept") || name.equals("invoke")) && methodCounter == 2);
        }

        @Override
        public void visitInnerClass(String name, String outerName, String innerName, int access) {
            if (access == 0x1018) {
                this.nestedKotlinClassWithMethodReference = name;
            }
        }

        @Override
        protected InputStream getClassContainingLambdaAsInputStream() {
            return getKotlinClassContainingLambdaAsInputStream(jobRunrJob);
        }

        @Override
        protected void parse(InputStream inputStream) {
            Optional field = ReflectionUtils.findField(jobRunrJob.getClass(), "function");
            if (field.isPresent()) {
                getJobDetailsFromKotlinFunction(field.get());
            } else {
                super.parse(inputStream);
                parseNestedClassIfItIsAMethodReference();
            }
        }

        private void getJobDetailsFromKotlinFunction(Field field) {
            Object function = getValueFromField(field, jobRunrJob);
            //Field owner = ReflectionUtils.getField(function.getClass(), "owner");
            Field receiver = ReflectionUtils.getField(function.getClass(), "receiver");
            Field name = ReflectionUtils.getField(function.getClass(), "name");
            Class receiverClass = getValueFromField(receiver, function).getClass();
            String methodName = cast(getValueFromField(name, function));
            jobDetailsFinderContext.setClassName(receiverClass.getName());
            jobDetailsFinderContext.setMethodName(methodName);
        }

        private void parseNestedClassIfItIsAMethodReference() {
            if (nestedKotlinClassWithMethodReference != null) {
                String location = "/" + nestedKotlinClassWithMethodReference + ".class";
                super.parse(jobRunrJob.getClass().getResourceAsStream(location));
                while (jobDetailsFinderContext.getInstructions().size() > 1) {
                    jobDetailsFinderContext.pollFirstInstruction();
                }
            }
        }
    }

    private static abstract class JobDetailsFinder extends ClassVisitor {

        protected final JobDetailsFinderContext jobDetailsFinderContext;

        private JobDetailsFinder(JobDetailsFinderContext jobDetailsFinderContext) {
            super(Opcodes.ASM7);
            this.jobDetailsFinderContext = jobDetailsFinderContext;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if (isLambdaContainingJobDetails(name)) {
                return new MethodVisitor(Opcodes.ASM7) {

                    @Override
                    public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
                        VisitFieldInstruction instruction = AllJVMInstructions.get(opcode, jobDetailsFinderContext);
                        instruction.load(owner, name, descriptor);
                    }

                    @Override
                    public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
                        InvokeDynamicInstruction instruction = AllJVMInstructions.get(Opcodes.INVOKEDYNAMIC, jobDetailsFinderContext);
                        instruction.load(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
                    }

                    @Override
                    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
                        VisitMethodInstruction visitMethodInstruction = AllJVMInstructions.get(opcode, jobDetailsFinderContext);
                        visitMethodInstruction.load(owner, name, descriptor, isInterface);
                    }

                    @Override
                    public void visitInsn(int opcode) {
                        ZeroOperandInstruction zeroOperandInstruction = AllJVMInstructions.get(opcode, jobDetailsFinderContext);
                        zeroOperandInstruction.load();
                    }

                    @Override
                    public void visitVarInsn(int opcode, int var) {
                        VisitLocalVariableInstruction instruction = AllJVMInstructions.get(opcode, jobDetailsFinderContext);
                        instruction.load(var);
                    }

                    @Override
                    public void visitIntInsn(int opcode, int operand) {
                        SingleIntOperandInstruction singleIntOperandInstruction = AllJVMInstructions.get(opcode, jobDetailsFinderContext);
                        singleIntOperandInstruction.load(operand);
                    }

                    @Override
                    public void visitLdcInsn(Object value) {
                        LdcInstruction ldcInstruction = AllJVMInstructions.get(Opcodes.LDC, jobDetailsFinderContext);
                        ldcInstruction.load(value);
                    }

                    @Override
                    public void visitTypeInsn(int opcode, String type) {
                        VisitTypeInstruction instruction = AllJVMInstructions.get(opcode, jobDetailsFinderContext);
                        instruction.load(type);
                    }
                };
            } else {
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        }

        protected abstract boolean isLambdaContainingJobDetails(String name);

        protected abstract InputStream getClassContainingLambdaAsInputStream();

        public JobDetails getJobDetails() {
            return jobDetailsFinderContext.getJobDetails();
        }

        protected void parse(InputStream inputStream) {
            try {
                ClassReader parser = new ClassReader(inputStream);
                parser.accept(this, ClassReader.SKIP_FRAMES);
            } catch (IOException e) {
                throw shouldNotHappenException(e);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy