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
This commit is contained in:
Risto Lahtela 2021-04-07 19:09:38 +03:00 committed by GitHub
parent 175094b6d6
commit 113d46669b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 2982 additions and 1387 deletions

View File

@ -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}
*/

View File

@ -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.
* <p>
@ -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.
* <hr>
* <p>
* Methods can have one of the following as method parameters:
@ -90,4 +96,30 @@ public interface DataExtension {
};
}
/**
* Obtain a new {@link ExtensionDataBuilder}.
* <p>
* 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.
* <p>
* 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();
}
}

View File

@ -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<Caller> register(DataExtension extension);
/**
* Obtain a new {@link ExtensionDataBuilder}, it is recommended to use {@link DataExtension#newExtensionDataBuilder()}.
* <p>
* 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.
* <p>

View File

@ -53,5 +53,4 @@ public @interface Conditional {
* @return {@code false} by default.
*/
boolean negated() default false;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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}.
* <p>
* Usage: {@code @DataBuilderProvider ExtensionDataBuilder method(UUID playerUUID)}
* <p>
* ExtensionDataBuilder can be obtained with {@link DataExtension#newExtensionDataBuilder()}.
*
* @author AuroraLS3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataBuilderProvider {
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.builder;
import java.util.Optional;
/**
* Represents a value given to {@link ExtensionDataBuilder}.
* <p>
* Please do not implement this class, it is an implementation detail.
* Obtain instances with {@link ValueBuilder}.
*
* @param <T> Type of the value.
*/
public interface DataValue<T> {
T getValue();
<M> M getInformation(Class<M> ofType);
default <I extends DataValue<T>> Optional<I> getMetadata(Class<I> metadataType) {
if (getClass().equals(metadataType)) return Optional.of(metadataType.cast(this));
return Optional.empty();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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.
* <p>
* Requires Capability DATA_EXTENSION_BUILDER_API
* <p>
* Obtain an instance with {@link DataExtension#newExtensionDataBuilder()}
* <p>
* 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.
* <p>
* Using same text for two values can be problematic as the text is used for id in the database.
* <p>
* 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 <T> Type of the data.
* @return This builder.
* @throws IllegalArgumentException If either parameter is null
*/
<T> ExtensionDataBuilder addValue(Class<T> ofType, DataValue<T> 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 <T> Type of the data.
* @return This builder.
* @throws IllegalArgumentException If either parameter is null
*/
<T> ExtensionDataBuilder addValue(Class<T> ofType, Supplier<DataValue<T>> 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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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)}.
* <p>
* Requires Capability DATA_EXTENSION_BUILDER_API
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Boolean> 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<Boolean> 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<String> buildString(String value);
/**
* Build a Number.
*
* @param value a non-floating point number.
* @return a data value to give to {@link ExtensionDataBuilder}.
*/
DataValue<Long> buildNumber(long value);
/**
* Build a Floating point number.
*
* @param value a floating point number.
* @return a data value to give to {@link ExtensionDataBuilder}.
*/
DataValue<Double> 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<Double> 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<String[]> 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<Table> buildTable(Table table, Color tableColor);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildBoolean(boolean).
*/
DataValue<Boolean> buildBoolean(Supplier<Boolean> value);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildBooleanProvidingCondition(boolean, String).
*/
DataValue<Boolean> buildBooleanProvidingCondition(Supplier<Boolean> value, String providedCondition);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildString(String)
*/
DataValue<String> buildString(Supplier<String> value);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildNumber(long)
*/
DataValue<Long> buildNumber(Supplier<Long> value);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildDouble(double)
*/
DataValue<Double> buildDouble(Supplier<Double> value);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildPercentage(double)
*/
DataValue<Double> buildPercentage(Supplier<Double> percentage);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildGroup(String[])
*/
DataValue<String[]> buildGroup(Supplier<String[]> groups);
/**
* Lambda version for conditional return or throwing {@link com.djrapitops.plan.extension.NotReadyException}.
*
* @see ValueBuilder#buildTable(Table, Color)
*/
DataValue<Table> buildTable(Supplier<Table> 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;
}
}

View File

@ -0,0 +1,6 @@
/**
* DataExtension Builder API.
* <p>
* Requires Capability DATA_EXTENSION_BUILDER_API
*/
package com.djrapitops.plan.extension.builder;

View File

@ -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<String> warnings = new ArrayList<>();
private PluginInfo pluginInfo;
private TabOrder tabOrder;
private List<TabInfo> tabInformation;
private List<InvalidateMethod> invalidMethods;
private MethodAnnotations methodAnnotations;
private Map<ExtensionMethod.ParameterType, ExtensionMethods> methods;
private Collection<Method> conditionalMethods;
private Collection<Tab> 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 <T extends DataExtension> String getPluginName(Class<T> extensionClass) {
return getClassAnnotation(extensionClass, PluginInfo.class).map(PluginInfo::name)
.orElseThrow(() -> new IllegalArgumentException("Given class had no PluginInfo annotation"));
}
private static <V extends DataExtension, T extends Annotation> Optional<T> getClassAnnotation(Class<V> from, Class<T> ofClass) {
return Optional.ofNullable(from.getAnnotation(ofClass));
}
public static <T extends DataExtension> String getPluginName(Class<T> 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<ExtensionMethod> getExtensionMethods() {
List<ExtensionMethod> 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 <T> void validateReturnType(Method method, Class<T> 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,28 +236,15 @@ public final class ExtensionExtractor {
// Has valid parameter & it is acceptable.
}
private void validateMethodAnnotations() {
validateBooleanProviderAnnotations();
validateNumberProviderAnnotations();
validateDoubleProviderAnnotations();
validatePercentageProviderAnnotations();
validateStringProviderAnnotations();
validateTableProviderAnnotations();
validateGroupProviderAnnotations();
}
private void validateBooleanProviderAnnotations() {
for (Map.Entry<Method, BooleanProvider> booleanProvider : methodAnnotations.getMethodAnnotations(BooleanProvider.class).entrySet()) {
Method method = booleanProvider.getKey();
BooleanProvider annotation = booleanProvider.getValue();
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);
String condition = MethodAnnotations.get(method, Conditional.class).map(Conditional::value).orElse(null);
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());
}
@ -216,104 +253,91 @@ public final class ExtensionExtractor {
throw new IllegalArgumentException(extensionName + "." + method.getName() + " can not be 'hidden' without a 'conditionName'");
}
}
}
private void validateNumberProviderAnnotations() {
for (Map.Entry<Method, NumberProvider> 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);
}
}
private void validateDoubleProviderAnnotations() {
for (Map.Entry<Method, DoubleProvider> 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);
}
}
private void validatePercentageProviderAnnotations() {
for (Map.Entry<Method, PercentageProvider> 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);
}
}
private void validateStringProviderAnnotations() {
for (Map.Entry<Method, StringProvider> 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);
}
}
private void validateTableProviderAnnotations() {
for (Method method : methodAnnotations.getMethodAnnotations(TableProvider.class).keySet()) {
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<Method, GroupProvider> 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);
}
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<Conditional> conditionals = methodAnnotations.getAnnotations(Conditional.class);
Collection<BooleanProvider> conditionProviders = methodAnnotations.getAnnotations(BooleanProvider.class);
Set<String> 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<Method> 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 <T extends Annotation> Optional<T> getClassAnnotation(Class<T> 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<String> 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<Method, Tab> tabs = this.methodAnnotations.getMethodAnnotations(Tab.class);
Set<String> tabNames = tabs.values().stream().map(Tab::value).collect(Collectors.toSet());
Set<String> 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<Method, Tab> 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<TabOrder> getTabOrder() {
return Optional.ofNullable(tabOrder);
return getClassAnnotation(TabOrder.class);
}
public Collection<Tab> getTabAnnotations() {
if (tabAnnotations == null) extractMethods();
return tabAnnotations;
}
public List<TabInfo> 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<ExtensionMethod.ParameterType, ExtensionMethods> getMethods() {
if (methods == null) extractMethods();
return methods;
}
public List<InvalidateMethod> getInvalidateMethodAnnotations() {
return invalidMethods != null ? invalidMethods : Collections.emptyList();
if (invalidMethods == null) extractInvalidMethods();
return invalidMethods;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 <T extends Annotation> Optional<T> getAnnotation(Class<T> ofType) {
return Optional.ofNullable(method.getAnnotation(ofType));
}
public <T extends Annotation> T getExistingAnnotation(Class<T> ofType) {
return getAnnotation(ofType).orElseThrow(() -> new IllegalArgumentException(method.getName() + " did not have " + ofType.getName() + " annotation"));
}
public <T extends Annotation> T getAnnotationOrNull(Class<T> 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 +
'}';
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ExtensionMethod> booleanProviders;
private final List<ExtensionMethod> numberProviders;
private final List<ExtensionMethod> doubleProviders;
private final List<ExtensionMethod> percentageProviders;
private final List<ExtensionMethod> stringProviders;
private final List<ExtensionMethod> tableProviders;
private final List<ExtensionMethod> groupProviders;
private final List<ExtensionMethod> 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<ExtensionMethod> getBooleanProviders() {
return booleanProviders;
}
public List<ExtensionMethod> getNumberProviders() {
return numberProviders;
}
public List<ExtensionMethod> getDoubleProviders() {
return doubleProviders;
}
public List<ExtensionMethod> getPercentageProviders() {
return percentageProviders;
}
public List<ExtensionMethod> getStringProviders() {
return stringProviders;
}
public List<ExtensionMethod> getTableProviders() {
return tableProviders;
}
public List<ExtensionMethod> getGroupProviders() {
return groupProviders;
}
public List<ExtensionMethod> 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 +
'}';
}
}

View File

@ -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<Class<?>, Map<Method, Annotation>> byAnnotationType;

View File

@ -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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> buildExpectedExtensionMethodMap(DataExtension extension, BiConsumer<ExtensionMethods, ExtensionMethod> addTo) throws NoSuchMethodException {
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<ExtensionMethod.ParameterType, ExtensionMethods> result = underTest.getMethods();
Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<Tab> expected = Collections.singletonList(extension.getClass().getMethod("method").getAnnotation(Tab.class));
Collection<Tab> 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()
);
}
}

View File

@ -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;
}

View File

@ -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<MethodWrapper<?>> getMethod() {
// method is transient and might be lost if flushed to disk.
return Optional.ofNullable(method);
public Optional<String> getMethodName() {
return Optional.ofNullable(methodName);
}
}

View File

@ -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<String, ProviderValueGatherer> extensionGatherers;
private final Map<String, DataValueGatherer> extensionGatherers;
@Inject
public ExtensionSvc(
@ -98,17 +98,17 @@ public class ExtensionSvc implements ExtensionService {
}
@Override
public Optional<Caller> register(DataExtension extension) {
ExtensionWrapper extractor = new ExtensionWrapper(extension);
String pluginName = extractor.getPluginName();
public Optional<Caller> 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());
}
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());
}
}
}

View File

@ -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
) {

View File

@ -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<TabInfo> tabInformation;
private final Map<ExtensionMethod.ParameterType, ExtensionMethods> 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<TabInformation> getPluginTabs() {
Map<String, TabInfo> tabInformation = extractor.getTabInformation()
.stream().collect(Collectors.toMap(TabInfo::tab, Function.identity(), (one, two) -> one));
Map<String, Integer> order = getTabOrder().map(this::orderToMap).orElse(new HashMap<>());
// Extracts PluginTabs
return extractor.getMethodAnnotations().getAnnotations(Tab.class).stream()
Set<String> usedTabs = extractor.getTabAnnotations().stream()
.map(Tab::value)
.distinct()
.map(tabName -> {
Optional<TabInfo> 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<Method, Tab> tabs = methodAnnotations.getMethodAnnotations(Tab.class);
Map<Method, Conditional> 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 <T extends Annotation> void extractProviders(PluginInfo pluginInfo, Map<Method, Tab> tabs, Map<Method, Conditional> conditions, Class<T> ofKind, DataProviderFactory<T> factory) {
String pluginName = pluginInfo.name();
for (Map.Entry<Method, T> entry : extractor.getMethodAnnotations().getMethodAnnotations(ofKind).entrySet()) {
Method method = entry.getKey();
T annotation = entry.getValue();
Conditional conditional = conditions.get(method);
Optional<Tab> tab = Optional.ofNullable(tabs.get(method));
factory.placeToDataProviders(
providers, method, annotation,
conditional,
tab.map(Tab::value).orElse(null),
pluginName
);
}
}
public Collection<String> getWarnings() {
return extractor.getWarnings();
}
/**
* Functional interface for defining a method that places required DataProvider to DataProviders.
*
* @param <T> Type of the annotation on the method that is going to be extracted.
*/
interface DataProviderFactory<T extends Annotation> {
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<TabInfo> getTabInformation() {
return tabInformation;
}
public Map<ExtensionMethod.ParameterType, ExtensionMethods> getMethods() {
return methods;
}
}

View File

@ -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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import java.util.function.Supplier;
public class BooleanDataValue extends BuiltDataValue<Boolean> {
public BooleanDataValue(Boolean value, ProviderInformation information) {
super(value, information);
}
public BooleanDataValue(Supplier<Boolean> supplier, ProviderInformation information) {
super(supplier, information);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<T> implements DataValue<T> {
private final T value;
private final Supplier<T> supplier;
private final ProviderInformation information;
protected BuiltDataValue(T value, ProviderInformation information) {
this(value, null, information);
}
protected BuiltDataValue(Supplier<T> supplier, ProviderInformation information) {
this(null, supplier, information);
}
private BuiltDataValue(T value, Supplier<T> 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> M getInformation(Class<M> 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 +
'}';
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import java.util.function.Supplier;
public class DoubleDataValue extends BuiltDataValue<Double> {
public DoubleDataValue(Double value, ProviderInformation information) {
super(value, information);
}
public DoubleDataValue(Supplier<Double> supplier, ProviderInformation information) {
super(supplier, information);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ClassValuePair> 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 <T> ExtensionDataBuilder addValue(Class<T> ofType, DataValue<T> dataValue) {
if (ofType != null && dataValue != null) values.add(new ClassValuePair(ofType, dataValue));
return this;
}
@Override
public <T> ExtensionDataBuilder addValue(Class<T> ofType, Supplier<DataValue<T>> 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<ClassValuePair> 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<ClassValuePair> {
private final Class<?> type;
private final DataValue<?> value;
public <T> ClassValuePair(Class<T> type, DataValue<T> value) {
this.type = type;
this.value = value;
}
public <T> Optional<DataValue<T>> getValue(Class<T> ofType) {
if (type.equals(ofType)) {
return Optional.ofNullable((DataValue<T>) 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<String> 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<String> 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 +
'}';
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Boolean> buildBoolean(boolean value) {
return new BooleanDataValue(value, getProviderInformation());
}
@Override
public DataValue<Boolean> buildBooleanProvidingCondition(boolean value, String providedCondition) {
return new BooleanDataValue(value, getBooleanProviderInformation(providedCondition));
}
@Override
public DataValue<String> buildString(String value) {
return new StringDataValue(value, getProviderInformation());
}
@Override
public DataValue<Long> buildNumber(long value) {
return new NumberDataValue(value, getProviderInformation());
}
@Override
public DataValue<Double> buildDouble(double value) {
return new DoubleDataValue(value, getProviderInformation());
}
@Override
public DataValue<Double> buildPercentage(double value) {
return new DoubleDataValue(value, getPercentageProviderInformation());
}
@Override
public DataValue<String[]> buildGroup(String[] groups) {
return new GroupsDataValue(groups, getProviderInformation());
}
@Override
public DataValue<Boolean> buildBoolean(Supplier<Boolean> value) {
return new BooleanDataValue(value, getProviderInformation());
}
@Override
public DataValue<Boolean> buildBooleanProvidingCondition(Supplier<Boolean> value, String providedCondition) {
return new BooleanDataValue(value, getBooleanProviderInformation(providedCondition));
}
@Override
public DataValue<String> buildString(Supplier<String> value) {
return new StringDataValue(value, getProviderInformation());
}
@Override
public DataValue<Long> buildNumber(Supplier<Long> value) {
return new NumberDataValue(value, getProviderInformation());
}
@Override
public DataValue<Double> buildDouble(Supplier<Double> value) {
return new DoubleDataValue(value, getProviderInformation());
}
@Override
public DataValue<Double> buildPercentage(Supplier<Double> percentage) {
return new DoubleDataValue(percentage, getPercentageProviderInformation());
}
@Override
public DataValue<String[]> buildGroup(Supplier<String[]> groups) {
return new GroupsDataValue(groups, getProviderInformation());
}
@Override
public DataValue<Table> buildTable(Table table, Color tableColor) {
return new TableDataValue(table, getTableProviderInformation(tableColor));
}
@Override
public DataValue<Table> buildTable(Supplier<Table> table, Color tableColor) {
return new TableDataValue(table, getTableProviderInformation(tableColor));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import java.util.function.Supplier;
public class GroupsDataValue extends BuiltDataValue<String[]> {
public GroupsDataValue(String[] value, ProviderInformation information) {
super(value, information);
}
public GroupsDataValue(Supplier<String[]> supplier, ProviderInformation information) {
super(supplier, information);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import java.util.function.Supplier;
public class NumberDataValue extends BuiltDataValue<Long> {
public NumberDataValue(Long value, ProviderInformation information) {
super(value, information);
}
public NumberDataValue(Supplier<Long> supplier, ProviderInformation information) {
super(supplier, information);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import java.util.function.Supplier;
public class StringDataValue extends BuiltDataValue<String> {
public StringDataValue(String value, ProviderInformation information) {
super(value, information);
}
public StringDataValue(Supplier<String> supplier, ProviderInformation information) {
super(supplier, information);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Table> {
public TableDataValue(Table value, ProviderInformation information) {
super(value, information);
}
public TableDataValue(Supplier<Table> supplier, ProviderInformation information) {
super(supplier, information);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Boolean> methodWrapper = new MethodWrapper<>(method, Boolean.class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<MethodType, Map<Class<?>, List<DataProvider<?>>>> 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<DataProvider<?>> computeIfAbsent(MethodType methodType, Class<?> returnType) {
return byMethodType.computeIfAbsent(methodType, Maps::create).computeIfAbsent(returnType, Lists::create);
}
public <T> List<DataProvider<T>> getProvidersByTypes(MethodType methodType, Class<T> returnType) {
Map<Class<?>, List<DataProvider<?>>> byReturnType = byMethodType.getOrDefault(methodType, Collections.emptyMap());
List<DataProvider<?>> wildcardProvidersWithSpecificType = byReturnType.get(returnType);
if (wildcardProvidersWithSpecificType == null) {
return Collections.emptyList();
}
// Cast to T
List<DataProvider<T>> providers = new ArrayList<>();
for (DataProvider<?> dataProvider : wildcardProvidersWithSpecificType) {
providers.add((DataProvider<T>) dataProvider);
}
return providers;
}
public <T> List<DataProvider<T>> getPlayerMethodsByType(Class<T> returnType) {
return getProvidersByTypes(MethodType.PLAYER, returnType);
}
public <T> List<DataProvider<T>> getServerMethodsByType(Class<T> returnType) {
return getProvidersByTypes(MethodType.SERVER, returnType);
}
public <T> List<DataProvider<T>> getGroupMethodsByType(Class<T> returnType) {
return getProvidersByTypes(MethodType.GROUP, returnType);
}
public <T> void removeProviderWithMethod(MethodWrapper<T> toRemove) {
MethodType methodType = toRemove.getMethodType();
Map<Class<?>, List<DataProvider<?>>> byResultType = byMethodType.getOrDefault(methodType, Collections.emptyMap());
if (byResultType.isEmpty()) {
return;
}
Class<T> returnType = toRemove.getReturnType();
List<DataProvider<T>> providers = getProvidersByTypes(methodType, returnType);
DataProvider<T> providerToRemove = null;
for (DataProvider<T> provider : providers) {
if (provider.getMethod().equals(toRemove)) {
providerToRemove = provider;
break;
}
}
providers.remove(providerToRemove);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Double> methodWrapper = new MethodWrapper<>(method, Double.class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String[]> methodWrapper = new MethodWrapper<>(method, String[].class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -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<T> {
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<Throwable> actualCause = Optional.ofNullable(e.getCause()); // InvocationTargetException
return extension.getPluginName() + '.' + getMethodName() + " errored: " + actualCause.orElse(e).toString();
}
public String getMethodName() {
return method.getName();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Long> methodWrapper = new MethodWrapper<>(method, Long.class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Double> methodWrapper = new MethodWrapper<>(method, Double.class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> methodWrapper = new MethodWrapper<>(method, String.class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.providers;
import com.djrapitops.plan.extension.annotation.Conditional;
import com.djrapitops.plan.extension.annotation.TableProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.table.Table;
import java.lang.reflect.Method;
/**
* Contains code that acts on {@link TableProvider} annotations.
* <p>
* Please note that not all {@link ProviderInformation} is present in this annotation.
*
* @author AuroraLS3
*/
public class TableDataProvider {
private TableDataProvider() {
// Static method class
}
public static void placeToDataProviders(
DataProviders dataProviders, Method method, TableProvider annotation,
Conditional condition, String tab, String pluginName
) {
ProviderInformation information = ProviderInformation.builder(pluginName)
.setName(method.getName())
.setPriority(0)
.setCondition(condition)
.setTab(tab)
.setTableColor(annotation.tableColor())
.build();
MethodWrapper<Table> methodWrapper = new MethodWrapper<>(method, Table.class);
dataProviders.put(new DataProvider<>(information, methodWrapper));
}
}

View File

@ -1,160 +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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.providers.gathering;
import com.djrapitops.plan.exceptions.DataExtensionMethodCallException;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.implementation.ExtensionWrapper;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.providers.DataProviders;
import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerBooleanResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerBooleanResultTransaction;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Gathers BooleanProvider method data.
*
* @author AuroraLS3
*/
class BooleanProviderValueGatherer {
private final String pluginName;
private final DataExtension extension;
private final ServerUUID serverUUID;
private final Database database;
private final DataProviders dataProviders;
BooleanProviderValueGatherer(
String pluginName,
ServerUUID serverUUID, Database database,
ExtensionWrapper extensionWrapper
) {
this.pluginName = pluginName;
this.extension = extensionWrapper.getExtension();
this.serverUUID = serverUUID;
this.database = database;
this.dataProviders = extensionWrapper.getProviders();
}
Conditions gatherBooleanDataOfPlayer(UUID playerUUID, String playerName) {
Conditions conditions = new Conditions();
List<DataProvider<Boolean>> unsatisfiedProviders = new ArrayList<>(dataProviders.getPlayerMethodsByType(Boolean.class));
Set<DataProvider<Boolean>> satisfied;
// Method parameters abstracted away so that same method can be used for all parameter types
// Same with Method result store transaction creation
Function<MethodWrapper<Boolean>, Callable<Boolean>> methodCaller = method -> () -> method.callMethod(extension, Parameters.player(serverUUID, playerUUID, playerName));
BiFunction<MethodWrapper<Boolean>, Boolean, Transaction> storeTransactionCreator = (method, result) -> new StorePlayerBooleanResultTransaction(pluginName, serverUUID, method.getMethodName(), playerUUID, result);
do {
// Loop through all unsatisfied providers to see if more conditions are satisfied
satisfied = attemptToSatisfyMoreConditionsAndStoreResults(methodCaller, storeTransactionCreator, conditions, unsatisfiedProviders);
// Remove now satisfied Providers so that they are not called again
unsatisfiedProviders.removeAll(satisfied);
// If no new conditions could be satisfied, stop looping.
} while (!satisfied.isEmpty());
return conditions;
}
Conditions gatherBooleanDataOfServer() {
Conditions conditions = new Conditions();
List<DataProvider<Boolean>> unsatisfiedProviders = new ArrayList<>(dataProviders.getServerMethodsByType(Boolean.class));
Set<DataProvider<Boolean>> satisfied;
// Method parameters abstracted away so that same method can be used for all parameter types
// Same with Method result store transaction creation
Function<MethodWrapper<Boolean>, Callable<Boolean>> methodCaller = method -> () -> method.callMethod(extension, Parameters.server(serverUUID));
BiFunction<MethodWrapper<Boolean>, Boolean, Transaction> storeTransactionCreator = (method, result) -> new StoreServerBooleanResultTransaction(pluginName, serverUUID, method.getMethodName(), result);
do {
// Loop through all unsatisfied providers to see if more conditions are satisfied
satisfied = attemptToSatisfyMoreConditionsAndStoreResults(methodCaller, storeTransactionCreator, conditions, unsatisfiedProviders);
// Remove now satisfied Providers so that they are not called again
unsatisfiedProviders.removeAll(satisfied);
// If no new conditions could be satisfied, stop looping.
} while (!satisfied.isEmpty());
return conditions;
}
private Set<DataProvider<Boolean>> attemptToSatisfyMoreConditionsAndStoreResults(
Function<MethodWrapper<Boolean>, Callable<Boolean>> methodCaller,
BiFunction<MethodWrapper<Boolean>, Boolean, Transaction> storeTransactionCreator,
Conditions conditions, List<DataProvider<Boolean>> unsatisfiedProviders
) {
Set<DataProvider<Boolean>> satisfied = new HashSet<>();
for (DataProvider<Boolean> booleanProvider : unsatisfiedProviders) {
ProviderInformation information = booleanProvider.getProviderInformation();
Optional<String> condition = information.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
// Condition required by the BooleanProvider is not satisfied
continue;
}
String providedCondition = information.getProvidedCondition();
MethodWrapper<Boolean> method = booleanProvider.getMethod();
Boolean result = getMethodResult(methodCaller.apply(method), method);
if (result == null) {
// Error during method call
satisfied.add(booleanProvider); // Prevents further attempts to call this provider for this player.
continue;
}
if (providedCondition != null) {
if (Boolean.TRUE.equals(result)) {
// The condition was fulfilled (true) for this player.
conditions.conditionFulfilled(providedCondition);
} else {
// The negated condition was fulfilled (false) for this player.
conditions.conditionFulfilled("not_" + providedCondition);
}
}
satisfied.add(booleanProvider); // Prevents further attempts to call this provider for this player.
database.executeTransaction(new StoreIconTransaction(information.getIcon()));
database.executeTransaction(new StoreProviderTransaction(booleanProvider, serverUUID));
database.executeTransaction(storeTransactionCreator.apply(method, result));
}
return satisfied;
}
private <T> T getMethodResult(Callable<T> callable, MethodWrapper<T> method) {
try {
return callable.call();
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) {
throw new DataExtensionMethodCallException(e, pluginName, method);
}
}
}

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.extension.implementation.providers.gathering;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
@ -36,7 +37,14 @@ public class Conditions {
return !fulfilledConditions.contains(condition);
}
public static boolean matchesCondition(String condition1, String condition2) {
return Objects.equals(condition1, condition2) || // condition == condition, not_condition == not_condition
Objects.equals("not_" + condition1, condition2) || // condition == not_condition
Objects.equals(condition1, "not_" + condition2); // not_condition == condition
}
public void conditionFulfilled(String condition) {
if (condition == null || condition.isEmpty()) return;
fulfilledConditions.add(condition);
}
}

View File

@ -0,0 +1,455 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.providers.gathering;
import com.djrapitops.plan.exceptions.DataExtensionMethodCallException;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.annotation.*;
import com.djrapitops.plan.extension.builder.DataValue;
import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
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.ExtensionWrapper;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.builder.*;
import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.StorePluginTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreTabInformationTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreTableProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.*;
import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/**
* Object that can be called to place data about players to the database.
*
* @author AuroraLS3
*/
public class DataValueGatherer {
private final CallEvents[] callEvents;
private final ExtensionWrapper extension;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private final ErrorLogger errorLogger;
private final Set<ExtensionMethod> brokenMethods;
public DataValueGatherer(
ExtensionWrapper extension,
DBSystem dbSystem,
ServerInfo serverInfo,
ErrorLogger errorLogger
) {
this.callEvents = extension.getCallEvents();
this.extension = extension;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
this.errorLogger = errorLogger;
this.brokenMethods = new HashSet<>();
}
public boolean shouldSkipEvent(CallEvents event) {
if (event == CallEvents.MANUAL) {
return false;
}
for (CallEvents accepted : callEvents) {
if (event == accepted) {
return false;
}
}
return true;
}
public String getPluginName() {
return extension.getPluginName();
}
public void storeExtensionInformation() {
String pluginName = extension.getPluginName();
Icon pluginIcon = extension.getPluginIcon();
long time = System.currentTimeMillis();
ServerUUID serverUUID = serverInfo.getServerUUID();
Database database = dbSystem.getDatabase();
database.executeTransaction(new StoreIconTransaction(pluginIcon));
database.executeTransaction(new StorePluginTransaction(pluginName, time, serverUUID, pluginIcon));
for (TabInformation tab : extension.getPluginTabs()) {
database.executeTransaction(new StoreIconTransaction(tab.getTabIcon()));
database.executeTransaction(new StoreTabInformationTransaction(pluginName, serverUUID, tab));
}
database.executeTransaction(new RemoveInvalidResultsTransaction(pluginName, serverUUID, extension.getInvalidatedMethods()));
}
private void addValuesToBuilder(ExtensionDataBuilder dataBuilder, ExtensionMethods methods, Parameters parameters) {
for (ExtensionMethod provider : methods.getBooleanProviders()) {
if (brokenMethods.contains(provider)) continue;
BooleanProvider annotation = provider.getExistingAnnotation(BooleanProvider.class);
dataBuilder.addValue(Boolean.class, dataBuilder.valueBuilder(annotation.text())
.methodName(provider)
.icon(annotation.iconName(), annotation.iconFamily(), annotation.iconColor())
.description(annotation.description())
.priority(annotation.priority())
.showInPlayerTable(annotation.showInPlayerTable())
.hideFromUsers(annotation)
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildBooleanProvidingCondition(() -> callMethod(provider, parameters, Boolean.class), annotation.conditionName()));
}
for (ExtensionMethod provider : methods.getDoubleProviders()) {
if (brokenMethods.contains(provider)) continue;
DoubleProvider annotation = provider.getExistingAnnotation(DoubleProvider.class);
dataBuilder.addValue(Double.class, dataBuilder.valueBuilder(annotation.text())
.methodName(provider)
.icon(annotation.iconName(), annotation.iconFamily(), annotation.iconColor())
.description(annotation.description())
.priority(annotation.priority())
.showInPlayerTable(annotation.showInPlayerTable())
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildDouble(() -> callMethod(provider, parameters, Double.class)));
}
for (ExtensionMethod provider : methods.getPercentageProviders()) {
if (brokenMethods.contains(provider)) continue;
PercentageProvider annotation = provider.getExistingAnnotation(PercentageProvider.class);
dataBuilder.addValue(Double.class, dataBuilder.valueBuilder(annotation.text())
.methodName(provider)
.icon(annotation.iconName(), annotation.iconFamily(), annotation.iconColor())
.description(annotation.description())
.priority(annotation.priority())
.showInPlayerTable(annotation.showInPlayerTable())
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildPercentage(() -> callMethod(provider, parameters, Double.class)));
}
for (ExtensionMethod provider : methods.getNumberProviders()) {
if (brokenMethods.contains(provider)) continue;
NumberProvider annotation = provider.getExistingAnnotation(NumberProvider.class);
dataBuilder.addValue(Long.class, dataBuilder.valueBuilder(annotation.text())
.methodName(provider)
.icon(annotation.iconName(), annotation.iconFamily(), annotation.iconColor())
.description(annotation.description())
.priority(annotation.priority())
.showInPlayerTable(annotation.showInPlayerTable())
.format(annotation.format())
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildNumber(() -> callMethod(provider, parameters, Long.class)));
}
for (ExtensionMethod provider : methods.getStringProviders()) {
if (brokenMethods.contains(provider)) continue;
StringProvider annotation = provider.getExistingAnnotation(StringProvider.class);
dataBuilder.addValue(String.class, dataBuilder.valueBuilder(annotation.text())
.methodName(provider)
.icon(annotation.iconName(), annotation.iconFamily(), annotation.iconColor())
.description(annotation.description())
.priority(annotation.priority())
.showInPlayerTable(annotation.showInPlayerTable())
.showAsPlayerPageLink(annotation)
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildString(() -> callMethod(provider, parameters, String.class)));
}
for (ExtensionMethod provider : methods.getGroupProviders()) {
if (brokenMethods.contains(provider)) continue;
GroupProvider annotation = provider.getExistingAnnotation(GroupProvider.class);
dataBuilder.addValue(String[].class, dataBuilder.valueBuilder(annotation.text())
.methodName(provider)
.icon(annotation.iconName(), annotation.iconFamily(), Color.NONE)
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildGroup(() -> callMethod(provider, parameters, String[].class)));
}
for (ExtensionMethod provider : methods.getTableProviders()) {
if (brokenMethods.contains(provider)) continue;
TableProvider annotation = provider.getExistingAnnotation(TableProvider.class);
dataBuilder.addValue(Table.class, dataBuilder.valueBuilder(provider.getMethodName())
.conditional(provider.getAnnotationOrNull(Conditional.class))
.showOnTab(provider.getAnnotationOrNull(Tab.class))
.buildTable(() -> callMethod(provider, parameters, Table.class), annotation.tableColor()));
}
for (ExtensionMethod provider : methods.getDataBuilderProviders()) {
if (brokenMethods.contains(provider)) continue;
addDataFromAnotherBuilder(dataBuilder, parameters, provider);
}
}
private void addDataFromAnotherBuilder(ExtensionDataBuilder dataBuilder, Parameters parameters, ExtensionMethod provider) {
try {
ExtensionDataBuilder providedBuilder = callMethod(provider, parameters, ExtensionDataBuilder.class);
dataBuilder.addAll(providedBuilder);
} catch (DataExtensionMethodCallException methodError) {
logFailure(methodError);
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError unexpectedError) {
logFailure(unexpectedError);
}
}
private <T> T callMethod(ExtensionMethod provider, Parameters params, Class<T> returnType) {
try {
return new MethodWrapper<>(provider.getMethod(), returnType)
.callMethod(extension.getExtension(), params);
} catch (DataExtensionMethodCallException e) {
brokenMethods.add(provider);
throw e;
}
}
public void updateValues(UUID playerUUID, String playerName) {
Parameters parameters = Parameters.player(serverInfo.getServerUUID(), playerUUID, playerName);
ExtensionDataBuilder dataBuilder = extension.getExtension().newExtensionDataBuilder();
addValuesToBuilder(dataBuilder, extension.getMethods().get(ExtensionMethod.ParameterType.PLAYER_STRING), parameters);
addValuesToBuilder(dataBuilder, extension.getMethods().get(ExtensionMethod.ParameterType.PLAYER_UUID), parameters);
gatherPlayer(parameters, (ExtDataBuilder) dataBuilder);
}
public void updateValues() {
Parameters parameters = Parameters.server(serverInfo.getServerUUID());
ExtensionDataBuilder dataBuilder = extension.getExtension().newExtensionDataBuilder();
addValuesToBuilder(dataBuilder, extension.getMethods().get(ExtensionMethod.ParameterType.SERVER_NONE), parameters);
gather(parameters, (ExtDataBuilder) dataBuilder);
}
private void gatherPlayer(Parameters parameters, ExtDataBuilder dataBuilder) {
Conditions conditions = new Conditions();
for (ExtDataBuilder.ClassValuePair pair : dataBuilder.getValues()) {
try {
pair.getValue(Boolean.class).flatMap(data -> data.getMetadata(BooleanDataValue.class))
.ifPresent(data -> storePlayerBoolean(parameters, conditions, data));
pair.getValue(Long.class).flatMap(data -> data.getMetadata(NumberDataValue.class))
.ifPresent(data -> storePlayerNumber(parameters, conditions, data));
pair.getValue(Double.class).flatMap(data -> data.getMetadata(DoubleDataValue.class))
.ifPresent(data -> storePlayerDouble(parameters, conditions, data));
pair.getValue(String.class).flatMap(data -> data.getMetadata(StringDataValue.class))
.ifPresent(data -> storePlayerString(parameters, conditions, data));
pair.getValue(String[].class).flatMap(data -> data.getMetadata(GroupsDataValue.class))
.ifPresent(data -> storePlayerGroups(parameters, conditions, data));
pair.getValue(Table.class).flatMap(data -> data.getMetadata(TableDataValue.class))
.ifPresent(data -> storePlayerTable(parameters, conditions, data));
} catch (DataExtensionMethodCallException methodError) {
logFailure(methodError);
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError unexpectedError) {
logFailure(unexpectedError);
}
}
}
private void gather(Parameters parameters, ExtDataBuilder dataBuilder) {
Conditions conditions = new Conditions();
for (ExtDataBuilder.ClassValuePair pair : dataBuilder.getValues()) {
try {
pair.getValue(Boolean.class).flatMap(data -> data.getMetadata(BooleanDataValue.class))
.ifPresent(data -> storeBoolean(parameters, conditions, data));
pair.getValue(Long.class).flatMap(data -> data.getMetadata(NumberDataValue.class))
.ifPresent(data -> storeNumber(parameters, conditions, data));
pair.getValue(Double.class).flatMap(data -> data.getMetadata(DoubleDataValue.class))
.ifPresent(data -> storeDouble(parameters, conditions, data));
pair.getValue(String.class).flatMap(data -> data.getMetadata(StringDataValue.class))
.ifPresent(data -> storeString(parameters, conditions, data));
pair.getValue(Table.class).flatMap(data -> data.getMetadata(TableDataValue.class))
.ifPresent(data -> storeTable(parameters, conditions, data));
} catch (DataExtensionMethodCallException methodError) {
logFailure(methodError);
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError unexpectedError) {
logFailure(unexpectedError);
}
}
}
private void logFailure(DataExtensionMethodCallException methodCallFailed) {
ErrorContext.Builder context = ErrorContext.builder()
.whatToDo("Report and/or disable " + methodCallFailed.getPluginName() + " extension in the Plan config.")
.related(methodCallFailed.getPluginName())
.related("Method:" + methodCallFailed.getMethodName().orElse("-"));
errorLogger.warn(methodCallFailed, context.build());
}
private void logFailure(Throwable unexpectedError) {
ErrorContext.Builder context = ErrorContext.builder()
.whatToDo("Report and/or disable " + extension.getPluginName() + " extension in the Plan config.")
.related(extension.getPluginName());
errorLogger.warn(unexpectedError, context.build());
}
private <T> T getValue(Conditions conditions, DataValue<T> data, ProviderInformation information) {
Optional<String> condition = information.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
return null;
}
return data.getValue(); // can be null, can throw
}
private void storeBoolean(Parameters parameters, Conditions conditions, BooleanDataValue data) {
ProviderInformation information = data.getInformation();
Boolean value = getValue(conditions, data, information);
if (value == null) return;
if (value) {
conditions.conditionFulfilled(information.getProvidedCondition());
} else {
conditions.conditionFulfilled("not_" + information.getProvidedCondition());
}
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StoreServerBooleanResultTransaction(information, parameters, value));
}
private void storeNumber(Parameters parameters, Conditions conditions, NumberDataValue data) {
ProviderInformation information = data.getInformation();
Long value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StoreServerNumberResultTransaction(information, parameters, value));
}
private void storeDouble(Parameters parameters, Conditions conditions, DoubleDataValue data) {
ProviderInformation information = data.getInformation();
Double value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StoreServerDoubleResultTransaction(information, parameters, value));
}
private void storeString(Parameters parameters, Conditions conditions, StringDataValue data) {
ProviderInformation information = data.getInformation();
String value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StoreServerStringResultTransaction(information, parameters, value));
}
private void storeTable(Parameters parameters, Conditions conditions, TableDataValue data) {
ProviderInformation information = data.getInformation();
Table value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
for (Icon icon : value.getIcons()) {
if (icon != null) db.executeTransaction(new StoreIconTransaction(icon));
}
db.executeTransaction(new StoreTableProviderTransaction(information, parameters, value));
db.executeTransaction(new StoreServerTableResultTransaction(information, parameters, value));
}
private void storePlayerBoolean(Parameters parameters, Conditions conditions, BooleanDataValue data) {
ProviderInformation information = data.getInformation();
Boolean value = getValue(conditions, data, information);
if (value == null) return;
if (value) {
conditions.conditionFulfilled(information.getProvidedCondition());
} else {
conditions.conditionFulfilled("not_" + information.getProvidedCondition());
}
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StorePlayerBooleanResultTransaction(information, parameters, value));
}
private void storePlayerNumber(Parameters parameters, Conditions conditions, NumberDataValue data) {
ProviderInformation information = data.getInformation();
Long value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StorePlayerNumberResultTransaction(information, parameters, value));
}
private void storePlayerDouble(Parameters parameters, Conditions conditions, DoubleDataValue data) {
ProviderInformation information = data.getInformation();
Double value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StorePlayerDoubleResultTransaction(information, parameters, value));
}
private void storePlayerString(Parameters parameters, Conditions conditions, StringDataValue data) {
ProviderInformation information = data.getInformation();
String value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StorePlayerStringResultTransaction(information, parameters, value));
}
private void storePlayerGroups(Parameters parameters, Conditions conditions, GroupsDataValue data) {
ProviderInformation information = data.getInformation();
String[] value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(information, parameters));
db.executeTransaction(new StorePlayerGroupsResultTransaction(information, parameters, value));
}
private void storePlayerTable(Parameters parameters, Conditions conditions, TableDataValue data) {
ProviderInformation information = data.getInformation();
Table value = getValue(conditions, data, information);
if (value == null) return;
Database db = dbSystem.getDatabase();
for (Icon icon : value.getIcons()) {
if (icon != null) db.executeTransaction(new StoreIconTransaction(icon));
}
db.executeTransaction(new StoreTableProviderTransaction(information, parameters, value));
db.executeTransaction(new StorePlayerTableResultTransaction(information, parameters, value));
}
}

View File

@ -1,206 +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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.providers.gathering;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.ExtensionWrapper;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.providers.DataProviders;
import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.StorePluginTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreTabInformationTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.*;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import java.util.UUID;
/**
* Object that can be called to place data about players to the database.
*
* @author AuroraLS3
*/
public class ProviderValueGatherer {
private final CallEvents[] callEvents;
private final ExtensionWrapper extensionWrapper;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private final DataProviders dataProviders;
private final BooleanProviderValueGatherer booleanGatherer;
private final TableProviderValueGatherer tableGatherer;
private final Gatherer<Long> serverNumberGatherer;
private final Gatherer<Long> playerNumberGatherer;
private final Gatherer<Double> serverDoubleGatherer;
private final Gatherer<Double> playerDoubleGatherer;
private final Gatherer<String> serverStringGatherer;
private final Gatherer<String> playerStringGatherer;
private final Gatherer<String[]> playerGroupGatherer;
public ProviderValueGatherer(
ExtensionWrapper extension,
DBSystem dbSystem,
ServerInfo serverInfo
) {
this.callEvents = extension.getCallEvents();
this.extensionWrapper = extension;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
String pluginName = extension.getPluginName();
ServerUUID serverUUID = serverInfo.getServerUUID();
Database database = dbSystem.getDatabase();
dataProviders = extension.getProviders();
booleanGatherer = new BooleanProviderValueGatherer(
pluginName, serverUUID, database, extension
);
tableGatherer = new TableProviderValueGatherer(
pluginName, serverUUID, database, extension
);
serverNumberGatherer = new Gatherer<>(
Long.class, StoreServerNumberResultTransaction::new
);
serverDoubleGatherer = new Gatherer<>(
Double.class, StoreServerDoubleResultTransaction::new
);
serverStringGatherer = new Gatherer<>(
String.class, StoreServerStringResultTransaction::new
);
playerNumberGatherer = new Gatherer<>(
Long.class, StorePlayerNumberResultTransaction::new
);
playerDoubleGatherer = new Gatherer<>(
Double.class, StorePlayerDoubleResultTransaction::new
);
playerStringGatherer = new Gatherer<>(
String.class, StorePlayerStringResultTransaction::new
);
playerGroupGatherer = new Gatherer<>(
String[].class, StorePlayerGroupsResultTransaction::new
);
}
public void disableMethodFromUse(MethodWrapper<?> method) {
method.disable();
dataProviders.removeProviderWithMethod(method);
}
public boolean shouldSkipEvent(CallEvents event) {
if (event == CallEvents.MANUAL) {
return false;
}
for (CallEvents accepted : callEvents) {
if (event == accepted) {
return false;
}
}
return true;
}
public String getPluginName() {
return extensionWrapper.getPluginName();
}
public void storeExtensionInformation() {
String pluginName = extensionWrapper.getPluginName();
Icon pluginIcon = extensionWrapper.getPluginIcon();
long time = System.currentTimeMillis();
ServerUUID serverUUID = serverInfo.getServerUUID();
Database database = dbSystem.getDatabase();
database.executeTransaction(new StoreIconTransaction(pluginIcon));
database.executeTransaction(new StorePluginTransaction(pluginName, time, serverUUID, pluginIcon));
for (TabInformation tab : extensionWrapper.getPluginTabs()) {
database.executeTransaction(new StoreIconTransaction(tab.getTabIcon()));
database.executeTransaction(new StoreTabInformationTransaction(pluginName, serverUUID, tab));
}
database.executeTransaction(new RemoveInvalidResultsTransaction(pluginName, serverUUID, extensionWrapper.getInvalidatedMethods()));
}
public void updateValues(UUID playerUUID, String playerName) {
Conditions conditions = booleanGatherer.gatherBooleanDataOfPlayer(playerUUID, playerName);
Parameters params = Parameters.player(serverInfo.getServerUUID(), playerUUID, playerName);
playerNumberGatherer.gather(conditions, params);
playerDoubleGatherer.gather(conditions, params);
playerStringGatherer.gather(conditions, params);
tableGatherer.gatherTableDataOfPlayer(playerUUID, playerName, conditions);
playerGroupGatherer.gather(conditions, params);
}
public void updateValues() {
Conditions conditions = booleanGatherer.gatherBooleanDataOfServer();
Parameters params = Parameters.server(serverInfo.getServerUUID());
serverNumberGatherer.gather(conditions, params);
serverDoubleGatherer.gather(conditions, params);
serverStringGatherer.gather(conditions, params);
tableGatherer.gatherTableDataOfServer(conditions);
}
interface ResultTransactionConstructor<T> {
Transaction create(DataProvider<T> provider, Parameters parameters, T result);
}
class Gatherer<T> {
private final Class<T> type;
private final ResultTransactionConstructor<T> resultTransactionConstructor;
public Gatherer(
Class<T> type,
ResultTransactionConstructor<T> resultTransactionConstructor
) {
this.type = type;
this.resultTransactionConstructor = resultTransactionConstructor;
}
public void gather(Conditions conditions, Parameters parameters) {
for (DataProvider<T> provider : dataProviders.getProvidersByTypes(parameters.getMethodType(), type)) {
gather(conditions, provider, parameters);
}
}
private void gather(Conditions conditions, DataProvider<T> provider, Parameters parameters) {
ProviderInformation information = provider.getProviderInformation();
if (information.getCondition().map(conditions::isNotFulfilled).orElse(false)) {
return; // Condition not fulfilled
}
T result = provider.getMethod().callMethod(extensionWrapper.getExtension(), parameters);
if (result == null) {
return; // Error during method call
}
Database db = dbSystem.getDatabase();
db.executeTransaction(new StoreIconTransaction(information.getIcon()));
db.executeTransaction(new StoreProviderTransaction(provider, parameters.getServerUUID()));
db.executeTransaction(resultTransactionConstructor.create(provider, parameters, result));
}
}
}

View File

@ -1,126 +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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.providers.gathering;
import com.djrapitops.plan.exceptions.DataExtensionMethodCallException;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.ExtensionWrapper;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.providers.DataProviders;
import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreTableProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerTableResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerTableResultTransaction;
import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Gathers TableProvider method data.
*
* @author AuroraLS3
*/
class TableProviderValueGatherer {
private final String pluginName;
private final DataExtension extension;
private final ServerUUID serverUUID;
private final Database database;
private final DataProviders dataProviders;
TableProviderValueGatherer(
String pluginName,
ServerUUID serverUUID, Database database,
ExtensionWrapper extensionWrapper
) {
this.pluginName = pluginName;
this.extension = extensionWrapper.getExtension();
this.serverUUID = serverUUID;
this.database = database;
this.dataProviders = extensionWrapper.getProviders();
}
void gatherTableDataOfPlayer(UUID playerUUID, String playerName, Conditions conditions) {
// Method parameters abstracted away so that same method can be used for all parameter types
// Same with Method result store transaction creation
Function<MethodWrapper<Table>, Callable<Table>> methodCaller = method -> () -> method.callMethod(extension, Parameters.player(serverUUID, playerUUID, playerName));
BiFunction<MethodWrapper<Table>, Table, Transaction> storeTransactionCreator = (method, result) -> new StorePlayerTableResultTransaction(pluginName, serverUUID, method.getMethodName(), playerUUID, result);
for (DataProvider<Table> tableProvider : dataProviders.getPlayerMethodsByType(Table.class)) {
gatherTableDataOfProvider(methodCaller, storeTransactionCreator, conditions, tableProvider);
}
}
void gatherTableDataOfServer(Conditions conditions) {
// Method parameters abstracted away so that same method can be used for all parameter types
// Same with Method result store transaction creation
Function<MethodWrapper<Table>, Callable<Table>> methodCaller = method -> () -> method.callMethod(extension, Parameters.server(serverUUID));
BiFunction<MethodWrapper<Table>, Table, Transaction> storeTransactionCreator = (method, result) -> new StoreServerTableResultTransaction(pluginName, serverUUID, method.getMethodName(), result);
for (DataProvider<Table> tableProvider : dataProviders.getServerMethodsByType(Table.class)) {
gatherTableDataOfProvider(methodCaller, storeTransactionCreator, conditions, tableProvider);
}
}
private void gatherTableDataOfProvider(
Function<MethodWrapper<Table>, Callable<Table>> methodCaller,
BiFunction<MethodWrapper<Table>, Table, Transaction> storeTransactionCreator,
Conditions conditions,
DataProvider<Table> tableProvider
) {
ProviderInformation providerInformation = tableProvider.getProviderInformation();
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
return;
}
MethodWrapper<Table> method = tableProvider.getMethod();
Table result = getMethodResult(methodCaller.apply(method), method);
if (result == null) {
return; // Error during call
}
for (Icon icon : result.getIcons()) {
if (icon != null) {
database.executeTransaction(new StoreIconTransaction(icon));
}
}
database.executeTransaction(new StoreTableProviderTransaction(serverUUID, providerInformation, result));
database.executeTransaction(storeTransactionCreator.apply(method, result));
}
private <T> T getMethodResult(Callable<T> callable, MethodWrapper<T> method) {
try {
return callable.call();
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) {
throw new DataExtensionMethodCallException(e, pluginName, method);
}
}
}

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.extension.implementation.storage.transactions.provid
import com.djrapitops.plan.extension.FormatType;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionIconTable;
@ -42,12 +43,20 @@ import static com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderT
*/
public class StoreProviderTransaction extends ThrowawayTransaction {
private final DataProvider<?> provider;
private final ServerUUID serverUUID;
private final ProviderInformation info;
public StoreProviderTransaction(DataProvider<?> provider, ServerUUID serverUUID) {
this.provider = provider;
this(provider.getProviderInformation(), serverUUID);
}
public StoreProviderTransaction(ProviderInformation info, Parameters parameters) {
this(info, parameters.getServerUUID());
}
public StoreProviderTransaction(ProviderInformation info, ServerUUID serverUUID) {
this.serverUUID = serverUUID;
this.info = info;
}
@Override
@ -84,8 +93,6 @@ public class StoreProviderTransaction extends ThrowawayTransaction {
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ProviderInformation info = provider.getProviderInformation();
// Found for all providers
statement.setString(1, info.getText());
Sql.setStringOrNull(statement, 2, info.getDescription().orElse(null));
@ -130,8 +137,6 @@ public class StoreProviderTransaction extends ThrowawayTransaction {
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ProviderInformation info = provider.getProviderInformation();
// Found for all providers
statement.setString(1, info.getName());
statement.setString(2, info.getText());

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.extension.implementation.storage.transactions.provid
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionIconTable;
@ -36,7 +37,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionTableProviderTable.*;
/**
* Transaction to store information about a {@link com.djrapitops.plan.extension.implementation.providers.TableDataProvider}.
* Transaction to store information about a Table.
*
* @author AuroraLS3
*/
@ -46,6 +47,10 @@ public class StoreTableProviderTransaction extends ThrowawayTransaction {
private final ProviderInformation information;
private final Table table;
public StoreTableProviderTransaction(ProviderInformation information, Parameters parameters, Table table) {
this(parameters.getServerUUID(), information, table);
}
public StoreTableProviderTransaction(ServerUUID serverUUID, ProviderInformation information, Table table) {
this.information = information;
this.table = table;

View File

@ -16,6 +16,8 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
@ -31,7 +33,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.BooleanDataProvider}.
* Transaction to store method result of a boolean.
*
* @author AuroraLS3
*/
@ -52,6 +54,10 @@ public class StorePlayerBooleanResultTransaction extends ThrowawayTransaction {
this.value = value;
}
public StorePlayerBooleanResultTransaction(ProviderInformation information, Parameters parameters, boolean value) {
this(information.getPluginName(), parameters.getServerUUID(), information.getName(), parameters.getPlayerUUID(), value);
}
@Override
protected void performOperations() {
execute(storeValue());

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -51,13 +51,13 @@ public class StorePlayerDoubleResultTransaction extends ThrowawayTransaction {
private final double value;
private final boolean percentage;
public StorePlayerDoubleResultTransaction(DataProvider<Double> provider, Parameters parameters, double value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StorePlayerDoubleResultTransaction(ProviderInformation information, Parameters parameters, double value) {
this.pluginName = information.getPluginName();
this.providerName = information.getName();
this.serverUUID = parameters.getServerUUID();
this.playerUUID = parameters.getPlayerUUID();
this.value = value;
this.percentage = provider.getProviderInformation().isPercentage();
this.percentage = information.isPercentage();
}
@Override

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionGroupsTable;
@ -33,7 +33,7 @@ import java.util.UUID;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.GroupDataProvider}.
* Transaction to store method result of player's groups.
*
* @author AuroraLS3
*/
@ -46,9 +46,9 @@ public class StorePlayerGroupsResultTransaction extends ThrowawayTransaction {
private final String[] value;
public StorePlayerGroupsResultTransaction(DataProvider<String[]> provider, Parameters parameters, String[] value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StorePlayerGroupsResultTransaction(ProviderInformation information, Parameters parameters, String[] value) {
this.pluginName = information.getPluginName();
this.providerName = information.getName();
this.serverUUID = parameters.getServerUUID();
this.playerUUID = parameters.getPlayerUUID();
this.value = value;

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -33,7 +33,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.NumberDataProvider}.
* Transaction to store method result of a long.
*
* @author AuroraLS3
*/
@ -46,9 +46,9 @@ public class StorePlayerNumberResultTransaction extends ThrowawayTransaction {
private final long value;
public StorePlayerNumberResultTransaction(DataProvider<Long> provider, Parameters parameters, long value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StorePlayerNumberResultTransaction(ProviderInformation information, Parameters parameters, long value) {
this.pluginName = information.getPluginName();
this.providerName = information.getName();
this.serverUUID = parameters.getServerUUID();
this.playerUUID = parameters.getPlayerUUID();
this.value = value;

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -34,7 +34,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.StringDataProvider}.
* Transaction to store method result of a String.
*
* @author AuroraLS3
*/
@ -47,9 +47,9 @@ public class StorePlayerStringResultTransaction extends ThrowawayTransaction {
private final String value;
public StorePlayerStringResultTransaction(DataProvider<String> provider, Parameters parameters, String value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StorePlayerStringResultTransaction(ProviderInformation information, Parameters parameters, String value) {
this.pluginName = information.getPluginName();
this.providerName = information.getName();
this.serverUUID = parameters.getServerUUID();
this.playerUUID = parameters.getPlayerUUID();
this.value = StringUtils.truncate(value, 50);

View File

@ -17,6 +17,8 @@
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.Query;
@ -39,7 +41,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerTableValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.TableDataProvider}.
* Transaction to store method result of a Table.
*
* @author AuroraLS3
*/
@ -60,6 +62,10 @@ public class StorePlayerTableResultTransaction extends ThrowawayTransaction {
this.table = table;
}
public StorePlayerTableResultTransaction(ProviderInformation information, Parameters parameters, Table value) {
this(information.getPluginName(), parameters.getServerUUID(), information.getName(), parameters.getPlayerUUID(), value);
}
@Override
protected void performOperations() {
execute(storeValue());

View File

@ -16,6 +16,8 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
@ -29,7 +31,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionServerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.BooleanDataProvider}.
* Transaction to store method result of a boolean.
*
* @author AuroraLS3
*/
@ -48,6 +50,14 @@ public class StoreServerBooleanResultTransaction extends ThrowawayTransaction {
this.value = value;
}
public StoreServerBooleanResultTransaction(ProviderInformation information, Parameters parameters, boolean value) {
this(information.getPluginName(),
parameters.getServerUUID(),
information.getName(),
value
);
}
@Override
protected void performOperations() {
execute(storeValue());

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -31,7 +31,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionServerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.DoubleDataProvider}.
* Transaction to store method result of a double.
*
* @author AuroraLS3
*/
@ -44,12 +44,12 @@ public class StoreServerDoubleResultTransaction extends ThrowawayTransaction {
private final double value;
private final boolean percentage;
public StoreServerDoubleResultTransaction(DataProvider<Double> provider, Parameters parameters, double value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StoreServerDoubleResultTransaction(ProviderInformation info, Parameters parameters, double value) {
this.pluginName = info.getPluginName();
this.providerName = info.getName();
this.percentage = info.isPercentage();
this.serverUUID = parameters.getServerUUID();
this.value = value;
this.percentage = provider.getProviderInformation().isPercentage();
}
@Override

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -31,7 +31,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionServerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.NumberDataProvider}.
* Transaction to store method result of a long.
*
* @author AuroraLS3
*/
@ -43,9 +43,9 @@ public class StoreServerNumberResultTransaction extends ThrowawayTransaction {
private final long value;
public StoreServerNumberResultTransaction(DataProvider<Long> provider, Parameters parameters, long value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StoreServerNumberResultTransaction(ProviderInformation information, Parameters parameters, long value) {
this.pluginName = information.getPluginName();
this.providerName = information.getName();
this.serverUUID = parameters.getServerUUID();
this.value = value;
}

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -44,9 +44,9 @@ public class StoreServerStringResultTransaction extends ThrowawayTransaction {
private final String value;
public StoreServerStringResultTransaction(DataProvider<String> provider, Parameters parameters, String value) {
this.pluginName = provider.getProviderInformation().getPluginName();
this.providerName = provider.getProviderInformation().getName();
public StoreServerStringResultTransaction(ProviderInformation information, Parameters parameters, String value) {
this.pluginName = information.getPluginName();
this.providerName = information.getName();
this.serverUUID = parameters.getServerUUID();
this.value = StringUtils.truncate(value, 50);
}

View File

@ -17,6 +17,8 @@
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.Query;
@ -38,7 +40,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
import static com.djrapitops.plan.storage.database.sql.tables.ExtensionServerTableValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.TableDataProvider}.
* Transaction to store method result of a Table.
*
* @author AuroraLS3
*/
@ -57,6 +59,10 @@ public class StoreServerTableResultTransaction extends ThrowawayTransaction {
this.table = table;
}
public StoreServerTableResultTransaction(ProviderInformation information, Parameters parameters, Table value) {
this(information.getPluginName(), parameters.getServerUUID(), information.getName(), value);
}
@Override
protected void performOperations() {
execute(storeValue());

View File

@ -0,0 +1,191 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.annotation.Conditional;
import com.djrapitops.plan.extension.annotation.PluginInfo;
import com.djrapitops.plan.extension.annotation.StringProvider;
import com.djrapitops.plan.extension.builder.DataValue;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ClassValuePairOrderTest {
@Test
void providedConditionsComeBeforeConditions() throws NoSuchMethodException {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("condition")
@StringProvider(text = "a")
public String value() {
return "";
}
}
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
DataValue<Boolean> first = builder.valueBuilder("test")
.buildBooleanProvidingCondition(false, "condition");
DataValue<Boolean> second = builder.valueBuilder("test")
.conditional(Extension.class.getMethod("value").getAnnotation(Conditional.class))
.buildBoolean(false);
builder.addValue(Boolean.class, second);
builder.addValue(Boolean.class, first);
List<ExtDataBuilder.ClassValuePair> expected = Arrays.asList(
new ExtDataBuilder.ClassValuePair(Boolean.class, first),
new ExtDataBuilder.ClassValuePair(Boolean.class, second)
);
List<ExtDataBuilder.ClassValuePair> result = builder.getValues();
assertEquals(expected, result);
}
@Test
void booleansComeFirst() throws NoSuchMethodException {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("condition")
@StringProvider(text = "a")
public String value() {
return "";
}
}
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
DataValue<Boolean> first = builder.valueBuilder("test")
.buildBooleanProvidingCondition(false, "condition");
DataValue<String> second = builder.valueBuilder("test")
.conditional(Extension.class.getMethod("value").getAnnotation(Conditional.class))
.buildString("e");
builder.addValue(String.class, second);
builder.addValue(String.class, second);
builder.addValue(String.class, second);
builder.addValue(Boolean.class, first);
List<ExtDataBuilder.ClassValuePair> expected = Arrays.asList(
new ExtDataBuilder.ClassValuePair(Boolean.class, first),
new ExtDataBuilder.ClassValuePair(String.class, second),
new ExtDataBuilder.ClassValuePair(String.class, second),
new ExtDataBuilder.ClassValuePair(String.class, second)
);
List<ExtDataBuilder.ClassValuePair> result = builder.getValues();
assertEquals(expected, result);
}
@Test
void booleansWithConditionsComeFirst() throws NoSuchMethodException {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("condition")
@StringProvider(text = "a")
public String value() {
return "";
}
@Conditional("condition2")
@StringProvider(text = "a")
public String value2() {
return "";
}
}
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
DataValue<Boolean> first = builder.valueBuilder("test")
.buildBooleanProvidingCondition(false, "condition");
DataValue<Boolean> second = builder.valueBuilder("test")
.conditional(Extension.class.getMethod("value").getAnnotation(Conditional.class))
.buildBooleanProvidingCondition(false, "condition2");
DataValue<String> third = builder.valueBuilder("test")
.conditional(Extension.class.getMethod("value2").getAnnotation(Conditional.class))
.buildString("e");
builder.addValue(String.class, third);
builder.addValue(String.class, third);
builder.addValue(String.class, third);
builder.addValue(Boolean.class, second);
builder.addValue(Boolean.class, first);
List<ExtDataBuilder.ClassValuePair> expected = Arrays.asList(
new ExtDataBuilder.ClassValuePair(Boolean.class, first),
new ExtDataBuilder.ClassValuePair(Boolean.class, second),
new ExtDataBuilder.ClassValuePair(String.class, third),
new ExtDataBuilder.ClassValuePair(String.class, third),
new ExtDataBuilder.ClassValuePair(String.class, third)
);
List<ExtDataBuilder.ClassValuePair> result = builder.getValues();
assertEquals(expected, result);
}
@Test
void booleansWithConditionsComeFirst2() throws NoSuchMethodException {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("condition")
@StringProvider(text = "a")
public String value() {
return "";
}
@Conditional("condition2")
@StringProvider(text = "a")
public String value2() {
return "";
}
}
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
DataValue<Boolean> first = builder.valueBuilder("test")
.buildBooleanProvidingCondition(false, "condition");
DataValue<Boolean> second = builder.valueBuilder("test")
.conditional(Extension.class.getMethod("value").getAnnotation(Conditional.class))
.buildBooleanProvidingCondition(false, "condition2");
DataValue<String> third = builder.valueBuilder("test")
.conditional(Extension.class.getMethod("value2").getAnnotation(Conditional.class))
.buildString("e");
builder.addValue(String.class, third);
builder.addValue(String.class, third);
builder.addValue(Boolean.class, first);
builder.addValue(Boolean.class, second);
builder.addValue(String.class, third);
List<ExtDataBuilder.ClassValuePair> expected = Arrays.asList(
new ExtDataBuilder.ClassValuePair(Boolean.class, first),
new ExtDataBuilder.ClassValuePair(Boolean.class, second),
new ExtDataBuilder.ClassValuePair(String.class, third),
new ExtDataBuilder.ClassValuePair(String.class, third),
new ExtDataBuilder.ClassValuePair(String.class, third)
);
List<ExtDataBuilder.ClassValuePair> result = builder.getValues();
assertEquals(expected, result);
}
}

View File

@ -0,0 +1,56 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.builder;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.annotation.PluginInfo;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ExtendionDataBuilderTest {
@Test
void nullTextWhenCreatingValueBuilderThrowsException() {
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
assertEquals(
"'text' can't be null or empty",
assertThrows(IllegalArgumentException.class, () -> builder.valueBuilder(null)).getMessage()
);
}
@Test
void emptyTextWhenCreatingValueBuilderThrowsException() {
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
assertEquals(
"'text' can't be null or empty",
assertThrows(IllegalArgumentException.class, () -> builder.valueBuilder("")).getMessage()
);
}
@Test
void nullClassSupplierNotAdded() {
ExtDataBuilder builder = new ExtDataBuilder(new Extension());
builder.addValue(null, () -> null);
assertEquals(Collections.emptyList(), builder.getValues());
}
@PluginInfo(name = "Extension")
static class Extension implements DataExtension {}
}

View File

@ -18,7 +18,7 @@ package com.djrapitops.plan.storage.database;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.DeliveryUtilities;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
@ -59,7 +59,7 @@ public interface DatabaseTestPreparer {
return system().getDeliveryUtilities();
}
default ExtensionService extensionService() {return system().getExtensionService();}
default ExtensionSvc extensionService() {return system().getExtensionService();}
default void execute(Executable executable) {
try {

View File

@ -18,7 +18,7 @@ package com.djrapitops.plan.storage.database;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.DeliveryUtilities;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
@ -37,6 +37,7 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import utilities.DBPreparer;
import utilities.RandomData;
import utilities.TestErrorLogger;
import java.nio.file.Path;
@ -82,6 +83,7 @@ public class H2Test implements DatabaseTest,
@BeforeEach
void setUp() {
TestErrorLogger.throwErrors(true);
db().executeTransaction(new Patch() {
@Override
public boolean hasBeenApplied() {
@ -144,7 +146,7 @@ public class H2Test implements DatabaseTest,
}
@Override
public ExtensionService extensionService() {
public ExtensionSvc extensionService() {
return component.extensionService();
}

View File

@ -18,7 +18,7 @@ package com.djrapitops.plan.storage.database;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.DeliveryUtilities;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
@ -39,6 +39,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import utilities.DBPreparer;
import utilities.RandomData;
import utilities.TestConstants;
import utilities.TestErrorLogger;
import java.nio.file.Path;
import java.util.Optional;
@ -90,6 +91,7 @@ class MySQLTest implements DatabaseTest,
@BeforeEach
void setUp() {
TestErrorLogger.throwErrors(true);
db().executeTransaction(new Patch() {
@Override
public boolean hasBeenApplied() {
@ -148,7 +150,7 @@ class MySQLTest implements DatabaseTest,
}
@Override
public ExtensionService extensionService() {
public ExtensionSvc extensionService() {
return component.extensionService();
}

View File

@ -18,7 +18,7 @@ package com.djrapitops.plan.storage.database;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.DeliveryUtilities;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
@ -37,6 +37,7 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import utilities.DBPreparer;
import utilities.RandomData;
import utilities.TestErrorLogger;
import java.nio.file.Path;
@ -82,6 +83,7 @@ public class SQLiteTest implements DatabaseTest,
@BeforeEach
void setUp() {
TestErrorLogger.throwErrors(true);
db().executeTransaction(new Patch() {
@Override
public boolean hasBeenApplied() {
@ -141,7 +143,7 @@ public class SQLiteTest implements DatabaseTest,
}
@Override
public ExtensionService extensionService() {
public ExtensionSvc extensionService() {
return component.extensionService();
}

View File

@ -19,9 +19,10 @@ package com.djrapitops.plan.storage.database.queries;
import com.djrapitops.plan.delivery.rendering.html.structure.HtmlTable;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.extension.NotReadyException;
import com.djrapitops.plan.extension.annotation.*;
import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.results.*;
@ -42,6 +43,7 @@ import org.junit.jupiter.api.Test;
import utilities.OptionalAssert;
import utilities.RandomData;
import utilities.TestConstants;
import utilities.TestErrorLogger;
import java.util.List;
import java.util.Map;
@ -59,11 +61,13 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@BeforeEach
default void unregisterExtensions() {
ExtensionService extensionService = extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register();
extensionService.unregister(new PlayerExtension());
extensionService.unregister(new ServerExtension());
extensionService.unregister(new ConditionalExtension());
extensionService.unregister(new TableExtension());
extensionService.unregister(new ThrowingExtension());
}
@Test
@ -82,7 +86,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void extensionPlayerValuesAreStored() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register(new PlayerExtension());
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME, CallEvents.MANUAL);
@ -130,7 +134,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void extensionServerValuesAreStored() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register(new ServerExtension());
extensionService.updateServerValues(CallEvents.SERVER_EXTENSION_REGISTER);
@ -152,7 +156,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void extensionServerAggregateQueriesWork() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register(new PlayerExtension());
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME, CallEvents.MANUAL);
@ -183,7 +187,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void unsatisfiedPlayerConditionalResultsAreCleaned() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register(new ConditionalExtension());
@ -237,7 +241,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void unsatisfiedServerConditionalResultsAreCleaned() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
ConditionalExtension.condition = true;
extensionService.register(new ConditionalExtension());
@ -288,7 +292,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void extensionServerTableValuesAreInserted() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register(new TableExtension());
extensionService.updateServerValues(CallEvents.MANUAL);
@ -320,7 +324,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
@Test
default void extensionPlayerTableValuesAreInserted() {
ExtensionSvc extensionService = (ExtensionSvc) extensionService();
ExtensionSvc extensionService = extensionService();
extensionService.register(new TableExtension());
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME, CallEvents.MANUAL);
@ -352,6 +356,19 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
assertEquals(expected.toHtml(), table.getHtmlTable().toHtml());
}
@Test
default void extensionExceptionsAreCaught() {
TestErrorLogger.throwErrors(false);
ExtensionSvc extensionService = extensionService();
extensionService.register(new ThrowingExtension());
extensionService.updateServerValues(CallEvents.MANUAL);
extensionService.updatePlayerValues(playerUUID, TestConstants.PLAYER_ONE_NAME, CallEvents.MANUAL);
// 5 of the exceptions need to be logged, there are 8 exceptions total 3 of which are ignored.
assertEquals(5, TestErrorLogger.getCaught().size(), () -> "Not all exceptions got logged, logged exceptions: " + TestErrorLogger.getCaught().toString());
}
@PluginInfo(name = "ConditionalExtension")
class ConditionalExtension implements DataExtension {
@ -491,4 +508,53 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
.build();
}
}
@PluginInfo(name = "ThrowingExtension")
class ThrowingExtension implements DataExtension {
@BooleanProvider(text = "a boolean")
public boolean booleanMethod() {
throw new IllegalArgumentException("Failed to catch");
}
@BooleanProvider(text = "a boolean")
public boolean booleanPlayerMethod(UUID playerUUID) {
throw new NotReadyException();
}
@StringProvider(text = "a string")
public String stringMethod() {
throw new NoSuchMethodError();
}
@NumberProvider(text = "a string")
public long numberMethod() {
throw new UnsupportedOperationException();
}
@GroupProvider(text = "group")
public String[] groupMethod(UUID playerUUID) {
throw new NoClassDefFoundError();
}
@DataBuilderProvider
public ExtensionDataBuilder builder() {
return newExtensionDataBuilder()
.addValue(String.class, () -> {
throw new NotReadyException();
});
}
@DataBuilderProvider
public ExtensionDataBuilder builder2() {
return newExtensionDataBuilder()
.addValue(String.class, () -> {
throw new NoClassDefFoundError();
});
}
@DataBuilderProvider
public ExtensionDataBuilder builder3() {
throw new NoSuchMethodError();
}
}
}

View File

@ -19,29 +19,51 @@ package utilities;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TestErrorLogger implements ErrorLogger {
private static final List<Throwable> caught = new ArrayList<>();
private static boolean throwErrors = true;
public static void throwErrors(boolean throwErrors) {
caught.clear();
TestErrorLogger.throwErrors = throwErrors;
}
public static List<Throwable> getCaught() {
return caught;
}
@Override
public void critical(Throwable throwable, ErrorContext context) {
System.out.println("[CRITICAL] Exception occurred during test, context: " + context);
Logger.getGlobal().log(Level.SEVERE, "The exception: " + throwable.getMessage(), throwable);
throw new AssertionError(throwable);
throwOrStore(throwable);
}
@Override
public void error(Throwable throwable, ErrorContext context) {
System.out.println("[ERROR] Exception occurred during test, context: " + context);
Logger.getGlobal().log(Level.SEVERE, "The exception: " + throwable.getMessage(), throwable);
throw new AssertionError(throwable);
throwOrStore(throwable);
}
@Override
public void warn(Throwable throwable, ErrorContext context) {
System.out.println("[WARN] Exception occurred during test, context: " + context);
Logger.getGlobal().log(Level.SEVERE, "The exception: " + throwable.getMessage(), throwable);
throwOrStore(throwable);
}
public void throwOrStore(Throwable throwable) {
if (throwErrors) {
throw new AssertionError(throwable);
} else {
caught.add(throwable);
}
}
}

View File

@ -21,7 +21,6 @@ import com.djrapitops.plan.extension.Caller;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.NotReadyException;
import com.djrapitops.plan.extension.extractor.ExtensionExtractor;
import javax.inject.Inject;
import javax.inject.Named;
@ -181,7 +180,7 @@ public class ExtensionRegister {
}
private Optional<Caller> register(DataExtension dataExtension) {
String extensionName = ExtensionExtractor.getPluginName(dataExtension.getClass());
String extensionName = dataExtension.getPluginName();
if (disabledExtensions.contains(extensionName)) return Optional.empty();
return extensionService.register(dataExtension);