From bf32e56a863e83b82efe9732c585979afb0e62c3 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 24 Jan 2024 17:21:42 +0100 Subject: chore: internal reorganisation --- .../java/ftbsc/lll/processor/LilleroProcessor.java | 11 +- .../java/ftbsc/lll/processor/ProcessorOptions.java | 85 +++++ .../lll/processor/containers/ClassContainer.java | 130 +++++++ .../lll/processor/containers/FieldContainer.java | 109 ++++++ .../lll/processor/containers/InjectorInfo.java | 46 +++ .../lll/processor/containers/MethodContainer.java | 102 +++++ .../java/ftbsc/lll/processor/tools/ASTUtils.java | 409 --------------------- .../ftbsc/lll/processor/tools/JavaPoetUtils.java | 170 --------- .../lll/processor/tools/ProcessorOptions.java | 85 ----- .../processor/tools/containers/ClassContainer.java | 130 ------- .../processor/tools/containers/FieldContainer.java | 109 ------ .../processor/tools/containers/InjectorInfo.java | 46 --- .../tools/containers/MethodContainer.java | 102 ----- .../java/ftbsc/lll/processor/utils/ASTUtils.java | 409 +++++++++++++++++++++ .../ftbsc/lll/processor/utils/JavaPoetUtils.java | 171 +++++++++ 15 files changed, 1057 insertions(+), 1057 deletions(-) create mode 100644 src/main/java/ftbsc/lll/processor/ProcessorOptions.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/ClassContainer.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/FieldContainer.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/MethodContainer.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/ASTUtils.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java create mode 100644 src/main/java/ftbsc/lll/processor/utils/ASTUtils.java create mode 100644 src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java index 55eb81f..b693592 100644 --- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java +++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java @@ -8,10 +8,9 @@ import ftbsc.lll.IInjector; import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.OrphanElementException; import ftbsc.lll.processor.annotations.*; -import ftbsc.lll.processor.tools.ProcessorOptions; -import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.processor.tools.containers.InjectorInfo; -import ftbsc.lll.processor.tools.containers.MethodContainer; +import ftbsc.lll.processor.containers.ClassContainer; +import ftbsc.lll.processor.containers.InjectorInfo; +import ftbsc.lll.processor.containers.MethodContainer; import ftbsc.lll.proxies.ProxyType; import ftbsc.lll.proxies.impl.TypeProxy; @@ -29,8 +28,8 @@ import java.io.PrintWriter; import java.util.*; import java.util.stream.Collectors; -import static ftbsc.lll.processor.tools.ASTUtils.*; -import static ftbsc.lll.processor.tools.JavaPoetUtils.*; +import static ftbsc.lll.processor.utils.ASTUtils.*; +import static ftbsc.lll.processor.utils.JavaPoetUtils.*; /** * The actual annotation processor behind the magic. diff --git a/src/main/java/ftbsc/lll/processor/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/ProcessorOptions.java new file mode 100644 index 0000000..5972631 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/ProcessorOptions.java @@ -0,0 +1,85 @@ +package ftbsc.lll.processor; + +import ftbsc.lll.IInjector; +import ftbsc.lll.mapper.MapperProvider; +import ftbsc.lll.mapper.tools.Mapper; + +import javax.annotation.processing.ProcessingEnvironment; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Class in charge of containing, parsing and processing all processor options, + * from the simpler booleans to the more complicated mapper. + */ +public class ProcessorOptions { + + /** + * A {@link Set} of options currently supported by the processor. + */ + public static final Set SUPPORTED = new HashSet<>(Arrays.asList( + "mappingsFile", "anonymousClassWarning", "obfuscateInjectorMetadata", + "noServiceProvider" + )); + + /** + * The environment the processor is acting in. + */ + public final ProcessingEnvironment env; + + /** + * The {@link Mapper} used to convert classes and variables + * to their obfuscated equivalent. Will be null when no mapper is in use. + */ + public final Mapper mapper; + + /** + * Whether the processor should issue warnings when compiling code anonymous + * classes which can't be checked for validity. + */ + public final boolean anonymousClassWarning; + + /** + * Whether injector metadata (what is returned by the functions of {@link IInjector}) + * is to use obfuscated names instead of its normal names. + */ + public final boolean obfuscateInjectorMetadata; + + /** + * Whether the processor should skip the generation of the service provider. + */ + public final boolean noServiceProvider; + + /** + * The public constructor, parses and stores all given arguments. + * @param env the environment the processor is working in + */ + public ProcessorOptions(ProcessingEnvironment env) { + this.env = env; + String location = env.getOptions().get("mappingsFile"); + if(location != null) { + List lines = MapperProvider.fetchFromLocalOrRemote(location); + this.mapper = MapperProvider.getMapper(lines).getMapper(lines, true); + } else this.mapper = null; + this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); + this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); + this.noServiceProvider = parseBooleanArg(env.getOptions().get("noServiceProvider"), false); + } + + /** + * Parses a boolean arg from a String. + * @param arg the arg to parse + * @return the parsed boolean + */ + private static boolean parseBooleanArg(String arg, boolean defaultValue) { + if(arg == null) return defaultValue; + try { // 0 = false, any other integer = true + int i = Integer.parseInt(arg); + return i != 0; + } catch(NumberFormatException ignored) { + return Boolean.parseBoolean(arg); + } + } +} 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 the type of the annotation carrying the information + * @return the fully qualified name of the given class + * @since 0.5.0 + */ + public static ClassContainer from(T ann, Function> 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); + } +} diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java deleted file mode 100644 index 326f52d..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java +++ /dev/null @@ -1,409 +0,0 @@ -package ftbsc.lll.processor.tools; - -import ftbsc.lll.exceptions.AmbiguousDefinitionException; -import ftbsc.lll.exceptions.MappingNotFoundException; -import ftbsc.lll.exceptions.NotAProxyException; -import ftbsc.lll.exceptions.TargetNotFoundException; -import ftbsc.lll.mapper.tools.Mapper; -import ftbsc.lll.mapper.tools.data.ClassData; -import ftbsc.lll.mapper.tools.data.FieldData; -import ftbsc.lll.mapper.tools.data.MethodData; -import ftbsc.lll.processor.annotations.Target; -import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.proxies.ProxyType; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import javax.lang.model.type.*; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Collection of AST-related static utils that didn't really fit into the main class. - */ -public class ASTUtils { - /** - * Finds, among the methods of a class cl, the one annotated with ann, and tries to build - * an {@link Element} from it. - * @param parent the parent {@link Element} to the desired element - * @param ann the {@link Class} corresponding to the desired annotation - * @param the type of {@link Element} to use - * @return a {@link List} of {@link Element}s annotated with the given annotation - * @since 0.2.0 - */ - @SuppressWarnings("unchecked") - public static List findAnnotatedElement(Element parent, Class ann) { - return parent.getEnclosedElements() - .stream() - .filter(e -> e.getAnnotationsByType(ann).length != 0) - .map(e -> (T) e) - .collect(Collectors.toList()); - } - - /** - * 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; - } - } - - /** - * Takes in a {@link Collection} of AST {@link Modifier}s and - * returns them mapped to their reflective integer equivalent. - * @param modifiers the {@link Modifier}s - * @return an integer value representing them - * @since 0.5.0 - */ - public static int mapModifiers(Collection modifiers) { - int i = 0; - for(Modifier m : modifiers) - i |= mapModifier(m); - return i; - } - - /** - * Safely extracts a {@link Class} from an annotation and gets a {@link TypeMirror} representing it. - * @param ann the annotation containing the class - * @param classFunction the annotation function returning the class - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @param the type of the annotation carrying the information - * @return a {@link TypeMirror} representing the requested {@link Class} - * @since 0.3.0 - */ - public static TypeMirror getTypeFromAnnotation( - T ann, Function> classFunction, ProcessingEnvironment env) { - try { - String fqn = classFunction.apply(ann).getCanonicalName(); - if(fqn == null) - fqn = ""; - return env.getElementUtils().getTypeElement(fqn).asType(); - } catch(MirroredTypeException e) { - return e.getTypeMirror(); - } - } - - /** - * Gets the internal name from an {@link TypeMirror}. - * @param type the {@link TypeMirror} in question - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the internal name at compile time, or null if it wasn't a qualifiable - * @since 0.5.1 - */ - public static String internalNameFromType(TypeMirror type, ProcessingEnvironment env) { - //needed to actually turn elem into a TypeVariable, find it ignoring generics - Element elem = env.getTypeUtils().asElement(env.getTypeUtils().erasure(type)); - StringBuilder fqnBuilder = new StringBuilder(); - while(elem.getEnclosingElement() != null && elem.getEnclosingElement().getKind() != ElementKind.PACKAGE) { - fqnBuilder - .insert(0, elem.getSimpleName().toString()) - .insert(0, "$"); - elem = elem.getEnclosingElement(); - } - return fqnBuilder - .insert(0, env.getTypeUtils().erasure(elem.asType()).toString()) - .toString() - .replace('.', '/'); - } - - /** - * Builds a type descriptor from the given {@link TypeMirror}. - * @param t the {@link TypeMirror} representing the desired type - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return a {@link String} containing the relevant descriptor - */ - public static String descriptorFromType(TypeMirror t, ProcessingEnvironment env) { - t = env.getTypeUtils().erasure(t); //type erasure - - StringBuilder desc = new StringBuilder(); - //add array brackets - while(t.getKind() == TypeKind.ARRAY) { - desc.append("["); - t = ((ArrayType) t).getComponentType(); - } - - if(t.getKind() == TypeKind.TYPEVAR) - t = ((TypeVariable) t).getUpperBound(); - - if(t.getKind() == TypeKind.DECLARED) - desc - .append("L") - .append(internalNameFromType(t, env)) - .append(";"); - else { - switch(t.getKind()) { - case BOOLEAN: - desc.append("Z"); - break; - case CHAR: - desc.append("C"); - break; - case BYTE: - desc.append("B"); - break; - case SHORT: - desc.append("S"); - break; - case INT: - desc.append("I"); - break; - case FLOAT: - desc.append("F"); - break; - case LONG: - desc.append("J"); - break; - case DOUBLE: - desc.append("D"); - break; - case VOID: - desc.append("V"); - break; - } - } - - return desc.toString(); - } - - /** - * Builds a method descriptor from the given {@link ExecutableElement}. - * @param m the {@link ExecutableElement} for the method - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return a {@link String} containing the relevant descriptor - */ - public static String descriptorFromExecutableElement(ExecutableElement m, ProcessingEnvironment env) { - StringBuilder methodSignature = new StringBuilder(); - methodSignature.append("("); - m.getParameters().forEach(p -> methodSignature.append(descriptorFromType(p.asType(), env))); - methodSignature.append(")"); - methodSignature.append(descriptorFromType(m.getReturnType(), env)); - return methodSignature.toString(); - } - - /** - * Gets the {@link ClassData} corresponding to the given fully-qualified name, - * or creates a false one with the same, non-obfuscated name twice. - * @param name the internal name of the class to convert - * @param mapper the {@link Mapper} to use, may be null - * @return the fully qualified class name - * @since 0.6.1 - */ - public static ClassData getClassData(String name, Mapper mapper) { - try { - name = name.replace('.', '/'); //just in case - if(mapper != null) - return mapper.getClassData(name); - } catch(MappingNotFoundException ignored) {} - return new ClassData(name, name); - } - - /** - * Gets the {@link MethodData} corresponding to the method matching the given - * name, parent and descriptor, or creates a dummy one with fake data if no - * valid mapping is found. - * @param parent the internal name of the parent class - * @param name the name of the member - * @param descriptor the descriptor of the method - * @param mapper the {@link Mapper} to use, may be null - * @return the fully qualified class name - * @since 0.6.1 - */ - public static MethodData getMethodData(String parent, String name, String descriptor, Mapper mapper) { - try { - name = name.replace('.', '/'); //just in case - if(mapper != null) - return mapper.getMethodData(parent, name, descriptor); - } catch(MappingNotFoundException ignored) {} - return new MethodData(getClassData(name, mapper), name, name, descriptor); - } - - /** - * Gets the {@link FieldData} corresponding to the field matching the given - * name and parent, or creates a dummy one with fake data if no valid - * mapping is found. - * @param parent the internal name of the parent class - * @param name the name of the member - * @param mapper the {@link Mapper} to use, may be null - * @return the fully qualified class name - * @since 0.6.1 - */ - public static FieldData getFieldData(String parent, String name, Mapper mapper) { - try { - name = name.replace('.', '/'); //just in case - if(mapper != null) - return mapper.getFieldData(parent, name); - } catch(MappingNotFoundException ignored) {} - return new FieldData(getClassData(name, mapper), name, name); - } - - /** - * Finds a member given the name, the container class and (if it's a method) the descriptor. - * @param parent the {@link ClassContainer} representing the parent - * @param name the name to search for - * @param descr the descriptor to search for, or null if it's not a method - * @param strict whether to perform lookup in strict mode (see {@link Target#strict()} for more information) - * @param field whether the member being searched is a field - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the desired member, if it exists - * @throws AmbiguousDefinitionException if it finds more than one candidate - * @throws TargetNotFoundException if it finds no valid candidate - * @since 0.3.0 - */ - public static Element findMember( - ClassContainer parent, String name, String descr, - boolean strict, boolean field, ProcessingEnvironment env) { - if(parent.elem == null) - throw new TargetNotFoundException("parent class", parent.data.name); - //try to find by name - List candidates = parent.elem.getEnclosedElements() - .stream() - .filter(e -> (field && e instanceof VariableElement) || e instanceof ExecutableElement) - .filter(e -> e.getSimpleName().contentEquals(name)) - .collect(Collectors.toList()); - - if(candidates.isEmpty()) - throw new TargetNotFoundException(field ? "field" : "method", name, parent.data.name); - - if(candidates.size() == 1 && (!strict || descr == null)) - return candidates.get(0); - - if(descr == null) { - throw new AmbiguousDefinitionException(String.format( - "Found %d members named %s in class %s!", candidates.size(), name, parent.data.name)); - } else { - if(field) { - //fields can verify the signature for extra safety - //but there can only be 1 field with a given name - if(!descriptorFromType(candidates.get(0).asType(), env).equals(descr)) - throw new TargetNotFoundException("field", String.format( - "%s with descriptor %s", name, descr), parent.data.name); - } else { - candidates = candidates.stream() - .map(e -> (ExecutableElement) e) - .filter(strict - ? c -> descr.equals(descriptorFromExecutableElement(c, env)) - : c -> descr.split("\\)")[0].equalsIgnoreCase( - descriptorFromExecutableElement(c, env).split("\\)")[0]) - ).collect(Collectors.toList()); - } - if(candidates.isEmpty()) - throw new TargetNotFoundException("method", String.format( - "%s %s", name, descr), parent.data.name); - if(candidates.size() > 1) - throw new AmbiguousDefinitionException(String.format( - "Found %d methods named %s in class %s!", candidates.size(), name, parent.data.name)); - return candidates.get(0); - } - } - - /** - * Tries to find the method being overloaded by the given {@link ExecutableElement}. - * In case of multiple layers of overloading, it finds the original one. In case of - * no overloading, it returns the given method. - * @param context the {@link TypeElement} representing the parent class - * @param method an {@link ExecutableElement} representing the overloading method - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the original overloaded method, or the given method if it was not found - * @since 0.5.2 - */ - public static ExecutableElement findOverloadedMethod( - TypeElement context, ExecutableElement method, ProcessingEnvironment env) { - if (context.getSuperclass().getKind() == TypeKind.NONE) - return method; - - for (Element elem : context.getEnclosedElements()) { - if (elem.getKind() != ElementKind.METHOD) - continue; - if (env.getElementUtils().overrides(method, (ExecutableElement) elem, context)) { - method = (ExecutableElement) elem; - break; //found - } - } - - return findOverloadedMethod( - (TypeElement) env.getTypeUtils().asElement(context.getSuperclass()), - method, env - ); - } - - /** - * Tries to find the "synthetic bridge" generated by the compiler for a certain overridden - * method. A "bridge" only exists in cases where type erasure is involved (i.e. when the - * method being overridden uses a generic parameter that is not preserved in the overriding - * method). - * @param context the {@link TypeElement} representing the parent class - * @param method an {@link ExecutableElement} stub representing the overloading method - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the "bridge" - * @throws TargetNotFoundException if the method in question was not overriding anything, or - * if the method it was overriding does not require a bridge - * @since 0.5.2 - */ - public static ExecutableElement findSyntheticBridge( - TypeElement context, ExecutableElement method, ProcessingEnvironment env) throws TargetNotFoundException { - ExecutableElement overridding = findOverloadedMethod(context, method, env); - if(descriptorFromExecutableElement(overridding, env).equals(descriptorFromExecutableElement(method, env))) - throw new TargetNotFoundException( - "bridge method for", - overridding.getSimpleName().toString(), - context.getQualifiedName().toString() - ); - else return overridding; - } - - /** - * Utility method for finding out what type of proxy a field is. - * It will fail if the return type is not a known type of proxy. - * @param v the annotated {@link VariableElement} - * @return the {@link ProxyType} for the element - * @throws NotAProxyException if it's neither - * @since 0.4.0 - */ - public static ProxyType getProxyType(VariableElement v) { - String returnTypeFQN = v.asType().toString(); - switch(returnTypeFQN) { - case "ftbsc.lll.proxies.impl.FieldProxy": - return ProxyType.FIELD; - case "ftbsc.lll.proxies.impl.MethodProxy": - return ProxyType.METHOD; - case "ftbsc.lll.proxies.impl.TypeProxy": - return ProxyType.TYPE; - case "ftbsc.lll.proxies.impl.PackageProxy": - return ProxyType.PACKAGE; - default: - throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString()); - } - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java deleted file mode 100644 index 5709574..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java +++ /dev/null @@ -1,170 +0,0 @@ -package ftbsc.lll.processor.tools; - -import com.squareup.javapoet.*; -import ftbsc.lll.processor.annotations.Find; -import ftbsc.lll.processor.annotations.Target; -import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.processor.tools.containers.FieldContainer; -import ftbsc.lll.processor.tools.containers.InjectorInfo; -import ftbsc.lll.processor.tools.containers.MethodContainer; -import ftbsc.lll.proxies.ProxyType; -import ftbsc.lll.proxies.impl.FieldProxy; -import ftbsc.lll.proxies.impl.MethodProxy; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import java.util.HashSet; - -import static ftbsc.lll.processor.tools.ASTUtils.getProxyType; -import static ftbsc.lll.processor.tools.ASTUtils.mapModifiers; - -/** - * Collection of static utils that rely on JavaPoet to function. - */ -public class JavaPoetUtils { - /** - * Builds a {@link MethodSpec} for a public method whose body simply returns a {@link String}. - * @param name the name of the method - * @param returnString the {@link String} to return - * @return the built {@link MethodSpec} - */ - public 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(); - } - - /** - * Appends to a given {@link MethodSpec.Builder} definitions for a proxy. - * @param var the {@link VariableElement} representing the proxy - * @param stub the stub {@link ExecutableElement} if present or relevant, null otherwise - * @param t the {@link Target} relevant to this finder if present or relevant, null otherwise - * @param con the {@link MethodSpec.Builder} to append to - * @param options the {@link ProcessorOptions} to be used - * @since 0.5.0 - */ - public static void appendMemberFinderDefinition( - VariableElement var, ExecutableElement stub, Target t, - MethodSpec.Builder con, ProcessorOptions options) { - ProxyType type = getProxyType(var); - if(type != ProxyType.METHOD && type != ProxyType.FIELD) - return; //this method is irrelevant to everyone else - - //we need this stuff - Find f = var.getAnnotation(Find.class); - final boolean isMethod = type == ProxyType.METHOD; - final String builderName = var.getSimpleName().toString() + "Builder"; - - String descriptorObf, nameObf; - ClassContainer parent; - Element target; - - if(isMethod) { - MethodContainer mc = MethodContainer.from(stub, t, f, options); - descriptorObf = mc.descriptorObf; - nameObf = mc.data.nameMapped; - parent = mc.parent; - target = mc.elem; - } else { - FieldContainer fc = FieldContainer.from(var, options); - descriptorObf = fc.descriptorObf; - nameObf = fc.data.nameMapped; - parent = fc.parent; - target = fc.elem; - } - - //initialize builder - con.addStatement("$T $L = $T.builder($S)", - isMethod ? MethodProxy.Builder.class : FieldProxy.Builder.class, - builderName, //variable name is always unique by definition - isMethod ? MethodProxy.class : FieldProxy.class, - nameObf - ); - - //set parent - con.addStatement( - "$L.setParent($S, $L)", - builderName, - parent.data.nameMapped.replace('/', '.'), - parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers()) - ); - - //set modifiers - con.addStatement( - "$L.setModifiers($L)", - builderName, - target == null ? 0 : mapModifiers(target.getModifiers()) - ); - - //set type(s) - con.addStatement( - "$L.setDescriptor($S)", - builderName, - descriptorObf - ); - - //build and set - con.addStatement( - "super.$L = $L.build()", - var.getSimpleName().toString(), - builderName - ); - } - - /** - * Generates a {@link HashSet} of dummy overrides for every abstract method in a given class, - * represented as a {@link TypeElement}. - * @param clazz the given class - * @return a {@link HashSet} containing the generated {@link MethodSpec}s - * @since 0.5.0 - */ - public static HashSet generateDummies(TypeElement clazz) { - HashSet specs = new HashSet<>(); - clazz - .getEnclosedElements() - .stream() - .filter(e -> e instanceof ExecutableElement) - .map(e -> (ExecutableElement) e) - .forEach(e -> { - if(e.getModifiers().contains(Modifier.ABSTRACT)) - specs.add(MethodSpec.overriding(e) - .addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called") - .build() - ); - }); - return specs; - } - - /** - * Generates the wrapper around a certain injector. - * @param inj the {@link InjectorInfo} carrying the information about the target injector - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the generated {@link MethodSpec} for the injector - * @since 0.6.0 - */ - public static MethodSpec generateInjector(InjectorInfo inj, ProcessingEnvironment env) { - MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject") - .addModifiers(Modifier.PUBLIC) - .returns(void.class) - .addAnnotation(Override.class) - .addParameter(ParameterSpec.builder( - TypeName.get(env - .getElementUtils() - .getTypeElement("org.objectweb.asm.tree.ClassNode").asType()), "clazz") - .build()) - .addParameter(ParameterSpec.builder( - TypeName.get(env - .getElementUtils() - .getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main") - .build()); - - if(inj.injector.getParameters().size() == 2) - injectBuilder.addStatement("super.$L(clazz, main)", inj.injector.getSimpleName()); - else injectBuilder.addStatement("super.$L(main)", inj.injector.getSimpleName()); - - return injectBuilder.build(); - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java deleted file mode 100644 index 70d43cc..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java +++ /dev/null @@ -1,85 +0,0 @@ -package ftbsc.lll.processor.tools; - -import ftbsc.lll.IInjector; -import ftbsc.lll.mapper.MapperProvider; -import ftbsc.lll.mapper.tools.Mapper; - -import javax.annotation.processing.ProcessingEnvironment; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Class in charge of containing, parsing and processing all processor options, - * from the simpler booleans to the more complicated mapper. - */ -public class ProcessorOptions { - - /** - * A {@link Set} of options currently supported by the processor. - */ - public static final Set SUPPORTED = new HashSet<>(Arrays.asList( - "mappingsFile", "anonymousClassWarning", "obfuscateInjectorMetadata", - "noServiceProvider" - )); - - /** - * The environment the processor is acting in. - */ - public final ProcessingEnvironment env; - - /** - * The {@link Mapper} used to convert classes and variables - * to their obfuscated equivalent. Will be null when no mapper is in use. - */ - public final Mapper mapper; - - /** - * Whether the processor should issue warnings when compiling code anonymous - * classes which can't be checked for validity. - */ - public final boolean anonymousClassWarning; - - /** - * Whether injector metadata (what is returned by the functions of {@link IInjector}) - * is to use obfuscated names instead of its normal names. - */ - public final boolean obfuscateInjectorMetadata; - - /** - * Whether the processor should skip the generation of the service provider. - */ - public final boolean noServiceProvider; - - /** - * The public constructor, parses and stores all given arguments. - * @param env the environment the processor is working in - */ - public ProcessorOptions(ProcessingEnvironment env) { - this.env = env; - String location = env.getOptions().get("mappingsFile"); - if(location != null) { - List lines = MapperProvider.fetchFromLocalOrRemote(location); - this.mapper = MapperProvider.getMapper(lines).getMapper(lines, true); - } else this.mapper = null; - this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); - this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); - this.noServiceProvider = parseBooleanArg(env.getOptions().get("noServiceProvider"), false); - } - - /** - * Parses a boolean arg from a String. - * @param arg the arg to parse - * @return the parsed boolean - */ - private static boolean parseBooleanArg(String arg, boolean defaultValue) { - if(arg == null) return defaultValue; - try { // 0 = false, any other integer = true - int i = Integer.parseInt(arg); - return i != 0; - } catch(NumberFormatException ignored) { - return Boolean.parseBoolean(arg); - } - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java deleted file mode 100644 index b145e71..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java +++ /dev/null @@ -1,130 +0,0 @@ -package ftbsc.lll.processor.tools.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.tools.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.tools.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 the type of the annotation carrying the information - * @return the fully qualified name of the given class - * @since 0.5.0 - */ - public static ClassContainer from(T ann, Function> 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/tools/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java deleted file mode 100644 index 6ef4741..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java +++ /dev/null @@ -1,109 +0,0 @@ -package ftbsc.lll.processor.tools.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.tools.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.tools.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/tools/containers/InjectorInfo.java b/src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java deleted file mode 100644 index 460401b..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java +++ /dev/null @@ -1,46 +0,0 @@ -package ftbsc.lll.processor.tools.containers; - -import ftbsc.lll.processor.annotations.Injector; -import ftbsc.lll.processor.annotations.Target; -import ftbsc.lll.processor.tools.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); - } -} \ No newline at end of file diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java deleted file mode 100644 index 266858d..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java +++ /dev/null @@ -1,102 +0,0 @@ -package ftbsc.lll.processor.tools.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.tools.ProcessorOptions; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; - -import static ftbsc.lll.processor.tools.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); - } -} diff --git a/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java b/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java new file mode 100644 index 0000000..04da70e --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java @@ -0,0 +1,409 @@ +package ftbsc.lll.processor.utils; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.exceptions.MappingNotFoundException; +import ftbsc.lll.exceptions.NotAProxyException; +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.Mapper; +import ftbsc.lll.mapper.tools.data.ClassData; +import ftbsc.lll.mapper.tools.data.FieldData; +import ftbsc.lll.mapper.tools.data.MethodData; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.containers.ClassContainer; +import ftbsc.lll.proxies.ProxyType; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Collection of AST-related static utils that didn't really fit into the main class. + */ +public class ASTUtils { + /** + * Finds, among the methods of a class cl, the one annotated with ann, and tries to build + * an {@link Element} from it. + * @param parent the parent {@link Element} to the desired element + * @param ann the {@link Class} corresponding to the desired annotation + * @param the type of {@link Element} to use + * @return a {@link List} of {@link Element}s annotated with the given annotation + * @since 0.2.0 + */ + @SuppressWarnings("unchecked") + public static List findAnnotatedElement(Element parent, Class ann) { + return parent.getEnclosedElements() + .stream() + .filter(e -> e.getAnnotationsByType(ann).length != 0) + .map(e -> (T) e) + .collect(Collectors.toList()); + } + + /** + * 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; + } + } + + /** + * Takes in a {@link Collection} of AST {@link Modifier}s and + * returns them mapped to their reflective integer equivalent. + * @param modifiers the {@link Modifier}s + * @return an integer value representing them + * @since 0.5.0 + */ + public static int mapModifiers(Collection modifiers) { + int i = 0; + for(Modifier m : modifiers) + i |= mapModifier(m); + return i; + } + + /** + * Safely extracts a {@link Class} from an annotation and gets a {@link TypeMirror} representing it. + * @param ann the annotation containing the class + * @param classFunction the annotation function returning the class + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @param the type of the annotation carrying the information + * @return a {@link TypeMirror} representing the requested {@link Class} + * @since 0.3.0 + */ + public static TypeMirror getTypeFromAnnotation( + T ann, Function> classFunction, ProcessingEnvironment env) { + try { + String fqn = classFunction.apply(ann).getCanonicalName(); + if(fqn == null) + fqn = ""; + return env.getElementUtils().getTypeElement(fqn).asType(); + } catch(MirroredTypeException e) { + return e.getTypeMirror(); + } + } + + /** + * Gets the internal name from an {@link TypeMirror}. + * @param type the {@link TypeMirror} in question + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the internal name at compile time, or null if it wasn't a qualifiable + * @since 0.5.1 + */ + public static String internalNameFromType(TypeMirror type, ProcessingEnvironment env) { + //needed to actually turn elem into a TypeVariable, find it ignoring generics + Element elem = env.getTypeUtils().asElement(env.getTypeUtils().erasure(type)); + StringBuilder fqnBuilder = new StringBuilder(); + while(elem.getEnclosingElement() != null && elem.getEnclosingElement().getKind() != ElementKind.PACKAGE) { + fqnBuilder + .insert(0, elem.getSimpleName().toString()) + .insert(0, "$"); + elem = elem.getEnclosingElement(); + } + return fqnBuilder + .insert(0, env.getTypeUtils().erasure(elem.asType()).toString()) + .toString() + .replace('.', '/'); + } + + /** + * Builds a type descriptor from the given {@link TypeMirror}. + * @param t the {@link TypeMirror} representing the desired type + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return a {@link String} containing the relevant descriptor + */ + public static String descriptorFromType(TypeMirror t, ProcessingEnvironment env) { + t = env.getTypeUtils().erasure(t); //type erasure + + StringBuilder desc = new StringBuilder(); + //add array brackets + while(t.getKind() == TypeKind.ARRAY) { + desc.append("["); + t = ((ArrayType) t).getComponentType(); + } + + if(t.getKind() == TypeKind.TYPEVAR) + t = ((TypeVariable) t).getUpperBound(); + + if(t.getKind() == TypeKind.DECLARED) + desc + .append("L") + .append(internalNameFromType(t, env)) + .append(";"); + else { + switch(t.getKind()) { + case BOOLEAN: + desc.append("Z"); + break; + case CHAR: + desc.append("C"); + break; + case BYTE: + desc.append("B"); + break; + case SHORT: + desc.append("S"); + break; + case INT: + desc.append("I"); + break; + case FLOAT: + desc.append("F"); + break; + case LONG: + desc.append("J"); + break; + case DOUBLE: + desc.append("D"); + break; + case VOID: + desc.append("V"); + break; + } + } + + return desc.toString(); + } + + /** + * Builds a method descriptor from the given {@link ExecutableElement}. + * @param m the {@link ExecutableElement} for the method + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return a {@link String} containing the relevant descriptor + */ + public static String descriptorFromExecutableElement(ExecutableElement m, ProcessingEnvironment env) { + StringBuilder methodSignature = new StringBuilder(); + methodSignature.append("("); + m.getParameters().forEach(p -> methodSignature.append(descriptorFromType(p.asType(), env))); + methodSignature.append(")"); + methodSignature.append(descriptorFromType(m.getReturnType(), env)); + return methodSignature.toString(); + } + + /** + * Gets the {@link ClassData} corresponding to the given fully-qualified name, + * or creates a false one with the same, non-obfuscated name twice. + * @param name the internal name of the class to convert + * @param mapper the {@link Mapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static ClassData getClassData(String name, Mapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getClassData(name); + } catch(MappingNotFoundException ignored) {} + return new ClassData(name, name); + } + + /** + * Gets the {@link MethodData} corresponding to the method matching the given + * name, parent and descriptor, or creates a dummy one with fake data if no + * valid mapping is found. + * @param parent the internal name of the parent class + * @param name the name of the member + * @param descriptor the descriptor of the method + * @param mapper the {@link Mapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static MethodData getMethodData(String parent, String name, String descriptor, Mapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getMethodData(parent, name, descriptor); + } catch(MappingNotFoundException ignored) {} + return new MethodData(getClassData(name, mapper), name, name, descriptor); + } + + /** + * Gets the {@link FieldData} corresponding to the field matching the given + * name and parent, or creates a dummy one with fake data if no valid + * mapping is found. + * @param parent the internal name of the parent class + * @param name the name of the member + * @param mapper the {@link Mapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static FieldData getFieldData(String parent, String name, Mapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getFieldData(parent, name); + } catch(MappingNotFoundException ignored) {} + return new FieldData(getClassData(name, mapper), name, name); + } + + /** + * Finds a member given the name, the container class and (if it's a method) the descriptor. + * @param parent the {@link ClassContainer} representing the parent + * @param name the name to search for + * @param descr the descriptor to search for, or null if it's not a method + * @param strict whether to perform lookup in strict mode (see {@link Target#strict()} for more information) + * @param field whether the member being searched is a field + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the desired member, if it exists + * @throws AmbiguousDefinitionException if it finds more than one candidate + * @throws TargetNotFoundException if it finds no valid candidate + * @since 0.3.0 + */ + public static Element findMember( + ClassContainer parent, String name, String descr, + boolean strict, boolean field, ProcessingEnvironment env) { + if(parent.elem == null) + throw new TargetNotFoundException("parent class", parent.data.name); + //try to find by name + List candidates = parent.elem.getEnclosedElements() + .stream() + .filter(e -> (field && e instanceof VariableElement) || e instanceof ExecutableElement) + .filter(e -> e.getSimpleName().contentEquals(name)) + .collect(Collectors.toList()); + + if(candidates.isEmpty()) + throw new TargetNotFoundException(field ? "field" : "method", name, parent.data.name); + + if(candidates.size() == 1 && (!strict || descr == null)) + return candidates.get(0); + + if(descr == null) { + throw new AmbiguousDefinitionException(String.format( + "Found %d members named %s in class %s!", candidates.size(), name, parent.data.name)); + } else { + if(field) { + //fields can verify the signature for extra safety + //but there can only be 1 field with a given name + if(!descriptorFromType(candidates.get(0).asType(), env).equals(descr)) + throw new TargetNotFoundException("field", String.format( + "%s with descriptor %s", name, descr), parent.data.name); + } else { + candidates = candidates.stream() + .map(e -> (ExecutableElement) e) + .filter(strict + ? c -> descr.equals(descriptorFromExecutableElement(c, env)) + : c -> descr.split("\\)")[0].equalsIgnoreCase( + descriptorFromExecutableElement(c, env).split("\\)")[0]) + ).collect(Collectors.toList()); + } + if(candidates.isEmpty()) + throw new TargetNotFoundException("method", String.format( + "%s %s", name, descr), parent.data.name); + if(candidates.size() > 1) + throw new AmbiguousDefinitionException(String.format( + "Found %d methods named %s in class %s!", candidates.size(), name, parent.data.name)); + return candidates.get(0); + } + } + + /** + * Tries to find the method being overloaded by the given {@link ExecutableElement}. + * In case of multiple layers of overloading, it finds the original one. In case of + * no overloading, it returns the given method. + * @param context the {@link TypeElement} representing the parent class + * @param method an {@link ExecutableElement} representing the overloading method + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the original overloaded method, or the given method if it was not found + * @since 0.5.2 + */ + public static ExecutableElement findOverloadedMethod( + TypeElement context, ExecutableElement method, ProcessingEnvironment env) { + if (context.getSuperclass().getKind() == TypeKind.NONE) + return method; + + for (Element elem : context.getEnclosedElements()) { + if (elem.getKind() != ElementKind.METHOD) + continue; + if (env.getElementUtils().overrides(method, (ExecutableElement) elem, context)) { + method = (ExecutableElement) elem; + break; //found + } + } + + return findOverloadedMethod( + (TypeElement) env.getTypeUtils().asElement(context.getSuperclass()), + method, env + ); + } + + /** + * Tries to find the "synthetic bridge" generated by the compiler for a certain overridden + * method. A "bridge" only exists in cases where type erasure is involved (i.e. when the + * method being overridden uses a generic parameter that is not preserved in the overriding + * method). + * @param context the {@link TypeElement} representing the parent class + * @param method an {@link ExecutableElement} stub representing the overloading method + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the "bridge" + * @throws TargetNotFoundException if the method in question was not overriding anything, or + * if the method it was overriding does not require a bridge + * @since 0.5.2 + */ + public static ExecutableElement findSyntheticBridge( + TypeElement context, ExecutableElement method, ProcessingEnvironment env) throws TargetNotFoundException { + ExecutableElement overridding = findOverloadedMethod(context, method, env); + if(descriptorFromExecutableElement(overridding, env).equals(descriptorFromExecutableElement(method, env))) + throw new TargetNotFoundException( + "bridge method for", + overridding.getSimpleName().toString(), + context.getQualifiedName().toString() + ); + else return overridding; + } + + /** + * Utility method for finding out what type of proxy a field is. + * It will fail if the return type is not a known type of proxy. + * @param v the annotated {@link VariableElement} + * @return the {@link ProxyType} for the element + * @throws NotAProxyException if it's neither + * @since 0.4.0 + */ + public static ProxyType getProxyType(VariableElement v) { + String returnTypeFQN = v.asType().toString(); + switch(returnTypeFQN) { + case "ftbsc.lll.proxies.impl.FieldProxy": + return ProxyType.FIELD; + case "ftbsc.lll.proxies.impl.MethodProxy": + return ProxyType.METHOD; + case "ftbsc.lll.proxies.impl.TypeProxy": + return ProxyType.TYPE; + case "ftbsc.lll.proxies.impl.PackageProxy": + return ProxyType.PACKAGE; + default: + throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString()); + } + } +} diff --git a/src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java new file mode 100644 index 0000000..629f77a --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java @@ -0,0 +1,171 @@ +package ftbsc.lll.processor.utils; + +import com.squareup.javapoet.*; +import ftbsc.lll.processor.ProcessorOptions; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.containers.ClassContainer; +import ftbsc.lll.processor.containers.FieldContainer; +import ftbsc.lll.processor.containers.InjectorInfo; +import ftbsc.lll.processor.containers.MethodContainer; +import ftbsc.lll.proxies.ProxyType; +import ftbsc.lll.proxies.impl.FieldProxy; +import ftbsc.lll.proxies.impl.MethodProxy; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.*; +import java.util.HashSet; + +import static ftbsc.lll.processor.utils.ASTUtils.getProxyType; +import static ftbsc.lll.processor.utils.ASTUtils.mapModifiers; + +/** + * Collection of static utils that rely on JavaPoet to function. + */ +public class JavaPoetUtils { + /** + * Builds a {@link MethodSpec} for a public method whose body simply returns a {@link String}. + * @param name the name of the method + * @param returnString the {@link String} to return + * @return the built {@link MethodSpec} + */ + public 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(); + } + + /** + * Appends to a given {@link MethodSpec.Builder} definitions for a proxy. + * @param var the {@link VariableElement} representing the proxy + * @param stub the stub {@link ExecutableElement} if present or relevant, null otherwise + * @param t the {@link Target} relevant to this finder if present or relevant, null otherwise + * @param con the {@link MethodSpec.Builder} to append to + * @param options the {@link ProcessorOptions} to be used + * @since 0.5.0 + */ + public static void appendMemberFinderDefinition( + VariableElement var, ExecutableElement stub, Target t, + MethodSpec.Builder con, ProcessorOptions options) { + ProxyType type = getProxyType(var); + if(type != ProxyType.METHOD && type != ProxyType.FIELD) + return; //this method is irrelevant to everyone else + + //we need this stuff + Find f = var.getAnnotation(Find.class); + final boolean isMethod = type == ProxyType.METHOD; + final String builderName = var.getSimpleName().toString() + "Builder"; + + String descriptorObf, nameObf; + ClassContainer parent; + Element target; + + if(isMethod) { + MethodContainer mc = MethodContainer.from(stub, t, f, options); + descriptorObf = mc.descriptorObf; + nameObf = mc.data.nameMapped; + parent = mc.parent; + target = mc.elem; + } else { + FieldContainer fc = FieldContainer.from(var, options); + descriptorObf = fc.descriptorObf; + nameObf = fc.data.nameMapped; + parent = fc.parent; + target = fc.elem; + } + + //initialize builder + con.addStatement("$T $L = $T.builder($S)", + isMethod ? MethodProxy.Builder.class : FieldProxy.Builder.class, + builderName, //variable name is always unique by definition + isMethod ? MethodProxy.class : FieldProxy.class, + nameObf + ); + + //set parent + con.addStatement( + "$L.setParent($S, $L)", + builderName, + parent.data.nameMapped.replace('/', '.'), + parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers()) + ); + + //set modifiers + con.addStatement( + "$L.setModifiers($L)", + builderName, + target == null ? 0 : mapModifiers(target.getModifiers()) + ); + + //set type(s) + con.addStatement( + "$L.setDescriptor($S)", + builderName, + descriptorObf + ); + + //build and set + con.addStatement( + "super.$L = $L.build()", + var.getSimpleName().toString(), + builderName + ); + } + + /** + * Generates a {@link HashSet} of dummy overrides for every abstract method in a given class, + * represented as a {@link TypeElement}. + * @param clazz the given class + * @return a {@link HashSet} containing the generated {@link MethodSpec}s + * @since 0.5.0 + */ + public static HashSet generateDummies(TypeElement clazz) { + HashSet specs = new HashSet<>(); + clazz + .getEnclosedElements() + .stream() + .filter(e -> e instanceof ExecutableElement) + .map(e -> (ExecutableElement) e) + .forEach(e -> { + if(e.getModifiers().contains(Modifier.ABSTRACT)) + specs.add(MethodSpec.overriding(e) + .addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called") + .build() + ); + }); + return specs; + } + + /** + * Generates the wrapper around a certain injector. + * @param inj the {@link InjectorInfo} carrying the information about the target injector + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the generated {@link MethodSpec} for the injector + * @since 0.6.0 + */ + public static MethodSpec generateInjector(InjectorInfo inj, ProcessingEnvironment env) { + MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject") + .addModifiers(Modifier.PUBLIC) + .returns(void.class) + .addAnnotation(Override.class) + .addParameter(ParameterSpec.builder( + TypeName.get(env + .getElementUtils() + .getTypeElement("org.objectweb.asm.tree.ClassNode").asType()), "clazz") + .build()) + .addParameter(ParameterSpec.builder( + TypeName.get(env + .getElementUtils() + .getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main") + .build()); + + if(inj.injector.getParameters().size() == 2) + injectBuilder.addStatement("super.$L(clazz, main)", inj.injector.getSimpleName()); + else injectBuilder.addStatement("super.$L(main)", inj.injector.getSimpleName()); + + return injectBuilder.build(); + } +} -- cgit v1.2.3-56-ga3b1