From 3d4b0c8a117a5ffc54e90b6b3b41f6f24e000cba Mon Sep 17 00:00:00 2001 From: Senmori Date: Fri, 13 Jul 2018 00:28:40 -0400 Subject: Misc changes for v1.2 * Add @ApiVersion and appropriate targets * Add ability to have @Command and @Permission annotations on classes that implement CommandExecutor. Thanks Hex for the suggestion. * Remove last reference to @Main. * Update README. * Bump version. * Remove deprecated annotations. --- README.md | 74 +++-- pom.xml | 2 +- .../java/annotation/PluginAnnotationProcessor.java | 358 ++++++++++++++------- .../plugin/java/annotation/command/Commands.java | 9 +- .../java/annotation/dependency/Dependency.java | 9 +- .../java/annotation/dependency/DependsOn.java | 9 +- .../java/annotation/dependency/LoadBefore.java | 5 +- .../annotation/dependency/LoadBeforePlugins.java | 3 + .../java/annotation/dependency/SoftDependency.java | 5 +- .../java/annotation/dependency/SoftDependsOn.java | 11 +- .../annotation/permission/ChildPermission.java | 4 +- .../java/annotation/permission/Permission.java | 8 +- .../java/annotation/permission/Permissions.java | 9 +- .../plugin/java/annotation/plugin/ApiVersion.java | 62 ++++ .../plugin/java/annotation/plugin/Description.java | 2 +- .../plugin/java/annotation/plugin/LoadOn.java | 27 -- .../plugin/java/annotation/plugin/LoadOrder.java | 27 ++ .../plugin/java/annotation/plugin/LogPrefix.java | 2 +- .../bukkit/plugin/java/annotation/plugin/Main.java | 62 ---- .../bukkit/plugin/java/annotation/plugin/Name.java | 23 -- .../java/annotation/plugin/UsesDatabase.java | 20 -- .../plugin/java/annotation/plugin/Version.java | 26 -- .../plugin/java/annotation/plugin/Website.java | 2 +- .../java/annotation/plugin/author/Author.java | 2 +- .../java/annotation/plugin/author/Authors.java | 3 + 25 files changed, 429 insertions(+), 335 deletions(-) create mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/ApiVersion.java delete mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOn.java create mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOrder.java delete mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/Main.java delete mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/Name.java delete mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/UsesDatabase.java delete mode 100644 src/main/java/org/bukkit/plugin/java/annotation/plugin/Version.java diff --git a/README.md b/README.md index 82e6b46..37b6414 100644 --- a/README.md +++ b/README.md @@ -6,66 +6,70 @@ See the [wiki](https://www.spigotmc.org/wiki/plugin-yml/) for more information. ## Example Usage ``` -package org.spigotmc.annotationtest; - -import org.bukkit.permissions.PermissionDefault; -import org.bukkit.plugin.PluginLoadOrder; -import org.bukkit.plugin.java.*; -import org.bukkit.plugin.java.annotation.*; -import org.bukkit.plugin.java.annotation.Commands.Cmd; -import org.bukkit.plugin.java.annotation.Permissions.Perm; - @Plugin(name = "TestPlugin", version = "1.0") -@Description(desc = "A test plugin") -@LoadOn(loadOn = PluginLoadOrder.POSTWORLD) // defaults to PluginLoadOrder.POSTWORLD if not preset -@Author(name = "md_5") -@Website(url = "spigotmc.org") -@LogPrefix(prefix = "Testing") -@Dependency(plugin = "WorldEdit") -@Dependency(plugin = "Towny") -@LoadBefore(plugin = "Essentials") -@SoftDependency(plugin = "FAWE") +@Description("A test plugin") +@LoadOrder(PluginLoadOrder.STARTUP) +@Author("md_5") +@Website("www.spigotmc.org") +@LogPrefix("Testing") +@Dependency("WorldEdit") +@Dependency("Towny") +@LoadBefore("Towny") +@SoftDependency("EssentialsX") @Command(name = "foo", desc = "Foo command", aliases = {"foobar", "fubar"}, permission = "test.foo", permissionMessage = "You do not have permission!", usage = "/ [test|stop]") @Permission(name = "test.foo", desc = "Allows foo command", defaultValue = PermissionDefault.OP) @Permission(name = "test.*", desc = "Wildcard permission", defaultValue = PermissionDefault.OP, children = {@ChildPermission(name ="test.foo")}) -public class Test extends JavaPlugin {} +@ApiVersion(ApiVersion.Target.v1_13) +public class TestPlugin extends JavaPlugin { ``` Output: ``` -# Auto-generated plugin.yml, generated at 2018/03/06 18:15:44 by org.bukkit.plugin.java.annotation.PluginAnnotationProcessor +# Auto-generated plugin.yml, generated at 2018/07/12 22:16:27 by org.bukkit.plugin.java.annotation.PluginAnnotationProcessor + +# Auto-generated plugin.yml, generated at 2018/07/13 00:16:24 by org.bukkit.plugin.java.annotation.PluginAnnotationProcessor -main: org.spigotmc.annotationtest.Test +main: org.spigotmc.spigot.TestPlugin name: TestPlugin version: '1.0' description: A test plugin -load: POSTWORLD +load: STARTUP author: md_5 -website: spigotmc.org +website: www.spigotmc.org prefix: Testing depend: - WorldEdit - Towny softdepend: -- FAWE +- EssentialsX loadbefore: -- Essentials +- Towny commands: - foo: - description: Foo command - aliases: - - foobar - - fubar - permission: test.foo - permission-message: You do not have permission! - usage: / [test|stop] + TestCommand: + aliases: testext2 + permission: test.testext + permission-message: Oopsy! + usage: /testext test test permissions: test.foo: description: Allows foo command - default: op test.*: description: Wildcard permission - default: op children: test.foo: true +api-version: '1.13' ``` + +As of version 1.2.0-SNAPSHOT you can now also use the ```@Command``` and ```@Permission``` +annotations on classes that implement CommandExecutor. + +For example: +``` +@Command(name = "TestCommand", aliases = "testext2", permission = "test.testext", permissionMessage = "Oopsy!", usage = "/testext test test") +@Permission(name = "test.testext", desc = "Provides access to /textext command", defaultValue = PermissionDefault.TRUE) +public class TestCommand implements CommandExecutor { +``` + +As of version 1.2.0-SNAPSHOT the ```@ApiVersion``` annotation was introduced to bring compatibility for +Bukkit's new ```api-version``` plugin.yml option. This defaults to ```ApiVersion.Target.DEFAULT``` if not specified or included. +All pre-1.13 plugins MUST use ```ApiVersion.Target.DEFAULT``` in order for the plugin to be loaded correctly. diff --git a/pom.xml b/pom.xml index 1ed211e..835a5e1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.spigotmc plugin-annotations - 1.1.0-SNAPSHOT + 1.2.0-SNAPSHOT jar Plugin Annotations diff --git a/src/main/java/org/bukkit/plugin/java/annotation/PluginAnnotationProcessor.java b/src/main/java/org/bukkit/plugin/java/annotation/PluginAnnotationProcessor.java index 9557b79..2f0f8d7 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/PluginAnnotationProcessor.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/PluginAnnotationProcessor.java @@ -2,25 +2,28 @@ package org.bukkit.plugin.java.annotation; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import org.bukkit.command.CommandExecutor; +import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.annotation.command.Command; +import org.bukkit.plugin.java.annotation.command.Commands; import org.bukkit.plugin.java.annotation.dependency.Dependency; import org.bukkit.plugin.java.annotation.dependency.LoadBefore; import org.bukkit.plugin.java.annotation.dependency.SoftDependency; import org.bukkit.plugin.java.annotation.permission.ChildPermission; import org.bukkit.plugin.java.annotation.permission.Permission; +import org.bukkit.plugin.java.annotation.permission.Permissions; +import org.bukkit.plugin.java.annotation.plugin.ApiVersion; import org.bukkit.plugin.java.annotation.plugin.Description; -import org.bukkit.plugin.java.annotation.plugin.LoadOn; +import org.bukkit.plugin.java.annotation.plugin.LoadOrder; import org.bukkit.plugin.java.annotation.plugin.LogPrefix; import org.bukkit.plugin.java.annotation.plugin.Plugin; -import org.bukkit.plugin.java.annotation.plugin.UsesDatabase; import org.bukkit.plugin.java.annotation.plugin.Website; import org.bukkit.plugin.java.annotation.plugin.author.Author; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.nodes.Tag; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -31,231 +34,358 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.swing.text.DateFormatter; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; import java.io.IOException; -import java.io.InputStream; import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -@SupportedAnnotationTypes("org.bukkit.plugin.java.annotation.*") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedAnnotationTypes( "org.bukkit.plugin.java.annotation.*" ) +@SupportedSourceVersion( SourceVersion.RELEASE_8 ) public class PluginAnnotationProcessor extends AbstractProcessor { private boolean hasMainBeenFound = false; - private static final DateTimeFormatter dFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH); + private static final DateTimeFormatter dFormat = DateTimeFormatter.ofPattern( "yyyy/MM/dd HH:mm:ss", Locale.ENGLISH ); @Override public boolean process(Set annots, RoundEnvironment rEnv) { - Element main = null; + Element mainPluginElement = null; hasMainBeenFound = false; - Set elements = rEnv.getElementsAnnotatedWith(Plugin.class); - if(elements.size() > 1) { - raiseError("Found more than one plugin main class"); + Set elements = rEnv.getElementsAnnotatedWith( Plugin.class ); + if ( elements.size() > 1 ) { + raiseError( "Found more than one plugin main class" ); return false; } - if(elements.isEmpty()) { + if ( elements.isEmpty() ) { return false; } - if(hasMainBeenFound){ - raiseError("The plugin class has already been located, aborting!"); + if ( hasMainBeenFound ) { + raiseError( "The plugin class has already been located, aborting!" ); return false; } - main = elements.iterator().next(); + mainPluginElement = elements.iterator().next(); hasMainBeenFound = true; - TypeElement mainType; - if(main instanceof TypeElement){ - mainType = (TypeElement) main; + TypeElement mainPluginType; + if ( mainPluginElement instanceof TypeElement ) { + mainPluginType = ( TypeElement ) mainPluginElement; } else { - raiseError("Element annotated with @Main is not a type!", main); + raiseError( "Element annotated with @Plugin is not a type!", mainPluginElement ); return false; } - if(!(mainType.getEnclosingElement() instanceof PackageElement) && !mainType.getModifiers().contains(Modifier.STATIC)){ - raiseError("Element annotated with @Main is not top-level or static nested!", mainType); + if ( !( mainPluginType.getEnclosingElement() instanceof PackageElement ) && !mainPluginType.getModifiers().contains( Modifier.STATIC ) ) { + raiseError( "Element annotated with @Plugin is not top-level or static nested!", mainPluginType ); return false; } - if(!processingEnv.getTypeUtils().isSubtype(mainType.asType(), fromClass(JavaPlugin.class))){ - raiseError("Class annotated with @Main is not an subclass of JavaPlugin!", mainType); + if ( !processingEnv.getTypeUtils().isSubtype( mainPluginType.asType(), fromClass( JavaPlugin.class ) ) ) { + raiseError( "Class annotated with @Plugin is not an subclass of JavaPlugin!", mainPluginType ); } Map yml = Maps.newLinkedHashMap(); // linked so we can maintain the same output into file for sanity // populate mainName - final String mainName = mainType.getQualifiedName().toString(); - yml.put("main", mainName); // always override this so we make sure the main class name is correct + final String mainName = mainPluginType.getQualifiedName().toString(); + yml.put( "main", mainName ); // always override this so we make sure the main class name is correct // populate plugin name - processAndPut(yml, "name", mainType, mainName.substring(mainName.lastIndexOf('.') + 1), Plugin.class, String.class, "name"); + processAndPut( yml, "name", mainPluginType, mainName.substring( mainName.lastIndexOf( '.' ) + 1 ), Plugin.class, String.class, "name" ); // populate version - processAndPut(yml, "version", mainType, Plugin.DEFAULT_VERSION, Plugin.class, String.class, "version"); + processAndPut( yml, "version", mainPluginType, Plugin.DEFAULT_VERSION, Plugin.class, String.class, "version" ); // populate plugin description - processAndPut(yml, "description", mainType, null, Description.class, String.class, "desc"); + processAndPut( yml, "description", mainPluginType, null, Description.class, String.class ); // populate plugin load order - processAndPut(yml, "load", mainType, null, LoadOn.class, String.class,"loadOn"); + processAndPut( yml, "load", mainPluginType, null, LoadOrder.class, String.class ); // authors - Author[] authors = mainType.getAnnotationsByType(Author.class); + Author[] authors = mainPluginType.getAnnotationsByType( Author.class ); List authorMap = Lists.newArrayList(); - for(Author auth : authors) { - authorMap.add(auth.name()); + for ( Author auth : authors ) { + authorMap.add( auth.value() ); } - if(authorMap.size() > 1) { - yml.put("authors", authorMap); - } else if(authorMap.size() == 1) { - yml.put("author", authorMap.iterator().next()); + if ( authorMap.size() > 1 ) { + yml.put( "authors", authorMap ); + } else if ( authorMap.size() == 1 ) { + yml.put( "author", authorMap.iterator().next() ); } // website - processAndPut(yml, "website", mainType, null, Website.class, String.class, "url"); + processAndPut( yml, "website", mainPluginType, null, Website.class, String.class ); // prefix - processAndPut(yml, "prefix", mainType, null, LogPrefix.class, String.class, "prefix"); + processAndPut( yml, "prefix", mainPluginType, null, LogPrefix.class, String.class ); // dependencies - Dependency[] dependencies = mainType.getAnnotationsByType(Dependency.class); + Dependency[] dependencies = mainPluginType.getAnnotationsByType( Dependency.class ); List hardDependencies = Lists.newArrayList(); - for(Dependency dep : dependencies) { - hardDependencies.add(dep.plugin()); + for ( Dependency dep : dependencies ) { + hardDependencies.add( dep.value() ); } - if(!hardDependencies.isEmpty()) yml.putIfAbsent("depend", hardDependencies); + if ( !hardDependencies.isEmpty() ) yml.put( "depend", hardDependencies ); // soft-dependencies - SoftDependency[] softDependencies = mainType.getAnnotationsByType(SoftDependency.class); - String[] softDepArr = new String[softDependencies.length]; - for(int i = 0; i < softDependencies.length; i++) { - softDepArr[i] = softDependencies[i].plugin(); + SoftDependency[] softDependencies = mainPluginType.getAnnotationsByType( SoftDependency.class ); + String[] softDepArr = new String[ softDependencies.length ]; + for ( int i = 0; i < softDependencies.length; i++ ) { + softDepArr[ i ] = softDependencies[ i ].value(); } - if(softDepArr.length > 0) yml.putIfAbsent("softdepend", softDepArr); + if ( softDepArr.length > 0 ) yml.put( "softdepend", softDepArr ); // load-before - LoadBefore[] loadBefore = mainType.getAnnotationsByType(LoadBefore.class); - String[] loadBeforeArr = new String[loadBefore.length]; - for(int i = 0; i < loadBefore.length; i++) { - loadBeforeArr[i] = loadBefore[i].plugin(); + LoadBefore[] loadBefore = mainPluginType.getAnnotationsByType( LoadBefore.class ); + String[] loadBeforeArr = new String[ loadBefore.length ]; + for ( int i = 0; i < loadBefore.length; i++ ) { + loadBeforeArr[ i ] = loadBefore[ i ].value(); } - if(loadBeforeArr.length > 0) yml.putIfAbsent("loadbefore", loadBeforeArr); + if ( loadBeforeArr.length > 0 ) yml.put( "loadbefore", loadBeforeArr ); // commands - Command[] commands = mainType.getAnnotationsByType(Command.class); - Map commandMap = Maps.newLinkedHashMap(); - for(Command command : commands) { - Map desc = Maps.newLinkedHashMap(); - String name = command.name(); - if(!command.desc().isEmpty()) desc.put("description", command.desc()); - if(command.aliases().length != 0) desc.put("aliases", command.aliases()); - if(!command.permission().isEmpty()) desc.put("permission", command.permission()); - if(!command.permissionMessage().isEmpty()) desc.put("permission-message", command.permissionMessage()); - if(!command.usage().isEmpty()) desc.put("usage", command.usage()); - commandMap.put(name, desc); + // Begin processing external command annotations + Map> commandMap = Maps.newLinkedHashMap(); + boolean result = processExternalCommands( rEnv.getElementsAnnotatedWith( Command.class ), mainPluginType, commandMap ); + if ( !result ) { + // #processExternalCommand already raised the errors + return false; + } + + Commands commands = mainPluginType.getAnnotation( Commands.class ); + + // Check main class for any command annotations + if ( commands != null ) { + Map> merged = Maps.newLinkedHashMap(); + merged.putAll( commandMap ); + merged.putAll( this.processCommands( commands ) ); + commandMap = merged; } - if(!commandMap.isEmpty()) yml.putIfAbsent("commands", commandMap); - - // permissions - Permission[] permissions = mainType.getAnnotationsByType(Permission.class); - Map permMap = Maps.newLinkedHashMap(); - for(Permission perm : permissions) { - Map desc = Maps.newLinkedHashMap(); - String name = perm.name(); - if(!perm.desc().isEmpty()) desc.put("description", perm.desc()); - desc.put("default", perm.defaultValue().toString()); - Map children = Maps.newLinkedHashMap(); - for(ChildPermission child : perm.children()) { - children.put(child.name(), child.inherit()); + + yml.put( "commands", commandMap ); + + // Permissions + Map> permissionMetadata = Maps.newLinkedHashMap(); + + Set permissionAnnotations = rEnv.getElementsAnnotatedWith( Command.class ); + if ( permissionAnnotations.size() > 0 ) { + for ( Element element : permissionAnnotations ) { + if ( element.equals( mainPluginElement ) ) { + continue; + } + if ( element.getAnnotation( Permission.class ) != null ) { + Permission permissionAnnotation = element.getAnnotation( Permission.class ); + permissionMetadata.put( permissionAnnotation.name(), this.processPermission( permissionAnnotation ) ); + } } - if(!children.isEmpty()) desc.put("children", children); - permMap.put(name, desc); } - if(!permMap.isEmpty()) yml.putIfAbsent("permissions", permMap); - // database D: //TODO: Remove me! - if(mainType.getAnnotation(UsesDatabase.class) != null) { - yml.put("database", true); - processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "Database support was dropped in Bukkit in version 1.12.", mainType); + Permissions permissions = mainPluginType.getAnnotation( Permissions.class ); + if ( permissions != null ) { + Map> joined = Maps.newLinkedHashMap(); + joined.putAll( permissionMetadata ); + joined.putAll( this.processPermissions( permissions ) ); + permissionMetadata = joined; + } + yml.put( "permissions", permissionMetadata ); + + // api-version + if ( mainPluginType.getAnnotation( ApiVersion.class ) != null ) { + ApiVersion apiVersion = mainPluginType.getAnnotation( ApiVersion.class ); + if ( apiVersion.value() != ApiVersion.Target.DEFAULT ) { + yml.put( "api-version", apiVersion.value().getVersion() ); + } } - Yaml yaml = new Yaml(); try { - FileObject file = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "plugin.yml"); - try(Writer w = file.openWriter()) { - w.append("# Auto-generated plugin.yml, generated at ") - .append(LocalDateTime.now().format(dFormat)) - .append(" by ") - .append(this.getClass().getName()) - .append("\n\n"); + Yaml yaml = new Yaml(); + FileObject file = this.processingEnv.getFiler().createResource( StandardLocation.CLASS_OUTPUT, "", "plugin.yml" ); + try ( Writer w = file.openWriter() ) { + w.append( "# Auto-generated plugin.yml, generated at " ) + .append( LocalDateTime.now().format( dFormat ) ) + .append( " by " ) + .append( this.getClass().getName() ) + .append( "\n\n" ); // have to format the yaml explicitly because otherwise it dumps child nodes as maps within braces. - String raw = yaml.dumpAs(yml, Tag.MAP, DumperOptions.FlowStyle.BLOCK); - w.write(raw); + String raw = yaml.dumpAs( yml, Tag.MAP, DumperOptions.FlowStyle.BLOCK ); + w.write( raw ); w.flush(); w.close(); } // try with resources will close the Writer since it implements Closeable - } catch (IOException e) { - throw new RuntimeException(e); + } catch ( IOException e ) { + throw new RuntimeException( e ); } - processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "NOTE: You are using org.bukkit.plugin.java.annotation, an experimental API!"); + processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, "NOTE: You are using org.bukkit.plugin.java.annotation, an experimental API!" ); return true; } private void raiseError(String message) { - this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message); + this.processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, message ); } private void raiseError(String message, Element element) { - this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element); + this.processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, message, element ); } private TypeMirror fromClass(Class clazz) { - return processingEnv.getElementUtils().getTypeElement(clazz.getName()).asType(); + return processingEnv.getElementUtils().getTypeElement( clazz.getName() ).asType(); } private R processAndPut( Map map, String name, Element el, R defaultVal, Class annotationType, Class returnType) { - return processAndPut(map, name, el, defaultVal, annotationType, returnType, "value"); + return processAndPut( map, name, el, defaultVal, annotationType, returnType, "value" ); } private R processAndPut( Map map, String name, Element el, R defaultVal, Class annotationType, Class returnType, String methodName) { - R result = process(el, defaultVal, annotationType, returnType, methodName); - if(result != null) - map.putIfAbsent(name, result); + R result = process( el, defaultVal, annotationType, returnType, methodName ); + if ( result != null ) + map.put( name, result ); return result; } private R process(Element el, R defaultVal, Class annotationType, Class returnType, String methodName) { R result; - A ann = el.getAnnotation(annotationType); - if(ann == null) result = defaultVal; + A ann = el.getAnnotation( annotationType ); + if ( ann == null ) result = defaultVal; else { try { - Method value = annotationType.getMethod(methodName); - Object res = value.invoke(ann); - result = (R) (returnType == String.class ? res.toString() : returnType.cast(res)); - } catch (Exception e) { - throw new RuntimeException(e); // shouldn't happen in theory (blame Choco if it does) + Method value = annotationType.getMethod( methodName ); + Object res = value.invoke( ann ); + result = ( R ) ( returnType == String.class ? res.toString() : returnType.cast( res ) ); + } catch ( Exception e ) { + throw new RuntimeException( e ); // shouldn't happen in theory (blame Choco if it does) } } return result; } + + private boolean processExternalCommands(Set commandExecutors, TypeElement mainPluginType, Map> commandMetadata) { + for ( Element element : commandExecutors ) { + // Check to see if someone annotated a non-class with this + if ( !( element instanceof TypeElement ) ) { + this.raiseError( "Specified Command Executor class is not a class." ); + return false; + } + + TypeElement typeElement = ( TypeElement ) element; + if ( typeElement.equals( mainPluginType ) ) { + continue; + } + + // Check to see if annotated class is actuall a command executor + TypeMirror mirror = this.processingEnv.getElementUtils().getTypeElement( CommandExecutor.class.getName() ).asType(); + if ( !( this.processingEnv.getTypeUtils().isAssignable( typeElement.asType(), mirror ) ) ) { + this.raiseError( "Specified Command Executor class is not assignable from CommandExecutor " ); + return false; + } + + Command annotation = typeElement.getAnnotation( Command.class ); + commandMetadata.put( annotation.name(), this.processCommand( annotation ) ); + } + return true; + } + + /** + * Processes a set of commands. + * + * @param commands The annotation. + * + * @return The generated command metadata. + */ + protected Map> processCommands(Commands commands) { + Map> commandList = Maps.newLinkedHashMap(); + for ( Command command : commands.value() ) { + commandList.put( command.name(), this.processCommand( command ) ); + } + return commandList; + } + + /** + * Processes a single command. + * + * @param commandAnnotation The annotation. + * + * @return The generated command metadata. + */ + protected Map processCommand(Command commandAnnotation) { + Map command = Maps.newLinkedHashMap(); + + if ( commandAnnotation.aliases().length == 1 ) { + command.put( "aliases", commandAnnotation.aliases()[ 0 ] ); + } else if ( commandAnnotation.aliases().length > 1 ) { + command.put( "aliases", commandAnnotation.aliases() ); + } + + if ( !"".equals( commandAnnotation.desc() ) ) { + command.put( "description", commandAnnotation.desc() ); + } + if ( !"".equals( commandAnnotation.permission() ) ) { + command.put( "permission", commandAnnotation.permission() ); + } + if ( !"".equals( commandAnnotation.permissionMessage() ) ) { + command.put( "permission-message", commandAnnotation.permissionMessage() ); + } + if ( !"".equals( commandAnnotation.usage() ) ) { + command.put( "usage", commandAnnotation.usage() ); + } + + return command; + } + + /** + * Processes a command. + * + * @param permissionAnnotation The annotation. + * + * @return The generated permission metadata. + */ + protected Map processPermission(Permission permissionAnnotation) { + Map permission = Maps.newLinkedHashMap(); + + if ( !"".equals( permissionAnnotation.desc() ) ) { + permission.put( "description", permissionAnnotation.desc() ); + } + if ( PermissionDefault.OP != permissionAnnotation.defaultValue() ) { + permission.put( "default", permissionAnnotation.defaultValue().toString().toLowerCase() ); + } + + if ( permissionAnnotation.children().length > 0 ) { + Map childrenList = Maps.newLinkedHashMap(); // maintain order + for ( ChildPermission childPermission : permissionAnnotation.children() ) { + childrenList.put( childPermission.name(), childPermission.inherit() ); + } + permission.put( "children", childrenList ); + } + + return permission; + } + + /** + * Processes a set of permissions. + * + * @param permissions The annotation. + * + * @return The generated permission metadata. + */ + protected Map> processPermissions(Permissions permissions) { + Map> permissionList = Maps.newLinkedHashMap(); + for ( Permission permission : permissions.value() ) { + permissionList.put( permission.name(), this.processPermission( permission ) ); + } + return permissionList; + } } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/command/Commands.java b/src/main/java/org/bukkit/plugin/java/annotation/command/Commands.java index 6ba1211..df66cc6 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/command/Commands.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/command/Commands.java @@ -7,9 +7,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Part of the plugin annotations framework. - *

- * Represents a list of this plugin's registered command(s). + * Part of the plugin annotations framework. + *

+ * Represents a list of this plugin's registered command(s). + *
+ * This specific annotation should not be used by people who do not know + * how repeating annotations work. */ @Documented @Target(ElementType.TYPE) diff --git a/src/main/java/org/bukkit/plugin/java/annotation/dependency/Dependency.java b/src/main/java/org/bukkit/plugin/java/annotation/dependency/Dependency.java index ac6e942..28b045b 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/dependency/Dependency.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/dependency/Dependency.java @@ -8,7 +8,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Defines a plugin dependency + * Defines a plugin dependency. + *
+ * The plugin's name attribute is required in order to load the dependency.
+ * If any plugin listed is not found the plugin will fail to load.
+ * If multiple plugins list each other as a dependency, so that there are no plugins with an unloadable dependency, + * all plugins will fail to load. */ @Documented @Target(ElementType.TYPE) @@ -18,5 +23,5 @@ public @interface Dependency { /** * A plugin that is required to be present in order for this plugin to load. */ - String plugin(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/dependency/DependsOn.java b/src/main/java/org/bukkit/plugin/java/annotation/dependency/DependsOn.java index 8d59aaa..54e9e5d 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/dependency/DependsOn.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/dependency/DependsOn.java @@ -7,9 +7,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Part of the plugin annotations framework. - *

- * Represents the plugins a plugin depends on in order to be loaded + * Part of the plugin annotations framework. + *

+ * Represents the plugins a plugin depends on in order to be loaded + *
+ * This specific annotation should not be used by people who do not know + * how repeating annotations work. */ @Documented @Target(ElementType.TYPE) diff --git a/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBefore.java b/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBefore.java index d5ba086..06e3f5b 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBefore.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBefore.java @@ -11,6 +11,9 @@ import java.lang.annotation.Target; * Part of the plugin annotations framework. *

* Represents the plugin this plugin should be loaded before + *
+ * The plugin's name attribute is required in order to specify the target.
+ * The plugin listed will be treated as a {@link SoftDependency}.
*/ @Documented @Retention(RetentionPolicy.SOURCE) @@ -20,5 +23,5 @@ public @interface LoadBefore { /** * A plugin that should be loaded after your plugin */ - String plugin(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBeforePlugins.java b/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBeforePlugins.java index 67991e9..7061622 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBeforePlugins.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/dependency/LoadBeforePlugins.java @@ -8,6 +8,9 @@ import java.lang.annotation.Target; /** * Defines a list of plugin to load after this plugin + *
+ * This specific annotation should not be used by people who do not know + * how repeating annotations work. */ @Documented @Retention(RetentionPolicy.SOURCE) diff --git a/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependency.java b/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependency.java index a53e16b..9f3c379 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependency.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependency.java @@ -11,6 +11,9 @@ import java.lang.annotation.Target; /** * Represents a soft (optional) dependency for this plugin. * If this dependency is not present, the plugin will still load. + *
+ * The name attribute of the plugin is required in order to specify the target.
+ * Circular soft-dependencies are loaded arbitrarily. */ @Documented @@ -21,5 +24,5 @@ public @interface SoftDependency { /** * A plugin that is required in order for this plugin to have full functionality. */ - String plugin(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependsOn.java b/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependsOn.java index 038831d..fb53a10 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependsOn.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/dependency/SoftDependsOn.java @@ -7,10 +7,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Part of the plugin annotations framework. - *

- * Represents the plugins this plugin should try to load before this plugin will attempt to load. - * A plugin will still load if a soft dependency is not present. + * Part of the plugin annotations framework. + *

+ * Represents the plugins this plugin should try to load before this plugin will attempt to load. + * A plugin will still load if a soft dependency is not present. + *
+ * This specific annotation should not be used by people who do not know + * how repeating annotations work. */ @Documented diff --git a/src/main/java/org/bukkit/plugin/java/annotation/permission/ChildPermission.java b/src/main/java/org/bukkit/plugin/java/annotation/permission/ChildPermission.java index 7fe1a3d..53b6ccf 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/permission/ChildPermission.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/permission/ChildPermission.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Defines a child permission for {@link Permission} + * Defines a child permission for a {@link Permission} */ @Documented @Target(ElementType.TYPE) @@ -15,7 +15,7 @@ import java.lang.annotation.Target; public @interface ChildPermission { /** * If true, this child node will inherit the parent {@link Permission}'s permission. - * If false, this child node inherits the inverse parent permission. + * If false, this child node inherits the inverse of the parent permission. */ boolean inherit() default true; diff --git a/src/main/java/org/bukkit/plugin/java/annotation/permission/Permission.java b/src/main/java/org/bukkit/plugin/java/annotation/permission/Permission.java index 58770e2..a00c0f9 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/permission/Permission.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/permission/Permission.java @@ -19,22 +19,22 @@ import java.lang.annotation.Target; @Repeatable(Permissions.class) public @interface Permission { /** - * This perm's name. + * This permission's name. */ String name(); /** - * This perm's description. + * This permission's description. */ String desc() default ""; /** - * This perm's default {@link PermissionDefault} + * This permission's default {@link PermissionDefault} */ PermissionDefault defaultValue() default PermissionDefault.OP; /** - * This permission's child nodes ({@link ChildPermission}) + * This permission's child nodes ( {@link ChildPermission} ) */ ChildPermission[] children() default {}; } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/permission/Permissions.java b/src/main/java/org/bukkit/plugin/java/annotation/permission/Permissions.java index 8dd8581..644b152 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/permission/Permissions.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/permission/Permissions.java @@ -7,9 +7,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Part of the plugin annotations framework. - *

- * Represents a list of this plugin's registered name. + * Part of the plugin annotations framework. + *

+ * Represents a list of this plugin's registered name. + *
+ * This specific annotation should not be used by people who do not know + * how repeating annotations work. */ @Documented @Retention(RetentionPolicy.SOURCE) diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/ApiVersion.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/ApiVersion.java new file mode 100644 index 0000000..b7f1e24 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/ApiVersion.java @@ -0,0 +1,62 @@ +package org.bukkit.plugin.java.annotation.plugin; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collection; + + +/** + * This annotation specifies the api version of the plugin. + *
+ * Defaults to {@link ApiVersion.Target#DEFAULT}. + *
+ * Pre-1.13 plugins do not need to use this annotation. + */ +@Documented +@Retention( RetentionPolicy.SOURCE ) +@Target( ElementType.TYPE ) +public @interface ApiVersion { + + Target value() default Target.DEFAULT; + + /** + * Specifies the target api-version for this plugin. + * + * All pre-1.13 plugins must use {@link #DEFAULT}. + */ + public static enum Target { + /** + * This target version specifies that the plugin was made for pre-1.13 Spigot versions. + */ + DEFAULT( null ), + + /** + * This target version specifies that the plugin was made with 1.13+ versions in mind. + */ + v1_13( "1.13", DEFAULT ); + + + private final String version; + private final Collection conflictsWith = Sets.newLinkedHashSet(); + + private Target(String version, Target... conflictsWith) { + this.version = version; + this.conflictsWith.addAll( Lists.newArrayList( conflictsWith ) ); + } + + + public String getVersion() { + return version; + } + + public boolean conflictsWith(Target target) { + return this.conflictsWith.contains( target ); + } + } +} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Description.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Description.java index 137249d..69abeca 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Description.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Description.java @@ -19,6 +19,6 @@ public @interface Description { /** * A human friendly description of the functionality this plugin provides. */ - String desc(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOn.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOn.java deleted file mode 100644 index 23716f7..0000000 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOn.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.bukkit.plugin.java.annotation.plugin; - -import org.bukkit.plugin.PluginLoadOrder; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Part of the plugin annotations framework. - *

- * Represents the optional load order of the plugin. - */ - -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) -public @interface LoadOn { - /** - * Explicitly state when the plugin should be loaded. - * If not defined, will default to {@link PluginLoadOrder#POSTWORLD}. - * See {@link PluginLoadOrder} - */ - PluginLoadOrder loadOn(); -} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOrder.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOrder.java new file mode 100644 index 0000000..528ad1a --- /dev/null +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/LoadOrder.java @@ -0,0 +1,27 @@ +package org.bukkit.plugin.java.annotation.plugin; + +import org.bukkit.plugin.PluginLoadOrder; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Part of the plugin annotations framework. + *

+ * Represents the optional load order of the plugin. + */ + +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface LoadOrder { + /** + * Explicitly state when the plugin should be loaded. + * If not defined, will default to {@link PluginLoadOrder#POSTWORLD}. + * See {@link PluginLoadOrder} + */ + PluginLoadOrder value() default PluginLoadOrder.POSTWORLD; +} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/LogPrefix.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/LogPrefix.java index baefbff..cdf0838 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/LogPrefix.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/LogPrefix.java @@ -19,5 +19,5 @@ public @interface LogPrefix { /** * The name to use when logging to console instead of the plugin's name. */ - String prefix(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Main.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Main.java deleted file mode 100644 index 3a21c6c..0000000 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Main.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.bukkit.plugin.java.annotation.plugin; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * DEPRECATED: Use {@link Plugin} instead. - * Marks this class (which must subclass JavaPlugin) as this plugin's main class. - *

- * This class is part of the plugin annotation framework that automates plugin.yml. - *

- * Example: - *

- * {@literal @}Main
- * {@literal @}Name("Test")
- * {@literal @}Version("v1.0")
- * {@literal @}Description("A test plugin.")
- * {@literal @}LoadOn(PluginLoadOrder.POSTWORLD)
- * {@literal @}Author("md_5")
- * {@literal @}Website("spigotmc.org")
- * {@literal @}UsesDatabase
- * {@literal @}DependsOn({"WorldEdit", "Towny"})
- * {@literal @}SoftDependsOn("Vault")
- * {@literal @}LogPrefix("Testing")
- * {@literal @}LoadBefore("Essentials")
- * {@literal @}Commands({
- *     {@literal @}Command(
- *         name = "foo",
- *         name = "Foo command",
- *         aliases = {"foobar", "fubar"},
- *         permission = "test.foo",
- *         permissionMessage = "You do not have permission!",
- *         usage = "/ [test|stop]"
- *     ),
- *     {@literal @}Command("bar")
- * })
- * {@literal @}Permissions({
- *     {@literal @}Perm(
- *         name = "test.foo",
- *         name = "Allows foo command",
- *         defaultValue = PermissionDefault.OP,
- *     ),
- *     {@literal @}Perm(
- *         name = "test.*",
- *         name = "Wildcard perm",
- *         defaultValue = PermissionDefault.OP,
- *         children = {"test.foo"}
- *     )
- * })
- * public class Test extends JavaPlugin { ... }
- * 
- * 
- * @deprecated use {@link Plugin} instead. - */ -@Deprecated -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) -public @interface Main {} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Name.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Name.java deleted file mode 100644 index 9a11752..0000000 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Name.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.bukkit.plugin.java.annotation.plugin; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Part of the plugin annotations framework. - *

- * Represents the name of the plugin. - *

- * If not present in a class annotated with {@link Main} the name defaults to Class.getSimpleName() and will emmit a warning. - * @deprecated use {@link Plugin#name()} instead. - */ -@Deprecated -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) -public @interface Name { - String name(); -} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/UsesDatabase.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/UsesDatabase.java deleted file mode 100644 index aedb9e4..0000000 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/UsesDatabase.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.bukkit.plugin.java.annotation.plugin; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Part of the plugin annotations framework. - *

- * Denotes this plugin as using Bukkit's bundled database system. - * @deprecated Bukkit no longer supports database(s) in the plugin.yml - */ - -@Deprecated -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) -public @interface UsesDatabase {} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Version.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Version.java deleted file mode 100644 index ba8c25e..0000000 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Version.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.bukkit.plugin.java.annotation.plugin; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Part of the plugin annotations framework. - *

- * Represents the version of the plugin. - *

- * If not present in a class annotated with {@link Main} the name defaults to "v0.0" and will emmit a warning. - * @deprecated use {@link Plugin#version()} instead - */ - -@Deprecated -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.TYPE) -public @interface Version { - String version(); - - String DEFAULT_VERSION = "v0.0"; -} diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Website.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Website.java index aa28475..9937138 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/Website.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/Website.java @@ -19,5 +19,5 @@ public @interface Website { /** * The url to the website where a user can download this plugin. */ - String url(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Author.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Author.java index e8172a2..d652259 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Author.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Author.java @@ -21,5 +21,5 @@ public @interface Author { /** * The name of the person who developed this plugin. */ - String name(); + String value(); } diff --git a/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Authors.java b/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Authors.java index 08ba1a3..b58e7cb 100644 --- a/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Authors.java +++ b/src/main/java/org/bukkit/plugin/java/annotation/plugin/author/Authors.java @@ -8,6 +8,9 @@ import java.lang.annotation.Target; /** * Represents a list of author(s) for this plugin. + *
+ * This specific annotation should not be used by people who do not know + * how repeating annotations work. */ @Documented @Retention(RetentionPolicy.SOURCE) -- cgit v1.2.3