LibJF/libjf-config-compiler-plugin/src/main/java/io/gitlab/jfronny/libjf/config/plugin/asm/ConfigInjectClassTransforme...

229 lines
10 KiB
Java

package io.gitlab.jfronny.libjf.config.plugin.asm;
import io.gitlab.jfronny.libjf.config.api.v1.Category;
import io.gitlab.jfronny.libjf.config.api.v1.JfConfig;
import io.gitlab.jfronny.libjf.config.plugin.BuildMetadata;
import io.gitlab.jfronny.libjf.config.plugin.util.ClInitInjectVisitor;
import io.gitlab.jfronny.libjf.config.plugin.util.GeneratorAdapter2;
import io.gitlab.jfronny.libjf.config.plugin.asm.value.DiscoveredValue;
import org.gradle.api.GradleException;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import java.util.*;
import static io.gitlab.jfronny.libjf.config.plugin.asm.value.KnownTypes.*;
import static org.objectweb.asm.Opcodes.*;
import static org.objectweb.asm.Type.*;
public class ConfigInjectClassTransformer extends ClassVisitor {
// Field names
public static final String PREFIX = "libjf$config$";
public static final String INIT_METHOD = PREFIX + "clinit";
public static final String REGISTER_METHOD = PREFIX + "register";
public static final String BUILDER_ROOT = PREFIX + "root";
public static final String CLINIT = "<clinit>";
// Transformation metadata
public final String modId;
public Type current;
private final Set<Type> knownConfigClasses;
// Transformer state
private final List<String> categories = new LinkedList<>();
private final List<String> referencedConfigs = new LinkedList<>();
private final List<String> presets = new LinkedList<>();
private final List<String> verifiers = new LinkedList<>();
private final List<DiscoveredValue> values = new LinkedList<>();
private boolean initFound = false;
private TransformerMode mode;
public boolean isCategory() {
return mode == TransformerMode.CONFIG_CATEGORY || isConfig();
}
public void ensureCategory() {
if (!isCategory()) throw new NotAConfigClassException();
}
public boolean isConfig() {
return mode == TransformerMode.CONFIG_ROOT;
}
public boolean isOther() {
return mode == TransformerMode.OTHER;
}
public ConfigInjectClassTransformer(String modId, ClassVisitor cw, Set<Type> knownConfigClasses) {
super(ASM9, cw);
this.modId = modId;
this.knownConfigClasses = knownConfigClasses;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.current = Type.getType('L' + name + ';');
mode = TransformerMode.OTHER;
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
if (mode == null) throw new IllegalStateException("Attempted to visit annotation before class");
if (descriptor.equals(Type.getDescriptor(Category.class)) && isOther()) {
mode = TransformerMode.CONFIG_CATEGORY;
return new AnnotationMetaGatheringVisitor(super.visitAnnotation(descriptor, visible), referencedConfigs);
}
else if (descriptor.equals(Type.getDescriptor(JfConfig.class)) && isOther()) {
mode = TransformerMode.CONFIG_ROOT;
knownConfigClasses.add(current);
return new AnnotationMetaGatheringVisitor(super.visitAnnotation(descriptor, visible), referencedConfigs);
} else {
return super.visitAnnotation(descriptor, visible);
}
}
@Override
public void visitNestMember(String nestMember) {
ensureCategory();
categories.add(nestMember);
super.visitNestMember(nestMember);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
ensureCategory();
if (isConfig()) {
if (CLINIT.equals(name)) {
initFound = true;
return new ClInitInjectVisitor(
super.visitMethod(access, name, descriptor, signature, exceptions),
current.getInternalName(),
INIT_METHOD,
"()V"
);
}
}
if (name.startsWith(PREFIX)) {
throw new GradleException("This class declares methods generated by this plugin manually. Do not transform classes twice!");
}
if ((access & ACC_STATIC) == ACC_STATIC) {
// Possibly add verifier or preset
return new MethodMetaGatheringVisitor(
super.visitMethod(access, name, descriptor, signature, exceptions),
name, presets, verifiers);
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
ensureCategory();
if ((access & ACC_STATIC) == ACC_STATIC) {
return new FieldMetaGatheringVisitor(super.visitField(access, name, descriptor, signature, value), name, values, Type.getType(descriptor));
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public void visitEnd() {
ensureCategory();
if (isConfig()) {
if (!initFound) {
// Generate <clinit> if missing
GeneratorAdapter2 m = method(ACC_PRIVATE | ACC_STATIC, CLINIT, "()V", null, null);
m.invokeStatic(current, new Method(INIT_METHOD, "()V"));
m.returnValue();
m.endMethod();
}
// Generate main method
{
GeneratorAdapter2 m = method(ACC_PRIVATE | ACC_STATIC, INIT_METHOD, "()V", null, null);
m.comment("Migrate files from old paths");
m.invokeIStatic(CONFIG_HOLDER_TYPE, new Method("getInstance", CONFIG_HOLDER_TYPE, new Type[0]));
m.push(modId);
m.invokeInterface(CONFIG_HOLDER_TYPE, new Method("migrateFiles", "(Ljava/lang/String;)V"));
m.comment("Invoke DSL, continue in " + BUILDER_ROOT);
m.push(modId);
m.invokeIStatic(DSL_TYPE, new Method("create", DSL_DEFAULTED_TYPE, new Type[]{STRING_TYPE}));
m.λ("apply",
getMethodDescriptor(CONFIG_BUILDER_FUNCTION_TYPE),
getMethodType(CONFIG_BUILDER_TYPE, CONFIG_BUILDER_TYPE),
current.getInternalName(),
BUILDER_ROOT);
m.invokeInterface(DSL_DEFAULTED_TYPE, new Method("config", CONFIG_INSTANCE_TYPE, new Type[]{CONFIG_BUILDER_FUNCTION_TYPE}));
m.pop();
m.returnValue();
m.endMethod();
}
// Generate register method for impl
// This method does nothing but is needed to properly implement the interface
// This also ensures the static initializer is called
{
GeneratorAdapter2 m = method(ACC_PUBLIC | ACC_STATIC, REGISTER_METHOD, getMethodDescriptor(VOID_TYPE, DSL_DEFAULTED_TYPE), null, null);
m.comment("This placeholder method is referenced in the FMJ to ensure the static initializer is called");
m.returnValue();
m.endMethod();
}
}
{
Type builderType = isConfig() ? CONFIG_BUILDER_TYPE : CATEGORY_BUILDER_TYPE;
GeneratorAdapter2 m = method(ACC_PRIVATE | ACC_STATIC, BUILDER_ROOT, getMethodDescriptor(builderType, builderType), null, null);
m.loadArg(0);
for (String name : referencedConfigs) {
m.comment("Reference the config of \"" + name + "\"");
m.push(name);
dslInvoke(m, "referenceConfig", CATEGORY_BUILDER_TYPE, STRING_TYPE);
}
for (String name : categories) {
m.comment("Register the category defined in " + name);
m.push(camelCase(name.substring(current.getInternalName().length())));
m.λ("apply",
getMethodDescriptor(CATEGORY_BUILDER_FUNCTION_TYPE),
getMethodType(CATEGORY_BUILDER_TYPE, CATEGORY_BUILDER_TYPE),
name,
BUILDER_ROOT);
dslInvoke(m, "category", CATEGORY_BUILDER_TYPE, STRING_TYPE, CATEGORY_BUILDER_FUNCTION_TYPE);
}
for (String verifier : verifiers) {
m.comment("Register the verifier \"" + verifier + "\"");
m.runnable(current.getInternalName(), verifier);
dslInvoke(m, "addVerifier", CATEGORY_BUILDER_TYPE, RUNNABLE_TYPE);
}
for (String preset : presets) {
m.comment("Register the preset \"" + preset + "\"");
m.push(preset);
m.runnable(current.getInternalName(), preset);
dslInvoke(m, "addPreset", CATEGORY_BUILDER_TYPE, STRING_TYPE, RUNNABLE_TYPE);
}
for (DiscoveredValue value : values) {
m.comment("Register the value \"" + value + "\"");
value.generateRegistration(m, this);
}
m.returnValue();
m.endMethod();
for (DiscoveredValue value : values) {
value.generateλ(this);
}
}
super.visitEnd();
}
public void dslInvoke(GeneratorAdapter m, String name, Type returnType, Type... arguments) {
Type builderType = isConfig() ? CONFIG_BUILDER_TYPE : CATEGORY_BUILDER_TYPE;
m.invokeInterface(builderType, new Method(name, returnType, arguments));
if (isConfig()) m.checkCast(CONFIG_BUILDER_TYPE);
}
private String camelCase(String s) {
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
}
public GeneratorAdapter2 method(int access, String name, String descriptor, String signature, String[] exceptions) {
if (BuildMetadata.IS_RELEASE && !name.equals(CLINIT)) access |= ACC_SYNTHETIC;
return new GeneratorAdapter2(super.visitMethod(access, name, descriptor, signature, exceptions), access, name, descriptor);
}
}