mirror of
https://github.com/zaaarf/lillero-processor.git
synced 2024-11-22 17:14:56 +01:00
feat: added @FindField and @FindMethod (untested)
This commit is contained in:
parent
60bd356b5d
commit
6ff87be213
6 changed files with 189 additions and 28 deletions
|
@ -7,9 +7,12 @@ import com.squareup.javapoet.TypeName;
|
||||||
import ftbsc.lll.tools.DescriptorBuilder;
|
import ftbsc.lll.tools.DescriptorBuilder;
|
||||||
|
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import java.lang.annotation.Annotation;
|
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.
|
* 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
|
* Finds, among the methods of a class cl, the one annotated with ann, and tries to build
|
||||||
* a {@link ExecutableElement} from it.
|
* 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 cl the {@link ExecutableElement} for the class containing the desired method
|
||||||
* @param ann the {@link Class} corresponding to the desired annotation
|
* @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 List<ExecutableElement> findAnnotatedMethods(TypeElement cl, Class<? extends Annotation> ann) {
|
||||||
public static ExecutableElement findAnnotatedMethod(TypeElement cl, Class<? extends Annotation> ann) {
|
return cl.getEnclosedElements()
|
||||||
return (ExecutableElement) cl.getEnclosedElements()
|
|
||||||
.stream()
|
.stream()
|
||||||
.filter(e -> e.getAnnotation(ann) != null)
|
.filter(e -> e.getAnnotation(ann) != null)
|
||||||
.findFirst()
|
.map(e -> (ExecutableElement) e)
|
||||||
.get(); //will never be null so can ignore warning
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,4 +86,41 @@ public class ASTUtils {
|
||||||
methodSignature.append(descriptorFromType(m.getReturnType()));
|
methodSignature.append(descriptorFromType(m.getReturnType()));
|
||||||
return methodSignature.toString();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,9 @@ package ftbsc.lll.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
import com.squareup.javapoet.*;
|
||||||
import ftbsc.lll.IInjector;
|
import ftbsc.lll.IInjector;
|
||||||
import ftbsc.lll.processor.annotations.Injector;
|
import ftbsc.lll.processor.annotations.*;
|
||||||
import ftbsc.lll.processor.annotations.Patch;
|
import ftbsc.lll.proxies.FieldProxy;
|
||||||
import ftbsc.lll.processor.annotations.Target;
|
import ftbsc.lll.proxies.MethodProxy;
|
||||||
import ftbsc.lll.tools.DescriptorBuilder;
|
|
||||||
import ftbsc.lll.tools.SrgMapper;
|
import ftbsc.lll.tools.SrgMapper;
|
||||||
|
|
||||||
import javax.annotation.processing.*;
|
import javax.annotation.processing.*;
|
||||||
|
@ -19,16 +18,17 @@ import javax.tools.FileObject;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
import javax.tools.StandardLocation;
|
import javax.tools.StandardLocation;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ftbsc.lll.processor.ASTUtils.descriptorFromMethodSpec;
|
import static ftbsc.lll.processor.ASTUtils.*;
|
||||||
import static ftbsc.lll.processor.ASTUtils.findAnnotatedMethod;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual annotation processor behind the magic.
|
* 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.
|
* 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}
|
* 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.
|
* @param elem the element to check.
|
||||||
* @return whether it can be converted into a valid {@link IInjector}.
|
* @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();
|
List<? extends TypeMirror> params = ((ExecutableType) e.asType()).getParameterTypes();
|
||||||
return e.getAnnotation(Injector.class) != null
|
return e.getAnnotation(Injector.class) != null
|
||||||
&& e.getAnnotation(Target.class) == null
|
&& e.getAnnotation(Target.class) == null
|
||||||
&& e.getModifiers().contains(Modifier.PUBLIC)
|
|
||||||
&& e.getModifiers().contains(Modifier.STATIC)
|
|
||||||
&& params.size() == 2
|
&& params.size() == 2
|
||||||
&& processingEnv.getTypeUtils().isSameType(params.get(0), classNodeType)
|
&& processingEnv.getTypeUtils().isSameType(params.get(0), classNodeType)
|
||||||
&& processingEnv.getTypeUtils().isSameType(params.get(1), methodNodeType);
|
&& 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
|
} //pretty sure class names de facto never change but better safe than sorry
|
||||||
String targetClassSrgName = mapper.getMcpClass(targetClassCanonicalName.replace('.', '/'));
|
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 targetMethodDescriptor = descriptorFromMethodSpec(targetMethod);
|
||||||
String targetMethodSrgName = mapper.getSrgMember(
|
String targetMethodSrgName = mapper.getSrgMember(
|
||||||
targetClassCanonicalName.replace('.', '/'),
|
targetClassCanonicalName.replace('.', '/'),
|
||||||
targetMethod.getSimpleName() + " " + targetMethodDescriptor
|
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();
|
Element packageElement = cl.getEnclosingElement();
|
||||||
while (packageElement.getKind() != ElementKind.PACKAGE)
|
while (packageElement.getKind() != ElementKind.PACKAGE)
|
||||||
|
@ -149,6 +147,7 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
MethodSpec inject = MethodSpec.methodBuilder("inject")
|
MethodSpec inject = MethodSpec.methodBuilder("inject")
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.returns(void.class)
|
.returns(void.class)
|
||||||
|
.addAnnotation(Override.class)
|
||||||
.addParameter(ParameterSpec.builder(
|
.addParameter(ParameterSpec.builder(
|
||||||
TypeName.get(processingEnv
|
TypeName.get(processingEnv
|
||||||
.getElementUtils()
|
.getElementUtils()
|
||||||
|
@ -157,17 +156,24 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
TypeName.get(processingEnv
|
TypeName.get(processingEnv
|
||||||
.getElementUtils()
|
.getElementUtils()
|
||||||
.getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main").build())
|
.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();
|
.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)
|
TypeSpec injectorClass = TypeSpec.classBuilder(injectorSimpleClassName)
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(injectorModifiers.toArray(new Modifier[0]))
|
||||||
|
.superclass(cl.asType())
|
||||||
.addSuperinterface(ClassName.get(IInjector.class))
|
.addSuperinterface(ClassName.get(IInjector.class))
|
||||||
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
||||||
.addMethod(buildStringReturnMethod("reason", ann.reason()))
|
.addMethod(buildStringReturnMethod("reason", ann.reason()))
|
||||||
.addMethod(buildStringReturnMethod("targetClass", targetClassSrgName.replace('/', '.')))
|
.addMethod(buildStringReturnMethod("targetClass", targetClassSrgName.replace('/', '.')))
|
||||||
.addMethod(buildStringReturnMethod("methodName", targetMethodSrgName))
|
.addMethod(buildStringReturnMethod("methodName", targetMethodSrgName))
|
||||||
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
|
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
|
||||||
|
.addMethods(generateRequestedProxies(cl, mapper))
|
||||||
.addMethod(inject)
|
.addMethod(inject)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -194,11 +200,84 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
private static MethodSpec buildStringReturnMethod(String name, String returnString) {
|
private static MethodSpec buildStringReturnMethod(String name, String returnString) {
|
||||||
return MethodSpec.methodBuilder(name)
|
return MethodSpec.methodBuilder(name)
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(Override.class)
|
||||||
.returns(String.class)
|
.returns(String.class)
|
||||||
.addStatement("return $S", returnString)
|
.addStatement("return $S", returnString)
|
||||||
.build();
|
.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.
|
* Generates the Service Provider file for the generated injectors.
|
||||||
*/
|
*/
|
||||||
|
|
21
src/main/java/ftbsc/lll/processor/annotations/FindField.java
Normal file
21
src/main/java/ftbsc/lll/processor/annotations/FindField.java
Normal file
|
@ -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 "";
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -6,10 +6,10 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a method as the injector method for purposes of generation.
|
* 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
|
* The method itself should take in a ClassNode and MethodNode (from the ObjectWeb ASM library)
|
||||||
* (from the ObjectWeb ASM library) as parameters. It will be discarded otherwise.
|
* as parameters. It will be discarded otherwise.
|
||||||
* It will also be discarded unless the containing class is not annotated with {@link Patch}
|
* It will also be discarded unless the containing class is annotated with {@link Patch}
|
||||||
* and no other method within the class is annotated with {@link Target}.
|
* and another method within the class is annotated with {@link Target}.
|
||||||
* @see Patch
|
* @see Patch
|
||||||
* @see Target
|
* @see Target
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,8 +8,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
* Marks a method as the target method.
|
* Marks a method as the target method.
|
||||||
* The method itself should have the same name, return type and parameters as the desired
|
* The method itself should have the same name, return type and parameters as the desired
|
||||||
* Minecraft method.
|
* Minecraft method.
|
||||||
* It will also be discarded unless the containing class is not annotated with {@link Patch}
|
* It will also be discarded unless the containing class is annotated with {@link Patch}
|
||||||
* and no other method within the class is annotated with {@link Injector}.
|
* and another method within the class is annotated with {@link Injector}.
|
||||||
* @see Patch
|
* @see Patch
|
||||||
* @see Injector
|
* @see Injector
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue