mirror of
https://github.com/zaaarf/geb-processor.git
synced 2024-11-23 02:04:48 +01:00
feat: implemented processor
This commit is contained in:
parent
2f798cb8a7
commit
6ad2287f40
2 changed files with 135 additions and 56 deletions
|
@ -18,8 +18,8 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'ftbsc:geb:0.1.4'
|
||||||
implementation 'com.squareup:javapoet:1.13.0'
|
implementation 'com.squareup:javapoet:1.13.0'
|
||||||
implementation 'ftbsc:geb:0.1.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|
|
@ -1,84 +1,163 @@
|
||||||
package ftbsc.geb.processor;
|
package ftbsc.geb.processor;
|
||||||
|
|
||||||
import ftbsc.geb.api.annotations.Event;
|
import com.squareup.javapoet.*;
|
||||||
import ftbsc.geb.api.annotations.Listen;
|
import ftbsc.geb.api.annotations.Listen;
|
||||||
import ftbsc.geb.api.annotations.ListenerInstance;
|
|
||||||
|
|
||||||
import javax.annotation.processing.AbstractProcessor;
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
import javax.annotation.processing.RoundEnvironment;
|
import javax.annotation.processing.RoundEnvironment;
|
||||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.*;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
|
||||||
import javax.lang.model.element.TypeElement;
|
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import java.lang.annotation.Annotation;
|
import javax.tools.FileObject;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import javax.tools.StandardLocation;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
@SupportedAnnotationTypes({"ftbsc.geb.api.annotations.*"})
|
@SupportedAnnotationTypes({"ftbsc.geb.api.annotations.*"})
|
||||||
public class GEBProcessor extends AbstractProcessor {
|
public class GEBProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
|
private final Map<TypeMirror, Set<ListenerContainer>> listenerMap = new HashMap<>();
|
||||||
|
|
||||||
|
private final Set<String> generatedClasses = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
|
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
|
||||||
boolean claimed = false;
|
for(TypeElement ann : set)
|
||||||
for(TypeElement ann : set) {
|
|
||||||
BiConsumer<GEBProcessor, Element> processMethod;
|
|
||||||
if(ann.getQualifiedName().contentEquals(Listen.class.getName()))
|
if(ann.getQualifiedName().contentEquals(Listen.class.getName()))
|
||||||
processMethod = GEBProcessor::processListener;
|
for(Element e : env.getElementsAnnotatedWith(ann))
|
||||||
else if(ann.getQualifiedName().contentEquals(Event.class.getName()))
|
this.processListener(e);
|
||||||
processMethod = GEBProcessor::processEvent;
|
if(!this.listenerMap.isEmpty()) {
|
||||||
else continue;
|
this.generateClasses();
|
||||||
|
this.generateServiceProvider();
|
||||||
claimed = true;
|
return true;
|
||||||
|
} else return false;
|
||||||
for(Element e : env.getElementsAnnotatedWith(ann))
|
|
||||||
processMethod.accept(this, e);
|
|
||||||
}
|
|
||||||
return claimed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<Element, List<ExecutableElement>> listeners = new HashMap<>();
|
private final TypeMirror listenerInterface = this.processingEnv.getElementUtils()
|
||||||
|
.getTypeElement("ftbsc.geb.api.IListener").asType();
|
||||||
|
|
||||||
private static List<Element> getMembersAnnotatedWith(TypeElement typeElement, Class<? extends Annotation> ann) {
|
private final TypeMirror eventInterface = this.processingEnv.getElementUtils()
|
||||||
return typeElement.getEnclosedElements()
|
.getTypeElement("ftbsc.geb.api.IEvent").asType();
|
||||||
.stream()
|
|
||||||
.filter(elem -> elem.getAnnotation(ann) != null)
|
private final TypeMirror dispatcherInterface = this.processingEnv.getElementUtils()
|
||||||
.collect(Collectors.toList());
|
.getTypeElement("ftbsc.geb.api.IEventDispatcher").asType();
|
||||||
}
|
|
||||||
|
|
||||||
private void processListener(Element target) {
|
private void processListener(Element target) {
|
||||||
ExecutableElement listener = (ExecutableElement) target; //this will never fail
|
ExecutableElement listener = (ExecutableElement) target; //this cast will never fail
|
||||||
Listen listenerAnn = target.getAnnotation(Listen.class);
|
|
||||||
|
|
||||||
//ensure the parent is a class
|
|
||||||
if(!(target.getEnclosingElement() instanceof TypeElement))
|
|
||||||
return; //TODO throw error, means the annotated field was in a method
|
|
||||||
TypeElement parent = (TypeElement) target.getEnclosingElement();
|
|
||||||
|
|
||||||
//ensure the parent is instance of IListener
|
//ensure the parent is instance of IListener
|
||||||
TypeElement cursor = parent;
|
TypeMirror parentType = listener.getEnclosingElement().asType();
|
||||||
TypeMirror listenerInterface = this.processingEnv.getElementUtils().getTypeElement("ftbsc.geb.api.IListener").asType()
|
if(!this.processingEnv.getTypeUtils().isAssignable(parentType, this.listenerInterface))
|
||||||
; while(cursor != null) {
|
return; //TODO throw error, parent doesn't implement the interface
|
||||||
if(cursor.getInterfaces().contains(listenerInterface))
|
|
||||||
break;
|
|
||||||
|
|
||||||
Element superclass = this.processingEnv.getTypeUtils().asElement(cursor.getSuperclass());
|
//ensure the listener method has only a single IEvent parameter
|
||||||
if(superclass instanceof TypeElement)
|
List<? extends VariableElement> params = listener.getParameters();
|
||||||
cursor = (TypeElement) superclass;
|
if(listener.getParameters().size() != 1)
|
||||||
else return; //TODO throw error, parent doesnt implement the interface
|
return; //TODO throw error, bad parameter amount
|
||||||
}
|
TypeMirror event = params.get(0).asType();
|
||||||
|
if(!this.processingEnv.getTypeUtils().isAssignable(event, this.eventInterface))
|
||||||
|
return; //TODO throw error, bad parameter type
|
||||||
|
|
||||||
List<Element> instanceSources = getMembersAnnotatedWith(parent, ListenerInstance.class);
|
if(!this.listenerMap.containsKey(event))
|
||||||
|
this.listenerMap.put(event, new HashSet<>());
|
||||||
if(instanceSources.size() != 1)
|
this.listenerMap.get(event).add(new ListenerContainer(listener));
|
||||||
return; //TODO throw error, there should always be only one per class
|
|
||||||
|
|
||||||
Element instanceSource = instanceSources.get(0);
|
|
||||||
List<ExecutableElement> listenerList = listeners.computeIfAbsent(instanceSource, k -> new ArrayList<>());
|
|
||||||
listenerList.add(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEvent(Element target) {
|
private void generateClasses() {
|
||||||
//TODO
|
this.listenerMap.forEach((event, listeners) -> {
|
||||||
|
TypeElement eventClass = (TypeElement) this.processingEnv.getTypeUtils().asElement(event);
|
||||||
|
|
||||||
|
//reorder the injectors to follow priority
|
||||||
|
List<ListenerContainer> ordered = listeners.stream().sorted(Comparator.comparingInt(
|
||||||
|
container -> container.annotation.priority()
|
||||||
|
)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
ParameterSpec eventParam = ParameterSpec.builder(TypeName.get(this.eventInterface), "event").build();
|
||||||
|
ParameterSpec listenersParam = ParameterSpec.builder(ParameterizedTypeName.get(
|
||||||
|
ClassName.get("java.util", "Map"), ParameterizedTypeName.get(
|
||||||
|
ClassName.get("java.lang", "Class"),
|
||||||
|
WildcardTypeName.subtypeOf(TypeName.get(this.dispatcherInterface))),
|
||||||
|
ClassName.get(this.listenerInterface)), "listeners")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MethodSpec.Builder callListenersBuilder = MethodSpec.methodBuilder("callListeners")
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(Override.class)
|
||||||
|
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) //because why not
|
||||||
|
.addMember("value" , "{$S}", "unchecked").build())
|
||||||
|
.addParameter(eventParam)
|
||||||
|
.addParameter(listenersParam)
|
||||||
|
.returns(boolean.class);
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
for(ListenerContainer listener : ordered) {
|
||||||
|
String varName = String.format("listener%d", counter);
|
||||||
|
callListenersBuilder
|
||||||
|
.addStatement("$T $L = $N.get($T.class)", varName, this.listenerInterface, listenersParam, listener.parent)
|
||||||
|
.addStatement("if($L.isActive()) (($T) $L).$L($N)", varName, listener.parent,
|
||||||
|
listener.method.getSimpleName().toString(), eventParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodSpec eventType = MethodSpec.methodBuilder("eventType")
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(Override.class)
|
||||||
|
.returns(Class.class)
|
||||||
|
.addStatement("return $T.class", event)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String clazzName = String.format("%sDispatcher", eventClass.getSimpleName());
|
||||||
|
TypeSpec clazz = TypeSpec.classBuilder(clazzName)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addSuperinterface(this.dispatcherInterface)
|
||||||
|
.addMethod(callListenersBuilder.build())
|
||||||
|
.addMethod(eventType)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String packageName = "ftbsc.geb.generated";
|
||||||
|
JavaFile javaFile = JavaFile.builder(packageName, clazz).build();
|
||||||
|
String resultingClassName = String.format("%s.%s", packageName, clazzName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
JavaFileObject injectorFile = processingEnv.getFiler().createSourceFile(resultingClassName);
|
||||||
|
PrintWriter out = new PrintWriter(injectorFile.openWriter());
|
||||||
|
javaFile.writeTo(out);
|
||||||
|
out.close();
|
||||||
|
} catch(IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the Service Provider file for the dispatchers.
|
||||||
|
*/
|
||||||
|
private void generateServiceProvider() {
|
||||||
|
try {
|
||||||
|
FileObject serviceProvider = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "",
|
||||||
|
"META-INF/services/ftbsc.geb.api.IEventDispatcher");
|
||||||
|
PrintWriter out = new PrintWriter(serviceProvider.openWriter());
|
||||||
|
this.generatedClasses.forEach(out::println);
|
||||||
|
out.close();
|
||||||
|
} catch(IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ListenerContainer {
|
||||||
|
public final ExecutableElement method;
|
||||||
|
public final TypeMirror parent;
|
||||||
|
public final Listen annotation;
|
||||||
|
|
||||||
|
public ListenerContainer(ExecutableElement method) {
|
||||||
|
this.method = method;
|
||||||
|
this.parent = method.getEnclosingElement().asType();
|
||||||
|
this.annotation = method.getAnnotation(Listen.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue