mirror of
https://github.com/zaaarf/lillero-processor.git
synced 2024-11-14 17:29:20 +01:00
feat: created container types for methods and fields (efficiency and code quality), made temporary fix for anonymous classes
This commit is contained in:
parent
3cea0d3e66
commit
96c06c797e
9 changed files with 492 additions and 252 deletions
|
@ -10,6 +10,7 @@ import ftbsc.lll.processor.annotations.Injector;
|
||||||
import ftbsc.lll.processor.annotations.Patch;
|
import ftbsc.lll.processor.annotations.Patch;
|
||||||
import ftbsc.lll.processor.annotations.Target;
|
import ftbsc.lll.processor.annotations.Target;
|
||||||
import ftbsc.lll.processor.tools.containers.ClassContainer;
|
import ftbsc.lll.processor.tools.containers.ClassContainer;
|
||||||
|
import ftbsc.lll.processor.tools.containers.MethodContainer;
|
||||||
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
import ftbsc.lll.proxies.ProxyType;
|
import ftbsc.lll.proxies.ProxyType;
|
||||||
import ftbsc.lll.proxies.impl.TypeProxy;
|
import ftbsc.lll.proxies.impl.TypeProxy;
|
||||||
|
@ -39,7 +40,7 @@ import static ftbsc.lll.processor.tools.JavaPoetUtils.*;
|
||||||
*/
|
*/
|
||||||
@SupportedAnnotationTypes("ftbsc.lll.processor.annotations.Patch")
|
@SupportedAnnotationTypes("ftbsc.lll.processor.annotations.Patch")
|
||||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||||
@SupportedOptions({"mappingsFile", "badPracticeWarnings"})
|
@SupportedOptions({"mappingsFile", "badPracticeWarnings", "anonymousClassWarning"})
|
||||||
public class LilleroProcessor extends AbstractProcessor {
|
public class LilleroProcessor extends AbstractProcessor {
|
||||||
/**
|
/**
|
||||||
* A {@link Set} of {@link String}s that will contain the fully qualified names
|
* A {@link Set} of {@link String}s that will contain the fully qualified names
|
||||||
|
@ -59,6 +60,12 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
*/
|
*/
|
||||||
public static boolean badPracticeWarnings = true;
|
public static boolean badPracticeWarnings = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the processor should issue warnings when compiling code anonymous
|
||||||
|
* classes which can't be checked for validity.
|
||||||
|
*/
|
||||||
|
public static boolean anonymousClassWarning = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the processor with the processing environment by
|
* Initializes the processor with the processing environment by
|
||||||
* setting the {@code processingEnv} field to the value of the
|
* setting the {@code processingEnv} field to the value of the
|
||||||
|
@ -98,13 +105,24 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
String warns = processingEnv.getOptions().get("badPracticeWarnings");
|
String warns = processingEnv.getOptions().get("badPracticeWarnings");
|
||||||
if(warns == null)
|
if(warns == null)
|
||||||
badPracticeWarnings = true;
|
badPracticeWarnings = true;
|
||||||
else {
|
else badPracticeWarnings = parseBooleanArg(warns);
|
||||||
try { // 0 = false, any other integer = true
|
String anonymousClassWarn = processingEnv.getOptions().get("anonymousClassWarning");
|
||||||
int i = Integer.parseInt(warns);
|
if(anonymousClassWarn == null)
|
||||||
badPracticeWarnings = i != 0;
|
anonymousClassWarning = true;
|
||||||
} catch(NumberFormatException ignored) {
|
else anonymousClassWarning = parseBooleanArg(anonymousClassWarn);
|
||||||
badPracticeWarnings = Boolean.parseBoolean(warns);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a boolean arg from a String.
|
||||||
|
* @param arg the arg to parse
|
||||||
|
* @return the parsed boolean
|
||||||
|
*/
|
||||||
|
private static boolean parseBooleanArg(String arg) {
|
||||||
|
try { // 0 = false, any other integer = true
|
||||||
|
int i = Integer.parseInt(arg);
|
||||||
|
return i != 0;
|
||||||
|
} catch(NumberFormatException ignored) {
|
||||||
|
return Boolean.parseBoolean(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,14 +192,13 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
private void generateClasses(TypeElement cl) {
|
private void generateClasses(TypeElement cl) {
|
||||||
//find class information
|
//find class information
|
||||||
Patch patchAnn = cl.getAnnotation(Patch.class);
|
Patch patchAnn = cl.getAnnotation(Patch.class);
|
||||||
ClassContainer targetClass = new ClassContainer(
|
ClassContainer targetClass = ClassContainer.from(
|
||||||
getClassFullyQualifiedName(
|
|
||||||
patchAnn,
|
patchAnn,
|
||||||
Patch::value,
|
Patch::value,
|
||||||
patchAnn.className()
|
patchAnn.className(),
|
||||||
), this.processingEnv, this.mapper
|
this.processingEnv,
|
||||||
|
this.mapper
|
||||||
);
|
);
|
||||||
|
|
||||||
//find package information
|
//find package information
|
||||||
Element packageElement = cl.getEnclosingElement();
|
Element packageElement = cl.getEnclosingElement();
|
||||||
while (packageElement.getKind() != ElementKind.PACKAGE)
|
while (packageElement.getKind() != ElementKind.PACKAGE)
|
||||||
|
@ -209,17 +226,17 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
//case-specific handling
|
//case-specific handling
|
||||||
if(type == ProxyType.TYPE) {
|
if(type == ProxyType.TYPE) {
|
||||||
//find and validate
|
//find and validate
|
||||||
ClassContainer clazz = findClassOrFallback(targetClass, proxyVar.getAnnotation(Find.class), this.processingEnv, this.mapper);
|
ClassContainer clazz = ClassContainer.findOrFallback(targetClass, proxyVar.getAnnotation(Find.class), this.processingEnv, this.mapper);
|
||||||
//types can be generated with a single instruction
|
//types can be generated with a single instruction
|
||||||
constructorBuilder.addStatement(
|
constructorBuilder.addStatement(
|
||||||
"super.$L = $T.from($S, 0, $L)",
|
"super.$L = $T.from($S, 0, $L)",
|
||||||
proxyVar.getSimpleName().toString(),
|
proxyVar.getSimpleName().toString(),
|
||||||
TypeProxy.class,
|
TypeProxy.class,
|
||||||
clazz.fqnObf, //use obf name, at runtime it will be obfuscated
|
clazz.fqnObf, //use obf name, at runtime it will be obfuscated
|
||||||
mapModifiers(clazz.elem.getModifiers())
|
clazz.elem == null ? 0 : mapModifiers(clazz.elem.getModifiers())
|
||||||
);
|
);
|
||||||
} else if(type == ProxyType.FIELD || type == ProxyType.METHOD)
|
} else if(type == ProxyType.FIELD || type == ProxyType.METHOD)
|
||||||
appendMemberFinderDefinition(targetClass, proxyVar, null, constructorBuilder, this.processingEnv, this.mapper);
|
appendMemberFinderDefinition(targetClass, proxyVar, null, null, constructorBuilder, this.processingEnv, this.mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this will contain the classes to generate: the key is the class name
|
//this will contain the classes to generate: the key is the class name
|
||||||
|
@ -236,8 +253,7 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
List<ExecutableElement> injectorCandidates = injectors;
|
List<ExecutableElement> injectorCandidates = injectors;
|
||||||
List<VariableElement> finderCandidates = methodFinders;
|
List<VariableElement> finderCandidates = methodFinders;
|
||||||
|
|
||||||
if(!targetAnn.of().equals("")) {
|
//find target by name
|
||||||
//case 1: find target by name
|
|
||||||
injectorCandidates =
|
injectorCandidates =
|
||||||
injectorCandidates
|
injectorCandidates
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -248,24 +264,6 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
.stream()
|
.stream()
|
||||||
.filter(i -> i.getSimpleName().contentEquals(targetAnn.of()))
|
.filter(i -> i.getSimpleName().contentEquals(targetAnn.of()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} else if(injectors.size() == 1 && targets.size() == 1) {
|
|
||||||
//case 2: there is only one target, must use it for that injector
|
|
||||||
finderCandidates = new ArrayList<>(); //no candidates
|
|
||||||
injectorCandidates = new ArrayList<>();
|
|
||||||
injectorCandidates.add(injectors.get(0));
|
|
||||||
} else {
|
|
||||||
//case 3: try to match by injectTargetName or same name for finders
|
|
||||||
injectorCandidates =
|
|
||||||
injectorCandidates
|
|
||||||
.stream()
|
|
||||||
.filter(t -> t.getSimpleName().toString().equalsIgnoreCase("inject" + tg.getSimpleName()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
finderCandidates =
|
|
||||||
finderCandidates
|
|
||||||
.stream()
|
|
||||||
.filter(t -> t.getSimpleName().contentEquals(tg.getSimpleName()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
//throw exception if user is a moron and defined a finder and an injector with the same name
|
//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)
|
if(finderCandidates.size() != 0 && injectorCandidates.size() != 0)
|
||||||
|
@ -274,7 +272,13 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
);
|
);
|
||||||
else if(finderCandidates.size() == 0 && injectorCandidates.size() == 0)
|
else if(finderCandidates.size() == 0 && injectorCandidates.size() == 0)
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
|
||||||
String.format("Found orphan @Target annotation on method %s, it will be ignored!", tg.getSimpleName().toString()));
|
String.format(
|
||||||
|
"Found orphan @Target annotation on method %s.%s pointing at method %s, it will be ignored!",
|
||||||
|
cl.getSimpleName().toString(),
|
||||||
|
tg.getSimpleName().toString(),
|
||||||
|
targetAnn.of()
|
||||||
|
)
|
||||||
|
);
|
||||||
else if(finderCandidates.size() == 0 && injectorCandidates.size() != 1)
|
else if(finderCandidates.size() == 0 && injectorCandidates.size() != 1)
|
||||||
throw new AmbiguousDefinitionException(
|
throw new AmbiguousDefinitionException(
|
||||||
String.format("Found multiple candidate injectors for target %s::%s!", cl.getSimpleName(), tg.getSimpleName())
|
String.format("Found multiple candidate injectors for target %s::%s!", cl.getSimpleName(), tg.getSimpleName())
|
||||||
|
@ -290,7 +294,7 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
matchedInjectors.add(injector);
|
matchedInjectors.add(injector);
|
||||||
toGenerate.put(
|
toGenerate.put(
|
||||||
String.format("%sInjector%d", cl.getSimpleName(), iterationNumber),
|
String.format("%sInjector%d", cl.getSimpleName(), iterationNumber),
|
||||||
new InjectorInfo(injector, tg)
|
new InjectorInfo(injector, tg, targetAnn)
|
||||||
);
|
);
|
||||||
iterationNumber++; //increment is only used by injectors
|
iterationNumber++; //increment is only used by injectors
|
||||||
} else {
|
} else {
|
||||||
|
@ -301,6 +305,7 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
targetClass,
|
targetClass,
|
||||||
finder,
|
finder,
|
||||||
tg,
|
tg,
|
||||||
|
targetAnn,
|
||||||
constructorBuilder,
|
constructorBuilder,
|
||||||
this.processingEnv,
|
this.processingEnv,
|
||||||
this.mapper
|
this.mapper
|
||||||
|
@ -320,8 +325,6 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
//iterate over the map and generate the classes
|
//iterate over the map and generate the classes
|
||||||
for(String injName : toGenerate.keySet()) {
|
for(String injName : toGenerate.keySet()) {
|
||||||
String targetMethodDescriptor = descriptorFromExecutableElement(toGenerate.get(injName).target);
|
|
||||||
String targetMethodName = findMemberName(targetClass.fqnObf, toGenerate.get(injName).target.getSimpleName().toString(), targetMethodDescriptor, this.mapper);
|
|
||||||
MethodSpec inject = MethodSpec.methodBuilder("inject")
|
MethodSpec inject = MethodSpec.methodBuilder("inject")
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.returns(void.class)
|
.returns(void.class)
|
||||||
|
@ -345,8 +348,8 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
||||||
.addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason))
|
.addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason))
|
||||||
.addMethod(buildStringReturnMethod("targetClass", targetClass.fqn))
|
.addMethod(buildStringReturnMethod("targetClass", targetClass.fqn))
|
||||||
.addMethod(buildStringReturnMethod("methodName", targetMethodName))
|
.addMethod(buildStringReturnMethod("methodName", toGenerate.get(injName).target.name))
|
||||||
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
|
.addMethod(buildStringReturnMethod("methodDesc", toGenerate.get(injName).target.descriptor))
|
||||||
.addMethods(generateDummies(targets))
|
.addMethods(generateDummies(targets))
|
||||||
.addMethod(inject)
|
.addMethod(inject)
|
||||||
.build();
|
.build();
|
||||||
|
@ -405,20 +408,21 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
public final String reason;
|
public final String reason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ExecutableElement} corresponding to the target method.
|
* The {@link MethodContainer} corresponding to the target method.
|
||||||
*/
|
*/
|
||||||
private final ExecutableElement target;
|
private final MethodContainer target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public constructor.
|
* Public constructor.
|
||||||
* @param injector the injector {@link ExecutableElement}
|
* @param injector the injector {@link ExecutableElement}
|
||||||
* @param targetStub the target {@link ExecutableElement}
|
* @param targetStub the target {@link ExecutableElement}
|
||||||
|
* @param targetAnn the relevant {@link Target} annotation
|
||||||
*/
|
*/
|
||||||
public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub) {
|
public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub, Target targetAnn) {
|
||||||
this.injector = injector;
|
this.injector = injector;
|
||||||
this.targetStub = targetStub;
|
this.targetStub = targetStub;
|
||||||
this.reason = injector.getAnnotation(Injector.class).reason();
|
this.reason = injector.getAnnotation(Injector.class).reason();
|
||||||
this.target = findMethodFromStub(targetStub, null, processingEnv);
|
this.target = MethodContainer.from(targetStub, targetAnn, null, processingEnv, mapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,10 +26,10 @@ public @interface Find {
|
||||||
Class<?> value() default Object.class;
|
Class<?> value() default Object.class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For a {@link TypeProxy}, this can be either the fully-qualified name
|
* This can be either the fully-qualified name to be used in place of {@link #value()} to
|
||||||
* to be used in place of {@link #value()} or an inner class name to append
|
* represent the parent class or an inner class name to append after a $ symbol to the
|
||||||
* after a $ symbol to the already acquired fully-qualified name.
|
* already acquired fully-qualified name.
|
||||||
* For others, this is refers to the parent class.
|
* For a {@link TypeProxy}, this refers to the class itself rather than the parent.
|
||||||
* @return the name of the inner class that contains the target,
|
* @return the name of the inner class that contains the target,
|
||||||
* defaults to empty string (not an inner class)
|
* defaults to empty string (not an inner class)
|
||||||
* @since 0.5.0
|
* @since 0.5.0
|
||||||
|
@ -50,4 +50,22 @@ public @interface Find {
|
||||||
* @since 0.5.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
String name() default "";
|
String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This overrules the type of a field. Only to be used in the case (such as fields of
|
||||||
|
* anonymous classes) of fields whose parents cannot be reached at processing time.
|
||||||
|
* @return a {@link Class} representing the type.
|
||||||
|
* @deprecated This is only meant as a temporary solution until a better handling
|
||||||
|
* is implemented; only use this if strictly necessary as it may be
|
||||||
|
* removed or changed even across revisions.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Class<?> type() default Object.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the inner class name to be used with {@link #type()}
|
||||||
|
* @deprecated See {@link #type()}'s deprecation notice for more info.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
String typeInner() default "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,14 @@ public @interface Target {
|
||||||
* @return the name of the element this is supposed to apply to
|
* @return the name of the element this is supposed to apply to
|
||||||
* @since 0.5.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
String of() default "";
|
String of();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a name which overrides the name of the annotated one, may be used in
|
||||||
|
* cases such as constructors
|
||||||
|
* @since 0.5.0
|
||||||
|
*/
|
||||||
|
String methodName() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When set to false, tells the processor to first try to match a single method by name,
|
* When set to false, tells the processor to first try to match a single method by name,
|
||||||
|
|
|
@ -4,16 +4,18 @@ import ftbsc.lll.exceptions.AmbiguousDefinitionException;
|
||||||
import ftbsc.lll.exceptions.MappingNotFoundException;
|
import ftbsc.lll.exceptions.MappingNotFoundException;
|
||||||
import ftbsc.lll.exceptions.NotAProxyException;
|
import ftbsc.lll.exceptions.NotAProxyException;
|
||||||
import ftbsc.lll.exceptions.TargetNotFoundException;
|
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.annotations.Target;
|
||||||
import ftbsc.lll.processor.tools.containers.ClassContainer;
|
import ftbsc.lll.processor.tools.containers.ClassContainer;
|
||||||
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
import ftbsc.lll.proxies.ProxyType;
|
import ftbsc.lll.proxies.ProxyType;
|
||||||
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.*;
|
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.MirroredTypeException;
|
import javax.lang.model.type.MirroredTypeException;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -21,6 +23,7 @@ import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromExecutableElement;
|
import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromExecutableElement;
|
||||||
|
import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of AST-related static utils that didn't really fit into the main class.
|
* Collection of AST-related static utils that didn't really fit into the main class.
|
||||||
|
@ -96,26 +99,22 @@ public class ASTUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
|
* Safely extracts a {@link Class} from an annotation and gets a {@link TypeMirror} representing it.
|
||||||
* @param ann the annotation containing the class
|
* @param ann the annotation containing the class
|
||||||
* @param parentFunction the annotation function returning the class
|
* @param classFunction the annotation function returning the class
|
||||||
* @param name a string containing the FQN, the inner class name or nothing
|
* @param env the {@link ProcessingEnvironment} to perform the operation in
|
||||||
* @param <T> the type of the annotation carrying the information
|
* @param <T> the type of the annotation carrying the information
|
||||||
* @return the fully qualified name of the given class
|
* @return a {@link TypeMirror} representing the requested {@link Class}
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
public static <T extends Annotation> String getClassFullyQualifiedName(T ann, Function<T, Class<?>> parentFunction, String name) {
|
public static <T extends Annotation> TypeMirror getTypeFromAnnotation(
|
||||||
if(name.contains("."))
|
T ann, Function<T, Class<?>> classFunction, ProcessingEnvironment env) {
|
||||||
return name;
|
|
||||||
String fqn;
|
|
||||||
try {
|
try {
|
||||||
fqn = parentFunction.apply(ann).getCanonicalName();
|
String fqn = classFunction.apply(ann).getCanonicalName();
|
||||||
|
return env.getElementUtils().getTypeElement(fqn).asType();
|
||||||
} catch(MirroredTypeException e) {
|
} catch(MirroredTypeException e) {
|
||||||
fqn = e.getTypeMirror().toString();
|
return e.getTypeMirror();
|
||||||
}
|
}
|
||||||
if(!name.equals(""))
|
|
||||||
fqn = String.format("%s$%s", fqn, name);
|
|
||||||
return fqn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,48 +132,11 @@ public class ASTUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the class name and maps it to the correct format.
|
|
||||||
* @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.5.0
|
|
||||||
*/
|
|
||||||
public static String findClassNameFromAnnotations(String fallback, Find finderAnn) {
|
|
||||||
if(finderAnn != null) {
|
|
||||||
String fullyQualifiedName =
|
|
||||||
getClassFullyQualifiedName(
|
|
||||||
finderAnn,
|
|
||||||
Find::value,
|
|
||||||
finderAnn.className()
|
|
||||||
);
|
|
||||||
if(!fullyQualifiedName.equals("java.lang.Object"))
|
|
||||||
return findClassName(fullyQualifiedName, null);
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
patchAnn.className()
|
|
||||||
), finderAnn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the member name and maps it to the correct format.
|
* Finds the member name and maps it to the correct format.
|
||||||
* @param parentFQN the already mapped FQN of the parent class
|
* @param parentFQN the already mapped FQN of the parent class
|
||||||
* @param memberName the name of the member
|
* @param memberName the name of the member
|
||||||
* @param methodDescriptor the descriptor of the method, may be null if it's not a method
|
* @param methodDescriptor the descriptor of the method, may be null
|
||||||
* @param mapper the {@link ObfuscationMapper} to use, may be null
|
* @param mapper the {@link ObfuscationMapper} to use, may be null
|
||||||
* @return the internal class name
|
* @return the internal class name
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
|
@ -189,37 +151,42 @@ public class ASTUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a member given the name, the container class and (if it's a method) the descriptor.
|
* Finds a member given the name, the container class and (if it's a method) the descriptor.
|
||||||
* @param parentFQN the fully qualified name of the parent class
|
* @param parent the {@link ClassContainer} representing the parent
|
||||||
* @param name the name to search for
|
* @param name the name to search for
|
||||||
* @param descr the descriptor to search for, or null if it's not a method
|
* @param descr the descriptor to search for, or null if it's not a method
|
||||||
* @param strict whether the search should be strict (see {@link Target#strict()} for more info),
|
* @param strict whether to perform lookup in strict mode (see {@link Target#strict()} for more information)
|
||||||
* only applies to method searches
|
|
||||||
* @param field whether the member being searched is a field
|
* @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
|
* @return the desired member, if it exists
|
||||||
* @throws AmbiguousDefinitionException if it finds more than one candidate
|
* @throws AmbiguousDefinitionException if it finds more than one candidate
|
||||||
* @throws TargetNotFoundException if it finds no valid candidate
|
* @throws TargetNotFoundException if it finds no valid candidate
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
public static Element findMember(String parentFQN, String name, String descr, boolean strict, boolean field, ProcessingEnvironment env) {
|
public static Element findMember(ClassContainer parent, String name, String descr, boolean strict, boolean field) {
|
||||||
TypeElement parent = env.getElementUtils().getTypeElement(parentFQN);
|
if(parent.elem == null)
|
||||||
if(parent == null)
|
throw new TargetNotFoundException("parent class", parent.fqn);
|
||||||
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
|
//try to find by name
|
||||||
List<Element> candidates = parent.getEnclosedElements()
|
List<Element> candidates = parent.elem.getEnclosedElements()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(e -> (field && e instanceof VariableElement) || e instanceof ExecutableElement)
|
.filter(e -> (field && e instanceof VariableElement) || e instanceof ExecutableElement)
|
||||||
.filter(e -> e.getSimpleName().contentEquals(name))
|
.filter(e -> e.getSimpleName().contentEquals(name))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if(candidates.size() == 0)
|
if(candidates.size() == 0)
|
||||||
throw new TargetNotFoundException(field ? "field" : "method", name, parentFQN);
|
throw new TargetNotFoundException(field ? "field" : "method", name, parent.fqn);
|
||||||
if(candidates.size() == 1 && (!strict || field))
|
|
||||||
|
if(candidates.size() == 1 && (!strict || descr == null))
|
||||||
return candidates.get(0);
|
return candidates.get(0);
|
||||||
if(field || descr == null) {
|
|
||||||
|
if(descr == null) {
|
||||||
throw new AmbiguousDefinitionException(
|
throw new AmbiguousDefinitionException(
|
||||||
String.format("Found %d members named %s in class %s!", candidates.size(), name, parentFQN)
|
String.format("Found %d members named %s in class %s!", candidates.size(), name, parent.fqn)
|
||||||
);
|
);
|
||||||
|
} 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()).equals(descr))
|
||||||
|
throw new TargetNotFoundException("field", String.format("%s with descriptor %s", name, descr), parent.fqn);
|
||||||
} else {
|
} else {
|
||||||
candidates = candidates.stream()
|
candidates = candidates.stream()
|
||||||
.map(e -> (ExecutableElement) e)
|
.map(e -> (ExecutableElement) e)
|
||||||
|
@ -227,44 +194,17 @@ public class ASTUtils {
|
||||||
? c -> descr.equals(descriptorFromExecutableElement(c))
|
? c -> descr.equals(descriptorFromExecutableElement(c))
|
||||||
: c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c).split("\\)")[0])
|
: c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c).split("\\)")[0])
|
||||||
).collect(Collectors.toList());
|
).collect(Collectors.toList());
|
||||||
|
}
|
||||||
if(candidates.size() == 0)
|
if(candidates.size() == 0)
|
||||||
throw new TargetNotFoundException("method", String.format("%s %s", name, descr), parentFQN);
|
throw new TargetNotFoundException("method", String.format("%s %s", name, descr), parent.fqn);
|
||||||
if(candidates.size() > 1)
|
if(candidates.size() > 1)
|
||||||
throw new AmbiguousDefinitionException(
|
throw new AmbiguousDefinitionException(
|
||||||
String.format("Found %d methods named %s in class %s!", candidates.size(), name, parentFQN)
|
String.format("Found %d methods named %s in class %s!", candidates.size(), name, parent.fqn)
|
||||||
);
|
);
|
||||||
return candidates.get(0);
|
return candidates.get(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the real class method corresponding to a stub annotated with {@link Target}.
|
|
||||||
* @param stub the {@link ExecutableElement} for the stub
|
|
||||||
* @param f the {@link Find} annotation containing fallback data, may be null
|
|
||||||
* @param env the {@link ProcessingEnvironment} to perform the operation in
|
|
||||||
* @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 ExecutableElement findMethodFromStub(ExecutableElement stub, Find f, 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
|
|
||||||
String parentFQN = findClassNameFromAnnotations(patchAnn, f);
|
|
||||||
String methodName = stub.getSimpleName().toString();
|
|
||||||
String methodDescriptor = descriptorFromExecutableElement(stub);
|
|
||||||
return (ExecutableElement) findMember(
|
|
||||||
parentFQN,
|
|
||||||
methodName,
|
|
||||||
methodDescriptor,
|
|
||||||
targetAnn != null && targetAnn.strict(),
|
|
||||||
false, //only evaluate if target is null
|
|
||||||
env
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method for finding out what type of proxy a field 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.
|
* It will fail if the return type is not a known type of proxy.
|
||||||
|
@ -288,21 +228,4 @@ public class ASTUtils {
|
||||||
throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString());
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package ftbsc.lll.processor.tools;
|
package ftbsc.lll.processor.tools;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
import com.squareup.javapoet.*;
|
||||||
import ftbsc.lll.processor.LilleroProcessor;
|
|
||||||
import ftbsc.lll.processor.annotations.Find;
|
import ftbsc.lll.processor.annotations.Find;
|
||||||
import ftbsc.lll.processor.tools.containers.ArrayContainer;
|
import ftbsc.lll.processor.annotations.Target;
|
||||||
import ftbsc.lll.processor.tools.containers.ClassContainer;
|
import ftbsc.lll.processor.tools.containers.ClassContainer;
|
||||||
|
import ftbsc.lll.processor.tools.containers.FieldContainer;
|
||||||
|
import ftbsc.lll.processor.tools.containers.MethodContainer;
|
||||||
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
import ftbsc.lll.proxies.ProxyType;
|
import ftbsc.lll.proxies.ProxyType;
|
||||||
import ftbsc.lll.proxies.impl.FieldProxy;
|
import ftbsc.lll.proxies.impl.FieldProxy;
|
||||||
|
@ -16,13 +17,12 @@ import javax.lang.model.element.Element;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.lang.model.element.VariableElement;
|
import javax.lang.model.element.VariableElement;
|
||||||
import javax.lang.model.type.TypeKind;
|
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import javax.tools.Diagnostic;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import static ftbsc.lll.processor.tools.ASTUtils.*;
|
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.
|
* Collection of static utils that rely on JavaPoet to function.
|
||||||
|
@ -104,76 +104,45 @@ public class JavaPoetUtils {
|
||||||
return methodSignature.toString();
|
return methodSignature.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}.
|
|
||||||
* @param b the {@link MethodSpec.Builder}
|
|
||||||
* @param proxyBuilderName the name of the proxy builder
|
|
||||||
* @param proxyBuilderMethod the method to call
|
|
||||||
* @param t the {@link TypeMirror} to add
|
|
||||||
* @since 0.4.0
|
|
||||||
*/
|
|
||||||
public static void addTypeToProxyGenerator(MethodSpec.Builder b, String proxyBuilderName, String proxyBuilderMethod, TypeMirror t) {
|
|
||||||
String insn = String.format("%s.%s", proxyBuilderName, proxyBuilderMethod);
|
|
||||||
if(t.getKind().isPrimitive() || t.getKind() == TypeKind.VOID)
|
|
||||||
b.addStatement(insn + "($T.class)", t);
|
|
||||||
else {
|
|
||||||
ArrayContainer arr = new ArrayContainer(t);
|
|
||||||
TypeName type = TypeName.get(arr.innermostComponent);
|
|
||||||
if(type instanceof ParameterizedTypeName)
|
|
||||||
type = ((ParameterizedTypeName) type).rawType;
|
|
||||||
b.addStatement(
|
|
||||||
insn + "($S, $L)",
|
|
||||||
type.toString(),
|
|
||||||
arr.arrayLevel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends to a given {@link MethodSpec.Builder} definitions for a proxy.
|
* Appends to a given {@link MethodSpec.Builder} definitions for a proxy.
|
||||||
* @param fallback the {@link ClassContainer} to fall back on
|
* @param fallback the {@link ClassContainer} to fall back on
|
||||||
* @param var the {@link VariableElement} representing the proxy
|
* @param var the {@link VariableElement} representing the proxy
|
||||||
* @param stub the stub {@link ExecutableElement} if present or relevant, null otherwise
|
* @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 con the {@link MethodSpec.Builder} to append to
|
||||||
* @param env the {@link ProcessingEnvironment} to perform the operation in
|
* @param env the {@link ProcessingEnvironment} to perform the operation in
|
||||||
* @param mapper the {@link ObfuscationMapper} to use, may be null
|
* @param mapper the {@link ObfuscationMapper} to use, may be null
|
||||||
* @since 0.5.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
public static void appendMemberFinderDefinition(
|
public static void appendMemberFinderDefinition(
|
||||||
ClassContainer fallback, VariableElement var, ExecutableElement stub, MethodSpec.Builder con, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
ClassContainer fallback, VariableElement var, ExecutableElement stub, Target t,
|
||||||
|
MethodSpec.Builder con, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
||||||
ProxyType type = getProxyType(var);
|
ProxyType type = getProxyType(var);
|
||||||
if(type != ProxyType.METHOD && type != ProxyType.FIELD)
|
if(type != ProxyType.METHOD && type != ProxyType.FIELD)
|
||||||
return; //this method is irrelevant to everyoen else
|
return; //this method is irrelevant to everyone else
|
||||||
|
|
||||||
//we need this stuff
|
//we need this stuff
|
||||||
Find f = var.getAnnotation(Find.class);
|
Find f = var.getAnnotation(Find.class);
|
||||||
ClassContainer parent = findClassOrFallback(fallback, f, env, mapper);
|
|
||||||
final boolean isMethod = type == ProxyType.METHOD;
|
final boolean isMethod = type == ProxyType.METHOD;
|
||||||
final String builderName = var.getSimpleName().toString() + "Builder";
|
final String builderName = var.getSimpleName().toString() + "Builder";
|
||||||
|
|
||||||
String name, nameObf;
|
String descriptor, nameObf;
|
||||||
|
ClassContainer parent;
|
||||||
Element target;
|
Element target;
|
||||||
|
|
||||||
if(isMethod) {
|
if(isMethod) {
|
||||||
ExecutableElement executableTarget;
|
MethodContainer mc = MethodContainer.from(stub, t, f, env, mapper);
|
||||||
if(f.name().equals("")) //find and validate from stub
|
descriptor = mc.descriptor;
|
||||||
executableTarget = findMethodFromStub(stub, f, env);
|
nameObf = mc.nameObf;
|
||||||
else { //find and validate by name alone
|
parent = mc.parent;
|
||||||
if(LilleroProcessor.badPracticeWarnings) //warn user that he is doing bad stuff
|
target = mc.elem;
|
||||||
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 {
|
} else {
|
||||||
//find and validate target
|
FieldContainer fc = FieldContainer.from(var, env, mapper);
|
||||||
name = f.name().equals("") ? var.getSimpleName().toString() : f.name();
|
descriptor = fc.descriptor;
|
||||||
target = findMember(parent.fqn, name, null, false, true, env);
|
nameObf = fc.nameObf;
|
||||||
nameObf = findMemberName(parent.fqnObf, name, null, mapper);
|
parent = fc.parent;
|
||||||
|
target = fc.elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
//initialize builder
|
//initialize builder
|
||||||
|
@ -189,23 +158,22 @@ public class JavaPoetUtils {
|
||||||
"$L.setParent($S, $L)",
|
"$L.setParent($S, $L)",
|
||||||
builderName,
|
builderName,
|
||||||
parent.fqnObf,
|
parent.fqnObf,
|
||||||
mapModifiers(parent.elem.getModifiers())
|
parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers())
|
||||||
);
|
);
|
||||||
|
|
||||||
//set modifiers
|
//set modifiers
|
||||||
con.addStatement(
|
con.addStatement(
|
||||||
"$L.setModifiers($L)",
|
"$L.setModifiers($L)",
|
||||||
builderName,
|
builderName,
|
||||||
mapModifiers(target.getModifiers())
|
target == null ? 0 :mapModifiers(target.getModifiers())
|
||||||
);
|
);
|
||||||
|
|
||||||
if(isMethod) { //set parameters and return type
|
//set type(s)
|
||||||
ExecutableElement executableTarget = (ExecutableElement) target;
|
con.addStatement(
|
||||||
for(VariableElement p : executableTarget.getParameters())
|
"$L.setDescriptor($S)",
|
||||||
addTypeToProxyGenerator(con, builderName, "addParameter", p.asType());
|
builderName,
|
||||||
addTypeToProxyGenerator(con, builderName, "setReturnType", executableTarget.getReturnType());
|
descriptor
|
||||||
} else //set type
|
);
|
||||||
addTypeToProxyGenerator(con,builderName, "setType", target.asType());
|
|
||||||
|
|
||||||
//build and set
|
//build and set
|
||||||
con.addStatement(
|
con.addStatement(
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
package ftbsc.lll.processor.tools.containers;
|
package ftbsc.lll.processor.tools.containers;
|
||||||
|
|
||||||
import ftbsc.lll.exceptions.TargetNotFoundException;
|
import ftbsc.lll.exceptions.TargetNotFoundException;
|
||||||
|
import ftbsc.lll.processor.LilleroProcessor;
|
||||||
|
import ftbsc.lll.processor.annotations.Find;
|
||||||
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static ftbsc.lll.processor.tools.ASTUtils.findClassName;
|
import static ftbsc.lll.processor.tools.ASTUtils.findClassName;
|
||||||
|
import static ftbsc.lll.processor.tools.ASTUtils.getTypeFromAnnotation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for information about a class.
|
* Container for information about a class.
|
||||||
|
@ -27,21 +35,100 @@ public class ClassContainer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Element} corresponding to the class.
|
* 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;
|
public final Element elem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public constructor.
|
* Public constructor.
|
||||||
* @param fqn the fully-qualified name of the target class
|
* @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 env the {@link ProcessingEnvironment} to be used to locate the class
|
* @param env the {@link ProcessingEnvironment} to be used to locate the class
|
||||||
* @param mapper the {@link ObfuscationMapper} to be used, may be null
|
* @param mapper the {@link ObfuscationMapper} to be used, may be null
|
||||||
*/
|
*/
|
||||||
public ClassContainer(String fqn, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
public ClassContainer(String fqn, String[] innerNames, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
||||||
this.fqn = fqn;
|
//find and validate
|
||||||
this.fqnObf = findClassName(fqn, mapper);
|
Element elem = env.getElementUtils().getTypeElement(fqn); //use unobfuscated name
|
||||||
Element elem = env.getElementUtils().getTypeElement(fqn); //at compile time we have an unobfuscated environment
|
|
||||||
if(elem == null)
|
if(elem == null)
|
||||||
throw new TargetNotFoundException("class", fqn);
|
throw new TargetNotFoundException("class", fqn);
|
||||||
else this.elem = elem;
|
|
||||||
|
if(innerNames != null) {
|
||||||
|
for(String inner : innerNames) {
|
||||||
|
try {
|
||||||
|
int anonClassCounter = Integer.parseInt(inner);
|
||||||
|
//anonymous classes cannot be validated!
|
||||||
|
if(LilleroProcessor.anonymousClassWarning)
|
||||||
|
env.getMessager().printMessage(
|
||||||
|
Diagnostic.Kind.WARNING,
|
||||||
|
String.format(
|
||||||
|
"Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!",
|
||||||
|
fqn, 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.fqn = fqn;
|
||||||
|
this.fqnObf = findClassName(fqn, 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 className a string containing the FQN, the inner class name or nothing
|
||||||
|
* @param env the {@link ProcessingEnvironment} to be used to locate the class
|
||||||
|
* @param mapper the {@link ObfuscationMapper} to be used, may be null
|
||||||
|
* @param <T> the type of the annotation carrying the information
|
||||||
|
* @return the fully qualified name of the given class
|
||||||
|
* @since 0.5.0
|
||||||
|
*/
|
||||||
|
public static <T extends Annotation> ClassContainer from(
|
||||||
|
T ann, Function<T, Class<?>> classFunction, String className,
|
||||||
|
ProcessingEnvironment env, ObfuscationMapper mapper)
|
||||||
|
{
|
||||||
|
String fqn;
|
||||||
|
String[] inner;
|
||||||
|
if(className.contains(".")) {
|
||||||
|
String[] split = className.split("//$");
|
||||||
|
fqn = split[0];
|
||||||
|
inner = split.length == 1 ? null : Arrays.copyOfRange(split, 1, split.length - 1);
|
||||||
|
} else {
|
||||||
|
fqn = getTypeFromAnnotation(ann, classFunction, env).toString();
|
||||||
|
inner = className.equals("") ? null : className.split("//$");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassContainer(fqn, inner, env, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 findOrFallback(ClassContainer fallback, Find f, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
||||||
|
ClassContainer cl = ClassContainer.from(f, Find::value, f.className(), env, mapper);
|
||||||
|
return cl.fqn.equals("java.lang.Object")
|
||||||
|
? fallback
|
||||||
|
: cl;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package ftbsc.lll.processor.tools.containers;
|
||||||
|
|
||||||
|
import ftbsc.lll.exceptions.AmbiguousDefinitionException;
|
||||||
|
import ftbsc.lll.processor.annotations.Find;
|
||||||
|
import ftbsc.lll.processor.annotations.Patch;
|
||||||
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
|
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
|
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.*;
|
||||||
|
import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for information about a field.
|
||||||
|
* Used internally for efficiency reasons.
|
||||||
|
* @since 0.5.0
|
||||||
|
*/
|
||||||
|
public class FieldContainer {
|
||||||
|
/**
|
||||||
|
* The name of the field.
|
||||||
|
*/
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor of the field.
|
||||||
|
*/
|
||||||
|
public final String descriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The obfuscated name of the field.
|
||||||
|
* If the mapper passed is null, then this will be identical to {@link #name}.
|
||||||
|
*/
|
||||||
|
public final String nameObf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ClassContainer} representing the parent of this field.
|
||||||
|
* May be null if the parent is a class type that can not be checked
|
||||||
|
* at processing time (such as an anonymous class)
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor.
|
||||||
|
* @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 mapper the {@link ObfuscationMapper} to be used, may be null
|
||||||
|
*/
|
||||||
|
public FieldContainer(ClassContainer parent, String name, String descriptor, ObfuscationMapper mapper) {
|
||||||
|
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.name = name;
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
} else {
|
||||||
|
this.elem = (VariableElement) findMember(parent, name, descriptor, descriptor != null, true);
|
||||||
|
this.name = this.elem.getSimpleName().toString();
|
||||||
|
this.descriptor = descriptorFromType(this.elem.asType());
|
||||||
|
}
|
||||||
|
this.nameObf = findMemberName(parent.fqnObf, name, descriptor, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a {@link FieldContainer} from a finder.
|
||||||
|
* @param finder the {@link VariableElement} annotated with {@link Find} for this field
|
||||||
|
* @param env the {@link ProcessingEnvironment} to perform the operation in
|
||||||
|
* @param mapper the {@link ObfuscationMapper} to be used, may be null
|
||||||
|
* @return the built {@link FieldContainer}
|
||||||
|
* @since 0.5.0
|
||||||
|
*/
|
||||||
|
public static FieldContainer from(VariableElement finder, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
||||||
|
//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(patchAnn, Patch::value, patchAnn.className(), env, null),
|
||||||
|
f, env, mapper
|
||||||
|
);
|
||||||
|
|
||||||
|
String name = f.name().equals("") ? finder.getSimpleName().toString() : f.name();
|
||||||
|
String descriptor;
|
||||||
|
TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, env);
|
||||||
|
if(fieldType.toString().equals("java.lang.Object"))
|
||||||
|
descriptor = null;
|
||||||
|
else {
|
||||||
|
if(fieldType.getKind() == TypeKind.DECLARED)
|
||||||
|
descriptor = //jank af but this is temporary anyway
|
||||||
|
"L" + new ClassContainer(
|
||||||
|
fieldType.toString(), f.typeInner().split("//$"), env, mapper
|
||||||
|
).fqn.replace('.', '/') + ";";
|
||||||
|
else descriptor = descriptorFromType(fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FieldContainer(parent, name, descriptor, mapper);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package ftbsc.lll.processor.tools.containers;
|
||||||
|
|
||||||
|
import ftbsc.lll.exceptions.AmbiguousDefinitionException;
|
||||||
|
import ftbsc.lll.exceptions.TargetNotFoundException;
|
||||||
|
import ftbsc.lll.processor.LilleroProcessor;
|
||||||
|
import ftbsc.lll.processor.annotations.Find;
|
||||||
|
import ftbsc.lll.processor.annotations.Patch;
|
||||||
|
import ftbsc.lll.processor.annotations.Target;
|
||||||
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
|
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
|
|
||||||
|
import static ftbsc.lll.processor.tools.ASTUtils.findMember;
|
||||||
|
import static ftbsc.lll.processor.tools.ASTUtils.findMemberName;
|
||||||
|
import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromExecutableElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for information about a method.
|
||||||
|
* Used internally for efficiency reasons.
|
||||||
|
* @since 0.5.0
|
||||||
|
*/
|
||||||
|
public class MethodContainer {
|
||||||
|
/**
|
||||||
|
* The name of the method.
|
||||||
|
*/
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor of the method.
|
||||||
|
*/
|
||||||
|
public final String descriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The obfuscated name of the method.
|
||||||
|
* If the mapper passed is null, then this will be identical to {@link #name}.
|
||||||
|
*/
|
||||||
|
public final String nameObf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ClassContainer} representing the parent of this method.
|
||||||
|
* May be null if the parent is a class type that can not be checked
|
||||||
|
* at processing time (such as an anonymous class)
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor.
|
||||||
|
* @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 mapper the {@link ObfuscationMapper} to be used, may be null
|
||||||
|
*/
|
||||||
|
public MethodContainer(ClassContainer parent, String name, String descriptor, boolean strict, ObfuscationMapper mapper) {
|
||||||
|
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;
|
||||||
|
this.name = name;
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
} else {
|
||||||
|
this.elem = (ExecutableElement) findMember(parent, name, descriptor, descriptor != null && strict, false);
|
||||||
|
this.name = this.elem.getSimpleName().toString();
|
||||||
|
this.descriptor = descriptorFromExecutableElement(this.elem);
|
||||||
|
}
|
||||||
|
this.nameObf = findMemberName(parent.fqnObf, name, descriptor, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 env the {@link ProcessingEnvironment} to perform the operation in
|
||||||
|
* @param mapper the {@link ObfuscationMapper} to be used, may be null
|
||||||
|
* @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, ProcessingEnvironment env, ObfuscationMapper mapper) {
|
||||||
|
//the parent always has a @Patch annotation
|
||||||
|
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
|
||||||
|
ClassContainer parent = ClassContainer.findOrFallback(
|
||||||
|
ClassContainer.from(patchAnn, Patch::value, patchAnn.className(), env, null),
|
||||||
|
f, env, mapper
|
||||||
|
);
|
||||||
|
|
||||||
|
String name, descriptor;
|
||||||
|
if(f != null && !f.name().equals("")) { //match 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()));
|
||||||
|
name = f.name();
|
||||||
|
descriptor = null;
|
||||||
|
} else {
|
||||||
|
if(t != null && !t.methodName().equals(""))
|
||||||
|
name = t.methodName(); //name was specified in target
|
||||||
|
else name = stub.getSimpleName().toString();
|
||||||
|
descriptor = t != null && t.strict() ? descriptorFromExecutableElement(stub) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MethodContainer(parent, name, descriptor, t != null && t.strict(), mapper);
|
||||||
|
}
|
||||||
|
}
|
|
@ -180,8 +180,16 @@ public class ObfuscationMapper {
|
||||||
* @throws AmbiguousDefinitionException if not enough data was given to uniquely identify a mapping
|
* @throws AmbiguousDefinitionException if not enough data was given to uniquely identify a mapping
|
||||||
*/
|
*/
|
||||||
public String get(String memberName, String methodDescriptor) {
|
public String get(String memberName, String methodDescriptor) {
|
||||||
if(methodDescriptor == null)
|
if(methodDescriptor == null) {
|
||||||
return members.get(memberName);
|
String res = members.get(memberName);
|
||||||
|
if(res != null) return res;
|
||||||
|
else {
|
||||||
|
List<String> candidates = members.keySet().stream().filter(k -> k.startsWith(memberName)).collect(Collectors.toList());
|
||||||
|
if(candidates.size() == 1)
|
||||||
|
return candidates.get(0);
|
||||||
|
else throw new AmbiguousDefinitionException("Mapper could not uniquely identify method " + this.unobf + "::" + memberName);
|
||||||
|
}
|
||||||
|
}
|
||||||
List<String> candidates = members.keySet().stream().filter(m -> m.startsWith(memberName)).collect(Collectors.toList());
|
List<String> candidates = members.keySet().stream().filter(m -> m.startsWith(memberName)).collect(Collectors.toList());
|
||||||
if(candidates.size() == 1)
|
if(candidates.size() == 1)
|
||||||
return members.get(candidates.get(0));
|
return members.get(candidates.get(0));
|
||||||
|
@ -193,7 +201,7 @@ public class ObfuscationMapper {
|
||||||
case 1:
|
case 1:
|
||||||
return members.get(candidates.get(0));
|
return members.get(candidates.get(0));
|
||||||
default:
|
default:
|
||||||
throw new AmbiguousDefinitionException("Mapper could not uniquely identify method " + unobf + "::" + memberName);
|
throw new AmbiguousDefinitionException("Mapper could not uniquely identify method " + this.unobf + "::" + memberName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue