aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/ftbsc/geb/processor/GEBProcessor.java191
1 files changed, 135 insertions, 56 deletions
diff --git a/src/main/java/ftbsc/geb/processor/GEBProcessor.java b/src/main/java/ftbsc/geb/processor/GEBProcessor.java
index c72bf8c..d464549 100644
--- a/src/main/java/ftbsc/geb/processor/GEBProcessor.java
+++ b/src/main/java/ftbsc/geb/processor/GEBProcessor.java
@@ -1,84 +1,163 @@
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.ListenerInstance;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.*;
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.function.BiConsumer;
import java.util.stream.Collectors;
+/**
+ *
+ */
@SupportedAnnotationTypes({"ftbsc.geb.api.annotations.*"})
public class GEBProcessor extends AbstractProcessor {
+
+ private final Map<TypeMirror, Set<ListenerContainer>> listenerMap = new HashMap<>();
+
+ private final Set<String> generatedClasses = new HashSet<>();
+
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
- boolean claimed = false;
- for(TypeElement ann : set) {
- BiConsumer<GEBProcessor, Element> processMethod;
+ for(TypeElement ann : set)
if(ann.getQualifiedName().contentEquals(Listen.class.getName()))
- processMethod = GEBProcessor::processListener;
- else if(ann.getQualifiedName().contentEquals(Event.class.getName()))
- processMethod = GEBProcessor::processEvent;
- else continue;
+ for(Element e : env.getElementsAnnotatedWith(ann))
+ this.processListener(e);
+ if(!this.listenerMap.isEmpty()) {
+ this.generateClasses();
+ this.generateServiceProvider();
+ return true;
+ } else return false;
+ }
- claimed = true;
+ private final TypeMirror listenerInterface = this.processingEnv.getElementUtils()
+ .getTypeElement("ftbsc.geb.api.IListener").asType();
- for(Element e : env.getElementsAnnotatedWith(ann))
- processMethod.accept(this, e);
- }
- return claimed;
- }
+ private final TypeMirror eventInterface = this.processingEnv.getElementUtils()
+ .getTypeElement("ftbsc.geb.api.IEvent").asType();
- private final Map<Element, List<ExecutableElement>> listeners = new HashMap<>();
+ private final TypeMirror dispatcherInterface = this.processingEnv.getElementUtils()
+ .getTypeElement("ftbsc.geb.api.IEventDispatcher").asType();
- private static List<Element> getMembersAnnotatedWith(TypeElement typeElement, Class<? extends Annotation> ann) {
- return typeElement.getEnclosedElements()
- .stream()
- .filter(elem -> elem.getAnnotation(ann) != null)
- .collect(Collectors.toList());
+ private void processListener(Element target) {
+ ExecutableElement listener = (ExecutableElement) target; //this cast will never fail
+
+ //ensure the parent is instance of IListener
+ TypeMirror parentType = listener.getEnclosingElement().asType();
+ if(!this.processingEnv.getTypeUtils().isAssignable(parentType, this.listenerInterface))
+ return; //TODO throw error, parent doesn't implement the interface
+
+ //ensure the listener method has only a single IEvent parameter
+ List<? extends VariableElement> params = listener.getParameters();
+ if(listener.getParameters().size() != 1)
+ 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
+
+ if(!this.listenerMap.containsKey(event))
+ this.listenerMap.put(event, new HashSet<>());
+ this.listenerMap.get(event).add(new ListenerContainer(listener));
}
- private void processListener(Element target) {
- ExecutableElement listener = (ExecutableElement) target; //this will never fail
- Listen listenerAnn = target.getAnnotation(Listen.class);
+ private void generateClasses() {
+ this.listenerMap.forEach((event, listeners) -> {
+ TypeElement eventClass = (TypeElement) this.processingEnv.getTypeUtils().asElement(event);
- //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();
+ //reorder the injectors to follow priority
+ List<ListenerContainer> ordered = listeners.stream().sorted(Comparator.comparingInt(
+ container -> container.annotation.priority()
+ )).collect(Collectors.toList());
- //ensure the parent is instance of IListener
- TypeElement cursor = parent;
- TypeMirror listenerInterface = this.processingEnv.getElementUtils().getTypeElement("ftbsc.geb.api.IListener").asType()
-; while(cursor != null) {
- if(cursor.getInterfaces().contains(listenerInterface))
- break;
-
- Element superclass = this.processingEnv.getTypeUtils().asElement(cursor.getSuperclass());
- if(superclass instanceof TypeElement)
- cursor = (TypeElement) superclass;
- else return; //TODO throw error, parent doesnt implement the interface
- }
-
- List<Element> instanceSources = getMembersAnnotatedWith(parent, ListenerInstance.class);
-
- if(instanceSources.size() != 1)
- 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);
+ 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 void processEvent(Element target) {
- //TODO
+ 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);
+ }
}
}