mirror of
https://github.com/aikar/commands.git
synced 2026-05-31 06:11:55 +00:00
Support simple meta-annotations
This adds basic support for meta-annotations for commands and
parameters and allows users to create their own custom annotations
combining certain existing annotations and values. For example, a user
can now define their own `CustomAnnotation` which combines a
condition and a flag:
```java
@Conditions("conditionname:confitionconfig")
@Flags("flagconfig=flagvalue")
public @interface CustomAnnotation {}
```
(Necessary `@Retention` and `@Target` ignore for brevity)
And use it just like they would normally.
This works by recursively going through the annotations, instead of
just looking at the root level. The reason most existing annotations
had to be touched is for them to be allowed on other annotations as
most of them were restricted to method or fields.
Currently there's no limit on nesting because if the user wants to
nest it to obscure levels - so be it. We could decide to limit this at
some point to prevent users from shooting themselves in the foot if
that's necessary.
`@Dependency` and was specifically ignored as I don't think it makes
sense for that to be supported in this use case.
Relates to: #89
This commit is contained in:
committed by
Daniel Ennis
parent
eb7064f7c4
commit
667dfec904
@@ -50,7 +50,7 @@ class Annotations <M extends CommandManager> extends AnnotationLookups {
|
||||
}
|
||||
|
||||
String getAnnotationValue(AnnotatedElement object, Class<? extends Annotation> annoClass, int options) {
|
||||
Annotation annotation = object.getAnnotation(annoClass);
|
||||
Annotation annotation = getAnnotationRecursive(object, annoClass);
|
||||
String value = null;
|
||||
|
||||
if (annotation != null) {
|
||||
@@ -103,6 +103,20 @@ class Annotations <M extends CommandManager> extends AnnotationLookups {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Annotation getAnnotationRecursive(AnnotatedElement object, Class<? extends Annotation> annoClass) {
|
||||
if (object.isAnnotationPresent(annoClass)) {
|
||||
return object.getAnnotation(annoClass);
|
||||
} else {
|
||||
for (Annotation otherAnnotation : object.getDeclaredAnnotations()) {
|
||||
final Annotation foundAnnotation = getAnnotationRecursive(otherAnnotation.annotationType(), annoClass);
|
||||
if (foundAnnotation != null) {
|
||||
return foundAnnotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasOption(int options, int option) {
|
||||
return (options & option) == option;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,6 @@ import java.lang.annotation.Target;
|
||||
* Only one instance of this annotation can be used per root command.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface CatchUnknown {
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import java.lang.annotation.Target;
|
||||
* Used on a method, defines a root command alias to that specific command
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface CommandAlias {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.lang.annotation.Target;
|
||||
* @see {@link co.aikar.commands.CommandCompletions}
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface CommandCompletion {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
|
||||
* Permission format will vary based on implementation platform
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface CommandPermission {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import java.lang.annotation.Target;
|
||||
* @see {@link co.aikar.commands.CommandConditions}
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Conditions {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.lang.annotation.Target;
|
||||
* If used on a parameter, sets the value to be used for context resolution
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER})
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Default {
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.lang.annotation.Target;
|
||||
* This is used in the help menus.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Description {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import java.lang.annotation.Target;
|
||||
* If you want to restrict if an issuer can use the command, please use {@link co.aikar.commands.CommandConditions.Condition} instead.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Flags {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import java.lang.annotation.Target;
|
||||
* a method marked with this annotation should also use a {@link co.aikar.commands.CommandHelp} context parameter to show help.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface HelpCommand {
|
||||
/**
|
||||
* The value to forward to the @Subcommand annotation. Lists which subcommands to register to trigger help
|
||||
|
||||
@@ -35,7 +35,7 @@ import java.lang.annotation.Target;
|
||||
* be used for help in discovering the correct command, then you can add it as a tag.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface HelpSearchTags {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ import java.lang.annotation.Target;
|
||||
* you will need to allow for a nullable value.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Optional {
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@ import java.lang.annotation.Target;
|
||||
* This runs before any other command method each time it is invoked.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface PreCommand {}
|
||||
|
||||
@@ -32,6 +32,6 @@ import java.lang.annotation.Target;
|
||||
* Marks a command to not be included in stuff like tab completion and help pages
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Private {
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@ import java.lang.annotation.Target;
|
||||
* Don't join remaining arguments. Used on String parameters, which normally would combine the remaining arguments
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Single {}
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
|
||||
* For array based parameters, defines the regex pattern to split on
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Split {
|
||||
String value() default ",";
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import java.lang.annotation.Target;
|
||||
* Defines the part after root command like so: "/rootcommand {@link #value()}".
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Subcommand {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.lang.annotation.Target;
|
||||
* Use {@link Description} together with the help menu for that purpose.
|
||||
**/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER})
|
||||
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Syntax {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
|
||||
* You may also use {@link CommandCompletion} handler codes here to feed dynamic values and avoid repetition.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface Values {
|
||||
String value();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Daniel Ennis (Aikar) - MIT License
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package co.aikar.commands;
|
||||
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class AnnotationTest {
|
||||
|
||||
private final CommandManager<?, ?, ?, ?, ?, ?> manager = Mockito.mock(CommandManager.class);
|
||||
private Annotations<?> annotations = new Annotations<>(this.manager);
|
||||
|
||||
@Test
|
||||
public void testAnnotationsSimple() {
|
||||
final String aliasAnnotation = this.annotations.getAnnotationValue(TestClass.class, CommandAlias.class, Annotations.NOTHING);
|
||||
assertEquals("msg", aliasAnnotation);
|
||||
final String permissionAnnotation = this.annotations.getAnnotationValue(TestClass.class, CommandPermission.class, Annotations.NOTHING);
|
||||
assertEquals("test.test", permissionAnnotation);
|
||||
final String descriptionAnnotation = this.annotations.getAnnotationValue(TestClass.class, Description.class, Annotations.NOTHING);
|
||||
assertEquals("Just a test command", descriptionAnnotation);
|
||||
|
||||
|
||||
final String aliasAnnotationRoot = this.annotations.getAnnotationValue(TestWithRootAnnotation.class, CommandAlias.class, Annotations.NOTHING);
|
||||
assertEquals("test", aliasAnnotationRoot);
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@CommandAlias("msg")
|
||||
@CommandPermission("test.test")
|
||||
@Description("Just a test command")
|
||||
private @interface TestAnnotation {
|
||||
}
|
||||
|
||||
@TestAnnotation
|
||||
private static final class TestClass {
|
||||
}
|
||||
|
||||
@CommandAlias("test")
|
||||
private static final class TestWithRootAnnotation {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user