diff options
Diffstat (limited to 'src/main/java/ftbsc/lll/processor')
6 files changed, 146 insertions, 31 deletions
diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java index a7c1962..1839183 100644 --- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java +++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java @@ -4,8 +4,13 @@ import com.squareup.javapoet.*; import ftbsc.lll.IInjector; import ftbsc.lll.processor.annotations.Injector; import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.exceptions.MappingNotFoundException; +import ftbsc.lll.processor.exceptions.MappingsFileNotFoundException; import ftbsc.lll.tools.DescriptorBuilder; import ftbsc.lll.tools.SrgMapper; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.ExecutableElement; @@ -27,22 +32,24 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +/** + * The actual annotation processor behind the magic. + * It (implicitly) implements the {@link Processor} interface by extending {@link AbstractProcessor}. + */ @SupportedAnnotationTypes("ftbsc.lll.processor.annotations.Patch") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class LilleroProcessor extends AbstractProcessor { - private SrgMapper mapper; - - @Override - public void init(ProcessingEnvironment processingEnv) { - try { - mapper = new SrgMapper(Files.lines(Paths.get("build/createMcpToSrg/output.tsrg"))); - } catch(IOException e) { - throw new RuntimeException(e); - } - super.init(processingEnv); - } - + /** + * Where the actual processing happens. + * It filters through whatever annotated class it's fed, and checks whether it contains + * the required information. It then generates injectors and a service provider for every + * remaining class. + * @see LilleroProcessor#isValidInjector(TypeElement) + * @param annotations the annotation types requested to be processed + * @param roundEnv environment for information about the current and prior round + * @return whether or not the set of annotation types are claimed by this processor + */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<TypeElement> validInjectors = new HashSet<>(); @@ -66,7 +73,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 a MethodNode from the ObjectWeb library. + * that must be public, static and take in a {@link ClassNode} and a {@link MethodNode}. * @param elem the element to check. * @return whether it can be converted into a valid {@link IInjector}. */ @@ -77,6 +84,7 @@ public class LilleroProcessor extends AbstractProcessor { && elem.getEnclosedElements().stream().anyMatch(e -> { 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 @@ -85,6 +93,16 @@ public class LilleroProcessor extends AbstractProcessor { }); } + /** + * Finds, among the methods of a class cl, the one annotated with ann, and tries to build + * a {@link MethodSpec} 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 TypeElement} 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 + */ + @SuppressWarnings("OptionalGetWithoutIsPresent") private static MethodSpec findAnnotatedMethod(TypeElement cl, Class<? extends Annotation> ann) { return MethodSpec.overriding( (ExecutableElement) cl.getEnclosedElements() @@ -95,18 +113,19 @@ public class LilleroProcessor extends AbstractProcessor { ).build(); } - private static String getClassOrString(TypeName n) { //jank but fuck you - return n.isPrimitive() ? n + ".class" : "\"" + n + "\""; - } - - private static String descriptorFromType(TypeName type) { + /** + * Builds a type descriptor from the given {@link TypeName} + * @param type the {@link TypeName} representing the desired type + * @return a {@link String} containing the relevant descriptor + */ + public static String descriptorFromType(TypeName type) { StringBuilder desc = new StringBuilder(); //add array brackets while(type instanceof ArrayTypeName) { desc.append("["); type = ((ArrayTypeName) type).componentType; } - if(type instanceof ClassName) { //todo maybe? + if(type instanceof ClassName) { ClassName var = (ClassName) type; desc.append(DescriptorBuilder.nameToDescriptor(var.canonicalName(), 0)); desc.append(";"); @@ -131,6 +150,11 @@ public class LilleroProcessor extends AbstractProcessor { return desc.toString(); } + /** + * Builds a method descriptor from the given {@link MethodSpec}. + * @param m the {@link MethodSpec} for the method + * @return a {@link String} containing the relevant descriptor + */ public static String descriptorFromMethodSpec(MethodSpec m) { StringBuilder methodSignature = new StringBuilder(); methodSignature.append("("); @@ -140,11 +164,24 @@ public class LilleroProcessor extends AbstractProcessor { return methodSignature.toString(); } + /** + * Generates the Injector corresponding to the given class. + * Basically implements the {@link IInjector} interface for you. + * @param cl the {@link TypeElement} for the given class + */ private void generateInjector(TypeElement cl) { Patch ann = cl.getAnnotation(Patch.class); MethodSpec targetMethod = findAnnotatedMethod(cl, Target.class); MethodSpec injectorMethod = findAnnotatedMethod(cl, Injector.class); + SrgMapper mapper; + + try { + mapper = new SrgMapper(Files.lines(Paths.get("build/createMcpToSrg/output.tsrg"))); + } catch(IOException e) { + throw new MappingsFileNotFoundException(); + } + String packageName = cl.getQualifiedName().toString().replace("." + cl.getSimpleName().toString(), ""); String className = cl.getQualifiedName().toString(); @@ -165,20 +202,33 @@ public class LilleroProcessor extends AbstractProcessor { .addStatement("return $S", ann.reason()) .build(); + //pretty sure class names de facto never change but better safe than sorry + String targetClassSrgName = mapper.getMcpClass( + ClassName.get(ann.value()).canonicalName().replace('.', '/') + ); + + if(targetClassSrgName == null) + throw new MappingNotFoundException(ClassName.get(ann.value()).canonicalName()); + MethodSpec targetClass = MethodSpec.methodBuilder("targetClass") .addModifiers(Modifier.PUBLIC) .returns(String.class) - .addStatement("return $S", mapper.getMcpClass(ClassName.get(ann.value()).canonicalName().replace('.', '/'))) + .addStatement("return $S", targetClassSrgName) .build(); String targetMethodDescriptor = descriptorFromMethodSpec(targetMethod); + String targetMethodSrgName = mapper.getSrgMember( + ann.value().getName(), targetMethod.name + " " + targetMethodDescriptor + ); + + if(targetMethodSrgName == null) + throw new MappingNotFoundException(targetMethod.name + " " + targetMethodDescriptor); MethodSpec methodName = MethodSpec.methodBuilder("methodName") .addModifiers(Modifier.PUBLIC) .returns(String.class) - .addStatement("return $S", mapper.getSrgMember( - ann.value().getName(), targetMethod.name + " " + targetMethodDescriptor) - ).build(); + .addStatement("return $S", targetMethodSrgName) + .build(); MethodSpec methodDesc = MethodSpec.methodBuilder("methodDesc") .addModifiers(Modifier.PUBLIC) @@ -189,8 +239,14 @@ public class LilleroProcessor extends AbstractProcessor { MethodSpec inject = MethodSpec.methodBuilder("inject") .addModifiers(Modifier.PUBLIC) .returns(void.class) - .addParameter(ParameterSpec.builder((TypeName) processingEnv.getElementUtils().getTypeElement("org.objectweb.asm.tree.ClassNode").asType(), "clazz").build()) - .addParameter(ParameterSpec.builder((TypeName) processingEnv.getElementUtils().getTypeElement("org.objectweb.asm.tree.MethodNode").asType(), "main").build()) + .addParameter(ParameterSpec.builder( + (TypeName) processingEnv + .getElementUtils() + .getTypeElement("org.objectweb.asm.tree.ClassNode").asType(), "clazz").build()) + .addParameter(ParameterSpec.builder( + (TypeName) processingEnv + .getElementUtils() + .getTypeElement("org.objectweb.asm.tree.MethodNode").asType(), "main").build()) .addStatement("$S.$S(clazz, main)", className, injectorMethod.name) .build(); @@ -211,15 +267,24 @@ public class LilleroProcessor extends AbstractProcessor { JavaFile javaFile = JavaFile.builder(packageName, injectorClass).build(); javaFile.writeTo(out); out.close(); - } catch(IOException e) {} //todo + } catch(IOException e) { + throw new RuntimeException(e); + } } + /** + * Generates the Service Provider file for the generated injectors. + * It gets their names by appending "Injector" to the original class. + * @param inj a {@link Set} of {@link TypeElement} representing the valid injector generators + */ private void generateServiceProvider(Set<TypeElement> inj) { try { FileObject serviceProvider = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", "ftbsc.lll.IInjector"); PrintWriter out = new PrintWriter(serviceProvider.openWriter()); inj.forEach(i -> out.println(i.getQualifiedName() + "Injector")); out.close(); - } catch(IOException e) {} //todo + } catch(IOException e) { + throw new RuntimeException(e); + } } }
\ No newline at end of file diff --git a/src/main/java/ftbsc/lll/processor/annotations/Injector.java b/src/main/java/ftbsc/lll/processor/annotations/Injector.java index 6184610..32c91c3 100644 --- a/src/main/java/ftbsc/lll/processor/annotations/Injector.java +++ b/src/main/java/ftbsc/lll/processor/annotations/Injector.java @@ -3,8 +3,19 @@ package ftbsc.lll.processor.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Marks a method as the injector method for purposes of generation. + * The method itself should be {@code public static}, and take in a {@link ClassNode} + * and a {@link MethodNode} 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}. + * @see Patch + * @see Target + */ @Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) +@java.lang.annotation.Target(ElementType.METHOD) public @interface Injector {} diff --git a/src/main/java/ftbsc/lll/processor/annotations/Patch.java b/src/main/java/ftbsc/lll/processor/annotations/Patch.java index adbb674..9b0f5da 100644 --- a/src/main/java/ftbsc/lll/processor/annotations/Patch.java +++ b/src/main/java/ftbsc/lll/processor/annotations/Patch.java @@ -3,11 +3,24 @@ package ftbsc.lll.processor.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +/** + * Marks the class as containing an injector for a user-specified {@link Class}. + * It will be discarded unless {@link ftbsc.lll.processor.annotations.Target} and + * {@link Injector} are properly placed within. + * @see Target + * @see Injector + */ @Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) +@java.lang.annotation.Target(ElementType.TYPE) public @interface Patch { + /** + * @return the Minecraft {@link Class} to target for patching + */ Class<?> value(); + + /** + * @return the patching reason, for logging, defaults to "No reason specified." + */ String reason() default "No reason specified."; } diff --git a/src/main/java/ftbsc/lll/processor/annotations/Target.java b/src/main/java/ftbsc/lll/processor/annotations/Target.java index e408755..14b4e7c 100644 --- a/src/main/java/ftbsc/lll/processor/annotations/Target.java +++ b/src/main/java/ftbsc/lll/processor/annotations/Target.java @@ -4,6 +4,15 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; 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}. + * @see Patch + * @see Injector + */ @Retention(RetentionPolicy.SOURCE) @java.lang.annotation.Target(ElementType.METHOD) public @interface Target {} diff --git a/src/main/java/ftbsc/lll/processor/exceptions/MappingNotFoundException.java b/src/main/java/ftbsc/lll/processor/exceptions/MappingNotFoundException.java new file mode 100644 index 0000000..ca1f75c --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/exceptions/MappingNotFoundException.java @@ -0,0 +1,7 @@ +package ftbsc.lll.processor.exceptions; + +public class MappingNotFoundException extends RuntimeException { + public MappingNotFoundException(String mapping) { + super("Could not find mapping for " + mapping + "!"); + } +} diff --git a/src/main/java/ftbsc/lll/processor/exceptions/MappingsFileNotFoundException.java b/src/main/java/ftbsc/lll/processor/exceptions/MappingsFileNotFoundException.java new file mode 100644 index 0000000..8636226 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/exceptions/MappingsFileNotFoundException.java @@ -0,0 +1,10 @@ +package ftbsc.lll.processor.exceptions; + +/** + * Thrown upon failure to locate the output.tsrg file at runtime. + */ +public class MappingsFileNotFoundException extends RuntimeException { + public MappingsFileNotFoundException() { + super("Could not find a mappings file in the specified location!"); + } +} |