io.github.amayaframework.di.stub.BytecodeStubFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amaya-di Show documentation
Show all versions of amaya-di Show documentation
A framework responsible for monitoring and automating the dependency injection process.
package io.github.amayaframework.di.stub;
import com.github.romanqed.jeflect.*;
import com.github.romanqed.jfunc.Exceptions;
import com.github.romanqed.jfunc.Function0;
import io.github.amayaframework.di.Artifact;
import io.github.amayaframework.di.scheme.ClassScheme;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
/**
* A factory that creates instantiators based on proxy classes generated on the fly.
*/
public final class BytecodeStubFactory implements StubFactory {
private static final String STUB = "Stub";
private static final Type FUNCTION0 = Type.getType(Function0.class);
private static final Method INVOKE = Exceptions.suppress(() -> Function0.class.getDeclaredMethod("invoke"));
private final ObjectFactory> factory;
/**
* Constructs {@link BytecodeStubFactory} with the specified {@link ObjectFactory} instance,
* which will be used to load and instantiate the bytecode of proxy classes.
*
* @param factory the specified factory, must be non-null
*/
public BytecodeStubFactory(ObjectFactory> factory) {
this.factory = Objects.requireNonNull(factory);
}
/**
* Constructs {@link BytecodeStubFactory} with the {@link DefineObjectFactory} using
* the specified {@link DefineLoader}.
*
* @param loader the specified loader, must be non-null
*/
public BytecodeStubFactory(DefineLoader loader) {
this.factory = new DefineObjectFactory<>(loader);
}
/**
* Constructs {@link BytecodeStubFactory} with {@link DefineClassLoader}.
*/
public BytecodeStubFactory() {
this(new DefineClassLoader());
}
private static void generateConstructor(ClassWriter writer, String name, String[] order) {
var visitor = writer.visitMethod(
Opcodes.ACC_PUBLIC,
AsmUtil.INIT,
"([" + FUNCTION0.getDescriptor() + ")V",
null,
null
);
visitor.visitCode();
// Call parent constructor
visitor.visitVarInsn(Opcodes.ALOAD, 0);
visitor.visitMethodInsn(
Opcodes.INVOKESPECIAL,
AsmUtil.OBJECT.getInternalName(),
AsmUtil.INIT,
AsmUtil.EMPTY_DESCRIPTOR,
false
);
// Put artifact providers to fields
for (var i = 0; i < order.length; ++i) {
// Load this-ref and array-ref
visitor.visitVarInsn(Opcodes.ALOAD, 0);
visitor.visitVarInsn(Opcodes.ALOAD, 1);
// Push index on stack
AsmUtil.pushInt(visitor, i);
// Load provider from array and put to field
visitor.visitInsn(Opcodes.AALOAD);
visitor.visitFieldInsn(
Opcodes.PUTFIELD,
name,
order[i],
FUNCTION0.getDescriptor()
);
}
visitor.visitInsn(Opcodes.RETURN);
visitor.visitMaxs(0, 0);
visitor.visitEnd();
}
private static void loadArtifact(MethodVisitor visitor, String name, String field, Artifact artifact) {
// Load this-ref
visitor.visitVarInsn(Opcodes.ALOAD, 0);
// Load field
visitor.visitFieldInsn(
Opcodes.GETFIELD,
name,
field,
FUNCTION0.getDescriptor()
);
// Get artifact implementation
visitor.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
FUNCTION0.getInternalName(),
INVOKE.getName(),
Type.getMethodDescriptor(INVOKE),
true
);
// Cast implementation to artifact type
visitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(artifact.getType()));
}
private static void processExecutable(MethodVisitor visitor,
String name,
Artifact[] parameters,
Map fields) {
for (var parameter : parameters) {
var field = fields.get(parameter);
loadArtifact(visitor, name, field, parameter);
}
}
private static void generateInvokeMethod(ClassWriter writer,
String name,
ClassScheme scheme,
Map artifacts) {
// Prepare schemes
var constructor = scheme.getConstructorScheme();
var fields = scheme.getFieldSchemes();
var methods = scheme.getMethodSchemes();
var type = scheme.getTarget();
// Declare method signature
var visitor = writer.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
INVOKE.getName(),
Type.getMethodDescriptor(INVOKE),
null,
new String[]{Type.getInternalName(Throwable.class)}
);
visitor.visitCode();
// Create instance of target class
visitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(type));
visitor.visitInsn(Opcodes.DUP);
// Invoke constructor by scheme
processExecutable(visitor, name, constructor.getMapping(), artifacts);
AsmUtil.invoke(visitor, constructor.getTarget());
// Process field schemes
for (var field : fields) {
// ref. = (ArtifactType) this..invoke();
visitor.visitInsn(Opcodes.DUP);
var target = field.getTarget();
var artifact = field.getArtifact();
var source = artifacts.get(artifact);
loadArtifact(visitor, name, source, artifact);
visitor.visitFieldInsn(
Opcodes.PUTFIELD,
Type.getInternalName(type),
target.getName(),
Type.getDescriptor(target.getType())
);
}
// Process method schemes
for (var method : methods) {
visitor.visitInsn(Opcodes.DUP);
processExecutable(visitor, name, method.getMapping(), artifacts);
AsmUtil.invoke(visitor, method.getTarget());
}
// Return constructed instance
visitor.visitInsn(Opcodes.ARETURN);
visitor.visitMaxs(0, 0);
visitor.visitEnd();
}
private static byte[] generate(String name, ClassScheme scheme, Set artifacts) {
// Init writer
var writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// Declare class
writer.visit(
Opcodes.V11,
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
name,
null,
AsmUtil.OBJECT.getInternalName(),
new String[]{FUNCTION0.getInternalName()}
);
// Generate artifact-field mapping
var mapping = Mapping.of(artifacts);
// Declare fields
for (var field : mapping.fields.keySet()) {
writer.visitField(
Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
field,
FUNCTION0.getDescriptor(),
null,
null
);
}
// Generate constructor
generateConstructor(writer, name, mapping.order);
// Generate invoke method
generateInvokeMethod(writer, name, scheme, mapping.artifacts);
// Close writer
writer.visitEnd();
// Return bytecode
return writer.toByteArray();
}
private static Function0> instantiate(Class> clazz,
Set artifacts,
Function> provider) throws Throwable {
var constructor = clazz.getDeclaredConstructor(Function0[].class);
var arguments = new Function0>[artifacts.size()];
var count = 0;
for (var artifact : artifacts) {
arguments[count++] = Objects.requireNonNull(provider.apply(artifact));
}
return (Function0>) constructor.newInstance(new Object[]{arguments});
}
@Override
public Function0> create(ClassScheme scheme, Function> provider) {
Objects.requireNonNull(scheme);
Objects.requireNonNull(provider);
var target = scheme.getTarget();
var name = target.getName() + STUB;
var artifacts = scheme.getArtifacts();
return factory.create(
name,
() -> generate(Type.getInternalName(target) + STUB, scheme, artifacts),
clazz -> instantiate(clazz, artifacts, provider)
);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy