chore: added proper documentation, cleaned up build.gradle, added exceptions

This commit is contained in:
zaaarf 2023-02-26 20:16:48 +01:00
parent eca2d1f312
commit 96b2bc5536
No known key found for this signature in database
GPG key ID: 82240E075E31FA4C
7 changed files with 147 additions and 36 deletions

View file

@ -1,11 +1,8 @@
import org.gradle.internal.jvm.Jvm
plugins {
id 'java-gradle-plugin'
}
group 'ftbsc.lll.processor'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
@ -13,10 +10,9 @@ repositories {
}
dependencies {
implementation files(Jvm.current().toolsJar)
implementation 'com.squareup:javapoet:1.13.0'
implementation 'ftbsc:lll:0.2.0'
implementation 'org.ow2.asm:asm-commons:9.4'
implementation 'org.ow2.asm:asm-commons:9.4' //just for the javadocs
}
gradlePlugin {

View file

@ -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);
}
}
}

View file

@ -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 {}

View file

@ -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.";
}

View file

@ -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 {}

View file

@ -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 + "!");
}
}

View file

@ -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!");
}
}