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.
This commit is contained in:
Senmori 2018-07-13 00:28:40 -04:00 committed by md_5
parent e677cffa5d
commit 3d4b0c8a11
24 changed files with 405 additions and 311 deletions

View file

@ -6,66 +6,70 @@ See the [wiki](https://www.spigotmc.org/wiki/plugin-yml/) for more information.
## Example Usage ## 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") @Plugin(name = "TestPlugin", version = "1.0")
@Description(desc = "A test plugin") @Description("A test plugin")
@LoadOn(loadOn = PluginLoadOrder.POSTWORLD) // defaults to PluginLoadOrder.POSTWORLD if not preset @LoadOrder(PluginLoadOrder.STARTUP)
@Author(name = "md_5") @Author("md_5")
@Website(url = "spigotmc.org") @Website("www.spigotmc.org")
@LogPrefix(prefix = "Testing") @LogPrefix("Testing")
@Dependency(plugin = "WorldEdit") @Dependency("WorldEdit")
@Dependency(plugin = "Towny") @Dependency("Towny")
@LoadBefore(plugin = "Essentials") @LoadBefore("Towny")
@SoftDependency(plugin = "FAWE") @SoftDependency("EssentialsX")
@Command(name = "foo", desc = "Foo command", aliases = {"foobar", "fubar"}, permission = "test.foo", permissionMessage = "You do not have permission!", usage = "/<command> [test|stop]") @Command(name = "foo", desc = "Foo command", aliases = {"foobar", "fubar"}, permission = "test.foo", permissionMessage = "You do not have permission!", usage = "/<command> [test|stop]")
@Permission(name = "test.foo", desc = "Allows foo command", defaultValue = PermissionDefault.OP) @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")}) @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: 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
main: org.spigotmc.annotationtest.Test # Auto-generated plugin.yml, generated at 2018/07/13 00:16:24 by org.bukkit.plugin.java.annotation.PluginAnnotationProcessor
main: org.spigotmc.spigot.TestPlugin
name: TestPlugin name: TestPlugin
version: '1.0' version: '1.0'
description: A test plugin description: A test plugin
load: POSTWORLD load: STARTUP
author: md_5 author: md_5
website: spigotmc.org website: www.spigotmc.org
prefix: Testing prefix: Testing
depend: depend:
- WorldEdit - WorldEdit
- Towny - Towny
softdepend: softdepend:
- FAWE - EssentialsX
loadbefore: loadbefore:
- Essentials - Towny
commands: commands:
foo: TestCommand:
description: Foo command aliases: testext2
aliases: permission: test.testext
- foobar permission-message: Oopsy!
- fubar usage: /testext test test
permission: test.foo
permission-message: You do not have permission!
usage: /<command> [test|stop]
permissions: permissions:
test.foo: test.foo:
description: Allows foo command description: Allows foo command
default: op
test.*: test.*:
description: Wildcard permission description: Wildcard permission
default: op
children: children:
test.foo: true 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.

View file

@ -10,7 +10,7 @@
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>plugin-annotations</artifactId> <artifactId>plugin-annotations</artifactId>
<version>1.1.0-SNAPSHOT</version> <version>1.2.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Plugin Annotations</name> <name>Plugin Annotations</name>

View file

@ -2,25 +2,28 @@ package org.bukkit.plugin.java.annotation;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; 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.JavaPlugin;
import org.bukkit.plugin.java.annotation.command.Command; 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.Dependency;
import org.bukkit.plugin.java.annotation.dependency.LoadBefore; import org.bukkit.plugin.java.annotation.dependency.LoadBefore;
import org.bukkit.plugin.java.annotation.dependency.SoftDependency; import org.bukkit.plugin.java.annotation.dependency.SoftDependency;
import org.bukkit.plugin.java.annotation.permission.ChildPermission; import org.bukkit.plugin.java.annotation.permission.ChildPermission;
import org.bukkit.plugin.java.annotation.permission.Permission; 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.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.LogPrefix;
import org.bukkit.plugin.java.annotation.plugin.Plugin; 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.Website;
import org.bukkit.plugin.java.annotation.plugin.author.Author; import org.bukkit.plugin.java.annotation.plugin.author.Author;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.nodes.Tag;
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;
@ -31,231 +34,358 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement; import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.swing.text.DateFormatter;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import javax.tools.FileObject; import javax.tools.FileObject;
import javax.tools.StandardLocation; import javax.tools.StandardLocation;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Writer; import java.io.Writer;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@SupportedAnnotationTypes("org.bukkit.plugin.java.annotation.*") @SupportedAnnotationTypes( "org.bukkit.plugin.java.annotation.*" )
@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedSourceVersion( SourceVersion.RELEASE_8 )
public class PluginAnnotationProcessor extends AbstractProcessor { public class PluginAnnotationProcessor extends AbstractProcessor {
private boolean hasMainBeenFound = false; 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 @Override
public boolean process(Set<? extends TypeElement> annots, RoundEnvironment rEnv) { public boolean process(Set<? extends TypeElement> annots, RoundEnvironment rEnv) {
Element main = null; Element mainPluginElement = null;
hasMainBeenFound = false; hasMainBeenFound = false;
Set<? extends Element> elements = rEnv.getElementsAnnotatedWith(Plugin.class); Set<? extends Element> elements = rEnv.getElementsAnnotatedWith( Plugin.class );
if(elements.size() > 1) { if ( elements.size() > 1 ) {
raiseError("Found more than one plugin main class"); raiseError( "Found more than one plugin main class" );
return false; return false;
} }
if(elements.isEmpty()) { if ( elements.isEmpty() ) {
return false; return false;
} }
if(hasMainBeenFound){ if ( hasMainBeenFound ) {
raiseError("The plugin class has already been located, aborting!"); raiseError( "The plugin class has already been located, aborting!" );
return false; return false;
} }
main = elements.iterator().next(); mainPluginElement = elements.iterator().next();
hasMainBeenFound = true; hasMainBeenFound = true;
TypeElement mainType; TypeElement mainPluginType;
if(main instanceof TypeElement){ if ( mainPluginElement instanceof TypeElement ) {
mainType = (TypeElement) main; mainPluginType = ( TypeElement ) mainPluginElement;
} else { } else {
raiseError("Element annotated with @Main is not a type!", main); raiseError( "Element annotated with @Plugin is not a type!", mainPluginElement );
return false; return false;
} }
if(!(mainType.getEnclosingElement() instanceof PackageElement) && !mainType.getModifiers().contains(Modifier.STATIC)){ if ( !( mainPluginType.getEnclosingElement() instanceof PackageElement ) && !mainPluginType.getModifiers().contains( Modifier.STATIC ) ) {
raiseError("Element annotated with @Main is not top-level or static nested!", mainType); raiseError( "Element annotated with @Plugin is not top-level or static nested!", mainPluginType );
return false; return false;
} }
if(!processingEnv.getTypeUtils().isSubtype(mainType.asType(), fromClass(JavaPlugin.class))){ if ( !processingEnv.getTypeUtils().isSubtype( mainPluginType.asType(), fromClass( JavaPlugin.class ) ) ) {
raiseError("Class annotated with @Main is not an subclass of JavaPlugin!", mainType); raiseError( "Class annotated with @Plugin is not an subclass of JavaPlugin!", mainPluginType );
} }
Map<String, Object> yml = Maps.newLinkedHashMap(); // linked so we can maintain the same output into file for sanity Map<String, Object> yml = Maps.newLinkedHashMap(); // linked so we can maintain the same output into file for sanity
// populate mainName // populate mainName
final String mainName = mainType.getQualifiedName().toString(); final String mainName = mainPluginType.getQualifiedName().toString();
yml.put("main", mainName); // always override this so we make sure the main class name is correct yml.put( "main", mainName ); // always override this so we make sure the main class name is correct
// populate plugin name // 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 // 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 // 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 // populate plugin load order
processAndPut(yml, "load", mainType, null, LoadOn.class, String.class,"loadOn"); processAndPut( yml, "load", mainPluginType, null, LoadOrder.class, String.class );
// authors // authors
Author[] authors = mainType.getAnnotationsByType(Author.class); Author[] authors = mainPluginType.getAnnotationsByType( Author.class );
List<String> authorMap = Lists.newArrayList(); List<String> authorMap = Lists.newArrayList();
for(Author auth : authors) { for ( Author auth : authors ) {
authorMap.add(auth.name()); authorMap.add( auth.value() );
} }
if(authorMap.size() > 1) { if ( authorMap.size() > 1 ) {
yml.put("authors", authorMap); yml.put( "authors", authorMap );
} else if(authorMap.size() == 1) { } else if ( authorMap.size() == 1 ) {
yml.put("author", authorMap.iterator().next()); yml.put( "author", authorMap.iterator().next() );
} }
// website // website
processAndPut(yml, "website", mainType, null, Website.class, String.class, "url"); processAndPut( yml, "website", mainPluginType, null, Website.class, String.class );
// prefix // prefix
processAndPut(yml, "prefix", mainType, null, LogPrefix.class, String.class, "prefix"); processAndPut( yml, "prefix", mainPluginType, null, LogPrefix.class, String.class );
// dependencies // dependencies
Dependency[] dependencies = mainType.getAnnotationsByType(Dependency.class); Dependency[] dependencies = mainPluginType.getAnnotationsByType( Dependency.class );
List<String> hardDependencies = Lists.newArrayList(); List<String> hardDependencies = Lists.newArrayList();
for(Dependency dep : dependencies) { for ( Dependency dep : dependencies ) {
hardDependencies.add(dep.plugin()); hardDependencies.add( dep.value() );
} }
if(!hardDependencies.isEmpty()) yml.putIfAbsent("depend", hardDependencies); if ( !hardDependencies.isEmpty() ) yml.put( "depend", hardDependencies );
// soft-dependencies // soft-dependencies
SoftDependency[] softDependencies = mainType.getAnnotationsByType(SoftDependency.class); SoftDependency[] softDependencies = mainPluginType.getAnnotationsByType( SoftDependency.class );
String[] softDepArr = new String[softDependencies.length]; String[] softDepArr = new String[ softDependencies.length ];
for(int i = 0; i < softDependencies.length; i++) { for ( int i = 0; i < softDependencies.length; i++ ) {
softDepArr[i] = softDependencies[i].plugin(); softDepArr[ i ] = softDependencies[ i ].value();
} }
if(softDepArr.length > 0) yml.putIfAbsent("softdepend", softDepArr); if ( softDepArr.length > 0 ) yml.put( "softdepend", softDepArr );
// load-before // load-before
LoadBefore[] loadBefore = mainType.getAnnotationsByType(LoadBefore.class); LoadBefore[] loadBefore = mainPluginType.getAnnotationsByType( LoadBefore.class );
String[] loadBeforeArr = new String[loadBefore.length]; String[] loadBeforeArr = new String[ loadBefore.length ];
for(int i = 0; i < loadBefore.length; i++) { for ( int i = 0; i < loadBefore.length; i++ ) {
loadBeforeArr[i] = loadBefore[i].plugin(); loadBeforeArr[ i ] = loadBefore[ i ].value();
} }
if(loadBeforeArr.length > 0) yml.putIfAbsent("loadbefore", loadBeforeArr); if ( loadBeforeArr.length > 0 ) yml.put( "loadbefore", loadBeforeArr );
// commands // commands
Command[] commands = mainType.getAnnotationsByType(Command.class); // Begin processing external command annotations
Map<String, Object> commandMap = Maps.newLinkedHashMap(); Map<String, Map<String, Object>> commandMap = Maps.newLinkedHashMap();
for(Command command : commands) { boolean result = processExternalCommands( rEnv.getElementsAnnotatedWith( Command.class ), mainPluginType, commandMap );
Map<String, Object> desc = Maps.newLinkedHashMap(); if ( !result ) {
String name = command.name(); // #processExternalCommand already raised the errors
if(!command.desc().isEmpty()) desc.put("description", command.desc()); return false;
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);
} }
if(!commandMap.isEmpty()) yml.putIfAbsent("commands", commandMap);
// permissions Commands commands = mainPluginType.getAnnotation( Commands.class );
Permission[] permissions = mainType.getAnnotationsByType(Permission.class);
Map<String, Object> permMap = Maps.newLinkedHashMap(); // Check main class for any command annotations
for(Permission perm : permissions) { if ( commands != null ) {
Map<String, Object> desc = Maps.newLinkedHashMap(); Map<String, Map<String, Object>> merged = Maps.newLinkedHashMap();
String name = perm.name(); merged.putAll( commandMap );
if(!perm.desc().isEmpty()) desc.put("description", perm.desc()); merged.putAll( this.processCommands( commands ) );
desc.put("default", perm.defaultValue().toString()); commandMap = merged;
Map<String, Object> children = Maps.newLinkedHashMap(); }
for(ChildPermission child : perm.children()) {
children.put(child.name(), child.inherit()); yml.put( "commands", commandMap );
// Permissions
Map<String, Map<String, Object>> permissionMetadata = Maps.newLinkedHashMap();
Set<? extends Element> 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 ) );
}
}
}
Permissions permissions = mainPluginType.getAnnotation( Permissions.class );
if ( permissions != null ) {
Map<String, Map<String, Object>> 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() );
} }
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);
} }
Yaml yaml = new Yaml();
try { try {
FileObject file = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "plugin.yml"); Yaml yaml = new Yaml();
try(Writer w = file.openWriter()) { FileObject file = this.processingEnv.getFiler().createResource( StandardLocation.CLASS_OUTPUT, "", "plugin.yml" );
w.append("# Auto-generated plugin.yml, generated at ") try ( Writer w = file.openWriter() ) {
.append(LocalDateTime.now().format(dFormat)) w.append( "# Auto-generated plugin.yml, generated at " )
.append(" by ") .append( LocalDateTime.now().format( dFormat ) )
.append(this.getClass().getName()) .append( " by " )
.append("\n\n"); .append( this.getClass().getName() )
.append( "\n\n" );
// have to format the yaml explicitly because otherwise it dumps child nodes as maps within braces. // 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); String raw = yaml.dumpAs( yml, Tag.MAP, DumperOptions.FlowStyle.BLOCK );
w.write(raw); w.write( raw );
w.flush(); w.flush();
w.close(); w.close();
} }
// try with resources will close the Writer since it implements Closeable // try with resources will close the Writer since it implements Closeable
} catch (IOException e) { } catch ( IOException e ) {
throw new RuntimeException(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; return true;
} }
private void raiseError(String message) { 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) { 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) { private TypeMirror fromClass(Class<?> clazz) {
return processingEnv.getElementUtils().getTypeElement(clazz.getName()).asType(); return processingEnv.getElementUtils().getTypeElement( clazz.getName() ).asType();
} }
private <A extends Annotation, R> R processAndPut( private <A extends Annotation, R> R processAndPut(
Map<String, Object> map, String name, Element el, R defaultVal, Class<A> annotationType, Class<R> returnType) { Map<String, Object> map, String name, Element el, R defaultVal, Class<A> annotationType, Class<R> returnType) {
return processAndPut(map, name, el, defaultVal, annotationType, returnType, "value"); return processAndPut( map, name, el, defaultVal, annotationType, returnType, "value" );
} }
private <A extends Annotation, R> R processAndPut( private <A extends Annotation, R> R processAndPut(
Map<String, Object> map, String name, Element el, R defaultVal, Class<A> annotationType, Class<R> returnType, String methodName) { Map<String, Object> map, String name, Element el, R defaultVal, Class<A> annotationType, Class<R> returnType, String methodName) {
R result = process(el, defaultVal, annotationType, returnType, methodName); R result = process( el, defaultVal, annotationType, returnType, methodName );
if(result != null) if ( result != null )
map.putIfAbsent(name, result); map.put( name, result );
return result; return result;
} }
private <A extends Annotation, R> R process(Element el, R defaultVal, Class<A> annotationType, Class<R> returnType, String methodName) { private <A extends Annotation, R> R process(Element el, R defaultVal, Class<A> annotationType, Class<R> returnType, String methodName) {
R result; R result;
A ann = el.getAnnotation(annotationType); A ann = el.getAnnotation( annotationType );
if(ann == null) result = defaultVal; if ( ann == null ) result = defaultVal;
else { else {
try { try {
Method value = annotationType.getMethod(methodName); Method value = annotationType.getMethod( methodName );
Object res = value.invoke(ann); Object res = value.invoke( ann );
result = (R) (returnType == String.class ? res.toString() : returnType.cast(res)); result = ( R ) ( returnType == String.class ? res.toString() : returnType.cast( res ) );
} catch (Exception e) { } catch ( Exception e ) {
throw new RuntimeException(e); // shouldn't happen in theory (blame Choco if it does) throw new RuntimeException( e ); // shouldn't happen in theory (blame Choco if it does)
} }
} }
return result; return result;
} }
private boolean processExternalCommands(Set<? extends Element> commandExecutors, TypeElement mainPluginType, Map<String, Map<String, Object>> 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<String, Map<String, Object>> processCommands(Commands commands) {
Map<String, Map<String, Object>> 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<String, Object> processCommand(Command commandAnnotation) {
Map<String, Object> 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<String, Object> processPermission(Permission permissionAnnotation) {
Map<String, Object> 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<String, Boolean> 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<String, Map<String, Object>> processPermissions(Permissions permissions) {
Map<String, Map<String, Object>> permissionList = Maps.newLinkedHashMap();
for ( Permission permission : permissions.value() ) {
permissionList.put( permission.name(), this.processPermission( permission ) );
}
return permissionList;
}
} }

View file

@ -7,9 +7,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Part of the plugin annotations framework. * Part of the plugin annotations framework.
* <p> * <p>
* Represents a list of this plugin's registered command(s). * Represents a list of this plugin's registered command(s).
* <br>
* This specific annotation should not be used by people who do not know
* how repeating annotations work.
*/ */
@Documented @Documented
@Target(ElementType.TYPE) @Target(ElementType.TYPE)

View file

@ -8,7 +8,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Defines a plugin dependency * Defines a plugin dependency.
* <br>
* The plugin's <b>name</b> attribute is required in order to load the dependency.<br>
* If any plugin listed is not found the plugin will fail to load. <br>
* 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 @Documented
@Target(ElementType.TYPE) @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. * A plugin that is required to be present in order for this plugin to load.
*/ */
String plugin(); String value();
} }

View file

@ -7,9 +7,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Part of the plugin annotations framework. * Part of the plugin annotations framework.
* <p> * <p>
* Represents the plugins a plugin depends on in order to be loaded * Represents the plugins a plugin depends on in order to be loaded
* <br>
* This specific annotation should not be used by people who do not know
* how repeating annotations work.
*/ */
@Documented @Documented
@Target(ElementType.TYPE) @Target(ElementType.TYPE)

View file

@ -11,6 +11,9 @@ import java.lang.annotation.Target;
* Part of the plugin annotations framework. * Part of the plugin annotations framework.
* <p> * <p>
* Represents the plugin this plugin should be loaded before * Represents the plugin this plugin should be loaded before
* <br>
* The plugin's <b>name</b> attribute is required in order to specify the target. <br>
* The plugin listed will be treated as a {@link SoftDependency}. <br>
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -20,5 +23,5 @@ public @interface LoadBefore {
/** /**
* A plugin that should be loaded after your plugin * A plugin that should be loaded after your plugin
*/ */
String plugin(); String value();
} }

View file

@ -8,6 +8,9 @@ import java.lang.annotation.Target;
/** /**
* Defines a list of plugin to load after this plugin * Defines a list of plugin to load after this plugin
* <br>
* This specific annotation should not be used by people who do not know
* how repeating annotations work.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)

View file

@ -11,6 +11,9 @@ import java.lang.annotation.Target;
/** /**
* Represents a soft (optional) dependency for this plugin. * Represents a soft (optional) dependency for this plugin.
* If this dependency is not present, the plugin will still load. * If this dependency is not present, the plugin will still load.
* <br>
* The <b>name</b> attribute of the plugin is required in order to specify the target. <br>
* Circular soft-dependencies are loaded arbitrarily.
*/ */
@Documented @Documented
@ -21,5 +24,5 @@ public @interface SoftDependency {
/** /**
* A plugin that is required in order for this plugin to have full functionality. * A plugin that is required in order for this plugin to have full functionality.
*/ */
String plugin(); String value();
} }

View file

@ -7,10 +7,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Part of the plugin annotations framework. * Part of the plugin annotations framework.
* <p> * <p>
* Represents the plugins this plugin should try to load before this plugin will attempt to load. * 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. * A plugin will still load if a soft dependency is not present.
* <br>
* This specific annotation should not be used by people who do not know
* how repeating annotations work.
*/ */
@Documented @Documented

View file

@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Defines a child permission for {@link Permission} * Defines a child permission for a {@link Permission}
*/ */
@Documented @Documented
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@ -15,7 +15,7 @@ import java.lang.annotation.Target;
public @interface ChildPermission { public @interface ChildPermission {
/** /**
* If true, this child node will inherit the parent {@link Permission}'s permission. * 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; boolean inherit() default true;

View file

@ -19,22 +19,22 @@ import java.lang.annotation.Target;
@Repeatable(Permissions.class) @Repeatable(Permissions.class)
public @interface Permission { public @interface Permission {
/** /**
* This perm's name. * This permission's name.
*/ */
String name(); String name();
/** /**
* This perm's description. * This permission's description.
*/ */
String desc() default ""; String desc() default "";
/** /**
* This perm's default {@link PermissionDefault} * This permission's default {@link PermissionDefault}
*/ */
PermissionDefault defaultValue() default PermissionDefault.OP; PermissionDefault defaultValue() default PermissionDefault.OP;
/** /**
* This permission's child nodes ({@link ChildPermission}) * This permission's child nodes ( {@link ChildPermission} )
*/ */
ChildPermission[] children() default {}; ChildPermission[] children() default {};
} }

View file

@ -7,9 +7,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Part of the plugin annotations framework. * Part of the plugin annotations framework.
* <p> * <p>
* Represents a list of this plugin's registered name. * Represents a list of this plugin's registered name.
* <br>
* This specific annotation should not be used by people who do not know
* how repeating annotations work.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)

View file

@ -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.
* <br>
* Defaults to {@link ApiVersion.Target#DEFAULT}.
* <br>
* 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<Target> 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 );
}
}
}

View file

@ -19,6 +19,6 @@ public @interface Description {
/** /**
* A human friendly description of the functionality this plugin provides. * A human friendly description of the functionality this plugin provides.
*/ */
String desc(); String value();
} }

View file

@ -17,11 +17,11 @@ import java.lang.annotation.Target;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
public @interface LoadOn { public @interface LoadOrder {
/** /**
* Explicitly state when the plugin should be loaded. * Explicitly state when the plugin should be loaded.
* If not defined, will default to {@link PluginLoadOrder#POSTWORLD}. * If not defined, will default to {@link PluginLoadOrder#POSTWORLD}.
* See {@link PluginLoadOrder} * See {@link PluginLoadOrder}
*/ */
PluginLoadOrder loadOn(); PluginLoadOrder value() default PluginLoadOrder.POSTWORLD;
} }

View file

@ -19,5 +19,5 @@ public @interface LogPrefix {
/** /**
* The name to use when logging to console instead of the plugin's name. * The name to use when logging to console instead of the plugin's name.
*/ */
String prefix(); String value();
} }

View file

@ -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 <i>must</i> subclass JavaPlugin) as this plugin's main class.
* <p>
* This class is part of the plugin annotation framework that automates plugin.yml.
* <p>
* Example:
* <pre>
* <code>{@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 = "/<command> [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 { ... }
* </code>
* </pre>
* @deprecated use {@link Plugin} instead.
*/
@Deprecated
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Main {}

View file

@ -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.
* <p>
* Represents the name of the plugin.
* <p>
* 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();
}

View file

@ -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.
* <p>
* 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 {}

View file

@ -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.
* <p>
* Represents the version of the plugin.
* <p>
* 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";
}

View file

@ -19,5 +19,5 @@ public @interface Website {
/** /**
* The url to the website where a user can download this plugin. * The url to the website where a user can download this plugin.
*/ */
String url(); String value();
} }

View file

@ -21,5 +21,5 @@ public @interface Author {
/** /**
* The name of the person who developed this plugin. * The name of the person who developed this plugin.
*/ */
String name(); String value();
} }

View file

@ -8,6 +8,9 @@ import java.lang.annotation.Target;
/** /**
* Represents a list of author(s) for this plugin. * Represents a list of author(s) for this plugin.
* <br>
* This specific annotation should not be used by people who do not know
* how repeating annotations work.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)