aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/processor/containers
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ftbsc/lll/processor/containers')
-rw-r--r--src/main/java/ftbsc/lll/processor/containers/ClassContainer.java130
-rw-r--r--src/main/java/ftbsc/lll/processor/containers/FieldContainer.java109
-rw-r--r--src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java46
-rw-r--r--src/main/java/ftbsc/lll/processor/containers/MethodContainer.java102
4 files changed, 387 insertions, 0 deletions
diff --git a/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java
new file mode 100644
index 0000000..297349a
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java
@@ -0,0 +1,130 @@
+package ftbsc.lll.processor.containers;
+
+import ftbsc.lll.exceptions.TargetNotFoundException;
+import ftbsc.lll.mapper.tools.data.ClassData;
+import ftbsc.lll.processor.annotations.Find;
+import ftbsc.lll.processor.annotations.Patch;
+import ftbsc.lll.processor.ProcessorOptions;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import java.lang.annotation.Annotation;
+import java.util.function.Function;
+
+import static ftbsc.lll.processor.utils.ASTUtils.*;
+
+/**
+ * Container for information about a class.
+ * Used internally for efficiency reasons.
+ * @since 0.5.0
+ */
+public class ClassContainer {
+ /**
+ * The {@link ClassData} for the class represented by this container.
+ */
+ public final ClassData data;
+
+ /**
+ * The {@link Element} corresponding to the class.
+ * May only be null intentionally i.e. when the associated element is
+ * an anonymous class or a child of an anonymous class.
+ */
+ public final Element elem;
+
+ /**
+ * Private constructor, called from {@link #from(Annotation, Function, String, ProcessorOptions)}.
+ * @param fqn the fully-qualified name of the target class
+ * @param innerNames an array of Strings containing the path to the inner class, may be null
+ * @param options the {@link ProcessorOptions} to be used
+ */
+ private ClassContainer(String fqn, String[] innerNames, ProcessorOptions options) {
+ //find and validate
+ Element elem = options.env.getElementUtils().getTypeElement(fqn);
+
+ if(elem == null)
+ throw new TargetNotFoundException("class", fqn);
+
+ StringBuilder fqnBuilder = new StringBuilder(
+ internalNameFromType(elem.asType(), options.env).replace('/', '.')
+ );
+
+ if(innerNames != null) {
+ for(String inner : innerNames) {
+ if(inner == null) continue;
+ fqnBuilder.append("$").append(inner);
+ try {
+ int anonClassCounter = Integer.parseInt(inner);
+ //anonymous classes cannot be validated!
+ if(options.anonymousClassWarning)
+ options.env.getMessager().printMessage(
+ Diagnostic.Kind.WARNING,
+ String.format(
+ "Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!",
+ fqnBuilder, anonClassCounter
+ )
+ );
+ elem = null;
+ break;
+ } catch(NumberFormatException exc) {
+ elem = elem
+ .getEnclosedElements()
+ .stream()
+ .filter(e -> e instanceof TypeElement)
+ .filter(e -> e.getSimpleName().contentEquals(inner))
+ .findFirst()
+ .orElse(null);
+ }
+ if(elem == null)
+ throw new TargetNotFoundException("class", inner);
+ }
+ }
+ this.data = getClassData(fqnBuilder.toString(), options.mapper);
+ this.elem = elem;
+ }
+
+ /**
+ * Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
+ * @param ann the annotation containing the class
+ * @param classFunction the annotation function returning the class
+ * @param innerName a string containing the inner class name or nothing
+ * @param options the {@link ProcessorOptions} to be used
+ * @param <T> the type of the annotation carrying the information
+ * @return the fully qualified name of the given class
+ * @since 0.5.0
+ */
+ public static <T extends Annotation> ClassContainer from(T ann, Function<T, Class<?>> classFunction, String innerName, ProcessorOptions options) {
+ String fqn;
+ String[] inner;
+ fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString();
+ inner = innerName.equals("") ? null : innerName.split("//$");
+ return new ClassContainer(fqn, inner, options);
+ }
+
+ /**
+ * Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
+ * @param cl the {@link TypeElement} representing the class
+ * @param options the {@link ProcessorOptions} to be used
+ * @return the fully qualified name of the given class
+ * @since 0.6.0
+ */
+ public static ClassContainer from(TypeElement cl, ProcessorOptions options) {
+ return new ClassContainer(cl.getQualifiedName().toString(), null, options);
+ }
+
+ /**
+ * Finds and builds a {@link ClassContainer} based on information contained
+ * within {@link Patch} or a {@link Find} annotations, else returns a fallback.
+ * @param fallback the {@link ClassContainer} it falls back on
+ * @param p the {@link Patch} annotation to get info from
+ * @param f the {@link Find} annotation to get info from
+ * @param options the {@link ProcessorOptions} to be used
+ * @return the built {@link ClassContainer} or the fallback if not enough information was present
+ * @since 0.5.0
+ */
+ public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) {
+ if(f == null) return ClassContainer.from(p, Patch::value, p.innerName(), options);
+ ClassContainer cl = ClassContainer.from(f, Find::value, f.innerName(), options);
+ return cl.data.name.equals("java/lang/Object") ? fallback : cl;
+ }
+}
diff --git a/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java
new file mode 100644
index 0000000..2299c93
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java
@@ -0,0 +1,109 @@
+package ftbsc.lll.processor.containers;
+
+import ftbsc.lll.exceptions.AmbiguousDefinitionException;
+import ftbsc.lll.mapper.tools.MappingUtils;
+import ftbsc.lll.mapper.tools.data.FieldData;
+import ftbsc.lll.processor.annotations.Find;
+import ftbsc.lll.processor.annotations.Patch;
+import ftbsc.lll.processor.ProcessorOptions;
+import org.objectweb.asm.Type;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+import static ftbsc.lll.processor.utils.ASTUtils.*;
+
+/**
+ * Container for information about a field.
+ * Used internally for efficiency reasons.
+ * @since 0.5.0
+ */
+public class FieldContainer {
+ /**
+ * The {@link FieldData} for the field represented by this container.
+ */
+ public final FieldData data;
+
+ /**
+ * The descriptor of the field.
+ */
+ public final String descriptor;
+
+ /**
+ * The obfuscated descriptor of the field.
+ * If the mapper passed is null, then this will be identical to {@link #descriptor}.
+ */
+ public final String descriptorObf;
+
+ /**
+ * The {@link ClassContainer} representing the parent of this field.
+ */
+ public final ClassContainer parent;
+
+ /**
+ * The {@link VariableElement} corresponding to the field.
+ * May only be null intentionally i.e. when the field is
+ * a child of an anonymous class.
+ */
+ public final VariableElement elem;
+
+ /**
+ * Private constructor, called from {@link #from(VariableElement, ProcessorOptions)}.
+ * @param parent the {@link ClassContainer} representing the parent
+ * @param name the fully-qualified name of the target field
+ * @param descriptor the descriptor of the target field, may be null for verifiable fields
+ * @param options the {@link ProcessorOptions} to be used
+ */
+ private FieldContainer(ClassContainer parent, String name, String descriptor, ProcessorOptions options) {
+ this.parent = parent;
+ if(parent.elem == null) { //unverified
+ if(descriptor == null)
+ throw new AmbiguousDefinitionException("Cannot use name-based lookups for fields of unverifiable classes!");
+ this.elem = null;
+ this.descriptor = descriptor;
+ } else {
+ this.elem = (VariableElement) findMember(parent, name, descriptor, descriptor != null, true, options.env);
+ this.descriptor = descriptorFromType(this.elem.asType(), options.env);
+ name = this.elem.getSimpleName().toString();
+ }
+ this.data = getFieldData(parent.data.name, name, options.mapper);
+ this.descriptorObf = options.mapper == null ? this.descriptor
+ : MappingUtils.mapType(Type.getType(this.descriptor), options.mapper, false).getDescriptor();
+ }
+
+ /**
+ * Finds a {@link FieldContainer} from a finder.
+ * @param finder the {@link VariableElement} annotated with {@link Find} for this field
+ * @param options the {@link ProcessorOptions} to be used
+ * @return the built {@link FieldContainer}
+ * @since 0.5.0
+ */
+ public static FieldContainer from(VariableElement finder, ProcessorOptions options) {
+ //the parent always has a @Patch annotation
+ Patch patchAnn = finder.getEnclosingElement().getAnnotation(Patch.class);
+ //the finder always has a @Find annotation
+ Find f = finder.getAnnotation(Find.class);
+
+ ClassContainer parent = ClassContainer.findOrFallback(
+ ClassContainer.from((TypeElement) finder.getEnclosingElement(), options), patchAnn, f, options
+ );
+
+ String name = f.name().isEmpty() ? finder.getSimpleName().toString() : f.name();
+ String descriptor;
+ TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, options.env);
+ if(fieldType.toString().equals("java.lang.Object")) {
+ descriptor = null;
+ } else {
+ if(fieldType.getKind() != TypeKind.VOID && !fieldType.getKind().isPrimitive())
+ descriptor = //jank af but this is temporary anyway
+ "L" + ClassContainer.from(
+ f, Find::type, f.typeInner(), options
+ ).data.nameMapped + ";";
+ else descriptor = descriptorFromType(fieldType, options.env);
+ }
+
+ return new FieldContainer(parent, name, descriptor, options);
+ }
+}
diff --git a/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java b/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java
new file mode 100644
index 0000000..d56d846
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java
@@ -0,0 +1,46 @@
+package ftbsc.lll.processor.containers;
+
+import ftbsc.lll.processor.annotations.Injector;
+import ftbsc.lll.processor.annotations.Target;
+import ftbsc.lll.processor.ProcessorOptions;
+
+import javax.lang.model.element.ExecutableElement;
+
+/**
+ * Container for information about a class that is to be generated.
+ */
+public class InjectorInfo {
+ /**
+ * The {@link ExecutableElement} corresponding to the injector method.
+ */
+ public final ExecutableElement injector;
+
+ /**
+ * The {@link ExecutableElement} corresponding to the target method stub.
+ */
+ public final ExecutableElement targetStub;
+
+ /**
+ * The reason for the injection.
+ */
+ public final String reason;
+
+ /**
+ * The {@link MethodContainer} corresponding to the target method.
+ */
+ public final MethodContainer target;
+
+ /**
+ * Public constructor.
+ * @param injector the injector {@link ExecutableElement}
+ * @param targetStub the target {@link ExecutableElement}
+ * @param targetAnn the relevant {@link Target} annotation
+ * @param options the {@link ProcessorOptions} to be used
+ */
+ public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub, Target targetAnn, ProcessorOptions options) {
+ this.injector = injector;
+ this.targetStub = targetStub;
+ this.reason = injector.getAnnotation(Injector.class).reason();
+ this.target = MethodContainer.from(targetStub, targetAnn, null, options);
+ }
+}
diff --git a/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java
new file mode 100644
index 0000000..7d15599
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java
@@ -0,0 +1,102 @@
+package ftbsc.lll.processor.containers;
+
+import ftbsc.lll.exceptions.AmbiguousDefinitionException;
+import ftbsc.lll.exceptions.TargetNotFoundException;
+import ftbsc.lll.mapper.tools.MappingUtils;
+import ftbsc.lll.mapper.tools.data.MethodData;
+import ftbsc.lll.processor.annotations.Find;
+import ftbsc.lll.processor.annotations.Patch;
+import ftbsc.lll.processor.annotations.Target;
+import ftbsc.lll.processor.ProcessorOptions;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+import static ftbsc.lll.processor.utils.ASTUtils.*;
+
+/**
+ * Container for information about a method.
+ * Used internally for efficiency reasons.
+ * @since 0.5.0
+ */
+public class MethodContainer {
+ /**
+ * The {@link MethodData} for the method represented by this container.
+ */
+ public final MethodData data;
+
+ /**
+ * The obfuscated descriptor of the field.
+ * If the mapper passed is null, this will be identical to the one inside
+ * {@link #data}.
+ */
+ public final String descriptorObf;
+
+ /**
+ * The {@link ClassContainer} representing the parent of this method.
+ */
+ public final ClassContainer parent;
+
+ /**
+ * The {@link ExecutableElement} corresponding to the method.
+ * May only be null intentionally i.e. when the method is
+ * a child of an anonymous class.
+ */
+ public final ExecutableElement elem;
+
+ /**
+ * Private constructor, called from
+ * {@link #from(ExecutableElement, Target, Find, ProcessorOptions)}.
+ * @param parent the {@link ClassContainer} representing the parent
+ * @param name the fully-qualified name of the target method
+ * @param descriptor the descriptor of the target method
+ * @param strict whether the matching should be strict (see {@link Target#strict()} for more info)
+ * @param bridge whether the "bridge" should be matched instead (see {@link Target#bridge()} for more info)
+ * @param options the {@link ProcessorOptions} to be used
+ */
+ private MethodContainer(ClassContainer parent, String name, String descriptor, boolean strict, boolean bridge, ProcessorOptions options) {
+ this.parent = parent;
+ if(parent.elem == null) { //unverified
+ if(descriptor == null)
+ throw new AmbiguousDefinitionException("Cannot use name-based lookups for methods of unverifiable classes!");
+ this.elem = null;
+ } else {
+ ExecutableElement tmp = (ExecutableElement) findMember(
+ parent, name, descriptor, descriptor != null && strict,false, options.env
+ );
+ this.elem = bridge ? findSyntheticBridge((TypeElement) this.parent.elem, tmp, options.env) : tmp;
+ name = this.elem.getSimpleName().toString();
+ descriptor = descriptorFromExecutableElement(this.elem, options.env);
+ }
+ this.data = getMethodData(parent.data.name, name, descriptor, options.mapper);
+ this.descriptorObf = options.mapper == null ? this.data.signature.descriptor
+ : MappingUtils.mapMethodDescriptor(this.data.signature.descriptor, options.mapper, false);
+ }
+
+ /**
+ * Builds the {@link MethodContainer} corresponding to a stub annotated with {@link Target}.
+ * @param stub the {@link ExecutableElement} for the stub
+ * @param t the {@link Target} annotation relevant to this case
+ * @param f the {@link Find} annotation containing fallback data, may be null
+ * @param options the {@link ProcessorOptions} to be used
+ * @return the {@link MethodContainer} corresponding to the method
+ * @throws AmbiguousDefinitionException if it finds more than one candidate
+ * @throws TargetNotFoundException if it finds no valid candidate
+ * @since 0.3.0
+ */
+ public static MethodContainer from(ExecutableElement stub, Target t, Find f, ProcessorOptions options) {
+ //the parent always has a @Patch annotation
+ Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
+ ClassContainer parent = ClassContainer.findOrFallback(
+ ClassContainer.from((TypeElement) stub.getEnclosingElement(), options), patchAnn, f, options
+ );
+ String name = !t.methodName().isEmpty()
+ ? t.methodName() //name was specified in target
+ : stub.getSimpleName().toString();
+ String descriptor = t.strict()
+ ? descriptorFromExecutableElement(stub, options.env)
+ : null;
+
+ return new MethodContainer(parent, name, descriptor, t.strict(), t.bridge(), options);
+ }
+}