summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/ftbsc/lll/exceptions/NotAProxyException.java6
-rw-r--r--src/main/java/ftbsc/lll/exceptions/TargetNotFoundException.java5
-rw-r--r--src/main/java/ftbsc/lll/processor/LilleroProcessor.java249
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Find.java49
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Injector.java14
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/MultipleTargets.java (renamed from src/main/java/ftbsc/lll/processor/annotations/MultipleInjectors.java)8
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Patch.java28
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Target.java16
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/ASTUtils.java175
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java118
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/containers/ArrayContainer.java (renamed from src/main/java/ftbsc/lll/processor/tools/ArrayContainer.java)2
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java47
12 files changed, 432 insertions, 285 deletions
diff --git a/src/main/java/ftbsc/lll/exceptions/NotAProxyException.java b/src/main/java/ftbsc/lll/exceptions/NotAProxyException.java
index 4c2eeaa..1ce3c0f 100644
--- a/src/main/java/ftbsc/lll/exceptions/NotAProxyException.java
+++ b/src/main/java/ftbsc/lll/exceptions/NotAProxyException.java
@@ -1,8 +1,8 @@
package ftbsc.lll.exceptions;
import ftbsc.lll.processor.annotations.Find;
-import ftbsc.lll.proxies.FieldProxy;
-import ftbsc.lll.proxies.MethodProxy;
+import ftbsc.lll.proxies.impl.FieldProxy;
+import ftbsc.lll.proxies.impl.MethodProxy;
/**
* Thrown when a method is annotated with {@link Find} but does not
@@ -16,6 +16,6 @@ public class NotAProxyException extends RuntimeException {
* @param method the name of the method wrongly annotated
*/
public NotAProxyException(String parent, String method) {
- super(String.format("Annotated method %s::%s does not return a proxy!", parent, method));
+ super(String.format("Annotated field %s::%s does not return a proxy!", parent, method));
}
}
diff --git a/src/main/java/ftbsc/lll/exceptions/TargetNotFoundException.java b/src/main/java/ftbsc/lll/exceptions/TargetNotFoundException.java
index c82e0fc..45819bf 100644
--- a/src/main/java/ftbsc/lll/exceptions/TargetNotFoundException.java
+++ b/src/main/java/ftbsc/lll/exceptions/TargetNotFoundException.java
@@ -7,9 +7,10 @@ public class TargetNotFoundException extends RuntimeException {
/**
* Constructs a new target not found exception for the specified method stub.
+ * @param type the type of element being sought (class, method, etc.)
* @param stub the stub's name (and descriptor possibly)
*/
- public TargetNotFoundException(String stub) {
- super(String.format("Could not find member corresponding to stub: %s.", stub));
+ public TargetNotFoundException(String type, String stub) {
+ super(String.format("Could not find target %s %s.", type, stub));
}
}
diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
index 02d7e27..952647d 100644
--- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
+++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
@@ -8,9 +8,12 @@ import ftbsc.lll.processor.annotations.Find;
import ftbsc.lll.processor.annotations.Injector;
import ftbsc.lll.processor.annotations.Patch;
import ftbsc.lll.processor.annotations.Target;
+import ftbsc.lll.processor.tools.containers.ClassContainer;
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
-import ftbsc.lll.proxies.FieldProxy;
-import ftbsc.lll.proxies.MethodProxy;
+import ftbsc.lll.proxies.ProxyType;
+import ftbsc.lll.proxies.impl.FieldProxy;
+import ftbsc.lll.proxies.impl.MethodProxy;
+import ftbsc.lll.proxies.impl.TypeProxy;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
@@ -37,7 +40,7 @@ import static ftbsc.lll.processor.tools.JavaPoetUtils.*;
*/
@SupportedAnnotationTypes("ftbsc.lll.processor.annotations.Patch")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
-@SupportedOptions("mappingsFile")
+@SupportedOptions({"mappingsFile", "badPracticeWarnings"})
public class LilleroProcessor extends AbstractProcessor {
/**
* A {@link Set} of {@link String}s that will contain the fully qualified names
@@ -52,6 +55,12 @@ public class LilleroProcessor extends AbstractProcessor {
private ObfuscationMapper mapper;
/**
+ * Whether the processor should issue warnings when compiling code adopting
+ * bad practices.
+ */
+ public static boolean badPracticeWarnings = true;
+
+ /**
* Initializes the processor with the processing environment by
* setting the {@code processingEnv} field to the value of the
* {@code processingEnv} argument.
@@ -87,6 +96,17 @@ public class LilleroProcessor extends AbstractProcessor {
this.mapper = new ObfuscationMapper(new BufferedReader(new InputStreamReader(targetStream,
StandardCharsets.UTF_8)).lines());
}
+ String warns = processingEnv.getOptions().get("badPracticeWarnings");
+ if(warns == null)
+ badPracticeWarnings = true;
+ else {
+ try { // 0 = false, any other integer = true
+ int i = Integer.parseInt(warns);
+ badPracticeWarnings = i != 0;
+ } catch(NumberFormatException ignored) {
+ badPracticeWarnings = Boolean.parseBoolean(warns);
+ }
+ }
}
/**
@@ -110,7 +130,7 @@ public class LilleroProcessor extends AbstractProcessor {
.filter(this::isValidInjector)
.collect(Collectors.toSet());
if(!validInjectors.isEmpty()) {
- validInjectors.forEach(this::generateInjectors);
+ validInjectors.forEach(this::generateClasses);
if (!this.generatedInjectors.isEmpty()) {
generateServiceProvider();
return true;
@@ -141,7 +161,7 @@ public class LilleroProcessor extends AbstractProcessor {
&& processingEnv.getTypeUtils().isSameType(params.get(1), methodNodeType);
})) return true;
else {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, //TODO orphan targets
String.format("Missing valid @Injector method in @Patch class %s, skipping.", elem));
return false;
}
@@ -152,17 +172,16 @@ public class LilleroProcessor extends AbstractProcessor {
* Basically implements the {@link IInjector} interface for you.
* @param cl the {@link TypeElement} for the given class
*/
- private void generateInjectors(TypeElement cl) {
+ private void generateClasses(TypeElement cl) {
//find class information
Patch patchAnn = cl.getAnnotation(Patch.class);
- String targetClassFQN =
- findClassName(
- getClassFullyQualifiedName(
- patchAnn,
- Patch::value,
- getInnerName(patchAnn, Patch::innerClass, Patch::anonymousClassCounter)
- ), this.mapper
- ).replace('/', '.');
+ ClassContainer targetClass = new ClassContainer(
+ getClassFullyQualifiedName(
+ patchAnn,
+ Patch::value,
+ patchAnn.className()
+ ), this.processingEnv, this.mapper
+ );
//find package information
Element packageElement = cl.getEnclosingElement();
@@ -170,91 +189,118 @@ public class LilleroProcessor extends AbstractProcessor {
packageElement = packageElement.getEnclosingElement();
String packageName = packageElement.toString();
- //find injector(s) and target(s)
- List<ExecutableElement> injectors = findAnnotatedMethods(cl, Injector.class);
-
- List<ExecutableElement> targets = findAnnotatedMethods(cl, Target.class);
+ //find annotated elements
+ List<ExecutableElement> targets = findAnnotatedElement(cl, Target.class);
+ List<ExecutableElement> injectors = findAnnotatedElement(cl, Injector.class);
+ List<VariableElement> finders = findAnnotatedElement(cl, Find.class);
+
+ //initialize the constructor builder
+ MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder();
+
+ //take care of TypeProxies and FieldProxies first
+ for(VariableElement proxyVar : finders) {
+ ProxyType type = getProxyType(proxyVar);
+ if(type == ProxyType.METHOD) //methods will be handled later
+ continue;
+ //case-specific handling
+ if(type == ProxyType.TYPE) {
+ //find and validate
+ ClassContainer clazz = findClassOrFallback(targetClass, proxyVar.getAnnotation(Find.class), this.processingEnv, this.mapper);
+ //types can be generated with a single instruction
+ constructorBuilder.addStatement(
+ "super.$L = $T.from($S, 0, $L)",
+ proxyVar.getSimpleName().toString(),
+ TypeProxy.class,
+ clazz.fqnObf, //use obf name, at runtime it will be obfuscated
+ mapModifiers(clazz.elem.getModifiers())
+ );
+ } else if(type == ProxyType.FIELD)
+ appendMemberFinderDefinition(targetClass, proxyVar, null, constructorBuilder, this.processingEnv, this.mapper);
+ finders.remove(proxyVar); //remove finders that have already been processed
+ }
//declare it once for efficiency
- List<String> targetNames =
- targets.stream()
+ List<String> injectorNames =
+ injectors.stream()
.map(ExecutableElement::getSimpleName)
.map(Object::toString)
.collect(Collectors.toList());
//this will contain the classes to generate: the key is the class name
- Map<String, InjectorInfo> toGenerate = new HashMap<>();
+ HashMap<String, InjectorInfo> toGenerate = new HashMap<>();
- for(ExecutableElement inj : injectors) {
- Injector[] minjAnn = inj.getAnnotationsByType(Injector.class);
+ for(ExecutableElement tg : targets) {
+ Target[] mtgAnn = tg.getAnnotationsByType(Target.class);
int iterationNumber = 1;
- for(Injector injectorAnn : minjAnn) { //java is dumb
- List<ExecutableElement> injectionCandidates = targets;
-
- if(!injectorAnn.targetName().equals("") && targetNames.contains(injectorAnn.targetName())) {
- //case 1: it has a name, try to match it
- injectionCandidates =
- injectionCandidates
+ for(Target targetAnn : mtgAnn) {
+ List<ExecutableElement> injectorCandidates = injectors;
+ List<VariableElement> finderCandidates = finders;
+
+ if(!targetAnn.of().equals("") && injectorNames.contains(targetAnn.of())) {
+ //case 1: find target by name
+ injectorCandidates =
+ injectorCandidates
+ .stream()
+ .filter(i -> i.getSimpleName().contentEquals(targetAnn.of()))
+ .collect(Collectors.toList());
+ finderCandidates =
+ finderCandidates
.stream()
- .filter(i -> i.getSimpleName().contentEquals(injectorAnn.targetName()))
+ .filter(i -> i.getSimpleName().contentEquals(targetAnn.of()))
.collect(Collectors.toList());
- } else if(targets.size() == 1) {
- //case 2: there is only one target
- injectionCandidates = new ArrayList<>();
- injectionCandidates.add(targets.get(0));
+ } else if(injectors.size() == 1) {
+ //case 2: there is only one injector
+ finderCandidates = new ArrayList<>(); //no candidates
+ injectorCandidates = new ArrayList<>();
+ injectorCandidates.add(targets.get(0));
} else {
//case 3: try to match by injectTargetName
- String inferredName = inj.getSimpleName()
- .toString()
- .replaceFirst("inject", "");
- injectionCandidates =
- injectionCandidates
+ finderCandidates = new ArrayList<>(); //no candidates
+ String inferredName = "inject" + tg.getSimpleName();
+ injectorCandidates =
+ injectorCandidates
.stream()
.filter(t -> t.getSimpleName().toString().equalsIgnoreCase(inferredName))
.collect(Collectors.toList());
}
- ExecutableElement injectionTarget = null;
-
- if(injectionCandidates.size() == 1)
- injectionTarget = injectionCandidates.get(0);
+ //throw exception if user is a moron and defined a finder and an injector with the same name
+ if(finderCandidates.size() != 0 && injectorCandidates.size() != 0)
+ throw new AmbiguousDefinitionException(
+ String.format("Target specified user %s, but name was used by both a finder and injector.", targetAnn.of())
+ );
+ else if(finderCandidates.size() == 0 && injectorCandidates.size() != 1)
+ throw new AmbiguousDefinitionException(
+ String.format("Found multiple candidate injectors for target %s::%s!", cl.getSimpleName(), tg.getSimpleName())
+ );
+ else if(injectorCandidates.size() == 0 && finderCandidates.size() != 1)
+ throw new AmbiguousDefinitionException(
+ String.format("Found multiple candidate finders for target %s::%s!", cl.getSimpleName(), tg.getSimpleName())
+ );
else {
- List<TypeMirror> params = classArrayFromAnnotation(injectorAnn, Injector::params, processingEnv.getElementUtils());
-
- if(params.size() != 0) {
- StringBuilder descr = new StringBuilder("(");
- for(TypeMirror p : params)
- descr.append(descriptorFromType(TypeName.get(p)));
- descr.append(")");
- injectionCandidates =
- injectionCandidates
- .stream()
- .filter(t -> //we care about arguments but not really about return type
- descr.toString()
- .split("\\)")[0]
- .equalsIgnoreCase(descriptorFromExecutableElement(t).split("\\)")[0])
- ).collect(Collectors.toList());
+ if(injectorCandidates.size() == 1) {
+ //matched an injector!
+ toGenerate.put(
+ String.format("%sInjector%d", cl.getSimpleName(), iterationNumber),
+ new InjectorInfo(injectorCandidates.get(0), tg)
+ );
+ iterationNumber++; //increment is only used by injectors
+ } else {
+ //matched a finder!
+ VariableElement finder = finders.get(0);
+ Find f = finder.getAnnotation(Find.class);
+ appendMemberFinderDefinition(targetClass, finder, tg, constructorBuilder, this.processingEnv, this.mapper);
+ finders.remove(finder); //unlike injectors, finders can't apply to multiple targets
}
-
- if(injectionCandidates.size() == 1)
- injectionTarget = injectionCandidates.get(0);
}
- //if we haven't found it yet, it's an ambiguity
- if(injectionTarget == null)
- throw new AmbiguousDefinitionException(String.format("Unclear target for injector %s::%s!", cl.getSimpleName(), inj.getSimpleName()));
- else toGenerate.put(
- String.format("%sInjector%d", cl.getSimpleName(), iterationNumber),
- new InjectorInfo(inj, injectionTarget)
- );
- iterationNumber++;
}
}
//iterate over the map and generate the classes
for(String injName : toGenerate.keySet()) {
String targetMethodDescriptor = descriptorFromExecutableElement(toGenerate.get(injName).target);
- String targetMethodName = findMemberName(targetClassFQN, toGenerate.get(injName).target.getSimpleName().toString(), targetMethodDescriptor, this.mapper);
+ String targetMethodName = findMemberName(targetClass.fqnObf, toGenerate.get(injName).target.getSimpleName().toString(), targetMethodDescriptor, this.mapper);
MethodSpec stubOverride = MethodSpec.overriding(toGenerate.get(injName).targetStub)
.addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called")
@@ -279,12 +325,12 @@ public class LilleroProcessor extends AbstractProcessor {
.addModifiers(Modifier.PUBLIC)
.superclass(cl.asType())
.addSuperinterface(ClassName.get(IInjector.class))
+ .addMethod(constructorBuilder.build())
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
- .addMethod(buildStringReturnMethod("reason", patchAnn.reason()))
- .addMethod(buildStringReturnMethod("targetClass", targetClassFQN))
+ .addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason))
+ .addMethod(buildStringReturnMethod("targetClass", targetClass.fqn))
.addMethod(buildStringReturnMethod("methodName", targetMethodName))
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
- .addMethods(generateRequestedProxies(cl, this.mapper))
.addMethod(stubOverride)
.addMethod(inject)
.build();
@@ -306,53 +352,6 @@ public class LilleroProcessor extends AbstractProcessor {
}
/**
- * Finds any method annotated with {@link Find} within the given class, generates
- * the {@link MethodSpec} necessary for building it.
- * @param cl the class to search
- * @return a {@link List} of method specs
- * @since 0.2.0
- */
- private List<MethodSpec> generateRequestedProxies(TypeElement cl, ObfuscationMapper mapper) {
- List<MethodSpec> generated = new ArrayList<>();
- findAnnotatedMethods(cl, Find.class)
- .stream()
- .filter(m -> !m.getModifiers().contains(Modifier.STATIC)) //skip static stuff as we can't override it
- .filter(m -> !m.getModifiers().contains(Modifier.FINAL)) //in case someone is trying to be funny
- .forEach(m -> {
- boolean isMethod = isMethodProxyStub(m);
- Element target = findMemberFromStub(m, processingEnv);
-
- MethodSpec.Builder b = MethodSpec.overriding(m);
-
- String targetParentFQN = findClassName(((TypeElement) target.getEnclosingElement()).getQualifiedName().toString(), mapper);
- String methodDescriptor = isMethod ? descriptorFromExecutableElement((ExecutableElement) target) : null;
-
- b.addStatement("$T bd = $T.builder($S)",
- isMethod ? MethodProxy.Builder.class : FieldProxy.Builder.class,
- isMethod ? MethodProxy.class : FieldProxy.class,
- findMemberName(targetParentFQN, target.getSimpleName().toString(), methodDescriptor, mapper)
- );
-
- b.addStatement("bd.setParent($S)", targetParentFQN);
-
- for(Modifier mod : target.getModifiers())
- b.addStatement("bd.addModifier($L)", mapModifier(mod));
-
- if(isMethod) {
- ExecutableElement targetMethod = (ExecutableElement) target;
- for(VariableElement p : targetMethod.getParameters())
- addTypeToProxyGenerator(b, "bd", "addParameter", p.asType());
- addTypeToProxyGenerator(b, "bd", "setReturnType", targetMethod.getReturnType());
- } else addTypeToProxyGenerator(b, "bd", "setType", target.asType());
-
- b.addStatement("return bd.build()");
-
- generated.add(b.build());
- });
- return generated;
- }
-
- /**
* Generates the Service Provider file for the generated injectors.
*/
private void generateServiceProvider() {
@@ -385,6 +384,11 @@ public class LilleroProcessor extends AbstractProcessor {
public final ExecutableElement targetStub;
/**
+ * The reason for the injection.
+ */
+ public final String reason;
+
+ /**
* The {@link ExecutableElement} corresponding to the target method.
*/
private final ExecutableElement target;
@@ -397,7 +401,8 @@ public class LilleroProcessor extends AbstractProcessor {
public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub) {
this.injector = injector;
this.targetStub = targetStub;
- this.target = (ExecutableElement) findMemberFromStub(targetStub, processingEnv);
+ this.reason = injector.getAnnotation(Injector.class).reason();
+ this.target = findMethodFromStub(targetStub, processingEnv);
}
}
} \ No newline at end of file
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Find.java b/src/main/java/ftbsc/lll/processor/annotations/Find.java
index 5f31325..384fb60 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Find.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Find.java
@@ -1,7 +1,8 @@
package ftbsc.lll.processor.annotations;
-import ftbsc.lll.proxies.FieldProxy;
-import ftbsc.lll.proxies.MethodProxy;
+import ftbsc.lll.proxies.impl.FieldProxy;
+import ftbsc.lll.proxies.impl.MethodProxy;
+import ftbsc.lll.proxies.impl.TypeProxy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -14,43 +15,39 @@ import java.lang.annotation.RetentionPolicy;
* @since 0.4.0
*/
@Retention(RetentionPolicy.CLASS)
-@java.lang.annotation.Target(ElementType.METHOD)
+@java.lang.annotation.Target(ElementType.FIELD)
public @interface Find {
/**
* @return the {@link Class} object containing the target, or the
* {@link Object} class if not specified (the {@link Class} from
- * {@link Patch#value()} is instead used)
+ * {@link Patch#value()} is instead used).
+ * @since 0.5.0
*/
- Class<?> parent() default Object.class;
+ Class<?> value() default Object.class;
/**
+ * For a {@link TypeProxy}, this can be either the fully-qualified name
+ * to be used in place of {@link #value()} or an inner class name to append
+ * after a $ symbol to the already acquired fully-qualified name.
+ * For others, this is refers to the parent class.
* @return the name of the inner class that contains the target,
* defaults to empty string (not an inner class)
- * @since 0.4.0
+ * @since 0.5.0
*/
- String parentInnerClass() default "";
+ String className() default "";
/**
- * @return the anonymous class counter (1 for the first, 2 for
- * the second, 3 for the third...) for the class that contains
- * the target, defaults to 0 (not an anonymous class)
- * @since 0.4.0
- */
- int parentAnonymousClassCounter() default 0;
-
- /**
- * The name of the class member to find. If omitted, the name of the
- * annotated method will be used.
+ * For a {@link FieldProxy}, this is the name of the field to find. If omitted,
+ * it will fall back on the name of the annotated field.
+ * For a {@link MethodProxy} it indicates an attempt to match by name only, with
+ * this name. This will issue a warning unless warnings are disabled. It will fail
+ * and throw an exception if multiple methods with that name are found in the
+ * relevant class. It is generally recommended that you use a @link Target} stub
+ * for methods, as this can lead to unpredictable behaviour at runtime.
+ * It will have no effect on a {@link TypeProxy}.
* @return the name of the target, will default to the empty string
- * (the name of the annotated method will instead be used)
+ * (the name of the annotated method will instead be used).
+ * @since 0.5.0
*/
String name() default "";
-
- /**
- * Only use if the target is a method.
- * @return a list of the parameters of the method, will default to empty
- * array (in that case, an attempt will be made to match a method without
- * args first)
- */
- Class<?>[] params() default {};
}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Injector.java b/src/main/java/ftbsc/lll/processor/annotations/Injector.java
index 4b74961..8cebd24 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Injector.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Injector.java
@@ -16,19 +16,11 @@ import java.lang.annotation.RetentionPolicy;
* @see Target
*/
@Retention(RetentionPolicy.CLASS)
-@Repeatable(MultipleInjectors.class)
@java.lang.annotation.Target(ElementType.METHOD)
public @interface Injector {
/**
- * @return the name of the stub annotated with {@link Target} this is referring to.
- * @since 0.3.0
+ * @return the patching reason, for logging, defaults to "No reason specified."
+ * @since 0.5.0
*/
- String targetName() default "";
-
- /**
- * @return the parameters of the stub annotated with {@link Target} this is referring
- * to (used to discern in case of method stubs by the same name)
- * @since 0.3.0
- */
- Class<?>[] params() default {};
+ String reason() default "No reason specified.";
}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/MultipleInjectors.java b/src/main/java/ftbsc/lll/processor/annotations/MultipleTargets.java
index 8b4b3f8..aa37530 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/MultipleInjectors.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/MultipleTargets.java
@@ -6,14 +6,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Used to support {@link Injector} as a {@link Repeatable} annotation.
- * @since 0.3.0
+ * Used to support {@link Target} as a {@link Repeatable} annotation.
+ * @since 0.5.0
*/
@Retention(RetentionPolicy.CLASS)
@java.lang.annotation.Target(ElementType.METHOD)
-public @interface MultipleInjectors {
+public @interface MultipleTargets {
/**
* @return the {@link Injector} annotations, as an array
*/
- Injector[] value();
+ Target[] value();
}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Patch.java b/src/main/java/ftbsc/lll/processor/annotations/Patch.java
index 0dcc377..bac3f6a 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Patch.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Patch.java
@@ -1,5 +1,7 @@
package ftbsc.lll.processor.annotations;
+import ftbsc.lll.proxies.impl.TypeProxy;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -15,27 +17,17 @@ import java.lang.annotation.RetentionPolicy;
@java.lang.annotation.Target(ElementType.TYPE)
public @interface Patch {
/**
- * @return the Minecraft {@link Class} to target for patching
- */
- Class<?> value();
-
- /**
- * @return the patching reason, for logging, defaults to "No reason specified."
+ * @return the {@link Class} to target for patching
*/
- String reason() default "No reason specified.";
+ Class<?> value() default Object.class;
/**
- * @return the name of the inner class that should be targeted,
+ * This can be either the fully-qualified name to be used in place of {@link #value()}
+ * or an inner class name to append after a $ symbol to the already acquired
+ * fully-qualified name.
+ * @return the name of the inner class that contains the target,
* defaults to empty string (not an inner class)
- * @since 0.4.0
- */
- String innerClass() default "";
-
- /**
- * @return the anonymous class counter (1 for the first, 2 for
- * the second, 3 for the third...) for the class that should be
- * targeted, defaults to 0 (not an anonymous class)
- * @since 0.4.0
+ * @since 0.5.0
*/
- int anonymousClassCounter() default 0;
+ String className() default "";
}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Target.java b/src/main/java/ftbsc/lll/processor/annotations/Target.java
index 15967cb..ed5cc0f 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Target.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Target.java
@@ -1,23 +1,33 @@
package ftbsc.lll.processor.annotations;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Marks a method as the target method.
- * The method itself should have the same name, return type and parameters as the desired
- * Minecraft method.
- * It will also be discarded unless the containing class is annotated with {@link Patch}
+ * The method annotated with this, called "stub" within the documentation, should have the
+ * same name and parameters as the method it's supposed to represent.
+ * It will be discarded unless the containing class is annotated with {@link Patch}
* and another method within the class is annotated with {@link Injector}.
* @see Patch
* @see Injector
*/
@Retention(RetentionPolicy.CLASS)
+@Repeatable(MultipleTargets.class)
@java.lang.annotation.Target(ElementType.METHOD)
public @interface Target {
/**
+ * Indicates which of the methods annotated with {@link Find} or {@link Injector}
+ * is targeting this stub.
+ * @return the name of the element this is supposed to apply to
+ * @since 0.5.0
+ */
+ String of() default "";
+
+ /**
* When set to false, tells the processor to first try to match a single method by name,
* and to only check parameters if further clarification is needed.
* While non-strict mode is more computationally efficient, it's ultimately not
diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java
index 578d49f..1576a23 100644
--- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java
+++ b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java
@@ -1,6 +1,5 @@
package ftbsc.lll.processor.tools;
-import com.squareup.javapoet.*;
import ftbsc.lll.exceptions.AmbiguousDefinitionException;
import ftbsc.lll.exceptions.MappingNotFoundException;
import ftbsc.lll.exceptions.NotAProxyException;
@@ -8,9 +7,9 @@ import ftbsc.lll.exceptions.TargetNotFoundException;
import ftbsc.lll.processor.annotations.Find;
import ftbsc.lll.processor.annotations.Patch;
import ftbsc.lll.processor.annotations.Target;
+import ftbsc.lll.processor.tools.containers.ClassContainer;
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
-import ftbsc.lll.proxies.FieldProxy;
-import ftbsc.lll.proxies.MethodProxy;
+import ftbsc.lll.proxies.ProxyType;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
@@ -19,12 +18,12 @@ import javax.lang.model.util.Elements;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromExecutableElement;
-import static ftbsc.lll.processor.tools.JavaPoetUtils.methodDescriptorFromParams;
/**
* Collection of AST-related static utils that didn't really fit into the main class.
@@ -32,17 +31,19 @@ import static ftbsc.lll.processor.tools.JavaPoetUtils.methodDescriptorFromParams
public class ASTUtils {
/**
* Finds, among the methods of a class cl, the one annotated with ann, and tries to build
- * a {@link ExecutableElement} from it.
- * @param cl the {@link ExecutableElement} for the class containing the desired method
+ * 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
- * @return a {@link List} of {@link MethodSpec}s annotated with the given annotation
+ * @param <T> the type of {@link Element} to use
+ * @return a {@link List} of {@link Element}s annotated with the given annotation
* @since 0.2.0
*/
- public static List<ExecutableElement> findAnnotatedMethods(TypeElement cl, Class<? extends Annotation> ann) {
- return cl.getEnclosedElements()
+ @SuppressWarnings("unchecked")
+ public static <T extends Element> List<T> findAnnotatedElement(Element parent, Class<? extends Annotation> ann) {
+ return parent.getEnclosedElements()
.stream()
.filter(e -> e.getAnnotationsByType(ann).length != 0)
- .map(e -> (ExecutableElement) e)
+ .map(e -> (T) e)
.collect(Collectors.toList());
}
@@ -84,48 +85,43 @@ public class ASTUtils {
}
/**
+ * 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<Modifier> modifiers) {
+ int i = 0;
+ for(Modifier m : modifiers)
+ i |= mapModifier(m);
+ return i;
+ }
+
+ /**
* Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
* @param ann the annotation containing the class
* @param parentFunction the annotation function returning the class
- * @param innerName a string containing the inner class name or anonymous class number, may be null
+ * @param name a string containing the FQN, the inner class name or nothing
* @param <T> the type of the annotation carrying the information
* @return the fully qualified name of the given class
* @since 0.3.0
*/
- public static <T extends Annotation> String getClassFullyQualifiedName(T ann, Function<T, Class<?>> parentFunction, String innerName) {
+ public static <T extends Annotation> String getClassFullyQualifiedName(T ann, Function<T, Class<?>> parentFunction, String name) {
+ if(name.contains("."))
+ return name;
String fqn;
try {
fqn = parentFunction.apply(ann).getCanonicalName();
} catch(MirroredTypeException e) {
fqn = e.getTypeMirror().toString();
}
- if(innerName != null)
- fqn = String.format("%s$%s", fqn, innerName);
+ if(!name.equals(""))
+ fqn = String.format("%s$%s", fqn, name);
return fqn;
}
/**
- * Extracts the inner class name as a String from the annotation.
- * @param ann the annotation containing the class
- * @param innerClassFunction the annotation function returning the inner class name
- * @param anonymousCounterFunction the annotation function returning the anonymous class counter
- * @param <T> the type of the annotation carrying the information
- * @return the name of the inner class, or null if the target isn't an inner class
- * @since 0.4.0
- */
- public static <T extends Annotation> String getInnerName(T ann, Function<T, String> innerClassFunction, Function<T, Integer> anonymousCounterFunction) {
- String inner = null;
- if(!innerClassFunction.apply(ann).equals(""))
- inner = innerClassFunction.apply(ann);
- if(anonymousCounterFunction.apply(ann) != 0) {
- if(inner != null)
- throw new AmbiguousDefinitionException(String.format("Unclear inner class, is it %s or %d?", inner, anonymousCounterFunction.apply(ann)));
- else inner = anonymousCounterFunction.apply(ann).toString();
- }
- return inner;
- }
-
- /**
* Safely extracts a {@link Class} array from an annotation.
* @param ann the annotation containing the class
* @param fun the annotation function returning the class
@@ -165,30 +161,40 @@ public class ASTUtils {
/**
* Finds the class name and maps it to the correct format.
- * @param patchAnn the {@link Patch} annotation containing target class info
+ * @param fallback the (unobfuscated) FQN to fall back on
* @param finderAnn an annotation containing metadata about the target, may be null
* @return the fully qualified class name
* @since 0.3.0
*/
- private static String findClassName(Patch patchAnn, Find finderAnn) {
+ public static String findClassNameFromAnnotations(String fallback, Find finderAnn) {
String fullyQualifiedName;
if(finderAnn != null) {
fullyQualifiedName =
getClassFullyQualifiedName(
finderAnn,
- Find::parent,
- getInnerName(finderAnn, Find::parentInnerClass, Find::parentAnonymousClassCounter)
+ Find::value,
+ finderAnn.className()
);
if(!fullyQualifiedName.equals("java.lang.Object"))
return findClassName(fullyQualifiedName, null);
}
- fullyQualifiedName =
+ return findClassName(fallback, null);
+ }
+
+ /**
+ * Finds the class name and maps it to the correct format.
+ * @param patchAnn the {@link Patch} annotation containing target class info
+ * @param finderAnn an annotation containing metadata about the target, may be null
+ * @return the fully qualified class name
+ * @since 0.3.0
+ */
+ public static String findClassNameFromAnnotations(Patch patchAnn, Find finderAnn) {
+ return findClassNameFromAnnotations(
getClassFullyQualifiedName(
patchAnn,
Patch::value,
- getInnerName(patchAnn, Patch::innerClass, Patch::anonymousClassCounter)
- );
- return findClassName(fullyQualifiedName, null);
+ patchAnn.className()
+ ), null);
}
/**
@@ -222,10 +228,10 @@ public class ASTUtils {
* @throws TargetNotFoundException if it finds no valid candidate
* @since 0.3.0
*/
- private static Element findMember(String parentFQN, String name, String descr, boolean strict, boolean field, ProcessingEnvironment env) {
+ public static Element findMember(String parentFQN, String name, String descr, boolean strict, boolean field, ProcessingEnvironment env) {
TypeElement parent = env.getElementUtils().getTypeElement(parentFQN);
if(parent == null)
- throw new AmbiguousDefinitionException(String.format("Could not find parent class %s!", parentFQN));
+ throw new AmbiguousDefinitionException(String.format("Could not find parent class %s for member %s!", parentFQN, descr == null ? name : name + descr));
//try to find by name
List<Element> candidates = parent.getEnclosedElements()
.stream()
@@ -233,12 +239,12 @@ public class ASTUtils {
.filter(e -> e.getSimpleName().contentEquals(name))
.collect(Collectors.toList());
if(candidates.size() == 0)
- throw new TargetNotFoundException(String.format("%s %s", name, descr));
+ throw new TargetNotFoundException(field ? "field" : "method", String.format("%s %s", name, descr));
if(candidates.size() == 1 && (!strict || field))
return candidates.get(0);
- if(descr == null) {
+ if(field || descr == null) {
throw new AmbiguousDefinitionException(
- String.format("Found %d methods named %s in class %s!", candidates.size(), name, parentFQN)
+ String.format("Found %d members named %s in class %s!", candidates.size(), name, parentFQN)
);
} else {
candidates = candidates.stream()
@@ -248,7 +254,7 @@ public class ASTUtils {
: c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c).split("\\)")[0])
).collect(Collectors.toList());
if(candidates.size() == 0)
- throw new TargetNotFoundException(String.format("%s %s", name, descr));
+ throw new TargetNotFoundException("method", String.format("%s %s", name, descr));
if(candidates.size() > 1)
throw new AmbiguousDefinitionException(
String.format("Found %d methods named %s in class %s!", candidates.size(), name, parentFQN)
@@ -258,54 +264,73 @@ public class ASTUtils {
}
/**
- * Finds the real class member (field or method) corresponding to a stub annotated with
- * {@link Target} or {@link Find}.
+ * Finds the real class method corresponding to a stub annotated with {@link Target}.
* @param stub the {@link ExecutableElement} for the stub
* @param env the {@link ProcessingEnvironment} to perform the operation in
- * @return the {@link Element} corresponding to the method or field
+ * @return the {@link ExecutableElement} 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 Element findMemberFromStub(ExecutableElement stub, ProcessingEnvironment env) {
+ public static ExecutableElement findMethodFromStub(ExecutableElement stub, ProcessingEnvironment env) {
//the parent always has a @Patch annotation
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
//there should ever only be one of these two
Target targetAnn = stub.getAnnotation(Target.class); //if this is null strict mode is always disabled
Find findAnn = stub.getAnnotation(Find.class); //this may be null, it means no fallback info
- String parentFQN = findClassName(patchAnn, findAnn);
- String methodDescriptor =
- findAnn != null
- ? methodDescriptorFromParams(findAnn, Find::params, env.getElementUtils())
- : descriptorFromExecutableElement(stub);
- String memberName =
- findAnn != null && !findAnn.name().equals("")
- ? findAnn.name()
- : stub.getSimpleName().toString();
- return findMember(
+ String parentFQN = findClassNameFromAnnotations(patchAnn, findAnn);
+ String methodName = stub.getSimpleName().toString();
+ if(findAnn != null && !findAnn.name().equals(""))
+ throw new AmbiguousDefinitionException(String.format("Specified name %s in @Find annotation for method stub %s!", findAnn.name(), methodName));
+ String methodDescriptor = descriptorFromExecutableElement(stub);
+ return (ExecutableElement) findMember(
parentFQN,
- memberName,
+ methodName,
methodDescriptor,
targetAnn != null && targetAnn.strict(),
- targetAnn == null && !isMethodProxyStub(stub), //only evaluate if target is null
+ false, //only evaluate if target is null
env
);
}
/**
- * Utility method for finding out what type of proxy a method is.
+ * 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 m the annotated {@link ExecutableElement}
- * @return whether it returns a {@link MethodProxy} or a {@link FieldProxy}
+ * @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 boolean isMethodProxyStub(ExecutableElement m) {
- String returnTypeFQN = m.getReturnType().toString();
- if(returnTypeFQN.equals("ftbsc.lll.proxies.FieldProxy"))
- return false;
- else if(returnTypeFQN.equals("ftbsc.lll.proxies.MethodProxy"))
- return true;
- else throw new NotAProxyException(m.getEnclosingElement().getSimpleName().toString(), m.getSimpleName().toString());
+ 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());
+ }
+ }
+
+ /**
+ * Finds and builds a {@link ClassContainer} based on information contained
+ * within a {@link Find} annotation, else returns a fallback.
+ * @param fallback the {@link ClassContainer} it falls back on
+ * @param f the {@link Find} annotation to get info from
+ * @param env the {@link ProcessingEnvironment} to perform the operation in
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
+ * @return the built {@link ClassContainer} or the fallback if not enough information was present
+ * @since 0.5.0
+ */
+ public static ClassContainer findClassOrFallback(ClassContainer fallback, Find f, ProcessingEnvironment env, ObfuscationMapper mapper) {
+ String fqn = getClassFullyQualifiedName(f, Find::value, f.className());
+ return fqn.equals("java.lang.Object")
+ ? fallback
+ : new ClassContainer(fqn, env, mapper);
}
}
diff --git a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java
index 0591929..32a94bb 100644
--- a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java
+++ b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java
@@ -1,20 +1,31 @@
package ftbsc.lll.processor.tools;
import com.squareup.javapoet.*;
+import ftbsc.lll.processor.LilleroProcessor;
+import ftbsc.lll.processor.annotations.Find;
+import ftbsc.lll.processor.tools.containers.ArrayContainer;
+import ftbsc.lll.processor.tools.containers.ClassContainer;
+import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
+import ftbsc.lll.proxies.ProxyType;
import ftbsc.lll.tools.DescriptorBuilder;
-import ftbsc.lll.proxies.MethodProxy;
-import ftbsc.lll.proxies.FieldProxy;
+import ftbsc.lll.proxies.impl.MethodProxy;
+import ftbsc.lll.proxies.impl.FieldProxy;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
+import javax.tools.Diagnostic;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.function.Function;
-import static ftbsc.lll.processor.tools.ASTUtils.classArrayFromAnnotation;
+import static ftbsc.lll.processor.tools.ASTUtils.*;
+import static ftbsc.lll.processor.tools.ASTUtils.mapModifiers;
/**
* Collection of static utils that rely on JavaPoet to function.
@@ -97,23 +108,6 @@ public class JavaPoetUtils {
}
/**
- * Builds a (partial, not including the return type) method descriptor from its parameters
- * @param ann the annotation containing the class
- * @param fun the annotation function returning the class
- * @param elementUtils the {@link Elements} containing utils for the current processing environment
- * @param <T> the type of the annotation carrying the information
- * @return the method descriptor
- */
- public static <T extends Annotation> String methodDescriptorFromParams(T ann, Function<T, Class<?>[]> fun, Elements elementUtils) {
- List<TypeMirror> mirrors = classArrayFromAnnotation(ann, fun, elementUtils);
- StringBuilder sb = new StringBuilder("(");
- for(TypeMirror t : mirrors)
- sb.append(descriptorFromType(t));
- sb.append(")");
- return sb.toString();
- }
-
- /**
* Adds to the given {@link MethodSpec.Builder} the given line of code,
* containing a call to a method of a {@link MethodProxy.Builder} or a
* {@link FieldProxy.Builder}.
@@ -139,4 +133,88 @@ public class JavaPoetUtils {
);
}
}
+
+ /**
+ * Appends to a given {@link MethodSpec.Builder} definitions for a proxy.
+ * @param fallback the {@link ClassContainer} to fall back on
+ * @param var the {@link VariableElement} representing the proxy
+ * @param stub the stub {@link ExecutableElement} if present or relevant, null otherwise
+ * @param con the {@link MethodSpec.Builder} to append to
+ * @param env the {@link ProcessingEnvironment} to perform the operation in
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
+ * @since 0.5.0
+ */
+ public static void appendMemberFinderDefinition(
+ ClassContainer fallback, VariableElement var, ExecutableElement stub, MethodSpec.Builder con, ProcessingEnvironment env, ObfuscationMapper mapper) {
+ ProxyType type = getProxyType(var);
+ if(type != ProxyType.METHOD && type != ProxyType.FIELD)
+ return; //this method is irrelevant to everyoen else
+
+ //we need this stuff
+ Find f = var.getAnnotation(Find.class);
+ ClassContainer parent = findClassOrFallback(fallback, f, env, mapper);
+ final boolean isMethod = type == ProxyType.METHOD;
+ final String builderName = var.getSimpleName().toString() + "Builder";
+
+ String name, nameObf;
+ Element target;
+
+ if(isMethod) {
+ ExecutableElement executableTarget;
+ if(f.name().equals("")) //find and validate from stub
+ executableTarget = findMethodFromStub(stub, env);
+ else { //find and validate by name alone
+ if(LilleroProcessor.badPracticeWarnings) //warn user that he is doing bad stuff
+ env.getMessager().printMessage(Diagnostic.Kind.WARNING,
+ String.format("Matching method %s by name, this is bad practice and may lead to unexpected behaviour. Use @Target stubs instead!", f.name()));
+ executableTarget = (ExecutableElement) findMember(parent.fqn, f.name(), null, false, false, env);
+ }
+ name = executableTarget.getSimpleName().toString();
+ nameObf = findMemberName(parent.fqnObf, name, descriptorFromExecutableElement(executableTarget), mapper);
+ target = executableTarget;
+ } else {
+ //find and validate target
+ name = f.name().equals("") ? var.getSimpleName().toString() : f.name();
+ target = findMember(parent.fqn, name, null, false, true, env);
+ nameObf = findMemberName(parent.fqnObf, name, null, mapper);
+ }
+
+ //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.fqnObf,
+ mapModifiers(parent.elem.getModifiers())
+ );
+
+ //set modifiers
+ con.addStatement(
+ "$L.setModifiers($L)",
+ builderName,
+ mapModifiers(target.getModifiers())
+ );
+
+ if(isMethod) { //set parameters and return type
+ ExecutableElement executableTarget = (ExecutableElement) target;
+ for(VariableElement p : executableTarget.getParameters())
+ addTypeToProxyGenerator(con, builderName, "addParameter", p.asType());
+ addTypeToProxyGenerator(con, builderName, "setReturnType", executableTarget.getReturnType());
+ } else //set type
+ addTypeToProxyGenerator(con,builderName, "setType", target.asType());
+
+ //build and set
+ con.addStatement(
+ "super.$L = $L.build()",
+ var.getSimpleName().toString(),
+ builderName
+ );
+ }
}
diff --git a/src/main/java/ftbsc/lll/processor/tools/ArrayContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/ArrayContainer.java
index c32a621..7e7da25 100644
--- a/src/main/java/ftbsc/lll/processor/tools/ArrayContainer.java
+++ b/src/main/java/ftbsc/lll/processor/tools/containers/ArrayContainer.java
@@ -1,4 +1,4 @@
-package ftbsc.lll.processor.tools;
+package ftbsc.lll.processor.tools.containers;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java
new file mode 100644
index 0000000..bde527d
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java
@@ -0,0 +1,47 @@
+package ftbsc.lll.processor.tools.containers;
+
+import ftbsc.lll.exceptions.TargetNotFoundException;
+import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+
+import static ftbsc.lll.processor.tools.ASTUtils.findClassName;
+
+/**
+ * Container for information about a class.
+ * Used internally for efficiency reasons.
+ * @since 0.5.0
+ */
+public class ClassContainer {
+ /**
+ * The fully-qualified name of the class.
+ */
+ public final String fqn;
+
+ /**
+ * The obfuscated fully-qualified name of the class.
+ * If the mapper passed is null, then this will be identical to {@link #fqn}
+ */
+ public final String fqnObf;
+
+ /**
+ * The {@link Element} corresponding to the class.
+ */
+ public final Element elem;
+
+ /**
+ * Public constructor.
+ * @param fqn the fully-qualified name of the target class
+ * @param env the {@link ProcessingEnvironment} to be used to locate the class
+ * @param mapper the {@link ObfuscationMapper} to be used, may be null
+ */
+ public ClassContainer(String fqn, ProcessingEnvironment env, ObfuscationMapper mapper) {
+ this.fqn = fqn;
+ this.fqnObf = findClassName(fqn, mapper);
+ Element elem = env.getElementUtils().getTypeElement(fqn); //at compile time we have an unobfuscated environment
+ if(elem == null)
+ throw new TargetNotFoundException("class", fqn);
+ else this.elem = elem;
+ }
+} \ No newline at end of file