summaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/processor
diff options
context:
space:
mode:
author zaaarf <zaaarf@proton.me>2023-03-01 21:29:19 +0100
committer zaaarf <zaaarf@proton.me>2023-03-01 21:29:19 +0100
commit6ff87be213eb46e32b25c6d2ff3be8d1f1e830ed (patch)
tree74976f15ae10f2fc8b5a94fd6e6cc4a08e9c6a59 /src/main/java/ftbsc/lll/processor
parent60bd356b5d4f292365196abcdeb5e9325c809b09 (diff)
feat: added @FindField and @FindMethod (untested)
Diffstat (limited to 'src/main/java/ftbsc/lll/processor')
-rw-r--r--src/main/java/ftbsc/lll/processor/ASTUtils.java54
-rw-r--r--src/main/java/ftbsc/lll/processor/LilleroProcessor.java107
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/FindField.java21
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/FindMethod.java23
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Injector.java8
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Target.java4
6 files changed, 189 insertions, 28 deletions
diff --git a/src/main/java/ftbsc/lll/processor/ASTUtils.java b/src/main/java/ftbsc/lll/processor/ASTUtils.java
index 3c6854f..38e50e2 100644
--- a/src/main/java/ftbsc/lll/processor/ASTUtils.java
+++ b/src/main/java/ftbsc/lll/processor/ASTUtils.java
@@ -7,9 +7,12 @@ import com.squareup.javapoet.TypeName;
import ftbsc.lll.tools.DescriptorBuilder;
import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.stream.Collectors;
/**
* Collection of static utils that didn't really fit into the main class.
@@ -18,19 +21,17 @@ public class ASTUtils {
/**
* Finds, among the methods of a class cl, the one annotated with ann, and tries to build
* a {@link ExecutableElement} from it.
- * In case of multiple occurrences, only the first one is returned.
- * No check existance check is performed within the method.
* @param cl the {@link ExecutableElement} for the class containing the desired method
* @param ann the {@link Class} corresponding to the desired annotation
- * @return the {@link MethodSpec} representing the desired method
+ * @return a {@link List} of {@link MethodSpec}s annotated with the given annotation
+ * @since 0.2.0
*/
- @SuppressWarnings("OptionalGetWithoutIsPresent")
- public static ExecutableElement findAnnotatedMethod(TypeElement cl, Class<? extends Annotation> ann) {
- return (ExecutableElement) cl.getEnclosedElements()
+ public static List<ExecutableElement> findAnnotatedMethods(TypeElement cl, Class<? extends Annotation> ann) {
+ return cl.getEnclosedElements()
.stream()
.filter(e -> e.getAnnotation(ann) != null)
- .findFirst()
- .get(); //will never be null so can ignore warning
+ .map(e -> (ExecutableElement) e)
+ .collect(Collectors.toList());
}
/**
@@ -85,4 +86,41 @@ public class ASTUtils {
methodSignature.append(descriptorFromType(m.getReturnType()));
return methodSignature.toString();
}
+
+ /**
+ * Maps a {@link javax.lang.model.element.Modifier} to its reflective
+ * {@link java.lang.reflect.Modifier} equivalent.
+ * @param m the {@link Modifier} to map
+ * @return an integer representing the modifier
+ * @see java.lang.reflect.Modifier
+ * @since 0.2.0
+ */
+ public static int mapModifier(Modifier m) {
+ switch(m) {
+ case PUBLIC:
+ return java.lang.reflect.Modifier.PUBLIC;
+ case PROTECTED:
+ return java.lang.reflect.Modifier.PROTECTED;
+ case PRIVATE:
+ return java.lang.reflect.Modifier.PRIVATE;
+ case ABSTRACT:
+ return java.lang.reflect.Modifier.ABSTRACT;
+ case STATIC:
+ return java.lang.reflect.Modifier.STATIC;
+ case FINAL:
+ return java.lang.reflect.Modifier.FINAL;
+ case TRANSIENT:
+ return java.lang.reflect.Modifier.TRANSIENT;
+ case VOLATILE:
+ return java.lang.reflect.Modifier.VOLATILE;
+ case SYNCHRONIZED:
+ return java.lang.reflect.Modifier.SYNCHRONIZED;
+ case NATIVE:
+ return java.lang.reflect.Modifier.NATIVE;
+ case STRICTFP:
+ return java.lang.reflect.Modifier.STRICT;
+ default:
+ return 0;
+ }
+ }
}
diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
index ed46998..d4a6ae7 100644
--- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
+++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
@@ -2,10 +2,9 @@ package ftbsc.lll.processor;
import com.squareup.javapoet.*;
import ftbsc.lll.IInjector;
-import ftbsc.lll.processor.annotations.Injector;
-import ftbsc.lll.processor.annotations.Patch;
-import ftbsc.lll.processor.annotations.Target;
-import ftbsc.lll.tools.DescriptorBuilder;
+import ftbsc.lll.processor.annotations.*;
+import ftbsc.lll.proxies.FieldProxy;
+import ftbsc.lll.proxies.MethodProxy;
import ftbsc.lll.tools.SrgMapper;
import javax.annotation.processing.*;
@@ -19,16 +18,17 @@ import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.*;
-import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
-import static ftbsc.lll.processor.ASTUtils.descriptorFromMethodSpec;
-import static ftbsc.lll.processor.ASTUtils.findAnnotatedMethod;
+import static ftbsc.lll.processor.ASTUtils.*;
/**
* The actual annotation processor behind the magic.
@@ -78,7 +78,7 @@ public class LilleroProcessor extends AbstractProcessor {
/**
* This checks whether a given class contains the requirements to be parsed into a Lillero injector.
* It must have at least one method annotated with {@link Target}, and one method annotated with {@link Injector}
- * that must be public, static and take in a ClassNode and MethodNode from ObjectWeb's ASM library.
+ * that must take in a ClassNode and MethodNode from ObjectWeb's ASM library.
* @param elem the element to check.
* @return whether it can be converted into a valid {@link IInjector}.
*/
@@ -90,8 +90,6 @@ public class LilleroProcessor extends AbstractProcessor {
List<? extends TypeMirror> params = ((ExecutableType) e.asType()).getParameterTypes();
return e.getAnnotation(Injector.class) != null
&& e.getAnnotation(Target.class) == null
- && e.getModifiers().contains(Modifier.PUBLIC)
- && e.getModifiers().contains(Modifier.STATIC)
&& params.size() == 2
&& processingEnv.getTypeUtils().isSameType(params.get(0), classNodeType)
&& processingEnv.getTypeUtils().isSameType(params.get(1), methodNodeType);
@@ -129,14 +127,14 @@ public class LilleroProcessor extends AbstractProcessor {
} //pretty sure class names de facto never change but better safe than sorry
String targetClassSrgName = mapper.getMcpClass(targetClassCanonicalName.replace('.', '/'));
- ExecutableElement targetMethod = findAnnotatedMethod(cl, Target.class);
+ ExecutableElement targetMethod = findAnnotatedMethods(cl, Target.class).get(0); //there should only be one
String targetMethodDescriptor = descriptorFromMethodSpec(targetMethod);
String targetMethodSrgName = mapper.getSrgMember(
targetClassCanonicalName.replace('.', '/'),
targetMethod.getSimpleName() + " " + targetMethodDescriptor
);
- ExecutableElement injectorMethod = findAnnotatedMethod(cl, Injector.class);
+ ExecutableElement injectorMethod = findAnnotatedMethods(cl, Injector.class).get(0); //there should only be one
Element packageElement = cl.getEnclosingElement();
while (packageElement.getKind() != ElementKind.PACKAGE)
@@ -149,6 +147,7 @@ public class LilleroProcessor extends AbstractProcessor {
MethodSpec inject = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
+ .addAnnotation(Override.class)
.addParameter(ParameterSpec.builder(
TypeName.get(processingEnv
.getElementUtils()
@@ -157,17 +156,24 @@ public class LilleroProcessor extends AbstractProcessor {
TypeName.get(processingEnv
.getElementUtils()
.getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main").build())
- .addStatement("$T." + injectorMethod.getSimpleName() + "(clazz, main)", TypeName.get(cl.asType()))
+ .addStatement("super." + injectorMethod.getSimpleName() + "(clazz, main)", TypeName.get(cl.asType()))
.build();
+ List<Modifier> injectorModifiers = new ArrayList<>();
+ injectorModifiers.add(Modifier.PUBLIC);
+ if(cl.getModifiers().contains(Modifier.ABSTRACT))
+ injectorModifiers.add(Modifier.ABSTRACT); //so we dont actually have to instantiate the stubs
+
TypeSpec injectorClass = TypeSpec.classBuilder(injectorSimpleClassName)
- .addModifiers(Modifier.PUBLIC)
+ .addModifiers(injectorModifiers.toArray(new Modifier[0]))
+ .superclass(cl.asType())
.addSuperinterface(ClassName.get(IInjector.class))
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
.addMethod(buildStringReturnMethod("reason", ann.reason()))
.addMethod(buildStringReturnMethod("targetClass", targetClassSrgName.replace('/', '.')))
.addMethod(buildStringReturnMethod("methodName", targetMethodSrgName))
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
+ .addMethods(generateRequestedProxies(cl, mapper))
.addMethod(inject)
.build();
@@ -194,12 +200,85 @@ public class LilleroProcessor extends AbstractProcessor {
private static MethodSpec buildStringReturnMethod(String name, String returnString) {
return MethodSpec.methodBuilder(name)
.addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class)
.returns(String.class)
.addStatement("return $S", returnString)
.build();
}
/**
+ * Finds any method annotated with {@link FindMethod} or {@link FindField} within the given
+ * class, and builds the {@link MethodSpec} necessary for building it.
+ * @param cl the class to search
+ * @return a {@link List} of method specs
+ * @since 0.2.0
+ */
+ private List<MethodSpec> generateRequestedProxies(TypeElement cl, SrgMapper mapper) {
+ List<MethodSpec> generated = new ArrayList<>();
+ findAnnotatedMethods(cl, FindMethod.class)
+ .stream()
+ .filter(m -> !m.getModifiers().contains(Modifier.STATIC)) //skip static stuff as we can't override it
+ .filter(m -> !m.getModifiers().contains(Modifier.FINAL)) //in case someone is trying to be funny
+ .forEach(m -> {
+ FindMethod ann = m.getAnnotation(FindMethod.class);
+ String targetMethodName = ann.name().equals("") ? m.getSimpleName().toString() : ann.name();
+ try {
+ MethodSpec.Builder b = MethodSpec.overriding(m);
+ Method targetMethod = ann.parent().getMethod(
+ targetMethodName,
+ ann.params()
+ );
+ b.addStatement("$T bd = $T.builder($S)",
+ MethodProxy.Builder.class,
+ MethodProxy.class,
+ targetMethodName
+ );
+ b.addStatement("bd.setParent($S)", targetMethod.getDeclaringClass().getCanonicalName());
+ b.addStatement("bd.setModifier($L)", targetMethod.getModifiers());
+ for(Class<?> p : targetMethod.getParameterTypes())
+ b.addStatement("bd.addParameter($T.class)", p);
+ b.addStatement("bd.setReturnType($T.class)", targetMethod.getReturnType());
+ b.addStatement("return bd.build()");
+ generated.add(b.build());
+ } catch(NoSuchMethodException e) {
+ processingEnv.getMessager().printMessage(
+ Diagnostic.Kind.ERROR,
+ "Method not found: " + targetMethodName
+ );
+ }
+ });
+ findAnnotatedMethods(cl, FindField.class)
+ .stream()
+ .filter(m -> !m.getModifiers().contains(Modifier.STATIC))
+ .filter(m -> !m.getModifiers().contains(Modifier.FINAL))
+ .forEach(m -> {
+ FindField ann = m.getAnnotation(FindField.class);
+ String targetFieldName = ann.name().equals("") ? m.getSimpleName().toString() : ann.name();
+ try {
+ MethodSpec.Builder b = MethodSpec.overriding(m);
+ Field targetField = ann.parent().getField(targetFieldName);
+ b.addStatement("$T bd = $T.builder($S)",
+ FieldProxy.Builder.class,
+ FieldProxy.class,
+ targetFieldName
+ );
+ b.addStatement("bd.setParent($S)", targetField.getDeclaringClass().getCanonicalName());
+ b.addStatement("bd.setModifier($L)", targetField.getModifiers());
+ b.addStatement("bd.setType($T.class)", targetField.getType());
+ b.addStatement("return bd.build()");
+ generated.add(b.build());
+ } catch(NoSuchFieldException e) {
+ processingEnv.getMessager().printMessage(
+ Diagnostic.Kind.ERROR,
+ "Field not found: " + targetFieldName + " in class " + ann.parent().getCanonicalName()
+ );
+ }
+ });
+ return generated;
+ }
+
+
+ /**
* Generates the Service Provider file for the generated injectors.
*/
private void generateServiceProvider() {
diff --git a/src/main/java/ftbsc/lll/processor/annotations/FindField.java b/src/main/java/ftbsc/lll/processor/annotations/FindField.java
new file mode 100644
index 0000000..9b5a824
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/annotations/FindField.java
@@ -0,0 +1,21 @@
+package ftbsc.lll.processor.annotations;
+
+import ftbsc.lll.proxies.FieldProxy;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Overrides the marked method in the Injector, having the
+ * implementation return a built {@link FieldProxy}.
+ * @implNote if name is omitted, name of the annotated method
+ * is used.
+ * @since 0.2.0
+ */
+@Retention(RetentionPolicy.CLASS)
+@java.lang.annotation.Target(ElementType.METHOD)
+public @interface FindField {
+ Class<?> parent();
+ String name() default "";
+}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/FindMethod.java b/src/main/java/ftbsc/lll/processor/annotations/FindMethod.java
new file mode 100644
index 0000000..bf93442
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/annotations/FindMethod.java
@@ -0,0 +1,23 @@
+package ftbsc.lll.processor.annotations;
+
+import ftbsc.lll.proxies.MethodProxy;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Overrides the marked method in the Injector, having the
+ * implementation return a built {@link MethodProxy} with
+ * the specified parameters.
+ * @implNote if name is omitted, the name of the annotated
+ * method is used.
+ * @since 0.2.0
+ */
+@Retention(RetentionPolicy.CLASS)
+@java.lang.annotation.Target(ElementType.METHOD)
+public @interface FindMethod {
+ Class<?> parent();
+ String name() default "";
+ Class<?>[] params();
+}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Injector.java b/src/main/java/ftbsc/lll/processor/annotations/Injector.java
index 1e2d8dd..f5e22aa 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Injector.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Injector.java
@@ -6,10 +6,10 @@ import java.lang.annotation.RetentionPolicy;
/**
* Marks a method as the injector method for purposes of generation.
- * The method itself should be {@code public static}, and take in a ClassNode and MethodNode
- * (from the ObjectWeb ASM library) as parameters. It will be discarded otherwise.
- * It will also be discarded unless the containing class is not annotated with {@link Patch}
- * and no other method within the class is annotated with {@link Target}.
+ * The method itself should take in a ClassNode and MethodNode (from the ObjectWeb ASM library)
+ * as parameters. It will be discarded otherwise.
+ * It will also be discarded unless the containing class is annotated with {@link Patch}
+ * and another method within the class is annotated with {@link Target}.
* @see Patch
* @see Target
*/
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Target.java b/src/main/java/ftbsc/lll/processor/annotations/Target.java
index 3a1d209..885bd9c 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Target.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Target.java
@@ -8,8 +8,8 @@ import java.lang.annotation.RetentionPolicy;
* Marks a method as the target method.
* The method itself should have the same name, return type and parameters as the desired
* Minecraft method.
- * It will also be discarded unless the containing class is not annotated with {@link Patch}
- * and no other method within the class is annotated with {@link Injector}.
+ * It will also be discarded unless the containing class is annotated with {@link Patch}
+ * and another method within the class is annotated with {@link Injector}.
* @see Patch
* @see Injector
*/