mirror of
https://github.com/zaaarf/lillero-processor.git
synced 2024-11-22 04:44:52 +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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,11 +200,84 @@ 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.
|
||||
*/
|
||||
|
|
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.
|
||||
* 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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue