From 113d46669b08396b7c30a1ff0b853fba798cd840 Mon Sep 17 00:00:00 2001
From: Risto Lahtela <24460436+AuroraLS3@users.noreply.github.com>
Date: Wed, 7 Apr 2021 19:09:38 +0300
Subject: [PATCH] DataExtension Builder API (#1833)
* Rewrote annotated extension method extraction
* Enclose annotation based methods into a single ExtensionDataBuilder
All of the reflection related to extension annotation was difficult,
so now it's all dealt with in one place and it's abstracted away with the
upcoming builder based API.
Some odd additions had to be made to the interface, but they are mostly harmless,
as the annotations are used as method parameters where necessary. This way the
users of the API are unable to give weird values.
It was possible to reuse MethodWrapper and Parameters classes for calling the methods.
* Refactored server value storage to use builder data
* Refactored player value storage to use builder data
* Refactored table gathering to use builders
* Handle extension errors properly
* Javadocs for DataExtension builder API
* Removed DataProviders based implementation
- Renamed ProviderValueGatherer to DataValueGatherer
---
.../plan/capability/Capability.java | 4 +
.../plan/extension/DataExtension.java | 32 ++
.../plan/extension/ExtensionService.java | 11 +
.../extension/annotation/Conditional.java | 1 -
.../annotation/DataBuilderProvider.java | 39 ++
.../plan/extension/builder/DataValue.java | 40 ++
.../builder/ExtensionDataBuilder.java | 109 +++++
.../plan/extension/builder/ValueBuilder.java | 318 ++++++++++++
.../plan/extension/builder/package-info.java | 6 +
.../extractor/ExtensionExtractor.java | 361 ++++++++------
.../extension/extractor/ExtensionMethod.java | 135 ++++++
.../extension/extractor/ExtensionMethods.java | 145 ++++++
.../extractor/MethodAnnotations.java | 2 +
.../extractor/ExtensionExtractorTest.java | 300 +++++++++++-
.../java/com/djrapitops/plan/PlanSystem.java | 3 +-
.../DataExtensionMethodCallException.java | 16 +-
.../plan/extension/ExtensionSvc.java | 73 +--
.../implementation/CallerImplementation.java | 6 +-
.../implementation/ExtensionWrapper.java | 108 ++---
.../implementation/ProviderInformation.java | 10 +-
.../builder/BooleanDataValue.java | 31 ++
.../builder/BuiltDataValue.java | 83 ++++
.../builder/DoubleDataValue.java | 31 ++
.../builder/ExtDataBuilder.java | 149 ++++++
.../builder/ExtValueBuilder.java | 243 ++++++++++
.../builder/GroupsDataValue.java | 31 ++
.../builder/NumberDataValue.java | 31 ++
.../builder/StringDataValue.java | 31 ++
.../builder/TableDataValue.java | 32 ++
.../providers/BooleanDataProvider.java | 60 ---
.../providers/DataProviders.java | 95 ----
.../providers/DoubleDataProvider.java | 59 ---
.../providers/GroupDataProvider.java | 57 ---
.../providers/MethodWrapper.java | 15 +-
.../providers/NumberDataProvider.java | 59 ---
.../providers/PercentageDataProvider.java | 60 ---
.../providers/StringDataProvider.java | 59 ---
.../providers/TableDataProvider.java | 55 ---
.../BooleanProviderValueGatherer.java | 160 ------
.../providers/gathering/Conditions.java | 8 +
.../gathering/DataValueGatherer.java | 455 ++++++++++++++++++
.../gathering/ProviderValueGatherer.java | 206 --------
.../gathering/TableProviderValueGatherer.java | 126 -----
.../providers/StoreProviderTransaction.java | 17 +-
.../StoreTableProviderTransaction.java | 7 +-
.../StorePlayerBooleanResultTransaction.java | 8 +-
.../StorePlayerDoubleResultTransaction.java | 10 +-
.../StorePlayerGroupsResultTransaction.java | 10 +-
.../StorePlayerNumberResultTransaction.java | 10 +-
.../StorePlayerStringResultTransaction.java | 10 +-
.../StorePlayerTableResultTransaction.java | 8 +-
.../StoreServerBooleanResultTransaction.java | 12 +-
.../StoreServerDoubleResultTransaction.java | 12 +-
.../StoreServerNumberResultTransaction.java | 10 +-
.../StoreServerStringResultTransaction.java | 8 +-
.../StoreServerTableResultTransaction.java | 8 +-
.../builder/ClassValuePairOrderTest.java | 191 ++++++++
.../builder/ExtendionDataBuilderTest.java | 56 +++
.../database/DatabaseTestPreparer.java | 4 +-
.../plan/storage/database/H2Test.java | 6 +-
.../plan/storage/database/MySQLTest.java | 6 +-
.../plan/storage/database/SQLiteTest.java | 6 +-
.../queries/ExtensionsDatabaseTest.java | 84 +++-
.../test/java/utilities/TestErrorLogger.java | 28 +-
.../implementation/ExtensionRegister.java | 3 +-
65 files changed, 2982 insertions(+), 1387 deletions(-)
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/DataBuilderProvider.java
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/builder/DataValue.java
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ExtensionDataBuilder.java
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ValueBuilder.java
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/builder/package-info.java
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethod.java
create mode 100644 Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BooleanDataValue.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BuiltDataValue.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/DoubleDataValue.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtValueBuilder.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/GroupsDataValue.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/NumberDataValue.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/StringDataValue.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/TableDataValue.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/BooleanDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DataProviders.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DoubleDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/GroupDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/NumberDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/PercentageDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/StringDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/TableDataProvider.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/BooleanProviderValueGatherer.java
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/DataValueGatherer.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/ProviderValueGatherer.java
delete mode 100644 Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/TableProviderValueGatherer.java
create mode 100644 Plan/common/src/test/java/com/djrapitops/plan/extension/implementation/builder/ClassValuePairOrderTest.java
create mode 100644 Plan/common/src/test/java/com/djrapitops/plan/extension/implementation/builder/ExtendionDataBuilderTest.java
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java b/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java
index e1ae52a1c..3607f6675 100644
--- a/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java
+++ b/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java
@@ -56,6 +56,10 @@ enum Capability {
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
*/
DATA_EXTENSION_SHOW_IN_PLAYER_TABLE,
+ /**
+ * DataExtension API addition, {@link com.djrapitops.plan.extension.builder.ExtensionDataBuilder}.
+ */
+ DATA_EXTENSION_BUILDER_API,
/**
* {@link com.djrapitops.plan.query.QueryService} and {@link com.djrapitops.plan.query.CommonQueries}
*/
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/DataExtension.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/DataExtension.java
index bada70e21..5806d6ea4 100644
--- a/Plan/api/src/main/java/com/djrapitops/plan/extension/DataExtension.java
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/DataExtension.java
@@ -16,6 +16,9 @@
*/
package com.djrapitops.plan.extension;
+import com.djrapitops.plan.extension.annotation.PluginInfo;
+import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
+
/**
* Interface to implement data extensions with.
*
@@ -30,6 +33,9 @@ package com.djrapitops.plan.extension;
* {@link com.djrapitops.plan.extension.annotation.DoubleProvider} for {@code double} values.
* {@link com.djrapitops.plan.extension.annotation.PercentageProvider} for {@code double} values that represent a percentage.
* {@link com.djrapitops.plan.extension.annotation.StringProvider} for {@link String} values.
+ * {@link com.djrapitops.plan.extension.annotation.TableProvider} for {@link com.djrapitops.plan.extension.table.Table}s.
+ * {@link com.djrapitops.plan.extension.annotation.GroupProvider} for Player specific group names, such as permission groups.
+ * {@link com.djrapitops.plan.extension.annotation.DataBuilderProvider} for {@link ExtensionDataBuilder}s.
*
*
* Methods can have one of the following as method parameters:
@@ -90,4 +96,30 @@ public interface DataExtension {
};
}
+ /**
+ * Obtain a new {@link ExtensionDataBuilder}.
+ *
+ * Requires Capability DATA_EXTENSION_BUILDER_API
+ *
+ * @return new builder.
+ */
+ default ExtensionDataBuilder newExtensionDataBuilder() {
+ return ExtensionService.getInstance().newExtensionDataBuilder(this);
+ }
+
+ /**
+ * Get the name of the plugin from PluginInfo annotation.
+ *
+ * Requires Capability DATA_EXTENSION_BUILDER_API
+ *
+ * @return new builder.
+ */
+ default String getPluginName() {
+ PluginInfo annotation = getClass().getAnnotation(PluginInfo.class);
+ if (annotation == null) {
+ throw new IllegalArgumentException(getClass().getSimpleName() + " did not have @PluginInfo annotation!");
+ }
+ return annotation.name();
+ }
+
}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/ExtensionService.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/ExtensionService.java
index 1d0be17cd..9bcf81c3a 100644
--- a/Plan/api/src/main/java/com/djrapitops/plan/extension/ExtensionService.java
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/ExtensionService.java
@@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.extension;
+import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
import com.djrapitops.plan.extension.extractor.ExtensionExtractor;
import java.util.Optional;
@@ -60,6 +61,16 @@ public interface ExtensionService {
*/
Optional register(DataExtension extension);
+ /**
+ * Obtain a new {@link ExtensionDataBuilder}, it is recommended to use {@link DataExtension#newExtensionDataBuilder()}.
+ *
+ * Requires Capability DATA_EXTENSION_BUILDER_API
+ *
+ * @param extension Extension for which this builder is.
+ * @return a new builder.
+ */
+ ExtensionDataBuilder newExtensionDataBuilder(DataExtension extension);
+
/**
* Unregister your {@link DataExtension} implementation.
*
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/Conditional.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/Conditional.java
index 17fd4ac83..191db0bd7 100644
--- a/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/Conditional.java
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/Conditional.java
@@ -53,5 +53,4 @@ public @interface Conditional {
* @return {@code false} by default.
*/
boolean negated() default false;
-
}
\ No newline at end of file
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/DataBuilderProvider.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/DataBuilderProvider.java
new file mode 100644
index 000000000..0c49bb264
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/annotation/DataBuilderProvider.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.annotation;
+
+import com.djrapitops.plan.extension.DataExtension;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Method annotation to provide a {@link com.djrapitops.plan.extension.builder.ExtensionDataBuilder}.
+ *
+ * Usage: {@code @DataBuilderProvider ExtensionDataBuilder method(UUID playerUUID)}
+ *
+ * ExtensionDataBuilder can be obtained with {@link DataExtension#newExtensionDataBuilder()}.
+ *
+ * @author AuroraLS3
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface DataBuilderProvider {
+
+}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/DataValue.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/DataValue.java
new file mode 100644
index 000000000..66c94ef6c
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/DataValue.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.builder;
+
+import java.util.Optional;
+
+/**
+ * Represents a value given to {@link ExtensionDataBuilder}.
+ *
+ * Please do not implement this class, it is an implementation detail.
+ * Obtain instances with {@link ValueBuilder}.
+ *
+ * @param Type of the value.
+ */
+public interface DataValue {
+
+ T getValue();
+
+ M getInformation(Class ofType);
+
+ default > Optional getMetadata(Class metadataType) {
+ if (getClass().equals(metadataType)) return Optional.of(metadataType.cast(this));
+ return Optional.empty();
+ }
+
+}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ExtensionDataBuilder.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ExtensionDataBuilder.java
new file mode 100644
index 000000000..ffdcabe05
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ExtensionDataBuilder.java
@@ -0,0 +1,109 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.builder;
+
+import com.djrapitops.plan.extension.DataExtension;
+import com.djrapitops.plan.extension.icon.Color;
+import com.djrapitops.plan.extension.table.Table;
+
+import java.util.function.Supplier;
+
+/**
+ * Builder API for Extension data.
+ *
+ * Requires Capability DATA_EXTENSION_BUILDER_API
+ *
+ * Obtain an instance with {@link DataExtension#newExtensionDataBuilder()}
+ *
+ * Used with {@link com.djrapitops.plan.extension.annotation.DataBuilderProvider}.
+ * See documentation on how to use the API.
+ *
+ * @author AuroraLS3
+ */
+public interface ExtensionDataBuilder {
+
+ /**
+ * Creates a new {@link ValueBuilder} in order to use addValue methods.
+ *
+ * Using same text for two values can be problematic as the text is used for id in the database.
+ *
+ * If you need to use {@link com.djrapitops.plan.extension.annotation.InvalidateMethod} with built values,
+ * lowercase 'text' and remove all whitespace. Example {@code valueBuilder("Times Jumped"); @InvalidateMethod("timesjumped")}
+ *
+ * @param text Text that should be displayed next to the value.
+ * @return a new value builder.
+ * @throws IllegalArgumentException If text is null or empty String.
+ */
+ ValueBuilder valueBuilder(String text);
+
+ /**
+ * Add a value.
+ *
+ * @param ofType Class for type of the data, matches what Provider annotations want.
+ * @param dataValue Use {@link ValueBuilder} to create one.
+ * @param Type of the data.
+ * @return This builder.
+ * @throws IllegalArgumentException If either parameter is null
+ */
+ ExtensionDataBuilder addValue(Class ofType, DataValue dataValue);
+
+ /**
+ * Compared to the other addValue method, this method allows you to use {@link com.djrapitops.plan.extension.NotReadyException} when building your data.
+ *
+ * @param ofType Class for type of the data, matches what Provider annotations want.
+ * @param dataValue Use {@link ValueBuilder} to create one.
+ * @param Type of the data.
+ * @return This builder.
+ * @throws IllegalArgumentException If either parameter is null
+ */
+ ExtensionDataBuilder addValue(Class ofType, Supplier> dataValue);
+
+ /**
+ * Add a table.
+ *
+ * @param name Name of the table, used in the database.
+ * @param table Table built using {@link Table#builder()}
+ * @param color Color of the table
+ * @return This builder.
+ */
+ default ExtensionDataBuilder addTable(String name, Table table, Color color) {
+ if (name == null) throw new IllegalArgumentException("'name' can not be null!");
+ return addTable(name, table, color, null);
+ }
+
+ /**
+ * Add a table to a specific tab.
+ *
+ * @param name Name of the table, used in the database.
+ * @param table Table built using {@link Table#builder()}
+ * @param color Color of the table
+ * @param tab Name of the tab, remember to define {@link com.djrapitops.plan.extension.annotation.TabInfo}.
+ * @return This builder.
+ */
+ default ExtensionDataBuilder addTable(String name, Table table, Color color, String tab) {
+ return addValue(Table.class, valueBuilder(name)
+ .showOnTab(tab)
+ .buildTable(table, color));
+ }
+
+ /**
+ * Adds all values and tables in another builder to this builder.
+ *
+ * @param builder Builder to combine with this one.
+ */
+ void addAll(ExtensionDataBuilder builder);
+}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ValueBuilder.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ValueBuilder.java
new file mode 100644
index 000000000..1efea98f6
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/ValueBuilder.java
@@ -0,0 +1,318 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.builder;
+
+import com.djrapitops.plan.extension.FormatType;
+import com.djrapitops.plan.extension.annotation.BooleanProvider;
+import com.djrapitops.plan.extension.annotation.Conditional;
+import com.djrapitops.plan.extension.annotation.StringProvider;
+import com.djrapitops.plan.extension.annotation.Tab;
+import com.djrapitops.plan.extension.extractor.ExtensionMethod;
+import com.djrapitops.plan.extension.icon.Color;
+import com.djrapitops.plan.extension.icon.Family;
+import com.djrapitops.plan.extension.icon.Icon;
+import com.djrapitops.plan.extension.table.Table;
+
+import java.util.function.Supplier;
+
+/**
+ * Used for building {@link DataValue}s for {@link ExtensionDataBuilder#addValue(Class, DataValue)}.
+ *
+ * Requires Capability DATA_EXTENSION_BUILDER_API
+ *
+ * Obtain an instance with {@link ExtensionDataBuilder#valueBuilder(String)}.
+ */
+public interface ValueBuilder {
+
+ /**
+ * Description about the value that is shown on hover.
+ *
+ * @param description Describe what the value is about, maximum 150 characters.
+ * @return This builder.
+ */
+ ValueBuilder description(String description);
+
+ /**
+ * Display-priority of the value, highest value is placed top most.
+ *
+ * Two values with same priority may appear in a random order.
+ *
+ * @param priority Priority between 0 and {@code Integer.MAX_VALUE}.
+ * @return This builder.
+ */
+ ValueBuilder priority(int priority);
+
+ /**
+ * Show this value in the players table.
+ *
+ * @return This builder.
+ */
+ ValueBuilder showInPlayerTable();
+
+ /**
+ * Icon displayed next to the value.
+ *
+ * See https://fontawesome.com/icons (select 'free')) for icons
+ *
+ * @param iconName Name of the icon
+ * @param iconFamily Family of the icon
+ * @param iconColor Color of the icon
+ * @return This builder.
+ */
+ default ValueBuilder icon(String iconName, Family iconFamily, Color iconColor) {
+ return icon(Icon.called(iconName).of(iconFamily).of(iconColor).build());
+ }
+
+ /**
+ * Icon displayed next to the value.
+ *
+ * See https://fontawesome.com/icons (select 'free')) for icons
+ *
+ * @param icon Icon built using the methods in {@link Icon}.
+ * @return This builder.
+ */
+ ValueBuilder icon(Icon icon);
+
+ /**
+ * Show the value on a specific tab.
+ *
+ * Remember to define {@link com.djrapitops.plan.extension.annotation.TabInfo} annotation.
+ *
+ * @param tabName Name of the tab.
+ * @return This builder.
+ */
+ ValueBuilder showOnTab(String tabName);
+
+ /**
+ * {@link ValueBuilder#buildNumber(long)} specific method, format the value as a epoch ms timestamp.
+ *
+ * @return This builder.
+ */
+ default ValueBuilder formatAsDateWithYear() {
+ return format(FormatType.DATE_YEAR);
+ }
+
+ /**
+ * {@link ValueBuilder#buildNumber(long)} specific method, format the value as a epoch ms timestamp.
+ *
+ * @return This builder.
+ */
+ default ValueBuilder formatAsDateWithSeconds() {
+ return format(FormatType.DATE_SECOND);
+ }
+
+ /**
+ * {@link ValueBuilder#buildNumber(long)} specific method, format the value as milliseconds of time.
+ *
+ * @return This builder.
+ */
+ default ValueBuilder formatAsTimeAmount() {
+ return format(FormatType.TIME_MILLISECONDS);
+ }
+
+ /**
+ * {@link ValueBuilder#buildNumber(long)} specific method, format the value with {@link FormatType}
+ *
+ * @return This builder.
+ */
+ ValueBuilder format(FormatType formatType);
+
+ /**
+ * {@link ValueBuilder#buildString(String)} specific method, link the value to a player page.
+ *
+ * @return This builder.
+ */
+ ValueBuilder showAsPlayerPageLink();
+
+ /**
+ * Build a Boolean. Displayed as "Yes/No" on the page.
+ *
+ * @param value true/false
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildBoolean(boolean value);
+
+ /**
+ * Build a Boolean that provides a value for {@link Conditional}. Displayed as "Yes/No" on the page.
+ *
+ * @param value true/false
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildBooleanProvidingCondition(boolean value, String providedCondition);
+
+ /**
+ * Build a String.
+ *
+ * @param value any string. Limited to 50 characters.
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildString(String value);
+
+ /**
+ * Build a Number.
+ *
+ * @param value a non-floating point number.
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildNumber(long value);
+
+ /**
+ * Build a Floating point number.
+ *
+ * @param value a floating point number.
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildDouble(double value);
+
+ /**
+ * Build a Percentage.
+ *
+ * @param percentage value between 0.0 and 1.0
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildPercentage(double percentage);
+
+ /**
+ * Build a list of groups.
+ *
+ * @param groups names of groups a player is in.
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildGroup(String[] groups);
+
+ /**
+ * Build a table.
+ *
+ * @param table Table built using {@link Table#builder()}
+ * @param tableColor Color of the table
+ * @return a data value to give to {@link ExtensionDataBuilder}.
+ */
+ DataValue buildTable(Table table, Color tableColor);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildBoolean(boolean).
+ */
+ DataValue buildBoolean(Supplier value);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildBooleanProvidingCondition(boolean, String).
+ */
+ DataValue buildBooleanProvidingCondition(Supplier value, String providedCondition);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildString(String)
+ */
+ DataValue buildString(Supplier value);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildNumber(long)
+ */
+ DataValue buildNumber(Supplier value);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildDouble(double)
+ */
+ DataValue buildDouble(Supplier value);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildPercentage(double)
+ */
+ DataValue buildPercentage(Supplier percentage);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildGroup(String[])
+ */
+ DataValue buildGroup(Supplier groups);
+
+ /**
+ * Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
+ *
+ * @see ValueBuilder#buildTable(Table, Color)
+ */
+ DataValue buildTable(Supplier table, Color tableColor);
+
+ /**
+ * Implementation detail - for abstracting annotations with the builder API.
+ *
+ * @param annotation BooleanProvider annotation.
+ * @return This builder.
+ */
+ ValueBuilder hideFromUsers(BooleanProvider annotation);
+
+ /**
+ * Implementation detail - for abstracting annotations with the builder API.
+ *
+ * @param conditional Conditional annotation.
+ * @return This builder.
+ */
+ ValueBuilder conditional(Conditional conditional);
+
+ /**
+ * Implementation detail - for abstracting annotations with the builder API.
+ *
+ * @param annotation StringProvider annotation.
+ * @return This builder.
+ */
+ default ValueBuilder showAsPlayerPageLink(StringProvider annotation) {
+ if (annotation.playerName()) return showAsPlayerPageLink();
+ return this;
+ }
+
+ /**
+ * Implementation detail - for abstracting annotations with the builder API.
+ *
+ * @param method Method this value is from.
+ * @return This builder.
+ */
+ ValueBuilder methodName(ExtensionMethod method);
+
+ /**
+ * Implementation detail - for abstracting annotations with the builder API.
+ *
+ * @param show true/false
+ * @return This builder.
+ */
+ default ValueBuilder showInPlayerTable(boolean show) {
+ if (show) return showInPlayerTable();
+ return this;
+ }
+
+ /**
+ * Implementation detail - for abstracting annotations with the builder API.
+ *
+ * @param annotation Tab annotation.
+ * @return This builder.
+ */
+ default ValueBuilder showOnTab(Tab annotation) {
+ if (annotation != null) return showOnTab(annotation.value());
+ return this;
+ }
+}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/package-info.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/package-info.java
new file mode 100644
index 000000000..0ce6474ca
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/builder/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * DataExtension Builder API.
+ *
+ * Requires Capability DATA_EXTENSION_BUILDER_API
+ */
+package com.djrapitops.plan.extension.builder;
\ No newline at end of file
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java
index c7a062d17..71c2a4165 100644
--- a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java
@@ -19,11 +19,11 @@ 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 com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
import com.djrapitops.plan.extension.table.Table;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
@@ -43,10 +43,12 @@ public final class ExtensionExtractor {
private final List warnings = new ArrayList<>();
private PluginInfo pluginInfo;
- private TabOrder tabOrder;
private List tabInformation;
private List invalidMethods;
private MethodAnnotations methodAnnotations;
+ private Map methods;
+ private Collection conditionalMethods;
+ private Collection tabAnnotations;
private static final String WAS_OVER_50_CHARACTERS = "' was over 50 characters.";
@@ -56,78 +58,126 @@ public final class ExtensionExtractor {
}
/**
- * Use this method in an unit test to validate your DataExtension.
- *
- * @throws IllegalArgumentException If an implementation error is found.
+ * @deprecated Use {@link DataExtension#getPluginName()} instead.
*/
- public void validateAnnotations() {
- extractAnnotationInformation();
-
- if (!warnings.isEmpty()) {
- throw new IllegalArgumentException("Warnings: " + warnings.toString());
- }
+ @Deprecated
+ public static String getPluginName(Class extensionClass) {
+ return getClassAnnotation(extensionClass, PluginInfo.class).map(PluginInfo::name)
+ .orElseThrow(() -> new IllegalArgumentException("Given class had no PluginInfo annotation"));
}
private static Optional getClassAnnotation(Class from, Class ofClass) {
return Optional.ofNullable(from.getAnnotation(ofClass));
}
- public static String getPluginName(Class extensionClass) {
- return getClassAnnotation(extensionClass, PluginInfo.class).map(PluginInfo::name)
- .orElseThrow(() -> new IllegalArgumentException("Given class had no PluginInfo annotation"));
- }
-
- private Method[] getMethods() {
- return extension.getClass().getMethods();
- }
-
- public void extractAnnotationInformation() {
+ /**
+ * Use this method in an unit test to validate your DataExtension.
+ *
+ * @throws IllegalArgumentException If an implementation error is found.
+ */
+ public void validateAnnotations() {
extractPluginInfo();
extractInvalidMethods();
-
- extractMethodAnnotations();
- validateMethodAnnotations();
-
- validateConditionals();
-
+ extractMethods();
extractTabInfo();
+
+ if (!warnings.isEmpty()) {
+ throw new IllegalArgumentException("Warnings: " + warnings.toString());
+ }
}
- private void extractMethodAnnotations() {
- methodAnnotations = new MethodAnnotations();
+ private Collection getExtensionMethods() {
+ List extensionMethods = new ArrayList<>();
+ for (Method method : extension.getClass().getMethods()) {
+ try {
+ extensionMethods.add(new ExtensionMethod(extension, method));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(extensionName + '.' + e.getMessage());
+ }
+ }
+ return extensionMethods;
+ }
- for (Method method : getMethods()) {
- int modifiers = method.getModifiers();
- if (Modifier.isPrivate(modifiers)
- || Modifier.isProtected(modifiers)
- || Modifier.isStatic(modifiers)
- || Modifier.isNative(modifiers)) {
+ /**
+ * @deprecated No longer used anywhere, no-op.
+ */
+ @Deprecated
+ public void extractAnnotationInformation() {
+ // no-op
+ }
+
+ private void extractMethods() {
+ methodAnnotations = new MethodAnnotations();
+ methods = new EnumMap<>(ExtensionMethod.ParameterType.class);
+ methods.put(ExtensionMethod.ParameterType.SERVER_NONE, new ExtensionMethods());
+ methods.put(ExtensionMethod.ParameterType.PLAYER_STRING, new ExtensionMethods());
+ methods.put(ExtensionMethod.ParameterType.PLAYER_UUID, new ExtensionMethods());
+ methods.put(ExtensionMethod.ParameterType.GROUP, new ExtensionMethods());
+
+ conditionalMethods = new ArrayList<>();
+ tabAnnotations = new ArrayList<>();
+
+ for (ExtensionMethod method : getExtensionMethods()) {
+ if (method.isInaccessible()) {
continue;
}
- MethodAnnotations.get(method, BooleanProvider.class).ifPresent(annotation -> methodAnnotations.put(method, BooleanProvider.class, annotation));
- MethodAnnotations.get(method, NumberProvider.class).ifPresent(annotation -> methodAnnotations.put(method, NumberProvider.class, annotation));
- MethodAnnotations.get(method, DoubleProvider.class).ifPresent(annotation -> methodAnnotations.put(method, DoubleProvider.class, annotation));
- MethodAnnotations.get(method, PercentageProvider.class).ifPresent(annotation -> methodAnnotations.put(method, PercentageProvider.class, annotation));
- MethodAnnotations.get(method, StringProvider.class).ifPresent(annotation -> methodAnnotations.put(method, StringProvider.class, annotation));
+ method.getAnnotation(BooleanProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addBooleanMethod(method);
+ methodAnnotations.put(method.getMethod(), BooleanProvider.class, annotation);
+ });
+ method.getAnnotation(NumberProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addNumberMethod(method);
+ methodAnnotations.put(method.getMethod(), NumberProvider.class, annotation);
+ });
+ method.getAnnotation(DoubleProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addDoubleMethod(method);
+ methodAnnotations.put(method.getMethod(), DoubleProvider.class, annotation);
+ });
+ method.getAnnotation(PercentageProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addPercentageMethod(method);
+ methodAnnotations.put(method.getMethod(), PercentageProvider.class, annotation);
+ });
+ method.getAnnotation(StringProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addStringMethod(method);
+ methodAnnotations.put(method.getMethod(), StringProvider.class, annotation);
+ });
+ method.getAnnotation(TableProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addTableMethod(method);
+ methodAnnotations.put(method.getMethod(), TableProvider.class, annotation);
+ });
+ method.getAnnotation(GroupProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addGroupMethod(method);
+ methodAnnotations.put(method.getMethod(), GroupProvider.class, annotation);
+ });
+ method.getAnnotation(DataBuilderProvider.class).ifPresent(annotation -> {
+ validateMethod(method, annotation);
+ methods.get(method.getParameterType()).addDataBuilderMethod(method);
+ methodAnnotations.put(method.getMethod(), DataBuilderProvider.class, annotation);
+ });
- MethodAnnotations.get(method, Conditional.class).ifPresent(annotation -> methodAnnotations.put(method, Conditional.class, annotation));
- 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));
+ method.getAnnotation(Conditional.class).ifPresent(annotation -> {
+ conditionalMethods.add(method.getMethod());
+ methodAnnotations.put(method.getMethod(), Conditional.class, annotation);
+ });
+ method.getAnnotation(Tab.class).ifPresent(annotation -> {
+ tabAnnotations.add(annotation);
+ methodAnnotations.put(method.getMethod(), Tab.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);
- }
+ validateConditionals();
}
private void validateReturnType(Method method, Class expectedType) {
@@ -144,7 +194,7 @@ public final class ExtensionExtractor {
private void validateMethodAnnotationPropertyLength(String property, String name, int maxLength, Method method) {
if (property.length() > maxLength) {
- warnings.add(extensionName + "." + method.getName() + " '" + name + WAS_OVER_50_CHARACTERS);
+ warnings.add(extensionName + "." + method.getName() + " '" + name + "' was over " + maxLength + " characters.");
}
}
@@ -186,135 +236,109 @@ public final class ExtensionExtractor {
// Has valid parameter & it is acceptable.
}
- private void validateMethodAnnotations() {
- validateBooleanProviderAnnotations();
- validateNumberProviderAnnotations();
- validateDoubleProviderAnnotations();
- validatePercentageProviderAnnotations();
- validateStringProviderAnnotations();
- validateTableProviderAnnotations();
- validateGroupProviderAnnotations();
- }
+ private void validateMethod(ExtensionMethod extensionMethod, BooleanProvider annotation) {
+ Method method = extensionMethod.getMethod();
+ validateReturnType(method, boolean.class);
+ validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
+ validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
+ validateMethodAnnotationPropertyLength(annotation.conditionName(), "conditionName", 50, method);
+ validateMethodArguments(method, false, UUID.class, String.class, Group.class);
- private void validateBooleanProviderAnnotations() {
- for (Map.Entry booleanProvider : methodAnnotations.getMethodAnnotations(BooleanProvider.class).entrySet()) {
- Method method = booleanProvider.getKey();
- BooleanProvider annotation = booleanProvider.getValue();
+ String condition = extensionMethod.getAnnotation(Conditional.class).map(Conditional::value).orElse(null);
+ if (annotation.conditionName().equals(condition)) {
+ warnings.add(extensionName + "." + method.getName() + " can not be conditional of itself. required condition: " + condition + ", provided condition: " + annotation.conditionName());
+ }
- validateReturnType(method, boolean.class);
- validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
- validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
- validateMethodAnnotationPropertyLength(annotation.conditionName(), "conditionName", 50, method);
- validateMethodArguments(method, false, UUID.class, String.class, Group.class);
-
- String condition = MethodAnnotations.get(method, Conditional.class).map(Conditional::value).orElse(null);
- if (annotation.conditionName().equals(condition)) {
- warnings.add(extensionName + "." + method.getName() + " can not be conditional of itself. required condition: " + condition + ", provided condition: " + annotation.conditionName());
- }
-
- if (annotation.conditionName().isEmpty() && annotation.hidden()) {
- throw new IllegalArgumentException(extensionName + "." + method.getName() + " can not be 'hidden' without a 'conditionName'");
- }
+ if (annotation.conditionName().isEmpty() && annotation.hidden()) {
+ throw new IllegalArgumentException(extensionName + "." + method.getName() + " can not be 'hidden' without a 'conditionName'");
}
}
- private void validateNumberProviderAnnotations() {
- for (Map.Entry numberProvider : methodAnnotations.getMethodAnnotations(NumberProvider.class).entrySet()) {
- Method method = numberProvider.getKey();
- NumberProvider annotation = numberProvider.getValue();
+ private void validateMethod(ExtensionMethod extensionMethod, NumberProvider annotation) {
+ Method method = extensionMethod.getMethod();
- validateReturnType(method, long.class);
- validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
- validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
- validateMethodArguments(method, false, UUID.class, String.class, Group.class);
- }
+ validateReturnType(method, long.class);
+ validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
+ validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
+ validateMethodArguments(method, false, UUID.class, String.class, Group.class);
}
- private void validateDoubleProviderAnnotations() {
- for (Map.Entry doubleProvider : methodAnnotations.getMethodAnnotations(DoubleProvider.class).entrySet()) {
- Method method = doubleProvider.getKey();
- DoubleProvider annotation = doubleProvider.getValue();
+ private void validateMethod(ExtensionMethod extensionMethod, DoubleProvider annotation) {
+ Method method = extensionMethod.getMethod();
- validateReturnType(method, double.class);
- validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
- validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
- validateMethodArguments(method, false, UUID.class, String.class, Group.class);
- }
+ validateReturnType(method, double.class);
+ validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
+ validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
+ validateMethodArguments(method, false, UUID.class, String.class, Group.class);
}
- private void validatePercentageProviderAnnotations() {
- for (Map.Entry percentageProvider : methodAnnotations.getMethodAnnotations(PercentageProvider.class).entrySet()) {
- Method method = percentageProvider.getKey();
- PercentageProvider annotation = percentageProvider.getValue();
+ private void validateMethod(ExtensionMethod extensionMethod, PercentageProvider annotation) {
+ Method method = extensionMethod.getMethod();
- validateReturnType(method, double.class);
- validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
- validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
- validateMethodArguments(method, false, UUID.class, String.class, Group.class);
- }
+ validateReturnType(method, double.class);
+ validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
+ validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
+ validateMethodArguments(method, false, UUID.class, String.class, Group.class);
}
- private void validateStringProviderAnnotations() {
- for (Map.Entry stringProvider : methodAnnotations.getMethodAnnotations(StringProvider.class).entrySet()) {
- Method method = stringProvider.getKey();
- StringProvider annotation = stringProvider.getValue();
+ private void validateMethod(ExtensionMethod extensionMethod, StringProvider annotation) {
+ Method method = extensionMethod.getMethod();
- validateReturnType(method, String.class);
- validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
- validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
- validateMethodArguments(method, false, UUID.class, String.class, Group.class);
- }
+ validateReturnType(method, String.class);
+ validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
+ validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
+ validateMethodArguments(method, false, UUID.class, String.class, Group.class);
}
- private void validateTableProviderAnnotations() {
- for (Method method : methodAnnotations.getMethodAnnotations(TableProvider.class).keySet()) {
- validateReturnType(method, Table.class);
- validateMethodArguments(method, false, UUID.class, String.class, Group.class);
- }
+ private void validateMethod(ExtensionMethod extensionMethod, TableProvider annotation) {
+ Method method = extensionMethod.getMethod();
+
+ validateReturnType(method, Table.class);
+ validateMethodArguments(method, false, UUID.class, String.class, Group.class);
}
- private void validateGroupProviderAnnotations() {
- for (Map.Entry groupProvider : methodAnnotations.getMethodAnnotations(GroupProvider.class).entrySet()) {
- Method method = groupProvider.getKey();
- GroupProvider annotation = groupProvider.getValue();
+ private void validateMethod(ExtensionMethod extensionMethod, GroupProvider annotation) {
+ Method method = extensionMethod.getMethod();
- validateReturnType(method, String[].class);
- validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
- validateMethodArguments(method, true, UUID.class, String.class);
- }
+ validateReturnType(method, String[].class);
+ validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
+ validateMethodArguments(method, true, UUID.class, String.class);
+ }
+
+ private void validateMethod(ExtensionMethod extensionMethod, DataBuilderProvider annotation) {
+ Method method = extensionMethod.getMethod();
+
+ validateReturnType(method, ExtensionDataBuilder.class);
+ validateMethodArguments(method, false, UUID.class, String.class);
}
private void validateConditionals() {
- Collection conditionals = methodAnnotations.getAnnotations(Conditional.class);
- Collection conditionProviders = methodAnnotations.getAnnotations(BooleanProvider.class);
-
- Set providedConditions = conditionProviders.stream().map(BooleanProvider::conditionName).collect(Collectors.toSet());
-
- for (Conditional condition : conditionals) {
- String conditionName = condition.value();
-
- if (conditionName.length() > 50) {
- warnings.add(extensionName + ": '" + conditionName + "' conditionName was over 50 characters.");
- }
-
- if (!providedConditions.contains(conditionName)) {
- warnings.add(extensionName + ": '" + conditionName + "' Condition was not provided by any BooleanProvider.");
- }
- }
-
// Make sure that all methods annotated with Conditional have a Provider annotation
- Collection conditionalMethods = methodAnnotations.getMethodAnnotations(Conditional.class).keySet();
for (Method conditionalMethod : conditionalMethods) {
- if (!MethodAnnotations.hasAnyOf(conditionalMethod,
+ if (!hasAnyOf(conditionalMethod,
BooleanProvider.class, DoubleProvider.class, NumberProvider.class,
PercentageProvider.class, StringProvider.class, TableProvider.class,
- GroupProvider.class
+ GroupProvider.class, DataBuilderProvider.class
)) {
throw new IllegalArgumentException(extensionName + "." + conditionalMethod.getName() + " did not have any associated Provider for Conditional.");
}
+ if (hasAnyOf(conditionalMethod, DataBuilderProvider.class)) {
+ throw new IllegalArgumentException(extensionName + "." + conditionalMethod.getName() + " had Conditional, but DataBuilderProvider does not support it!");
+ }
}
}
+ private boolean hasAnyOf(Method method, Class>... annotationClasses) {
+ for (Annotation annotation : method.getAnnotations()) {
+ for (Class> annotationClass : annotationClasses) {
+ if (annotationClass.isAssignableFrom(annotation.getClass())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private Optional getClassAnnotation(Class ofClass) {
return getClassAnnotation(extension.getClass(), ofClass);
}
@@ -324,7 +348,7 @@ public final class ExtensionExtractor {
.orElseThrow(() -> new IllegalArgumentException("Given class had no PluginInfo annotation"));
if (pluginInfo.name().length() > 50) {
- warnings.add(extensionName + " PluginInfo 'name' was over 50 characters.");
+ warnings.add(extensionName + " PluginInfo 'name" + WAS_OVER_50_CHARACTERS);
}
}
@@ -343,17 +367,22 @@ public final class ExtensionExtractor {
}
});
- tabOrder = getClassAnnotation(TabOrder.class).orElse(null);
+ getClassAnnotation(TabOrder.class).map(TabOrder::value)
+ .ifPresent(order -> {
+ List orderAsList = Arrays.asList(order);
+ // Order by the 2nd list. https://stackoverflow.com/a/18130019
+ // O(n^2 log n), not very bad here because there aren't going to be more than 10 tabs maybe ever.
+ tabInformation.sort(Comparator.comparingInt(item -> orderAsList.indexOf(item.tab())));
+ });
- Map tabs = this.methodAnnotations.getMethodAnnotations(Tab.class);
- Set tabNames = tabs.values().stream().map(Tab::value).collect(Collectors.toSet());
+ Set tabNames = getTabAnnotations().stream().map(Tab::value).collect(Collectors.toSet());
// Check for unused TabInfo annotations
for (TabInfo tabInfo : tabInformation) {
String tabName = tabInfo.tab();
if (tabName.length() > 50) {
- warnings.add(extensionName + " TabInfo " + tabName + " name was over 50 characters.");
+ warnings.add(extensionName + " TabInfo " + tabName + " 'name" + WAS_OVER_50_CHARACTERS);
}
if (!tabNames.contains(tabName)) {
@@ -362,10 +391,9 @@ public final class ExtensionExtractor {
}
// Check Tab name lengths
- for (Map.Entry tab : tabs.entrySet()) {
- String tabName = tab.getValue().value();
+ for (String tabName : tabNames) {
if (tabName.length() > 50) {
- warnings.add(extensionName + "." + tab.getKey().getName() + " Tab '" + tabName + "' name was over 50 characters.");
+ warnings.add(extensionName + " Tab '" + tabName + "' 'name" + WAS_OVER_50_CHARACTERS);
}
}
}
@@ -391,22 +419,37 @@ public final class ExtensionExtractor {
}
public PluginInfo getPluginInfo() {
+ if (pluginInfo == null) extractPluginInfo();
return pluginInfo;
}
public Optional getTabOrder() {
- return Optional.ofNullable(tabOrder);
+ return getClassAnnotation(TabOrder.class);
+ }
+
+ public Collection getTabAnnotations() {
+ if (tabAnnotations == null) extractMethods();
+ return tabAnnotations;
}
public List getTabInformation() {
- return tabInformation != null ? tabInformation : Collections.emptyList();
+ if (tabInformation == null) extractTabInfo();
+ return tabInformation;
}
+ @Deprecated
public MethodAnnotations getMethodAnnotations() {
+ if (methodAnnotations == null) extractMethods();
return methodAnnotations;
}
+ public Map getMethods() {
+ if (methods == null) extractMethods();
+ return methods;
+ }
+
public List getInvalidateMethodAnnotations() {
- return invalidMethods != null ? invalidMethods : Collections.emptyList();
+ if (invalidMethods == null) extractInvalidMethods();
+ return invalidMethods;
}
}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethod.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethod.java
new file mode 100644
index 000000000..af51d794a
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethod.java
@@ -0,0 +1,135 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.extractor;
+
+import com.djrapitops.plan.extension.DataExtension;
+import com.djrapitops.plan.extension.Group;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Implementation detail, abstracts away some method reflection to a more usable API.
+ */
+public class ExtensionMethod {
+ private final DataExtension extension;
+ private final Method method;
+
+ private final Class> returnType;
+
+ public ExtensionMethod(DataExtension extension, Method method) {
+ this.extension = extension;
+ this.method = method;
+ returnType = method.getReturnType();
+ }
+
+ public boolean isInaccessible() {
+ int modifiers = method.getModifiers();
+ return Modifier.isPrivate(modifiers)
+ || Modifier.isProtected(modifiers)
+ || Modifier.isStatic(modifiers)
+ || Modifier.isNative(modifiers);
+ }
+
+ public Optional getAnnotation(Class ofType) {
+ return Optional.ofNullable(method.getAnnotation(ofType));
+ }
+
+ public T getExistingAnnotation(Class ofType) {
+ return getAnnotation(ofType).orElseThrow(() -> new IllegalArgumentException(method.getName() + " did not have " + ofType.getName() + " annotation"));
+ }
+
+ public T getAnnotationOrNull(Class ofType) {
+ return getAnnotation(ofType).orElse(null);
+ }
+
+ public ParameterType getParameterType() {
+ return ParameterType.getByMethodSignature(method);
+ }
+
+ public Class> getReturnType() {
+ return returnType;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public String getMethodName() {
+ return getMethod().getName();
+ }
+
+ public enum ParameterType {
+ SERVER_NONE(null),
+ PLAYER_STRING(String.class),
+ PLAYER_UUID(UUID.class),
+ GROUP(Group.class);
+
+ private final Class> type;
+
+ ParameterType(Class> type) {
+ this.type = type;
+ }
+
+ public static ParameterType getByMethodSignature(Method method) {
+ Class>[] parameters = method.getParameterTypes();
+ if (parameters.length == 0) return SERVER_NONE;
+
+ if (parameters.length > 1) {
+ // Has too many parameters
+ throw new IllegalArgumentException(method.getName() + " has too many parameters, only one parameter is required.");
+ }
+
+ Class> parameter = parameters[0];
+ if (String.class.equals(parameter)) return PLAYER_STRING;
+ if (UUID.class.equals(parameter)) return PLAYER_UUID;
+ if (Group.class.equals(parameter)) return GROUP;
+
+ throw new IllegalArgumentException(method.getName() + " does not have a valid parameter. Needs none, String, UUID or Group, had " + parameter.getSimpleName());
+ }
+
+ public Class> getType() {
+ return type;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ExtensionMethod that = (ExtensionMethod) o;
+ return Objects.equals(extension.getPluginName(), that.extension.getPluginName()) && Objects.equals(method, that.method) && Objects.equals(returnType, that.returnType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(extension.getPluginName(), method, returnType);
+ }
+
+ @Override
+ public String toString() {
+ return "ExtensionMethod{" +
+ "extension=" + extension +
+ ", method=" + method +
+ ", returnType=" + returnType +
+ '}';
+ }
+}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java
new file mode 100644
index 000000000..cfba74ba9
--- /dev/null
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java
@@ -0,0 +1,145 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.extractor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Implementation detail, abstracts away method type reflection to a more usable API.
+ */
+public class ExtensionMethods {
+
+ private final List booleanProviders;
+ private final List numberProviders;
+ private final List doubleProviders;
+ private final List percentageProviders;
+ private final List stringProviders;
+ private final List tableProviders;
+ private final List groupProviders;
+ private final List dataBuilderProviders;
+
+ public ExtensionMethods() {
+ booleanProviders = new ArrayList<>();
+ numberProviders = new ArrayList<>();
+ doubleProviders = new ArrayList<>();
+ percentageProviders = new ArrayList<>();
+ stringProviders = new ArrayList<>();
+ tableProviders = new ArrayList<>();
+ groupProviders = new ArrayList<>();
+ dataBuilderProviders = new ArrayList<>();
+ }
+
+ public List getBooleanProviders() {
+ return booleanProviders;
+ }
+
+ public List getNumberProviders() {
+ return numberProviders;
+ }
+
+ public List getDoubleProviders() {
+ return doubleProviders;
+ }
+
+ public List getPercentageProviders() {
+ return percentageProviders;
+ }
+
+ public List getStringProviders() {
+ return stringProviders;
+ }
+
+ public List getTableProviders() {
+ return tableProviders;
+ }
+
+ public List getGroupProviders() {
+ return groupProviders;
+ }
+
+ public List getDataBuilderProviders() {
+ return dataBuilderProviders;
+ }
+
+ public void addBooleanMethod(ExtensionMethod method) {
+ booleanProviders.add(method);
+ }
+
+ public void addNumberMethod(ExtensionMethod method) {
+ numberProviders.add(method);
+ }
+
+ public void addDoubleMethod(ExtensionMethod method) {
+ doubleProviders.add(method);
+ }
+
+ public void addPercentageMethod(ExtensionMethod method) {
+ percentageProviders.add(method);
+ }
+
+ public void addStringMethod(ExtensionMethod method) {
+ stringProviders.add(method);
+ }
+
+ public void addTableMethod(ExtensionMethod method) {
+ tableProviders.add(method);
+ }
+
+ public void addGroupMethod(ExtensionMethod method) {
+ groupProviders.add(method);
+ }
+
+ public void addDataBuilderMethod(ExtensionMethod method) {
+ dataBuilderProviders.add(method);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ExtensionMethods that = (ExtensionMethods) o;
+ return Objects.equals(booleanProviders, that.booleanProviders)
+ && Objects.equals(numberProviders, that.numberProviders)
+ && Objects.equals(doubleProviders, that.doubleProviders)
+ && Objects.equals(percentageProviders, that.percentageProviders)
+ && Objects.equals(stringProviders, that.stringProviders)
+ && Objects.equals(tableProviders, that.tableProviders)
+ && Objects.equals(groupProviders, that.groupProviders)
+ && Objects.equals(dataBuilderProviders, that.dataBuilderProviders);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(booleanProviders, numberProviders, doubleProviders, percentageProviders, stringProviders, tableProviders, groupProviders, dataBuilderProviders);
+ }
+
+ @Override
+ public String toString() {
+ return "ExtensionMethods{" +
+ "booleanProviders=" + booleanProviders +
+ ", numberProviders=" + numberProviders +
+ ", doubleProviders=" + doubleProviders +
+ ", percentageProviders=" + percentageProviders +
+ ", stringProviders=" + stringProviders +
+ ", tableProviders=" + tableProviders +
+ ", groupProviders=" + groupProviders +
+ ", dataBuilderProviders=" + dataBuilderProviders +
+ '}';
+ }
+}
diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/MethodAnnotations.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/MethodAnnotations.java
index 574c26281..1032c2556 100644
--- a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/MethodAnnotations.java
+++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/MethodAnnotations.java
@@ -27,7 +27,9 @@ import java.util.Optional;
* Implementation detail, utility class for handling method annotations.
*
* @author AuroraLS3
+ * @deprecated Old implementation used this.
*/
+@Deprecated
public class MethodAnnotations {
private final Map, Map> byAnnotationType;
diff --git a/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java b/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java
index 228d43c8e..860004788 100644
--- a/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java
+++ b/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java
@@ -19,9 +19,13 @@ 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 com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
+import com.djrapitops.plan.extension.table.Table;
import org.junit.jupiter.api.Test;
-import java.util.UUID;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -43,6 +47,13 @@ class ExtensionExtractorTest {
assertEquals("Given class had no PluginInfo annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
+ @Test
+ void pluginInfoIsRequired2() {
+ class Extension implements DataExtension {}
+
+ assertEquals("Extension did not have @PluginInfo annotation!", assertThrows(IllegalArgumentException.class, new Extension()::getPluginName).getMessage());
+ }
+
@Test
void providerMethodsAreRequired() {
@PluginInfo(name = "Extension")
@@ -52,6 +63,14 @@ class ExtensionExtractorTest {
assertEquals("Extension class had no methods annotated with a Provider annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
+ @Test
+ void extensionNameIsAvailable() {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {}
+
+ assertEquals("Extension", new Extension().getPluginName());
+ }
+
@Test
void publicProviderMethodsAreRequired() {
@PluginInfo(name = "Extension")
@@ -206,6 +225,20 @@ class ExtensionExtractorTest {
assertEquals("Extension.method has invalid return type. was: com.djrapitops.plan.extension.Group, expected: [Ljava.lang.String; (an array)", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
+ @Test
+ void dataBuilderProviderMustProvideDataBuilder() {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @DataBuilderProvider
+ 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: com.djrapitops.plan.extension.builder.ExtensionDataBuilder", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
+ }
+
@Test
void booleanProviderCanNotSupplyItsOwnConditional() {
@PluginInfo(name = "Extension")
@@ -235,21 +268,6 @@ class ExtensionExtractorTest {
assertEquals("Extension.method did not have any associated Provider for Conditional.", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
- @Test
- void conditionalNeedsToBeProvided() {
- @PluginInfo(name = "Extension")
- class Extension implements DataExtension {
- @Conditional("hasJoined")
- @BooleanProvider(text = "Banned", conditionName = "isBanned")
- public boolean method(UUID playerUUID) {
- return false;
- }
- }
-
- ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
- assertEquals("Warnings: [Extension: 'hasJoined' Condition was not provided by any BooleanProvider.]", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
- }
-
@Test
void methodNeedsValidParameters() {
@PluginInfo(name = "Extension")
@@ -280,4 +298,254 @@ class ExtensionExtractorTest {
assertEquals("Extension.method has too many parameters, only one of [class java.util.UUID, class java.lang.String, interface com.djrapitops.plan.extension.Group] is required as a parameter.", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
+ @Test
+ void methodsAreExtracted1() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("hasJoined")
+ @BooleanProvider(text = "Test", conditionName = "isBanned")
+ public boolean method() {
+ return false;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addBooleanMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void methodsAreExtracted2() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("hasJoined")
+ @NumberProvider(text = "Test")
+ public long method() {
+ return 0;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addNumberMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void methodsAreExtracted3() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("hasJoined")
+ @DoubleProvider(text = "Test")
+ public double method() {
+ return 0;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addDoubleMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void methodsAreExtracted4() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("hasJoined")
+ @PercentageProvider(text = "Test")
+ public double method() {
+ return 0;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addPercentageMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void methodsAreExtracted5() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("hasJoined")
+ @StringProvider(text = "Test")
+ public String method() {
+ return "example";
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addStringMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void methodsAreExtracted6() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @TableProvider
+ public Table method() {
+ return Table.builder().build();
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addTableMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void methodsAreExtracted7() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @GroupProvider
+ public String[] method(UUID playerUUID) {
+ return new String[]{"example"};
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+
+ Map expected = new EnumMap<>(ExtensionMethod.ParameterType.class);
+ for (ExtensionMethod.ParameterType value : ExtensionMethod.ParameterType.values()) {
+ expected.put(value, new ExtensionMethods());
+ }
+ expected.get(ExtensionMethod.ParameterType.PLAYER_UUID).addGroupMethod(new ExtensionMethod(extension, extension.getClass().getMethod("method", UUID.class)));
+
+ assertEquals(expected, result);
+ }
+
+ private Map buildExpectedExtensionMethodMap(DataExtension extension, BiConsumer addTo) throws NoSuchMethodException {
+ Map map = new EnumMap<>(ExtensionMethod.ParameterType.class);
+ for (ExtensionMethod.ParameterType value : ExtensionMethod.ParameterType.values()) {
+ map.put(value, new ExtensionMethods());
+ }
+ addTo.accept(
+ map.get(ExtensionMethod.ParameterType.SERVER_NONE),
+ new ExtensionMethod(extension, extension.getClass().getMethod("method"))
+ );
+ return map;
+ }
+
+ @Test
+ void methodsAreExtracted8() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @DataBuilderProvider
+ public ExtensionDataBuilder method() {
+ return null;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ Map result = underTest.getMethods();
+ Map expected = buildExpectedExtensionMethodMap(extension, ExtensionMethods::addDataBuilderMethod);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void tabsAreExtracted() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Tab("Tab name")
+ @DataBuilderProvider
+ public ExtensionDataBuilder method() {
+ return null;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+
+ Collection expected = Collections.singletonList(extension.getClass().getMethod("method").getAnnotation(Tab.class));
+ Collection result = underTest.getTabAnnotations();
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void conditionalsAreExtracted() throws NoSuchMethodException {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("example")
+ @StringProvider(text = "Test")
+ public String method() {
+ return "example";
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+
+ String expected = Stream.of(extension.getClass().getMethod("method").getAnnotation(Conditional.class))
+ .map(Conditional::value).findFirst().orElseThrow(AssertionError::new);
+ String result = underTest.getMethodAnnotations().getAnnotations(Conditional.class).stream()
+ .map(Conditional::value).findFirst().orElseThrow(AssertionError::new);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void textOver50CharsIsWarnedAbout() {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @StringProvider(text = "aaaaaAAAAAbbbbbBBBBBcccccCCCCCdddddDDDDDeeeeeEEEEEfffffFFFF")
+ public String method() {
+ return "example";
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ assertEquals(
+ "Warnings: [Extension.method 'text' was over 50 characters.]",
+ assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage()
+ );
+ }
+
+ @Test
+ void descriptionOver50CharsIsWarnedAbout() {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @StringProvider(
+ text = "a",
+ description = "aaaaaAAAAAbbbbbBBBBBcccccCCCCCdddddDDDDDeeeeeEEEEEaaaaaAAAAAbbbbbBBBBBcccccCCCCCdddddDDDDDeeeeeEEEEEaaaaaAAAAAbbbbbBBBBBcccccCCCCCdddddDDDDDeeeeeEEEEEfffffFFFFF"
+ )
+ public String method() {
+ return "example";
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ assertEquals(
+ "Warnings: [Extension.method 'description' was over 150 characters.]",
+ assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage()
+ );
+ }
+
+ @Test
+ void dataBuilderProviderCanNotHaveConditional() {
+ @PluginInfo(name = "Extension")
+ class Extension implements DataExtension {
+ @Conditional("bad")
+ @DataBuilderProvider
+ public ExtensionDataBuilder method() {
+ return null;
+ }
+ }
+ Extension extension = new Extension();
+ ExtensionExtractor underTest = new ExtensionExtractor(extension);
+ assertEquals(
+ "Extension.method had Conditional, but DataBuilderProvider does not support it!",
+ assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage()
+ );
+ }
}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java
index 886a1c6d4..868ffedf1 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java
@@ -23,7 +23,6 @@ import com.djrapitops.plan.delivery.web.ResolverSvc;
import com.djrapitops.plan.delivery.web.ResourceSvc;
import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker;
import com.djrapitops.plan.delivery.webserver.WebServerSystem;
-import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.CacheSystem;
import com.djrapitops.plan.gathering.importing.ImportSystem;
@@ -275,7 +274,7 @@ public class PlanSystem implements SubSystem {
return enabled;
}
- public ExtensionService getExtensionService() {
+ public ExtensionSvc getExtensionService() {
return extensionService;
}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/exceptions/DataExtensionMethodCallException.java b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/DataExtensionMethodCallException.java
index 393659151..9c29b5495 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/exceptions/DataExtensionMethodCallException.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/DataExtensionMethodCallException.java
@@ -16,8 +16,6 @@
*/
package com.djrapitops.plan.exceptions;
-import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
-
import java.util.Optional;
/**
@@ -28,21 +26,19 @@ import java.util.Optional;
public class DataExtensionMethodCallException extends IllegalStateException {
private final String pluginName;
- // Non serializable field due to Method not being serializable.
- private final transient MethodWrapper> method;
+ private final String methodName;
- public DataExtensionMethodCallException(Throwable cause, String pluginName, MethodWrapper> method) {
- super(cause);
+ public DataExtensionMethodCallException(String message, Throwable cause, String pluginName, String methodName) {
+ super(message, cause);
this.pluginName = pluginName;
- this.method = method;
+ this.methodName = methodName;
}
public String getPluginName() {
return pluginName;
}
- public Optional> getMethod() {
- // method is transient and might be lost if flushed to disk.
- return Optional.ofNullable(method);
+ public Optional getMethodName() {
+ return Optional.ofNullable(methodName);
}
}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java
index 72e71d987..876050ebf 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java
@@ -16,12 +16,12 @@
*/
package com.djrapitops.plan.extension;
-import com.djrapitops.plan.exceptions.DataExtensionMethodCallException;
+import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
import com.djrapitops.plan.extension.implementation.CallerImplementation;
import com.djrapitops.plan.extension.implementation.ExtensionRegister;
import com.djrapitops.plan.extension.implementation.ExtensionWrapper;
-import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
-import com.djrapitops.plan.extension.implementation.providers.gathering.ProviderValueGatherer;
+import com.djrapitops.plan.extension.implementation.builder.ExtDataBuilder;
+import com.djrapitops.plan.extension.implementation.providers.gathering.DataValueGatherer;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.ExtensionSettings;
@@ -55,7 +55,7 @@ public class ExtensionSvc implements ExtensionService {
private final PluginLogger logger;
private final ErrorLogger errorLogger;
- private final Map extensionGatherers;
+ private final Map extensionGatherers;
@Inject
public ExtensionSvc(
@@ -98,17 +98,17 @@ public class ExtensionSvc implements ExtensionService {
}
@Override
- public Optional register(DataExtension extension) {
- ExtensionWrapper extractor = new ExtensionWrapper(extension);
- String pluginName = extractor.getPluginName();
+ public Optional register(DataExtension dataExtension) {
+ ExtensionWrapper extension = new ExtensionWrapper(dataExtension);
+ String pluginName = extension.getPluginName();
if (shouldNotAllowRegistration(pluginName)) return Optional.empty();
- for (String warning : extractor.getWarnings()) {
+ for (String warning : extension.getWarnings()) {
logger.warn("DataExtension API implementation mistake for " + pluginName + ": " + warning);
}
- ProviderValueGatherer gatherer = new ProviderValueGatherer(extractor, dbSystem, serverInfo);
+ DataValueGatherer gatherer = new DataValueGatherer(extension, dbSystem, serverInfo, errorLogger);
gatherer.storeExtensionInformation();
extensionGatherers.put(pluginName, gatherer);
@@ -120,9 +120,12 @@ public class ExtensionSvc implements ExtensionService {
@Override
public void unregister(DataExtension extension) {
- ExtensionWrapper extractor = new ExtensionWrapper(extension);
- String pluginName = extractor.getPluginName();
- extensionGatherers.remove(pluginName);
+ extensionGatherers.remove(extension.getPluginName());
+ }
+
+ @Override
+ public ExtensionDataBuilder newExtensionDataBuilder(DataExtension extension) {
+ return new ExtDataBuilder(extension);
}
private boolean shouldNotAllowRegistration(String pluginName) {
@@ -145,61 +148,27 @@ public class ExtensionSvc implements ExtensionService {
}
public void updatePlayerValues(UUID playerUUID, String playerName, CallEvents event) {
- for (ProviderValueGatherer gatherer : extensionGatherers.values()) {
+ for (DataValueGatherer gatherer : extensionGatherers.values()) {
updatePlayerValues(gatherer, playerUUID, playerName, event);
}
}
- public void updatePlayerValues(ProviderValueGatherer gatherer, UUID playerUUID, String playerName, CallEvents event) {
+ public void updatePlayerValues(DataValueGatherer gatherer, UUID playerUUID, String playerName, CallEvents event) {
if (gatherer.shouldSkipEvent(event)) return;
if (playerUUID == null && playerName == null) return;
- try {
- gatherer.updateValues(playerUUID, playerName);
- } catch (DataExtensionMethodCallException methodCallFailed) {
- logFailure(playerName, methodCallFailed);
- methodCallFailed.getMethod().ifPresent(gatherer::disableMethodFromUse);
- } catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError unexpectedError) {
- ErrorContext.Builder context = ErrorContext.builder()
- .whatToDo("Report and/or disable " + gatherer.getPluginName() + " extension in the Plan config.")
- .related(gatherer.getPluginName())
- .related(event)
- .related("Player: " + playerName + " " + playerUUID);
- errorLogger.warn(unexpectedError, context.build());
- }
- }
-
- private void logFailure(String playerName, DataExtensionMethodCallException methodCallFailed) {
- Throwable cause = methodCallFailed.getCause();
- ErrorContext.Builder context = ErrorContext.builder()
- .whatToDo("Report and/or disable " + methodCallFailed.getPluginName() + " extension in the Plan config.")
- .related(methodCallFailed.getPluginName())
- .related("Method:" + methodCallFailed.getMethod().map(MethodWrapper::getMethodName).orElse("-"))
- .related("Player: " + playerName);
- errorLogger.warn(cause, context.build());
+ gatherer.updateValues(playerUUID, playerName);
}
public void updateServerValues(CallEvents event) {
- for (ProviderValueGatherer gatherer : extensionGatherers.values()) {
+ for (DataValueGatherer gatherer : extensionGatherers.values()) {
updateServerValues(gatherer, event);
}
}
- public void updateServerValues(ProviderValueGatherer gatherer, CallEvents event) {
+ public void updateServerValues(DataValueGatherer gatherer, CallEvents event) {
if (gatherer.shouldSkipEvent(event)) return;
- try {
- gatherer.updateValues();
- } catch (DataExtensionMethodCallException methodCallFailed) {
- logFailure("server", methodCallFailed);
- methodCallFailed.getMethod().ifPresent(gatherer::disableMethodFromUse);
- } catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError unexpectedError) {
- ErrorContext.Builder context = ErrorContext.builder()
- .whatToDo("Report and/or disable " + gatherer.getPluginName() + " extension in the Plan config.")
- .related(gatherer.getPluginName())
- .related(event)
- .related("Gathering for server");
- errorLogger.warn(unexpectedError, context.build());
- }
+ gatherer.updateValues();
}
}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/CallerImplementation.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/CallerImplementation.java
index 1908ee570..c3bf670a5 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/CallerImplementation.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/CallerImplementation.java
@@ -19,7 +19,7 @@ package com.djrapitops.plan.extension.implementation;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.Caller;
import com.djrapitops.plan.extension.ExtensionSvc;
-import com.djrapitops.plan.extension.implementation.providers.gathering.ProviderValueGatherer;
+import com.djrapitops.plan.extension.implementation.providers.gathering.DataValueGatherer;
import com.djrapitops.plan.processing.Processing;
import java.util.UUID;
@@ -31,12 +31,12 @@ import java.util.UUID;
*/
public class CallerImplementation implements Caller {
- private final ProviderValueGatherer gatherer;
+ private final DataValueGatherer gatherer;
private final ExtensionSvc extensionService;
private final Processing processing;
public CallerImplementation(
- ProviderValueGatherer gatherer,
+ DataValueGatherer gatherer,
ExtensionSvc extensionService,
Processing processing
) {
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ExtensionWrapper.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ExtensionWrapper.java
index 247065fd5..0a7f36d9c 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ExtensionWrapper.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ExtensionWrapper.java
@@ -20,19 +20,13 @@ import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.annotation.*;
import com.djrapitops.plan.extension.extractor.ExtensionExtractor;
-import com.djrapitops.plan.extension.extractor.MethodAnnotations;
+import com.djrapitops.plan.extension.extractor.ExtensionMethod;
+import com.djrapitops.plan.extension.extractor.ExtensionMethods;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.providers.*;
import com.djrapitops.plan.utilities.java.Lists;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.Function;
+import java.util.*;
import java.util.stream.Collectors;
/**
@@ -47,9 +41,12 @@ import java.util.stream.Collectors;
public class ExtensionWrapper {
private final ExtensionExtractor extractor;
- private final DataProviders providers;
private final DataExtension extension;
+ private final PluginInfo pluginInfo;
+ private final List tabInformation;
+ private final Map methods;
+
/**
* Create an ExtensionWrapper.
*
@@ -60,10 +57,9 @@ public class ExtensionWrapper {
this.extension = extension;
extractor = new ExtensionExtractor(this.extension);
- extractor.extractAnnotationInformation();
-
- providers = new DataProviders();
- extractProviders();
+ pluginInfo = extractor.getPluginInfo();
+ tabInformation = extractor.getTabInformation();
+ methods = extractor.getMethods();
}
public CallEvents[] getCallEvents() {
@@ -75,30 +71,28 @@ public class ExtensionWrapper {
}
public String getPluginName() {
- return extractor.getPluginInfo().name();
+ return pluginInfo.name();
}
public Icon getPluginIcon() {
- PluginInfo pluginInfo = extractor.getPluginInfo();
return new Icon(pluginInfo.iconFamily(), pluginInfo.iconName(), pluginInfo.color());
}
public Collection getPluginTabs() {
- Map tabInformation = extractor.getTabInformation()
- .stream().collect(Collectors.toMap(TabInfo::tab, Function.identity(), (one, two) -> one));
-
Map order = getTabOrder().map(this::orderToMap).orElse(new HashMap<>());
- // Extracts PluginTabs
- return extractor.getMethodAnnotations().getAnnotations(Tab.class).stream()
+ Set usedTabs = extractor.getTabAnnotations().stream()
.map(Tab::value)
- .distinct()
- .map(tabName -> {
- Optional tabInfo = Optional.ofNullable(tabInformation.get(tabName));
+ .collect(Collectors.toSet());
+ return extractor.getTabInformation()
+ .stream()
+ .filter(info -> usedTabs.contains(info.tab()))
+ .map(tabInfo -> {
+ String tabName = tabInfo.tab();
return new TabInformation(
tabName,
- tabInfo.map(info -> new Icon(info.iconFamily(), info.iconName(), Color.NONE)).orElse(null),
- tabInfo.map(TabInfo::elementOrder).orElse(null),
+ new Icon(tabInfo.iconFamily(), tabInfo.iconName(), Color.NONE),
+ tabInfo.elementOrder(),
order.getOrDefault(tabName, 100)
);
}).collect(Collectors.toList());
@@ -120,57 +114,23 @@ public class ExtensionWrapper {
return Lists.mapUnique(extractor.getInvalidateMethodAnnotations(), InvalidateMethod::value);
}
- public DataProviders getProviders() {
- return providers;
- }
-
- private void extractProviders() {
- PluginInfo pluginInfo = extractor.getPluginInfo();
-
- MethodAnnotations methodAnnotations = extractor.getMethodAnnotations();
- Map tabs = methodAnnotations.getMethodAnnotations(Tab.class);
- Map conditions = methodAnnotations.getMethodAnnotations(Conditional.class);
-
- extractProviders(pluginInfo, tabs, conditions, BooleanProvider.class, BooleanDataProvider::placeToDataProviders);
- extractProviders(pluginInfo, tabs, conditions, DoubleProvider.class, DoubleDataProvider::placeToDataProviders);
- extractProviders(pluginInfo, tabs, conditions, PercentageProvider.class, PercentageDataProvider::placeToDataProviders);
- extractProviders(pluginInfo, tabs, conditions, NumberProvider.class, NumberDataProvider::placeToDataProviders);
- extractProviders(pluginInfo, tabs, conditions, StringProvider.class, StringDataProvider::placeToDataProviders);
- extractProviders(pluginInfo, tabs, conditions, TableProvider.class, TableDataProvider::placeToDataProviders);
- extractProviders(pluginInfo, tabs, conditions, GroupProvider.class, GroupDataProvider::placeToDataProviders);
- }
-
- private void extractProviders(PluginInfo pluginInfo, Map tabs, Map conditions, Class ofKind, DataProviderFactory factory) {
- String pluginName = pluginInfo.name();
-
- for (Map.Entry entry : extractor.getMethodAnnotations().getMethodAnnotations(ofKind).entrySet()) {
- Method method = entry.getKey();
- T annotation = entry.getValue();
- Conditional conditional = conditions.get(method);
- Optional tab = Optional.ofNullable(tabs.get(method));
-
- factory.placeToDataProviders(
- providers, method, annotation,
- conditional,
- tab.map(Tab::value).orElse(null),
- pluginName
- );
- }
- }
-
public Collection getWarnings() {
return extractor.getWarnings();
}
- /**
- * Functional interface for defining a method that places required DataProvider to DataProviders.
- *
- * @param Type of the annotation on the method that is going to be extracted.
- */
- interface DataProviderFactory {
- void placeToDataProviders(
- DataProviders dataProviders,
- Method method, T annotation, Conditional condition, String tab, String pluginName
- );
+ public ExtensionExtractor getExtractor() {
+ return extractor;
+ }
+
+ public PluginInfo getPluginInfo() {
+ return pluginInfo;
+ }
+
+ public List getTabInformation() {
+ return tabInformation;
+ }
+
+ public Map getMethods() {
+ return methods;
}
}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ProviderInformation.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ProviderInformation.java
index 60f36a6b1..eee2219bc 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ProviderInformation.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/ProviderInformation.java
@@ -45,7 +45,13 @@ public class ProviderInformation extends ExtensionDescription {
private final boolean percentage; // affects where doubles are stored
private ProviderInformation(ProviderInformation.Builder builder) {
- super(builder.name, builder.text, builder.description, builder.icon, builder.priority);
+ super(
+ builder.name,
+ builder.text,
+ builder.description,
+ builder.icon != null ? builder.icon : Icon.called("cube").build(),
+ builder.priority
+ );
pluginName = builder.pluginName;
showInPlayersTable = builder.showInPlayersTable;
tab = builder.tab;
@@ -136,7 +142,7 @@ public class ProviderInformation extends ExtensionDescription {
private String text;
private String description;
private Icon icon;
- private int priority;
+ private int priority = 0;
private boolean showInPlayersTable = false;
private String tab; // can be null
private Conditional condition; // can be null
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BooleanDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BooleanDataValue.java
new file mode 100644
index 000000000..1fb7c2fec
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BooleanDataValue.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+
+import java.util.function.Supplier;
+
+public class BooleanDataValue extends BuiltDataValue {
+ public BooleanDataValue(Boolean value, ProviderInformation information) {
+ super(value, information);
+ }
+
+ public BooleanDataValue(Supplier supplier, ProviderInformation information) {
+ super(supplier, information);
+ }
+}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BuiltDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BuiltDataValue.java
new file mode 100644
index 000000000..cb37a2b04
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/BuiltDataValue.java
@@ -0,0 +1,83 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.builder.DataValue;
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public abstract class BuiltDataValue implements DataValue {
+
+ private final T value;
+ private final Supplier supplier;
+ private final ProviderInformation information;
+
+ protected BuiltDataValue(T value, ProviderInformation information) {
+ this(value, null, information);
+ }
+
+ protected BuiltDataValue(Supplier supplier, ProviderInformation information) {
+ this(null, supplier, information);
+ }
+
+ private BuiltDataValue(T value, Supplier supplier, ProviderInformation information) {
+ this.value = value;
+ this.supplier = supplier;
+ this.information = information;
+ }
+
+ @Override
+ public T getValue() {
+ if (value != null) return value;
+ if (supplier != null) return supplier.get();
+ return null;
+ }
+
+ public ProviderInformation getInformation() {
+ return information;
+ }
+
+ @Override
+ public M getInformation(Class ofType) {
+ if (ProviderInformation.class.equals(ofType)) return ofType.cast(getInformation());
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BuiltDataValue> that = (BuiltDataValue>) o;
+ return Objects.equals(value, that.value) && Objects.equals(supplier, that.supplier) && Objects.equals(information, that.information);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, supplier, information);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{" +
+ "value=" + value +
+ ", supplier=" + supplier +
+ ", information=" + information +
+ '}';
+ }
+}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/DoubleDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/DoubleDataValue.java
new file mode 100644
index 000000000..c22f14cc9
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/DoubleDataValue.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+
+import java.util.function.Supplier;
+
+public class DoubleDataValue extends BuiltDataValue {
+ public DoubleDataValue(Double value, ProviderInformation information) {
+ super(value, information);
+ }
+
+ public DoubleDataValue(Supplier supplier, ProviderInformation information) {
+ super(supplier, information);
+ }
+}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java
new file mode 100644
index 000000000..2fa5075ee
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java
@@ -0,0 +1,149 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.DataExtension;
+import com.djrapitops.plan.extension.NotReadyException;
+import com.djrapitops.plan.extension.builder.DataValue;
+import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
+import com.djrapitops.plan.extension.builder.ValueBuilder;
+import com.djrapitops.plan.extension.implementation.providers.gathering.Conditions;
+
+import java.util.*;
+import java.util.function.Supplier;
+
+public class ExtDataBuilder implements ExtensionDataBuilder {
+
+ private final List values;
+ private final DataExtension extension;
+
+ public ExtDataBuilder(DataExtension extension) {
+ this.extension = extension;
+ values = new ArrayList<>();
+ }
+
+ @Override
+ public ValueBuilder valueBuilder(String text) {
+ if (text == null || text.isEmpty()) throw new IllegalArgumentException("'text' can't be null or empty");
+ return new ExtValueBuilder(text, extension);
+ }
+
+ @Override
+ public ExtensionDataBuilder addValue(Class ofType, DataValue dataValue) {
+ if (ofType != null && dataValue != null) values.add(new ClassValuePair(ofType, dataValue));
+ return this;
+ }
+
+ @Override
+ public ExtensionDataBuilder addValue(Class ofType, Supplier> dataValue) {
+ try {
+ if (ofType != null && dataValue != null) values.add(new ClassValuePair(ofType, dataValue.get()));
+ } catch (NotReadyException | UnsupportedOperationException ignored) {
+ // This exception is ignored by default to allow throwing errors inside the lambda to keep code clean.
+ }
+ // Other exceptions handled by ProviderValueGatherer during method call.
+ return this;
+ }
+
+ public List getValues() {
+ Collections.sort(values);
+ return values;
+ }
+
+ public String getExtensionName() {
+ return extension.getPluginName();
+ }
+
+ @Override
+ public void addAll(ExtensionDataBuilder builder) {
+ if (!(builder instanceof ExtDataBuilder)) return;
+ // From same DataExtension
+ if (!extension.getClass().equals(((ExtDataBuilder) builder).extension.getClass())) {
+ throw new IllegalArgumentException("Can not combine data from two different extensions! (" +
+ extension.getClass().getName() + ',' + ((ExtDataBuilder) builder).extension.getClass().getName() + ")");
+ }
+
+ this.values.addAll(((ExtDataBuilder) builder).values);
+ }
+
+ public static final class ClassValuePair implements Comparable {
+ private final Class> type;
+ private final DataValue> value;
+
+ public ClassValuePair(Class type, DataValue value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public Optional> getValue(Class ofType) {
+ if (type.equals(ofType)) {
+ return Optional.ofNullable((DataValue) value);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public int compareTo(ClassValuePair that) {
+ boolean thisIsBoolean = Boolean.class.isAssignableFrom(type) && value instanceof BooleanDataValue;
+ boolean otherIsBoolean = Boolean.class.isAssignableFrom(that.type) && that.value instanceof BooleanDataValue;
+ if (thisIsBoolean && !otherIsBoolean) {
+ return -1; // This is boolean, have it go first
+ } else if (!thisIsBoolean && otherIsBoolean) {
+ return 1; // Other is boolean, have it go first
+ } else if (thisIsBoolean) {
+ // Both are Booleans, so they might provide a condition
+
+ Optional otherCondition = ((BooleanDataValue) that.value).getInformation().getCondition();
+ String providedCondition = ((BooleanDataValue) value).getInformation().getProvidedCondition();
+ // Another provider's required condition is satisfied by this, have this first
+ if (otherCondition.filter(c -> Conditions.matchesCondition(c, providedCondition)).isPresent()) {
+ return -1;
+ }
+
+ // Required condition is satisfied by another provider, have that first
+ Optional condition = ((BooleanDataValue) value).getInformation().getCondition();
+ String otherProvidedCondition = ((BooleanDataValue) that.value).getInformation().getProvidedCondition();
+ if (condition.filter(c -> Conditions.matchesCondition(c, otherProvidedCondition)).isPresent()) {
+ return 1;
+ }
+ }
+ // Irrelevant, keep where is
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ClassValuePair that = (ClassValuePair) o;
+ return Objects.equals(type, that.type) && Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, value);
+ }
+
+ @Override
+ public String toString() {
+ return "ClassValuePair{" +
+ "type=" + type +
+ ", value=" + value +
+ '}';
+ }
+ }
+}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtValueBuilder.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtValueBuilder.java
new file mode 100644
index 000000000..7af5b4060
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtValueBuilder.java
@@ -0,0 +1,243 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.DataExtension;
+import com.djrapitops.plan.extension.FormatType;
+import com.djrapitops.plan.extension.annotation.BooleanProvider;
+import com.djrapitops.plan.extension.annotation.Conditional;
+import com.djrapitops.plan.extension.annotation.PluginInfo;
+import com.djrapitops.plan.extension.builder.DataValue;
+import com.djrapitops.plan.extension.builder.ValueBuilder;
+import com.djrapitops.plan.extension.extractor.ExtensionMethod;
+import com.djrapitops.plan.extension.icon.Color;
+import com.djrapitops.plan.extension.icon.Icon;
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+import com.djrapitops.plan.extension.table.Table;
+
+import java.util.function.Supplier;
+
+public class ExtValueBuilder implements ValueBuilder {
+
+ private final String pluginName;
+ private final String text;
+ private String providerName;
+ private String description;
+ private int priority = 0;
+ private boolean showInPlayerTable = false;
+ private Icon icon;
+ private String tabName;
+
+ private boolean hidden = false;
+ private boolean formatAsPlayerName = false;
+ private FormatType formatType = FormatType.NONE;
+ private Conditional conditional;
+
+ public ExtValueBuilder(String text, DataExtension extension) {
+ this.text = text;
+ pluginName = extension.getClass().getAnnotation(PluginInfo.class).name();
+ }
+
+ @Override
+ public ValueBuilder methodName(ExtensionMethod method) {
+ this.providerName = method.getMethod().getName();
+ return this;
+ }
+
+ @Override
+ public ValueBuilder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder priority(int priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder showInPlayerTable() {
+ this.showInPlayerTable = true;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder icon(Icon icon) {
+ this.icon = icon;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder showOnTab(String tabName) {
+ this.tabName = tabName;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder format(FormatType formatType) {
+ this.formatType = formatType;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder showAsPlayerPageLink() {
+ formatAsPlayerName = true;
+ return this;
+ }
+
+ @Override
+ public ValueBuilder hideFromUsers(BooleanProvider annotation) {
+ this.hidden = annotation.hidden();
+ return this;
+ }
+
+ @Override
+ public ValueBuilder conditional(Conditional conditional) {
+ this.conditional = conditional;
+ return this;
+ }
+
+ private ProviderInformation getProviderInformation() {
+ return getProviderInformation(false, null);
+ }
+
+ private ProviderInformation getBooleanProviderInformation(String providedCondition) {
+ return getProviderInformation(false, providedCondition);
+ }
+
+ private ProviderInformation getPercentageProviderInformation() {
+ return getProviderInformation(true, null);
+ }
+
+ private ProviderInformation getProviderInformation(boolean percentage, String providedCondition) {
+ ProviderInformation.Builder builder = ProviderInformation.builder(pluginName)
+ .setName(providerName != null ? providerName
+ : text.toLowerCase().replaceAll("\\s", ""))
+ .setText(text)
+ .setDescription(description)
+ .setPriority(priority)
+ .setIcon(icon)
+ .setShowInPlayersTable(showInPlayerTable)
+ .setTab(tabName)
+ .setPlayerName(formatAsPlayerName)
+ .setFormatType(formatType)
+ .setHidden(hidden)
+ .setCondition(conditional);
+
+ if (percentage) {
+ builder = builder.setAsPercentage();
+ }
+
+ if (providedCondition != null && !providedCondition.isEmpty()) {
+ builder = builder.setProvidedCondition(providedCondition);
+ }
+
+ return builder.build();
+ }
+
+ private ProviderInformation getTableProviderInformation(Color tableColor) {
+ return ProviderInformation.builder(pluginName)
+ .setName(providerName != null ? providerName
+ : text.toLowerCase().replaceAll("\\s", ""))
+ .setPriority(0)
+ .setCondition(conditional)
+ .setTab(tabName)
+ .setTableColor(tableColor)
+ .build();
+ }
+
+ @Override
+ public DataValue buildBoolean(boolean value) {
+ return new BooleanDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildBooleanProvidingCondition(boolean value, String providedCondition) {
+ return new BooleanDataValue(value, getBooleanProviderInformation(providedCondition));
+ }
+
+ @Override
+ public DataValue buildString(String value) {
+ return new StringDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildNumber(long value) {
+ return new NumberDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildDouble(double value) {
+ return new DoubleDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildPercentage(double value) {
+ return new DoubleDataValue(value, getPercentageProviderInformation());
+ }
+
+ @Override
+ public DataValue buildGroup(String[] groups) {
+ return new GroupsDataValue(groups, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildBoolean(Supplier value) {
+ return new BooleanDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildBooleanProvidingCondition(Supplier value, String providedCondition) {
+ return new BooleanDataValue(value, getBooleanProviderInformation(providedCondition));
+ }
+
+ @Override
+ public DataValue buildString(Supplier value) {
+ return new StringDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildNumber(Supplier value) {
+ return new NumberDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildDouble(Supplier value) {
+ return new DoubleDataValue(value, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildPercentage(Supplier percentage) {
+ return new DoubleDataValue(percentage, getPercentageProviderInformation());
+ }
+
+ @Override
+ public DataValue buildGroup(Supplier groups) {
+ return new GroupsDataValue(groups, getProviderInformation());
+ }
+
+ @Override
+ public DataValue buildTable(Table table, Color tableColor) {
+ return new TableDataValue(table, getTableProviderInformation(tableColor));
+ }
+
+ @Override
+ public DataValue buildTable(Supplier table, Color tableColor) {
+ return new TableDataValue(table, getTableProviderInformation(tableColor));
+ }
+}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/GroupsDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/GroupsDataValue.java
new file mode 100644
index 000000000..013e49291
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/GroupsDataValue.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+
+import java.util.function.Supplier;
+
+public class GroupsDataValue extends BuiltDataValue {
+ public GroupsDataValue(String[] value, ProviderInformation information) {
+ super(value, information);
+ }
+
+ public GroupsDataValue(Supplier supplier, ProviderInformation information) {
+ super(supplier, information);
+ }
+}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/NumberDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/NumberDataValue.java
new file mode 100644
index 000000000..46055e5f8
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/NumberDataValue.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+
+import java.util.function.Supplier;
+
+public class NumberDataValue extends BuiltDataValue {
+ public NumberDataValue(Long value, ProviderInformation information) {
+ super(value, information);
+ }
+
+ public NumberDataValue(Supplier supplier, ProviderInformation information) {
+ super(supplier, information);
+ }
+}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/StringDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/StringDataValue.java
new file mode 100644
index 000000000..1ace9ab50
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/StringDataValue.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+
+import java.util.function.Supplier;
+
+public class StringDataValue extends BuiltDataValue {
+ public StringDataValue(String value, ProviderInformation information) {
+ super(value, information);
+ }
+
+ public StringDataValue(Supplier supplier, ProviderInformation information) {
+ super(supplier, information);
+ }
+}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/TableDataValue.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/TableDataValue.java
new file mode 100644
index 000000000..ca27db653
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/TableDataValue.java
@@ -0,0 +1,32 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.extension.implementation.builder;
+
+import com.djrapitops.plan.extension.implementation.ProviderInformation;
+import com.djrapitops.plan.extension.table.Table;
+
+import java.util.function.Supplier;
+
+public class TableDataValue extends BuiltDataValue {
+ public TableDataValue(Table value, ProviderInformation information) {
+ super(value, information);
+ }
+
+ public TableDataValue(Supplier supplier, ProviderInformation information) {
+ super(supplier, information);
+ }
+}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/BooleanDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/BooleanDataProvider.java
deleted file mode 100644
index 7fbffe4e0..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/BooleanDataProvider.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.annotation.BooleanProvider;
-import com.djrapitops.plan.extension.annotation.Conditional;
-import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.ProviderInformation;
-
-import java.lang.reflect.Method;
-
-/**
- * Contains code that acts on {@link BooleanProvider} annotations.
- *
- * @author AuroraLS3
- */
-public class BooleanDataProvider {
-
- private BooleanDataProvider() {
- // Static method class
- }
-
- public static void placeToDataProviders(
- DataProviders dataProviders, Method method, BooleanProvider annotation,
- Conditional condition, String tab, String pluginName
- ) {
- ProviderInformation information = ProviderInformation.builder(pluginName)
- .setName(method.getName())
- .setText(annotation.text())
- .setDescription(annotation.description())
- .setPriority(annotation.priority())
- .setIcon(new Icon(
- annotation.iconFamily(),
- annotation.iconName(),
- annotation.iconColor())
- ).setShowInPlayersTable(annotation.showInPlayerTable())
- .setCondition(condition)
- .setTab(tab)
- .setHidden(annotation.hidden())
- .setProvidedCondition(annotation.conditionName())
- .build();
-
- MethodWrapper methodWrapper = new MethodWrapper<>(method, Boolean.class);
- dataProviders.put(new DataProvider<>(information, methodWrapper));
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DataProviders.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DataProviders.java
deleted file mode 100644
index dea2dd162..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DataProviders.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.implementation.MethodType;
-import com.djrapitops.plan.utilities.java.Lists;
-import com.djrapitops.plan.utilities.java.Maps;
-
-import java.util.*;
-
-/**
- * Group class for handling multiple different types of {@link DataProvider}s.
- *
- * @author AuroraLS3
- */
-public class DataProviders {
-
- private final Map, List>>> byMethodType;
-
- public DataProviders() {
- byMethodType = new EnumMap<>(MethodType.class);
- }
-
- public void put(DataProvider> provider) {
- MethodWrapper> method = provider.getMethod();
- MethodType methodType = method.getMethodType();
- Class> returnType = method.getReturnType();
-
- computeIfAbsent(methodType, returnType).add(provider);
- }
-
- private List> computeIfAbsent(MethodType methodType, Class> returnType) {
- return byMethodType.computeIfAbsent(methodType, Maps::create).computeIfAbsent(returnType, Lists::create);
- }
-
- public List> getProvidersByTypes(MethodType methodType, Class returnType) {
- Map, List>> byReturnType = byMethodType.getOrDefault(methodType, Collections.emptyMap());
- List> wildcardProvidersWithSpecificType = byReturnType.get(returnType);
- if (wildcardProvidersWithSpecificType == null) {
- return Collections.emptyList();
- }
- // Cast to T
- List> providers = new ArrayList<>();
- for (DataProvider> dataProvider : wildcardProvidersWithSpecificType) {
- providers.add((DataProvider) dataProvider);
- }
- return providers;
- }
-
- public List> getPlayerMethodsByType(Class returnType) {
- return getProvidersByTypes(MethodType.PLAYER, returnType);
- }
-
- public List> getServerMethodsByType(Class returnType) {
- return getProvidersByTypes(MethodType.SERVER, returnType);
- }
-
- public List> getGroupMethodsByType(Class returnType) {
- return getProvidersByTypes(MethodType.GROUP, returnType);
- }
-
- public void removeProviderWithMethod(MethodWrapper toRemove) {
- MethodType methodType = toRemove.getMethodType();
- Map, List>> byResultType = byMethodType.getOrDefault(methodType, Collections.emptyMap());
- if (byResultType.isEmpty()) {
- return;
- }
-
- Class returnType = toRemove.getReturnType();
- List> providers = getProvidersByTypes(methodType, returnType);
-
- DataProvider providerToRemove = null;
- for (DataProvider provider : providers) {
- if (provider.getMethod().equals(toRemove)) {
- providerToRemove = provider;
- break;
- }
- }
- providers.remove(providerToRemove);
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DoubleDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DoubleDataProvider.java
deleted file mode 100644
index 57dd81058..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/DoubleDataProvider.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.annotation.Conditional;
-import com.djrapitops.plan.extension.annotation.DoubleProvider;
-import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.ProviderInformation;
-
-import java.lang.reflect.Method;
-
-/**
- * Contains code that acts on {@link DoubleProvider} annotations.
- *
- * @author AuroraLS3
- */
-public class DoubleDataProvider {
-
- private DoubleDataProvider() {
- // Static method class
- }
-
- public static void placeToDataProviders(
- DataProviders dataProviders, Method method, DoubleProvider annotation,
- Conditional condition, String tab, String pluginName
- ) {
- ProviderInformation information = ProviderInformation.builder(pluginName)
- .setName(method.getName())
- .setText(annotation.text())
- .setDescription(annotation.description())
- .setPriority(annotation.priority())
- .setIcon(new Icon(
- annotation.iconFamily(),
- annotation.iconName(),
- annotation.iconColor())
- ).setShowInPlayersTable(annotation.showInPlayerTable())
- .setCondition(condition)
- .setTab(tab)
- .build();
-
- MethodWrapper methodWrapper = new MethodWrapper<>(method, Double.class);
-
- dataProviders.put(new DataProvider<>(information, methodWrapper));
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/GroupDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/GroupDataProvider.java
deleted file mode 100644
index 198599434..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/GroupDataProvider.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.annotation.Conditional;
-import com.djrapitops.plan.extension.annotation.GroupProvider;
-import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.ProviderInformation;
-
-import java.lang.reflect.Method;
-
-/**
- * Contains code that acts on {@link GroupProvider} annotations.
- *
- * @author AuroraLS3
- */
-public class GroupDataProvider {
-
- private GroupDataProvider() {
- // Static method class
- }
-
- public static void placeToDataProviders(
- DataProviders dataProviders, Method method, GroupProvider annotation,
- Conditional condition, String tab, String pluginName
- ) {
- ProviderInformation information = ProviderInformation.builder(pluginName)
- .setName(method.getName())
- .setText(annotation.text())
- .setPriority(0)
- .setIcon(new Icon(
- annotation.iconFamily(),
- annotation.iconName(),
- annotation.groupColor())
- ).setShowInPlayersTable(true)
- .setCondition(condition)
- .setTab(tab)
- .build();
-
- MethodWrapper methodWrapper = new MethodWrapper<>(method, String[].class);
- dataProviders.put(new DataProvider<>(information, methodWrapper));
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/MethodWrapper.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/MethodWrapper.java
index d46d196e1..104ca8930 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/MethodWrapper.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/MethodWrapper.java
@@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.providers;
+import com.djrapitops.plan.exceptions.DataExtensionMethodCallException;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.NotReadyException;
import com.djrapitops.plan.extension.implementation.MethodType;
@@ -23,6 +24,7 @@ import com.djrapitops.plan.extension.implementation.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
+import java.util.Optional;
/**
* Wrap a Method so that it is easier to call.
@@ -47,17 +49,22 @@ public class MethodWrapper {
try {
return returnType.cast(with.usingOn(extension, method));
} catch (InvocationTargetException notReadyToBeCalled) {
- if (notReadyToBeCalled.getCause() instanceof NotReadyException
- || notReadyToBeCalled.getCause() instanceof UnsupportedOperationException) {
+ Throwable cause = notReadyToBeCalled.getCause();
+ if (cause instanceof NotReadyException || cause instanceof UnsupportedOperationException) {
return null; // Data or API not available to make the call.
} else {
- throw new IllegalArgumentException(method.getDeclaringClass() + " method " + method.getName() + " could not be called: " + notReadyToBeCalled.getMessage(), notReadyToBeCalled);
+ throw new DataExtensionMethodCallException(getErrorMessage(extension, notReadyToBeCalled), notReadyToBeCalled, extension.getPluginName(), getMethodName());
}
} catch (IllegalAccessException e) {
- throw new IllegalArgumentException(method.getDeclaringClass() + " method " + method.getName() + " could not be called: " + e.getMessage(), e);
+ throw new DataExtensionMethodCallException(extension.getPluginName() + '.' + getMethodName() + " could not be accessed: " + e.getMessage(), e, extension.getPluginName(), getMethodName());
}
}
+ private String getErrorMessage(DataExtension extension, InvocationTargetException e) {
+ Optional actualCause = Optional.ofNullable(e.getCause()); // InvocationTargetException
+ return extension.getPluginName() + '.' + getMethodName() + " errored: " + actualCause.orElse(e).toString();
+ }
+
public String getMethodName() {
return method.getName();
}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/NumberDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/NumberDataProvider.java
deleted file mode 100644
index 1a95573e0..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/NumberDataProvider.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.annotation.Conditional;
-import com.djrapitops.plan.extension.annotation.NumberProvider;
-import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.ProviderInformation;
-
-import java.lang.reflect.Method;
-
-/**
- * Contains code that acts on {@link NumberProvider} annotations.
- *
- * @author AuroraLS3
- */
-public class NumberDataProvider {
-
- private NumberDataProvider() {
- // Static method class
- }
-
- public static void placeToDataProviders(
- DataProviders dataProviders, Method method, NumberProvider annotation,
- Conditional condition, String tab, String pluginName
- ) {
- ProviderInformation information = ProviderInformation.builder(pluginName)
- .setName(method.getName())
- .setText(annotation.text())
- .setDescription(annotation.description())
- .setPriority(annotation.priority())
- .setIcon(new Icon(
- annotation.iconFamily(),
- annotation.iconName(),
- annotation.iconColor())
- ).setShowInPlayersTable(annotation.showInPlayerTable())
- .setCondition(condition)
- .setTab(tab)
- .setFormatType(annotation.format())
- .build();
-
- MethodWrapper methodWrapper = new MethodWrapper<>(method, Long.class);
- dataProviders.put(new DataProvider<>(information, methodWrapper));
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/PercentageDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/PercentageDataProvider.java
deleted file mode 100644
index 4f4b400ab..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/PercentageDataProvider.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.annotation.Conditional;
-import com.djrapitops.plan.extension.annotation.PercentageProvider;
-import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.ProviderInformation;
-
-import java.lang.reflect.Method;
-
-/**
- * Contains code that acts on {@link PercentageProvider} annotations.
- *
- * @author AuroraLS3
- */
-public class PercentageDataProvider {
-
- private PercentageDataProvider() {
- // Static method class
- }
-
- public static void placeToDataProviders(
- DataProviders dataProviders, Method method, PercentageProvider annotation,
- Conditional condition, String tab, String pluginName
- ) {
- ProviderInformation information = ProviderInformation.builder(pluginName)
- .setName(method.getName())
- .setText(annotation.text())
- .setDescription(annotation.description())
- .setPriority(annotation.priority())
- .setIcon(new Icon(
- annotation.iconFamily(),
- annotation.iconName(),
- annotation.iconColor())
- ).setShowInPlayersTable(annotation.showInPlayerTable())
- .setCondition(condition)
- .setTab(tab)
- .setAsPercentage()
- .build();
-
- MethodWrapper methodWrapper = new MethodWrapper<>(method, Double.class);
-
- dataProviders.put(new DataProvider<>(information, methodWrapper));
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/StringDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/StringDataProvider.java
deleted file mode 100644
index 8a588f423..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/StringDataProvider.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see .
- */
-package com.djrapitops.plan.extension.implementation.providers;
-
-import com.djrapitops.plan.extension.annotation.Conditional;
-import com.djrapitops.plan.extension.annotation.StringProvider;
-import com.djrapitops.plan.extension.icon.Icon;
-import com.djrapitops.plan.extension.implementation.ProviderInformation;
-
-import java.lang.reflect.Method;
-
-/**
- * Contains code that acts on {@link StringProvider} annotations.
- *
- * @author AuroraLS3
- */
-public class StringDataProvider {
-
- private StringDataProvider() {
- // Static method class
- }
-
- public static void placeToDataProviders(
- DataProviders dataProviders, Method method, StringProvider annotation,
- Conditional condition, String tab, String pluginName
- ) {
- ProviderInformation information = ProviderInformation.builder(pluginName)
- .setName(method.getName())
- .setText(annotation.text())
- .setDescription(annotation.description())
- .setPriority(annotation.priority())
- .setIcon(new Icon(
- annotation.iconFamily(),
- annotation.iconName(),
- annotation.iconColor())
- ).setShowInPlayersTable(annotation.showInPlayerTable())
- .setCondition(condition)
- .setTab(tab)
- .setPlayerName(annotation.playerName())
- .build();
-
- MethodWrapper methodWrapper = new MethodWrapper<>(method, String.class);
- dataProviders.put(new DataProvider<>(information, methodWrapper));
- }
-}
\ No newline at end of file
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/TableDataProvider.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/TableDataProvider.java
deleted file mode 100644
index 8c0ed66c2..000000000
--- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/TableDataProvider.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * This file is part of Player Analytics (Plan).
- *
- * Plan is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License v3 as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Plan is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Plan. If not, see