Plan API 5.0-R0.1: DATA_EXTENSION_GROUPS Capability

This commit is contained in:
Rsl1122 2019-08-25 20:44:21 +03:00
parent 65ddbb2ef0
commit a7978c8bda
12 changed files with 157 additions and 20 deletions

View File

@ -2,7 +2,7 @@ plugins {
id "com.jfrog.bintray" version "1.8.4"
}
ext.apiVersion = '0.0.6'
ext.apiVersion = '5.0-R0.1'
bintray {
user = System.getenv('BINTRAY_USER')
@ -15,7 +15,7 @@ bintray {
issueTrackerUrl = 'https://github.com/plan-player-analytics/Plan/issues'
version {
name = "$apiVersion"
desc = "Plan APIv5 version $apiVersion"
desc = "Plan API version $apiVersion"
}
publications = ['BintrayPublication']
}

View File

@ -41,6 +41,10 @@ enum Capability {
* DataExtension API table package, TableProvider, Table and Table.Factory
*/
DATA_EXTENSION_TABLES,
/**
* DataExtension API groups, GroupProvider and Group parameter methods
*/
DATA_EXTENSION_GROUPS,
/**
* DataExtension API addition, allows throwing {@link com.djrapitops.plan.extension.NotReadyException} inside a Provider method when your API is not ready for a method call.
*/

View File

@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a boolean value about a Player.
* Method annotation to provide a boolean value.
* <p>
* Usage: {@code @BooleanProvider boolean method(UUID playerUUID)}
* <p>

View File

@ -28,6 +28,9 @@ import java.lang.annotation.Target;
* If {@link com.djrapitops.plan.extension.annotation.BooleanProvider} for the condition is not specified the
* method tagged with this annotation will not be called, (Condition is assumed false).
*
* Please note that Conditional does not cross method parameter boundaries - (Conditional on a player method does not
* take into account conditionals of server).
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)

View File

@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a double value about a Player.
* Method annotation to provide a double value.
* <p>
* Usage: {@code @DoubleProvider double method(UUID playerUUID)}
*

View File

@ -0,0 +1,64 @@
package com.djrapitops.plan.extension.annotation;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide {@code Group[]} array about a Player.
* <p>
* This method annotation only works when used with {@code UUID} or {@code String} as a method parameter (for Players).
* <p>
* For example:
* {@code @GroupProvider public Group[] getJobs(UUID playerUUID) {}}
* <p>
* Group data is parsed as Table for /server & /network page and similar to {@link StringProvider} for /player page.
* <p>
* Requires Capability {@code DATA_EXTENSION_GROUPS}
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GroupProvider {
/**
* Text displayed before the value, limited to 50 characters.
* <p>
* Should inform the user what the group names represent, for example
* "Town" or "Job"
*
* @return String of max 50 characters, remainder will be clipped.
*/
String text() default "Group";
/**
* Determine the color of the table header for this group.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color groupColor() default Color.NONE;
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "circle";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
}

View File

@ -26,7 +26,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a long (64bit number) value about a Player.
* Method annotation to provide a long (64bit number) value.
* <p>
* If you want to return int values, use this provider with a long as
* return type of the method.

View File

@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a double (Percentage) about a Player.
* Method annotation to provide a double (Percentage).
* <p>
* Usage: {@code @PercentageProvider double method(UUID playerUUID)}
* <p>

View File

@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a String value about a Player.
* Method annotation to provide a String value.
* <p>
* Usage: {@code @StringProvider String method(UUID playerUUID)}
* <p>

View File

@ -98,8 +98,10 @@ public final class ExtensionExtractor {
for (Method method : getMethods()) {
int modifiers = method.getModifiers();
if (!Modifier.isPublic(modifiers)
|| Modifier.isStatic(modifiers)) {
if (Modifier.isPrivate(modifiers)
|| Modifier.isProtected(modifiers)
|| Modifier.isStatic(modifiers)
|| Modifier.isNative(modifiers)) {
continue;
}
@ -113,17 +115,30 @@ public final class ExtensionExtractor {
MethodAnnotations.get(method, Tab.class).ifPresent(annotation -> methodAnnotations.put(method, Tab.class, annotation));
MethodAnnotations.get(method, TableProvider.class).ifPresent(annotation -> methodAnnotations.put(method, TableProvider.class, annotation));
MethodAnnotations.get(method, GroupProvider.class).ifPresent(annotation -> methodAnnotations.put(method, GroupProvider.class, annotation));
}
if (methodAnnotations.isEmpty()) {
throw new IllegalArgumentException(extensionName + " class had no methods annotated with a Provider annotation");
}
try {
methodAnnotations.makeMethodsAccessible();
} catch (SecurityException failedToMakeAccessible) {
throw new IllegalArgumentException(extensionName + " has non accessible Provider method that could not be made accessible: " +
failedToMakeAccessible.getMessage(), failedToMakeAccessible);
}
}
private <T> void validateReturnType(Method method, Class<T> expectedType) {
Class<?> returnType = method.getReturnType();
if (!expectedType.isAssignableFrom(returnType)) {
throw new IllegalArgumentException(extensionName + "." + method.getName() + " has invalid return type. was: " + returnType.getName() + ", expected: " + expectedType.getName());
String expectedName = expectedType.getName();
throw new IllegalArgumentException(extensionName + "." + method.getName() +
" has invalid return type. was: " +
returnType.getName() +
", expected: " +
(expectedName.startsWith("[L") ? expectedName + " (an array)" : expectedName));
}
}
@ -178,6 +193,7 @@ public final class ExtensionExtractor {
validatePercentageProviderAnnotations();
validateStringProviderAnnotations();
validateTableProviderAnnotations();
validateGroupProviderAnnotations();
}
private void validateBooleanProviderAnnotations() {
@ -215,9 +231,9 @@ public final class ExtensionExtractor {
}
private void validateDoubleProviderAnnotations() {
for (Map.Entry<Method, DoubleProvider> numberProvider : methodAnnotations.getMethodAnnotations(DoubleProvider.class).entrySet()) {
Method method = numberProvider.getKey();
DoubleProvider annotation = numberProvider.getValue();
for (Map.Entry<Method, DoubleProvider> doubleProvider : methodAnnotations.getMethodAnnotations(DoubleProvider.class).entrySet()) {
Method method = doubleProvider.getKey();
DoubleProvider annotation = doubleProvider.getValue();
validateReturnType(method, double.class);
validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
@ -227,9 +243,9 @@ public final class ExtensionExtractor {
}
private void validatePercentageProviderAnnotations() {
for (Map.Entry<Method, PercentageProvider> numberProvider : methodAnnotations.getMethodAnnotations(PercentageProvider.class).entrySet()) {
Method method = numberProvider.getKey();
PercentageProvider annotation = numberProvider.getValue();
for (Map.Entry<Method, PercentageProvider> percentageProvider : methodAnnotations.getMethodAnnotations(PercentageProvider.class).entrySet()) {
Method method = percentageProvider.getKey();
PercentageProvider annotation = percentageProvider.getValue();
validateReturnType(method, double.class);
validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
@ -239,9 +255,9 @@ public final class ExtensionExtractor {
}
private void validateStringProviderAnnotations() {
for (Map.Entry<Method, StringProvider> numberProvider : methodAnnotations.getMethodAnnotations(StringProvider.class).entrySet()) {
Method method = numberProvider.getKey();
StringProvider annotation = numberProvider.getValue();
for (Map.Entry<Method, StringProvider> stringProvider : methodAnnotations.getMethodAnnotations(StringProvider.class).entrySet()) {
Method method = stringProvider.getKey();
StringProvider annotation = stringProvider.getValue();
validateReturnType(method, String.class);
validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
@ -257,6 +273,17 @@ public final class ExtensionExtractor {
}
}
private void validateGroupProviderAnnotations() {
for (Map.Entry<Method, GroupProvider> groupProvider : methodAnnotations.getMethodAnnotations(GroupProvider.class).entrySet()) {
Method method = groupProvider.getKey();
GroupProvider annotation = groupProvider.getValue();
validateReturnType(method, Group[].class);
validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
validateMethodArguments(method, true, UUID.class, String.class);
}
}
private void validateConditionals() {
Collection<Conditional> conditionals = methodAnnotations.getAnnotations(Conditional.class);
Collection<BooleanProvider> conditionProviders = methodAnnotations.getAnnotations(BooleanProvider.class);
@ -280,7 +307,8 @@ public final class ExtensionExtractor {
for (Method conditionalMethod : conditionalMethods) {
if (!MethodAnnotations.hasAnyOf(conditionalMethod,
BooleanProvider.class, DoubleProvider.class, NumberProvider.class,
PercentageProvider.class, StringProvider.class
PercentageProvider.class, StringProvider.class, TableProvider.class,
GroupProvider.class
)) {
throw new IllegalArgumentException(extensionName + "." + conditionalMethod.getName() + " did not have any associated Provider for Conditional.");
}

View File

@ -73,4 +73,13 @@ public class MethodAnnotations {
public String toString() {
return "MethodAnnotations{" + byAnnotationType + '}';
}
void makeMethodsAccessible() {
byAnnotationType.values().stream()
.map(Map::keySet)
.flatMap(Collection::stream)
.distinct()
.filter(method -> !method.isAccessible())
.forEach(method -> method.setAccessible(true));
}
}

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.extension.extractor;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.Group;
import com.djrapitops.plan.extension.annotation.*;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
@ -180,6 +181,34 @@ class ExtensionExtractorTest {
assertEquals("Extension.method has invalid return type. was: java.lang.Double, expected: com.djrapitops.plan.extension.table.Table", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void groupProviderMustGroupArray() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@GroupProvider
public Double method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.Double, expected: [Lcom.djrapitops.plan.extension.Group; (an array)", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void groupProviderMustGroupArray2() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@GroupProvider
public Group method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: com.djrapitops.plan.extension.Group, expected: [Lcom.djrapitops.plan.extension.Group; (an array)", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void booleanProviderCanNotSupplyItsOwnConditional() {
@PluginInfo(name = "Extension")