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 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 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 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 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