mirror of
https://github.com/zaaarf/lillero-processor.git
synced 2024-11-14 05:29:20 +01:00
feat: initial untested draft of version 0.5.0 compatible with lillero 0.4
This commit is contained in:
parent
597fda2362
commit
193db6bf7b
12 changed files with 430 additions and 283 deletions
|
@ -1,8 +1,8 @@
|
||||||
package ftbsc.lll.exceptions;
|
package ftbsc.lll.exceptions;
|
||||||
|
|
||||||
import ftbsc.lll.processor.annotations.Find;
|
import ftbsc.lll.processor.annotations.Find;
|
||||||
import ftbsc.lll.proxies.FieldProxy;
|
import ftbsc.lll.proxies.impl.FieldProxy;
|
||||||
import ftbsc.lll.proxies.MethodProxy;
|
import ftbsc.lll.proxies.impl.MethodProxy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a method is annotated with {@link Find} but does not
|
* 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
|
* @param method the name of the method wrongly annotated
|
||||||
*/
|
*/
|
||||||
public NotAProxyException(String parent, String method) {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,10 @@ public class TargetNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new target not found exception for the specified method stub.
|
* 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)
|
* @param stub the stub's name (and descriptor possibly)
|
||||||
*/
|
*/
|
||||||
public TargetNotFoundException(String stub) {
|
public TargetNotFoundException(String type, String stub) {
|
||||||
super(String.format("Could not find member corresponding to stub: %s.", stub));
|
super(String.format("Could not find target %s %s.", type, stub));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,12 @@ import ftbsc.lll.processor.annotations.Find;
|
||||||
import ftbsc.lll.processor.annotations.Injector;
|
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.obfuscation.ObfuscationMapper;
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
import ftbsc.lll.proxies.FieldProxy;
|
import ftbsc.lll.proxies.ProxyType;
|
||||||
import ftbsc.lll.proxies.MethodProxy;
|
import ftbsc.lll.proxies.impl.FieldProxy;
|
||||||
|
import ftbsc.lll.proxies.impl.MethodProxy;
|
||||||
|
import ftbsc.lll.proxies.impl.TypeProxy;
|
||||||
|
|
||||||
import javax.annotation.processing.*;
|
import javax.annotation.processing.*;
|
||||||
import javax.lang.model.SourceVersion;
|
import javax.lang.model.SourceVersion;
|
||||||
|
@ -37,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")
|
@SupportedOptions({"mappingsFile", "badPracticeWarnings"})
|
||||||
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
|
||||||
|
@ -51,6 +54,12 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
*/
|
*/
|
||||||
private ObfuscationMapper mapper;
|
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
|
* 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
|
||||||
|
@ -87,6 +96,17 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
this.mapper = new ObfuscationMapper(new BufferedReader(new InputStreamReader(targetStream,
|
this.mapper = new ObfuscationMapper(new BufferedReader(new InputStreamReader(targetStream,
|
||||||
StandardCharsets.UTF_8)).lines());
|
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)
|
.filter(this::isValidInjector)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if(!validInjectors.isEmpty()) {
|
if(!validInjectors.isEmpty()) {
|
||||||
validInjectors.forEach(this::generateInjectors);
|
validInjectors.forEach(this::generateClasses);
|
||||||
if (!this.generatedInjectors.isEmpty()) {
|
if (!this.generatedInjectors.isEmpty()) {
|
||||||
generateServiceProvider();
|
generateServiceProvider();
|
||||||
return true;
|
return true;
|
||||||
|
@ -141,7 +161,7 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
&& processingEnv.getTypeUtils().isSameType(params.get(1), methodNodeType);
|
&& processingEnv.getTypeUtils().isSameType(params.get(1), methodNodeType);
|
||||||
})) return true;
|
})) return true;
|
||||||
else {
|
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));
|
String.format("Missing valid @Injector method in @Patch class %s, skipping.", elem));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -152,17 +172,16 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
* Basically implements the {@link IInjector} interface for you.
|
* Basically implements the {@link IInjector} interface for you.
|
||||||
* @param cl the {@link TypeElement} for the given class
|
* @param cl the {@link TypeElement} for the given class
|
||||||
*/
|
*/
|
||||||
private void generateInjectors(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);
|
||||||
String targetClassFQN =
|
ClassContainer targetClass = new ClassContainer(
|
||||||
findClassName(
|
getClassFullyQualifiedName(
|
||||||
getClassFullyQualifiedName(
|
patchAnn,
|
||||||
patchAnn,
|
Patch::value,
|
||||||
Patch::value,
|
patchAnn.className()
|
||||||
getInnerName(patchAnn, Patch::innerClass, Patch::anonymousClassCounter)
|
), this.processingEnv, this.mapper
|
||||||
), this.mapper
|
);
|
||||||
).replace('/', '.');
|
|
||||||
|
|
||||||
//find package information
|
//find package information
|
||||||
Element packageElement = cl.getEnclosingElement();
|
Element packageElement = cl.getEnclosingElement();
|
||||||
|
@ -170,91 +189,118 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
packageElement = packageElement.getEnclosingElement();
|
packageElement = packageElement.getEnclosingElement();
|
||||||
String packageName = packageElement.toString();
|
String packageName = packageElement.toString();
|
||||||
|
|
||||||
//find injector(s) and target(s)
|
//find annotated elements
|
||||||
List<ExecutableElement> injectors = findAnnotatedMethods(cl, Injector.class);
|
List<ExecutableElement> targets = findAnnotatedElement(cl, Target.class);
|
||||||
|
List<ExecutableElement> injectors = findAnnotatedElement(cl, Injector.class);
|
||||||
|
List<VariableElement> finders = findAnnotatedElement(cl, Find.class);
|
||||||
|
|
||||||
List<ExecutableElement> targets = findAnnotatedMethods(cl, Target.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
|
//declare it once for efficiency
|
||||||
List<String> targetNames =
|
List<String> injectorNames =
|
||||||
targets.stream()
|
injectors.stream()
|
||||||
.map(ExecutableElement::getSimpleName)
|
.map(ExecutableElement::getSimpleName)
|
||||||
.map(Object::toString)
|
.map(Object::toString)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
//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
|
||||||
Map<String, InjectorInfo> toGenerate = new HashMap<>();
|
HashMap<String, InjectorInfo> toGenerate = new HashMap<>();
|
||||||
|
|
||||||
for(ExecutableElement inj : injectors) {
|
for(ExecutableElement tg : targets) {
|
||||||
Injector[] minjAnn = inj.getAnnotationsByType(Injector.class);
|
Target[] mtgAnn = tg.getAnnotationsByType(Target.class);
|
||||||
int iterationNumber = 1;
|
int iterationNumber = 1;
|
||||||
for(Injector injectorAnn : minjAnn) { //java is dumb
|
for(Target targetAnn : mtgAnn) {
|
||||||
List<ExecutableElement> injectionCandidates = targets;
|
List<ExecutableElement> injectorCandidates = injectors;
|
||||||
|
List<VariableElement> finderCandidates = finders;
|
||||||
|
|
||||||
if(!injectorAnn.targetName().equals("") && targetNames.contains(injectorAnn.targetName())) {
|
if(!targetAnn.of().equals("") && injectorNames.contains(targetAnn.of())) {
|
||||||
//case 1: it has a name, try to match it
|
//case 1: find target by name
|
||||||
injectionCandidates =
|
injectorCandidates =
|
||||||
injectionCandidates
|
injectorCandidates
|
||||||
.stream()
|
.stream()
|
||||||
.filter(i -> i.getSimpleName().contentEquals(injectorAnn.targetName()))
|
.filter(i -> i.getSimpleName().contentEquals(targetAnn.of()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} else if(targets.size() == 1) {
|
finderCandidates =
|
||||||
//case 2: there is only one target
|
finderCandidates
|
||||||
injectionCandidates = new ArrayList<>();
|
.stream()
|
||||||
injectionCandidates.add(targets.get(0));
|
.filter(i -> i.getSimpleName().contentEquals(targetAnn.of()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} 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 {
|
} else {
|
||||||
//case 3: try to match by injectTargetName
|
//case 3: try to match by injectTargetName
|
||||||
String inferredName = inj.getSimpleName()
|
finderCandidates = new ArrayList<>(); //no candidates
|
||||||
.toString()
|
String inferredName = "inject" + tg.getSimpleName();
|
||||||
.replaceFirst("inject", "");
|
injectorCandidates =
|
||||||
injectionCandidates =
|
injectorCandidates
|
||||||
injectionCandidates
|
|
||||||
.stream()
|
.stream()
|
||||||
.filter(t -> t.getSimpleName().toString().equalsIgnoreCase(inferredName))
|
.filter(t -> t.getSimpleName().toString().equalsIgnoreCase(inferredName))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
ExecutableElement injectionTarget = null;
|
//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(injectionCandidates.size() == 1)
|
throw new AmbiguousDefinitionException(
|
||||||
injectionTarget = injectionCandidates.get(0);
|
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 {
|
else {
|
||||||
List<TypeMirror> params = classArrayFromAnnotation(injectorAnn, Injector::params, processingEnv.getElementUtils());
|
if(injectorCandidates.size() == 1) {
|
||||||
|
//matched an injector!
|
||||||
if(params.size() != 0) {
|
toGenerate.put(
|
||||||
StringBuilder descr = new StringBuilder("(");
|
String.format("%sInjector%d", cl.getSimpleName(), iterationNumber),
|
||||||
for(TypeMirror p : params)
|
new InjectorInfo(injectorCandidates.get(0), tg)
|
||||||
descr.append(descriptorFromType(TypeName.get(p)));
|
);
|
||||||
descr.append(")");
|
iterationNumber++; //increment is only used by injectors
|
||||||
injectionCandidates =
|
} else {
|
||||||
injectionCandidates
|
//matched a finder!
|
||||||
.stream()
|
VariableElement finder = finders.get(0);
|
||||||
.filter(t -> //we care about arguments but not really about return type
|
Find f = finder.getAnnotation(Find.class);
|
||||||
descr.toString()
|
appendMemberFinderDefinition(targetClass, finder, tg, constructorBuilder, this.processingEnv, this.mapper);
|
||||||
.split("\\)")[0]
|
finders.remove(finder); //unlike injectors, finders can't apply to multiple targets
|
||||||
.equalsIgnoreCase(descriptorFromExecutableElement(t).split("\\)")[0])
|
|
||||||
).collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
//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 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)
|
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")
|
.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)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.superclass(cl.asType())
|
.superclass(cl.asType())
|
||||||
.addSuperinterface(ClassName.get(IInjector.class))
|
.addSuperinterface(ClassName.get(IInjector.class))
|
||||||
|
.addMethod(constructorBuilder.build())
|
||||||
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
||||||
.addMethod(buildStringReturnMethod("reason", patchAnn.reason()))
|
.addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason))
|
||||||
.addMethod(buildStringReturnMethod("targetClass", targetClassFQN))
|
.addMethod(buildStringReturnMethod("targetClass", targetClass.fqn))
|
||||||
.addMethod(buildStringReturnMethod("methodName", targetMethodName))
|
.addMethod(buildStringReturnMethod("methodName", targetMethodName))
|
||||||
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
|
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
|
||||||
.addMethods(generateRequestedProxies(cl, this.mapper))
|
|
||||||
.addMethod(stubOverride)
|
.addMethod(stubOverride)
|
||||||
.addMethod(inject)
|
.addMethod(inject)
|
||||||
.build();
|
.build();
|
||||||
|
@ -305,53 +351,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.
|
* Generates the Service Provider file for the generated injectors.
|
||||||
*/
|
*/
|
||||||
|
@ -384,6 +383,11 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
*/
|
*/
|
||||||
public final ExecutableElement targetStub;
|
public final ExecutableElement targetStub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reason for the injection.
|
||||||
|
*/
|
||||||
|
public final String reason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ExecutableElement} corresponding to the target method.
|
* The {@link ExecutableElement} corresponding to the target method.
|
||||||
*/
|
*/
|
||||||
|
@ -397,7 +401,8 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub) {
|
public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub) {
|
||||||
this.injector = injector;
|
this.injector = injector;
|
||||||
this.targetStub = targetStub;
|
this.targetStub = targetStub;
|
||||||
this.target = (ExecutableElement) findMemberFromStub(targetStub, processingEnv);
|
this.reason = injector.getAnnotation(Injector.class).reason();
|
||||||
|
this.target = findMethodFromStub(targetStub, processingEnv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package ftbsc.lll.processor.annotations;
|
package ftbsc.lll.processor.annotations;
|
||||||
|
|
||||||
import ftbsc.lll.proxies.FieldProxy;
|
import ftbsc.lll.proxies.impl.FieldProxy;
|
||||||
import ftbsc.lll.proxies.MethodProxy;
|
import ftbsc.lll.proxies.impl.MethodProxy;
|
||||||
|
import ftbsc.lll.proxies.impl.TypeProxy;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
@ -14,43 +15,39 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
* @since 0.4.0
|
* @since 0.4.0
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.FIELD)
|
||||||
public @interface Find {
|
public @interface Find {
|
||||||
/**
|
/**
|
||||||
* @return the {@link Class} object containing the target, or the
|
* @return the {@link Class} object containing the target, or the
|
||||||
* {@link Object} class if not specified (the {@link Class} from
|
* {@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,
|
* @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.4.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
String parentInnerClass() default "";
|
String className() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the anonymous class counter (1 for the first, 2 for
|
* For a {@link FieldProxy}, this is the name of the field to find. If omitted,
|
||||||
* the second, 3 for the third...) for the class that contains
|
* it will fall back on the name of the annotated field.
|
||||||
* the target, defaults to 0 (not an anonymous class)
|
* For a {@link MethodProxy} it indicates an attempt to match by name only, with
|
||||||
* @since 0.4.0
|
* 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
|
||||||
int parentAnonymousClassCounter() default 0;
|
* 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}.
|
||||||
* The name of the class member to find. If omitted, the name of the
|
|
||||||
* annotated method will be used.
|
|
||||||
* @return the name of the target, will default to the empty string
|
* @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 "";
|
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 {};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,19 +16,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
* @see Target
|
* @see Target
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@Repeatable(MultipleInjectors.class)
|
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface Injector {
|
public @interface Injector {
|
||||||
/**
|
/**
|
||||||
* @return the name of the stub annotated with {@link Target} this is referring to.
|
* @return the patching reason, for logging, defaults to "No reason specified."
|
||||||
* @since 0.3.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
String targetName() default "";
|
String reason() default "No reason specified.";
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 {};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to support {@link Injector} as a {@link Repeatable} annotation.
|
* Used to support {@link Target} as a {@link Repeatable} annotation.
|
||||||
* @since 0.3.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface MultipleInjectors {
|
public @interface MultipleTargets {
|
||||||
/**
|
/**
|
||||||
* @return the {@link Injector} annotations, as an array
|
* @return the {@link Injector} annotations, as an array
|
||||||
*/
|
*/
|
||||||
Injector[] value();
|
Target[] value();
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package ftbsc.lll.processor.annotations;
|
package ftbsc.lll.processor.annotations;
|
||||||
|
|
||||||
|
import ftbsc.lll.proxies.impl.TypeProxy;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
@ -15,27 +17,17 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
@java.lang.annotation.Target(ElementType.TYPE)
|
@java.lang.annotation.Target(ElementType.TYPE)
|
||||||
public @interface Patch {
|
public @interface Patch {
|
||||||
/**
|
/**
|
||||||
* @return the Minecraft {@link Class} to target for patching
|
* @return the {@link Class} to target for patching
|
||||||
*/
|
*/
|
||||||
Class<?> value();
|
Class<?> value() default Object.class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the patching reason, for logging, defaults to "No reason specified."
|
* 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
|
||||||
String reason() default "No reason specified.";
|
* fully-qualified name.
|
||||||
|
* @return the name of the inner class that contains the target,
|
||||||
/**
|
|
||||||
* @return the name of the inner class that should be targeted,
|
|
||||||
* defaults to empty string (not an inner class)
|
* defaults to empty string (not an inner class)
|
||||||
* @since 0.4.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
String innerClass() 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 should be
|
|
||||||
* targeted, defaults to 0 (not an anonymous class)
|
|
||||||
* @since 0.4.0
|
|
||||||
*/
|
|
||||||
int anonymousClassCounter() default 0;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
package ftbsc.lll.processor.annotations;
|
package ftbsc.lll.processor.annotations;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a method as the target method.
|
* Marks a method as the target method.
|
||||||
* The method itself should have the same name, return type and parameters as the desired
|
* The method annotated with this, called "stub" within the documentation, should have the
|
||||||
* Minecraft method.
|
* same name and parameters as the method it's supposed to represent.
|
||||||
* It will also be discarded unless the containing class is annotated with {@link Patch}
|
* It will be discarded unless the containing class is annotated with {@link Patch}
|
||||||
* and another method within the class is annotated with {@link Injector}.
|
* and another method within the class is annotated with {@link Injector}.
|
||||||
* @see Patch
|
* @see Patch
|
||||||
* @see Injector
|
* @see Injector
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@Repeatable(MultipleTargets.class)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface Target {
|
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,
|
* 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.
|
* and to only check parameters if further clarification is needed.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package ftbsc.lll.processor.tools;
|
package ftbsc.lll.processor.tools;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
|
||||||
import ftbsc.lll.exceptions.AmbiguousDefinitionException;
|
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;
|
||||||
|
@ -8,9 +7,9 @@ import ftbsc.lll.exceptions.TargetNotFoundException;
|
||||||
import ftbsc.lll.processor.annotations.Find;
|
import ftbsc.lll.processor.annotations.Find;
|
||||||
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.obfuscation.ObfuscationMapper;
|
import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
|
||||||
import ftbsc.lll.proxies.FieldProxy;
|
import ftbsc.lll.proxies.ProxyType;
|
||||||
import ftbsc.lll.proxies.MethodProxy;
|
|
||||||
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.*;
|
import javax.lang.model.element.*;
|
||||||
|
@ -19,12 +18,12 @@ import javax.lang.model.util.Elements;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
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.methodDescriptorFromParams;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -32,17 +31,19 @@ import static ftbsc.lll.processor.tools.JavaPoetUtils.methodDescriptorFromParams
|
||||||
public class ASTUtils {
|
public class ASTUtils {
|
||||||
/**
|
/**
|
||||||
* Finds, among the methods of a class cl, the one annotated with ann, and tries to build
|
* Finds, among the methods of a class cl, the one annotated with ann, and tries to build
|
||||||
* a {@link ExecutableElement} from it.
|
* an {@link Element} from it.
|
||||||
* @param cl the {@link ExecutableElement} for the class containing the desired method
|
* @param parent the parent {@link Element} to the desired element
|
||||||
* @param ann the {@link Class} corresponding to the desired annotation
|
* @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
|
* @since 0.2.0
|
||||||
*/
|
*/
|
||||||
public static List<ExecutableElement> findAnnotatedMethods(TypeElement cl, Class<? extends Annotation> ann) {
|
@SuppressWarnings("unchecked")
|
||||||
return cl.getEnclosedElements()
|
public static <T extends Element> List<T> findAnnotatedElement(Element parent, Class<? extends Annotation> ann) {
|
||||||
|
return parent.getEnclosedElements()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(e -> e.getAnnotationsByType(ann).length != 0)
|
.filter(e -> e.getAnnotationsByType(ann).length != 0)
|
||||||
.map(e -> (ExecutableElement) e)
|
.map(e -> (T) e)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,48 +84,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.
|
* Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
|
||||||
* @param ann the annotation containing the class
|
* @param ann the annotation containing the class
|
||||||
* @param parentFunction the annotation function returning 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
|
* @param <T> the type of the annotation carrying the information
|
||||||
* @return the fully qualified name of the given class
|
* @return the fully qualified name of the given class
|
||||||
* @since 0.3.0
|
* @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;
|
String fqn;
|
||||||
try {
|
try {
|
||||||
fqn = parentFunction.apply(ann).getCanonicalName();
|
fqn = parentFunction.apply(ann).getCanonicalName();
|
||||||
} catch(MirroredTypeException e) {
|
} catch(MirroredTypeException e) {
|
||||||
fqn = e.getTypeMirror().toString();
|
fqn = e.getTypeMirror().toString();
|
||||||
}
|
}
|
||||||
if(innerName != null)
|
if(!name.equals(""))
|
||||||
fqn = String.format("%s$%s", fqn, innerName);
|
fqn = String.format("%s$%s", fqn, name);
|
||||||
return fqn;
|
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.
|
* Safely extracts a {@link Class} array from an annotation.
|
||||||
* @param ann the annotation containing the class
|
* @param ann the annotation containing the class
|
||||||
|
@ -165,30 +161,40 @@ public class ASTUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the class name and maps it to the correct format.
|
* 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
|
* @param finderAnn an annotation containing metadata about the target, may be null
|
||||||
* @return the fully qualified class name
|
* @return the fully qualified class name
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
private static String findClassName(Patch patchAnn, Find finderAnn) {
|
public static String findClassNameFromAnnotations(String fallback, Find finderAnn) {
|
||||||
String fullyQualifiedName;
|
String fullyQualifiedName;
|
||||||
if(finderAnn != null) {
|
if(finderAnn != null) {
|
||||||
fullyQualifiedName =
|
fullyQualifiedName =
|
||||||
getClassFullyQualifiedName(
|
getClassFullyQualifiedName(
|
||||||
finderAnn,
|
finderAnn,
|
||||||
Find::parent,
|
Find::value,
|
||||||
getInnerName(finderAnn, Find::parentInnerClass, Find::parentAnonymousClassCounter)
|
finderAnn.className()
|
||||||
);
|
);
|
||||||
if(!fullyQualifiedName.equals("java.lang.Object"))
|
if(!fullyQualifiedName.equals("java.lang.Object"))
|
||||||
return findClassName(fullyQualifiedName, null);
|
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(
|
getClassFullyQualifiedName(
|
||||||
patchAnn,
|
patchAnn,
|
||||||
Patch::value,
|
Patch::value,
|
||||||
getInnerName(patchAnn, Patch::innerClass, Patch::anonymousClassCounter)
|
patchAnn.className()
|
||||||
);
|
), null);
|
||||||
return findClassName(fullyQualifiedName, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -222,10 +228,10 @@ public class ASTUtils {
|
||||||
* @throws TargetNotFoundException if it finds no valid candidate
|
* @throws TargetNotFoundException if it finds no valid candidate
|
||||||
* @since 0.3.0
|
* @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);
|
TypeElement parent = env.getElementUtils().getTypeElement(parentFQN);
|
||||||
if(parent == null)
|
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
|
//try to find by name
|
||||||
List<Element> candidates = parent.getEnclosedElements()
|
List<Element> candidates = parent.getEnclosedElements()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -233,12 +239,12 @@ public class ASTUtils {
|
||||||
.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(String.format("%s %s", name, descr));
|
throw new TargetNotFoundException(field ? "field" : "method", String.format("%s %s", name, descr));
|
||||||
if(candidates.size() == 1 && (!strict || field))
|
if(candidates.size() == 1 && (!strict || field))
|
||||||
return candidates.get(0);
|
return candidates.get(0);
|
||||||
if(descr == null) {
|
if(field || descr == null) {
|
||||||
throw new AmbiguousDefinitionException(
|
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 {
|
} else {
|
||||||
candidates = candidates.stream()
|
candidates = candidates.stream()
|
||||||
|
@ -248,7 +254,7 @@ public class ASTUtils {
|
||||||
: 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(String.format("%s %s", name, descr));
|
throw new TargetNotFoundException("method", String.format("%s %s", name, descr));
|
||||||
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, parentFQN)
|
||||||
|
@ -258,54 +264,73 @@ public class ASTUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the real class member (field or method) corresponding to a stub annotated with
|
* Finds the real class method corresponding to a stub annotated with {@link Target}.
|
||||||
* {@link Target} or {@link Find}.
|
|
||||||
* @param stub the {@link ExecutableElement} for the stub
|
* @param stub the {@link ExecutableElement} for the stub
|
||||||
* @param env the {@link ProcessingEnvironment} to perform the operation in
|
* @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 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 findMemberFromStub(ExecutableElement stub, ProcessingEnvironment env) {
|
public static ExecutableElement findMethodFromStub(ExecutableElement stub, ProcessingEnvironment env) {
|
||||||
//the parent always has a @Patch annotation
|
//the parent always has a @Patch annotation
|
||||||
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
|
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
|
||||||
//there should ever only be one of these two
|
//there should ever only be one of these two
|
||||||
Target targetAnn = stub.getAnnotation(Target.class); //if this is null strict mode is always disabled
|
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
|
Find findAnn = stub.getAnnotation(Find.class); //this may be null, it means no fallback info
|
||||||
String parentFQN = findClassName(patchAnn, findAnn);
|
String parentFQN = findClassNameFromAnnotations(patchAnn, findAnn);
|
||||||
String methodDescriptor =
|
String methodName = stub.getSimpleName().toString();
|
||||||
findAnn != null
|
if(findAnn != null && !findAnn.name().equals(""))
|
||||||
? methodDescriptorFromParams(findAnn, Find::params, env.getElementUtils())
|
throw new AmbiguousDefinitionException(String.format("Specified name %s in @Find annotation for method stub %s!", findAnn.name(), methodName));
|
||||||
: descriptorFromExecutableElement(stub);
|
String methodDescriptor = descriptorFromExecutableElement(stub);
|
||||||
String memberName =
|
return (ExecutableElement) findMember(
|
||||||
findAnn != null && !findAnn.name().equals("")
|
|
||||||
? findAnn.name()
|
|
||||||
: stub.getSimpleName().toString();
|
|
||||||
return findMember(
|
|
||||||
parentFQN,
|
parentFQN,
|
||||||
memberName,
|
methodName,
|
||||||
methodDescriptor,
|
methodDescriptor,
|
||||||
targetAnn != null && targetAnn.strict(),
|
targetAnn != null && targetAnn.strict(),
|
||||||
targetAnn == null && !isMethodProxyStub(stub), //only evaluate if target is null
|
false, //only evaluate if target is null
|
||||||
env
|
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.
|
* It will fail if the return type is not a known type of proxy.
|
||||||
* @param m the annotated {@link ExecutableElement}
|
* @param v the annotated {@link VariableElement}
|
||||||
* @return whether it returns a {@link MethodProxy} or a {@link FieldProxy}
|
* @return the {@link ProxyType} for the element
|
||||||
* @throws NotAProxyException if it's neither
|
* @throws NotAProxyException if it's neither
|
||||||
* @since 0.4.0
|
* @since 0.4.0
|
||||||
*/
|
*/
|
||||||
public static boolean isMethodProxyStub(ExecutableElement m) {
|
public static ProxyType getProxyType(VariableElement v) {
|
||||||
String returnTypeFQN = m.getReturnType().toString();
|
String returnTypeFQN = v.asType().toString();
|
||||||
if(returnTypeFQN.equals("ftbsc.lll.proxies.FieldProxy"))
|
switch(returnTypeFQN) {
|
||||||
return false;
|
case "ftbsc.lll.proxies.impl.FieldProxy":
|
||||||
else if(returnTypeFQN.equals("ftbsc.lll.proxies.MethodProxy"))
|
return ProxyType.FIELD;
|
||||||
return true;
|
case "ftbsc.lll.proxies.impl.MethodProxy":
|
||||||
else throw new NotAProxyException(m.getEnclosingElement().getSimpleName().toString(), m.getSimpleName().toString());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
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.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.tools.DescriptorBuilder;
|
||||||
import ftbsc.lll.proxies.MethodProxy;
|
import ftbsc.lll.proxies.impl.MethodProxy;
|
||||||
import ftbsc.lll.proxies.FieldProxy;
|
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.ExecutableElement;
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
|
import javax.lang.model.element.VariableElement;
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import javax.lang.model.util.Elements;
|
import javax.lang.model.util.Elements;
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
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.
|
* Collection of static utils that rely on JavaPoet to function.
|
||||||
|
@ -96,23 +107,6 @@ public class JavaPoetUtils {
|
||||||
return methodSignature.toString();
|
return methodSignature.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
* 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
|
* containing a call to a method of a {@link MethodProxy.Builder} or a
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.ArrayType;
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue