229 lines
10 KiB
Java
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);
|
|
}
|
|
}
|