diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/AnnotationProcessorConstants.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/AnnotationProcessorConstants.java
new file mode 100644
index 000000000..a3ba59598
--- /dev/null
+++ b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/AnnotationProcessorConstants.java
@@ -0,0 +1,8 @@
+package com.velocitypowered.annotationprocessor;
+
+class AnnotationProcessorConstants {
+
+ static final String PLUGIN_ANNOTATION_CLASS = "com.velocitypowered.api.plugin.Plugin";
+ static final String SUBSCRIBE_ANNOTATION_CLASS = "com.velocitypowered.api.event.Subscribe";
+ static final String EVENTTASK_CLASS = "com.velocitypowered.api.event.EventTask";
+}
diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java
new file mode 100644
index 000000000..add7dfdcf
--- /dev/null
+++ b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.annotationprocessor;
+
+import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.EVENTTASK_CLASS;
+import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.PLUGIN_ANNOTATION_CLASS;
+import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.SUBSCRIBE_ANNOTATION_CLASS;
+
+import com.google.gson.Gson;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.plugin.Plugin;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+@SupportedAnnotationTypes({PLUGIN_ANNOTATION_CLASS, SUBSCRIBE_ANNOTATION_CLASS})
+public class ApiAnnotationProcessor extends AbstractProcessor {
+
+ private @Nullable String pluginClassFound;
+ private boolean warnedAboutMultiplePlugins;
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public synchronized boolean process(Set extends TypeElement> annotations,
+ RoundEnvironment roundEnv) {
+ if (ProcessorUtils.contains(annotations, Subscribe.class)) {
+ for (final Element e : roundEnv.getElementsAnnotatedWith(Subscribe.class)) {
+ if (e.getKind() != ElementKind.METHOD) {
+ this.processingEnv.getMessager().printMessage(
+ Kind.ERROR, "Invalid element of type " + e.getKind()
+ + " annotated with @Subscribe", e);
+ continue;
+ }
+ final ExecutableElement method = (ExecutableElement) e;
+
+ final Messager msg = this.processingEnv.getMessager();
+ if (method.getModifiers().contains(Modifier.STATIC)) {
+ msg.printMessage(Diagnostic.Kind.ERROR, "method must not be static", method);
+ }
+ if (!method.getModifiers().contains(Modifier.PUBLIC)) {
+ msg.printMessage(Diagnostic.Kind.ERROR, "method must be public", method);
+ }
+ if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+ msg.printMessage(Diagnostic.Kind.ERROR,
+ "method must not be abstract", method);
+ }
+ if (method.getEnclosingElement().getKind().isInterface()) {
+ msg.printMessage(Diagnostic.Kind.ERROR,
+ "interfaces cannot declare listeners", method);
+ }
+ if (method.getReturnType().getKind() != TypeKind.VOID
+ && !this.isTypeSubclass(method.getReturnType(), EVENTTASK_CLASS)) {
+ msg.printMessage(Kind.ERROR, "method must return void or EventTask", method);
+ }
+ final List extends VariableElement> parameters = method.getParameters();
+ if (parameters.isEmpty() || !this.isTypeSubclass(parameters.get(0), SUBSCRIBE_ANNOTATION_CLASS)) {
+ msg.printMessage(Diagnostic.Kind.ERROR,
+ "method must have an Event as its first parameter", method);
+ }
+ }
+ } else if (ProcessorUtils.contains(annotations, Plugin.class)) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) {
+ if (element.getKind() != ElementKind.CLASS) {
+ processingEnv.getMessager()
+ .printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with "
+ + Plugin.class.getCanonicalName());
+ return false;
+ }
+
+ Name qualifiedName = ((TypeElement) element).getQualifiedName();
+
+ if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
+ if (!warnedAboutMultiplePlugins) {
+ processingEnv.getMessager()
+ .printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support "
+ + "multiple plugins. We are using " + pluginClassFound
+ + " for your plugin's main class.");
+ warnedAboutMultiplePlugins = true;
+ }
+ return false;
+ }
+
+ Plugin plugin = element.getAnnotation(Plugin.class);
+ if (!SerializedPluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin "
+ + qualifiedName
+ + ". IDs must start alphabetically, have alphanumeric characters, and can "
+ + "contain dashes or underscores.");
+ return false;
+ }
+
+ // All good, generate the velocity-plugin.json.
+ SerializedPluginDescription description = SerializedPluginDescription
+ .from(plugin, qualifiedName.toString());
+ try {
+ FileObject object = processingEnv.getFiler()
+ .createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json");
+ try (Writer writer = new BufferedWriter(object.openWriter())) {
+ new Gson().toJson(description, writer);
+ }
+ pluginClassFound = qualifiedName.toString();
+ } catch (IOException e) {
+ processingEnv.getMessager()
+ .printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file");
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isTypeSubclass(final Element typedElement, final String subclass) {
+ return isTypeSubclass(typedElement.asType(), subclass);
+ }
+
+ private boolean isTypeSubclass(final TypeMirror typeMirror, final String subclass) {
+ final Elements elements = this.processingEnv.getElementUtils();
+ final Types types = this.processingEnv.getTypeUtils();
+
+ final TypeMirror event = types.getDeclaredType(elements.getTypeElement(subclass));
+ return types.isAssignable(typeMirror, event);
+ }
+}
diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/PluginAnnotationProcessor.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/PluginAnnotationProcessor.java
deleted file mode 100644
index 43d0e8c9d..000000000
--- a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/PluginAnnotationProcessor.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 Velocity Contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.velocitypowered.annotationprocessor;
-
-import com.google.gson.Gson;
-import com.velocitypowered.api.plugin.Plugin;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Objects;
-import java.util.Set;
-import javax.annotation.Nullable;
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.annotation.processing.RoundEnvironment;
-import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.Name;
-import javax.lang.model.element.TypeElement;
-import javax.tools.Diagnostic;
-import javax.tools.FileObject;
-import javax.tools.StandardLocation;
-
-@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
-public class PluginAnnotationProcessor extends AbstractProcessor {
-
- private @Nullable String pluginClassFound;
- private boolean warnedAboutMultiplePlugins;
-
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.latestSupported();
- }
-
- @Override
- public synchronized boolean process(Set extends TypeElement> annotations,
- RoundEnvironment roundEnv) {
- if (roundEnv.processingOver()) {
- return false;
- }
-
- for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) {
- if (element.getKind() != ElementKind.CLASS) {
- processingEnv.getMessager()
- .printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with "
- + Plugin.class.getCanonicalName());
- return false;
- }
-
- Name qualifiedName = ((TypeElement) element).getQualifiedName();
-
- if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
- if (!warnedAboutMultiplePlugins) {
- processingEnv.getMessager()
- .printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support "
- + "multiple plugins. We are using " + pluginClassFound
- + " for your plugin's main class.");
- warnedAboutMultiplePlugins = true;
- }
- return false;
- }
-
- Plugin plugin = element.getAnnotation(Plugin.class);
- if (!SerializedPluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin "
- + qualifiedName
- + ". IDs must start alphabetically, have alphanumeric characters, and can "
- + "contain dashes or underscores.");
- return false;
- }
-
- // All good, generate the velocity-plugin.json.
- SerializedPluginDescription description = SerializedPluginDescription
- .from(plugin, qualifiedName.toString());
- try {
- FileObject object = processingEnv.getFiler()
- .createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json");
- try (Writer writer = new BufferedWriter(object.openWriter())) {
- new Gson().toJson(description, writer);
- }
- pluginClassFound = qualifiedName.toString();
- } catch (IOException e) {
- processingEnv.getMessager()
- .printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file");
- }
- }
-
- return false;
- }
-}
diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ProcessorUtils.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ProcessorUtils.java
new file mode 100644
index 000000000..cb29d1a6e
--- /dev/null
+++ b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ProcessorUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.annotationprocessor;
+
+import java.util.Collection;
+import javax.lang.model.element.TypeElement;
+
+/*
+ * This file is derived from the Sponge API. The original copyright notice is reproduced below:
+ *
+ * This file is part of SpongeAPI, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) SpongePowered
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+final class ProcessorUtils {
+
+ private ProcessorUtils() {
+ }
+
+ static boolean contains(final Collection extends TypeElement> elements, final Class> clazz) {
+ if (elements.isEmpty()) {
+ return false;
+ }
+
+ final String name = clazz.getName();
+ for (final TypeElement element : elements) {
+ if (element.getQualifiedName().contentEquals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
index 98f9005d0..08c62f25d 100644
--- a/annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
+++ b/annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -1 +1 @@
-com.velocitypowered.annotationprocessor.PluginAnnotationProcessor
\ No newline at end of file
+com.velocitypowered.annotationprocessor.ApiAnnotationProcessor
\ No newline at end of file