[Merge][#638] DataExtension API (#987)

* Plan api module

* Information annotations for DataExtension API

* Added BooleanProvider annotation

* Added NumberProvider

* Added DoubleProvider

* Added PercentageProvider

* Added StringProvider

* Retention and Target to Providers

* Validation class for annotations

* Refactored ExtensionExtractor, Added Group parameter

* Method names used as provider identifiers

* Begun work on extracting data providers

* Provider Extraction + priority to Providers:

Implemented methods for extracting each kind of Provider annotation
related method information to a DataProvider.

DataProviders required some duplicate code due to type erasure of the
parameter class.

----

Added Display-priority to Providers, highest value is placed top most.

InvalidateMethod annotations are now extracted.

* Small amount of code cleanup

* Storage of plugin info, icon and tabs of DataExtension

* Simplified Provider method calling:

- Added an enum for each method kind
  - Moved parameter resolution to the end of the chain, right before
    invoking the method.
  - The enum is used for storage in DataProviders for easier access.
- ProviderInformation created as early as possible

* Player data gathering for all providers:

- Incremental condition resolution of BooleanProvider conditions
- Gathering of Number and String values
- Gathering and disambiguation between double and percentage providers.

* Storage of Provider icons

* String provider extra info

* plan_extension_providers table information

* plan_extension_user_values table information

* BooleanProvider store transaction

* NumberProvider store transaction

* StringProvider store transaction

* DoubleProvider, PercentageProvider store transaction

* Boolean result store transaction

* Double result store transaction

* Number result store transaction

* Percentage result store transaction

* String result store transaction

* Transaction for removing invalid providers from storage

* Interface for registering DataExtensions

* ExtensionService implementation

* Handling of by default empty annotation values

* Extension table create table statements

* Annotation String length enforcement:

- Description is 150 characters max, truncated
- Others are 50 characters max, truncated
- StringProvider value is 50 characters max, truncated

* Logging of implementation mistake warnings

* Added 'extensions' module:

- Module is in charge of registering built in DataExtensions
- This is done via ExtensionRegister

* Config enable check for DataExtension registration

* Fixed Extension SQL syntax errors

* Debug logging of DataExtension registration

* Extension data updating to login listeners

* Fix Extension SQL Syntax errors related to id selection

* Fixed many Extension Provider store errors

* Data objects for extension data about a player

These objects make it easier to construct query data from database.

* Fix flipped double and percentage storage transactions

* getByName to Extension enums

* Query for DataExtension data of a single player

* Deprecated player plugin tab related things

* DataExtension API player page plugin tab generation

* Registered AdvancedBan extension

* Added BooleanProvider#hidden

* Added Conditional#negated

* Added booleans to PluginInfo for controlling method calls.

* Bump extension API to 0.0.2

* Extract method call booleans from PluginInfo

* Added flow for Negated conditions

Negated conditions' names are prefixed with not_

This allows checking against negated Conditionals and does not need
adding more table rows.

* Registered BanManager Extension

* Transaction for removing unsatisfied Conditional data:

This is one of the most complex queries I have made.

- Select all fulfilled conditions for all players (conditionName when
  true and not_conditionName when false)
- Left join with player value & provider tables when uuids match, and
  when condition matches a condition in the query above.
- Filter the join query for values where the condition did not match
  any provided condition in the join (Is null)
- Delete all player values with IDs that are returned by the left join
  query after filtering

In addition:
- Added test for the transaction
- Added extension data removal to RemoveEverythingTransaction
- Added unregister method to ExtensionService

* Reverse table names since MySQL can not select and update:

More information
https://stackoverflow.com/questions/4429319/you-cant-specify-target-table-for-update-in-from-clause

Attempts to fix:
'You can't specify target table 'plan_extension_user_values' for update
in FROM clause'

* Nest query since MySQL can not select and update:

More information
https://stackoverflow.com/a/9843719

Attempts to fix:
'You can't specify target table 'plan_extension_user_values' for update
in FROM clause'

* Added missing "as ..." to the query:

Attempts to fix
"Every derived table must have its own alias"

* Added comment about nested query

* Implemented BooleanProvider hidden parameter

* Bump extension versions

* Registered DiscordSRV extension

* Fixing some new code smells:

Critical:
- String literal duplication fixed in a few places
- Cognitive complexity reduced in BooleanProviderValueGatherer

Major:
- Called Optional#isPresent before accessing value
  16 instances in ProviderTransactions
- private constructor to Extension tables
- Missing deprecated tags to deprecated plugin tab stuff
- Unused class variable removal
- Throw dedicated exceptions in ServerServerInfo
- Unused method removal

Minor:
- Renamed 'API' field to 'service' in ExtensionService
- Unused variable removal

* Fix SQL error introduced when 'hidden' was added

* Color replacement to StringProvider values

* Gatherer methods for server providers

* Storage for extension server data

* Moved some results classes around

* ExtensionServerData class

* Fixed ConfigReader sometimes adding empty string to a null value.

* ExtensionServerDataQuery + BooleanAggregateQuery:

- Queries plan_extension_server_values

- Queries plan_extension_player_values for aggregates

* More Aggregate Queries

* Server ExtensionData visualization

* Network ExtensionData visualization

* Removed date values from number aggregate query

* Registered Essentials extension

* Registered Sponge Economy Extension

* Removed update booleans from PluginInfo

* More flexible definition of calling Extension methods

* Implemented Caller and CallEvents filtering

* Implemented Extension player method calls

* CallEvents.SERVER_EXTENSION_REGISTER call

* CallEvents.SERVER_PERIODICAL calls

* Updated extension versions

* Updated Essentials extension

* Fixed 'sponge' module test errors

* Fix some code smells

* Fixed Transaction#executeOther when using dbType

* Fixed Aggregate label issues

* TableContainer no longer displays 'null'

* '(Legacy)' added to PluginData tabs
This commit is contained in:
Risto Lahtela 2019-03-31 15:59:35 +03:00 committed by GitHub
commit 802f0749eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 9987 additions and 259 deletions

41
Plan/api/build.gradle Normal file
View File

@ -0,0 +1,41 @@
plugins {
id "com.jfrog.bintray" version "1.8.1"
}
ext.apiVersion = '0.0.3'
bintray {
user = System.getenv('BINTRAY_USER')
key = System.getenv('BINTRAY_KEY')
pkg {
repo = 'Plan-repository'
name = 'Plan-API'
licenses = ['LGPL-v3.0']
vcsUrl = 'https://github.com/plan-player-analytics/Plan'
issueTrackerUrl = 'https://github.com/plan-player-analytics/Plan/issues'
version {
name = "$apiVersion"
desc = "Plan APIv5 version $apiVersion"
}
publications = ['BintrayPublication']
}
}
publishing {
publications {
BintrayPublication(MavenPublication) {
groupId = 'com.djrapitops'
artifactId = 'Plan-api'
version = "$apiVersion"
artifact jar
}
mavenJava(MavenPublication) {
groupId = 'com.djrapitops'
artifactId = 'Plan-api'
version = "$apiVersion"
artifact jar
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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;
/**
* Enum representing different events when Plan calls methods of {@link DataExtension} automatically.
* <p>
* You can also call the update methods via {@link Caller} manually.
*
* @author Rsl1122
*/
public enum CallEvents {
/**
* This event represents a manual call via {@link Caller}.
* Definition inside {@link DataExtension#callExtensionMethodsOn()} is NOT REQUIRED for using Caller methods.
*/
MANUAL,
/**
* This event represents call to player methods on a Player Join event.
* <p>
* The call is made from a listener at the last event priority (Bukkit/Bungee: MONITOR, Sponge: POST).
* Method calls are asynchronous.
*/
PLAYER_JOIN,
/**
* This event represents a call to player methods on a Player Leave event.
* <p>
* The call is made from a listener at the first event priority (Bukkit/Bungee: LOWEST, Sponge: PRE).
* Method calls are asynchronous.
*/
PLAYER_LEAVE,
/**
* This event represents a call to server methods when the {@link DataExtension} is registered.
* <p>
* Server methods include any {@link Group} parameter methods.
* <p>
* Method calls are asynchronous.
*/
SERVER_EXTENSION_REGISTER,
/**
* This event represents a call to server methods via a periodical task.
* <p>
* Server methods include any {@link Group} parameter methods.
* <p>
* Periodic task with a runs user configured period (Plan config).
* Method calls are asynchronous.
*/
SERVER_PERIODICAL
}

View File

@ -0,0 +1,50 @@
/*
* 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;
import java.util.UUID;
/**
* Interface for manually calling update methods on a registered {@link DataExtension}.
* <p>
* You can obtain an instance by registering an extension via {@link ExtensionService#register(DataExtension)}.
* <p>
* Plan calls the methods in DataExtension based on {@link CallEvents} defined by {@link }
*
* @author Rsl1122
*/
public interface Caller {
/**
* Calls all player methods of the associated {@link DataExtension}.
* <p>
* Player methods have {@code UUID} or {@code String} as a method parameter and a Provider annotation.
*
* @param playerUUID UUID of the player.
* @param playerName Name of the player.
* @throws IllegalArgumentException If playerUUID or playerName is null.
*/
void updatePlayerData(UUID playerUUID, String playerName) throws IllegalArgumentException;
/**
* Calls all server methods of the associated {@link DataExtension}.
* <p>
* Server methods have no parameters or {@link Group} method parameter and a Provider annotation.
*/
void updateServerData();
}

View File

@ -0,0 +1,93 @@
/*
* 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;
/**
* Interface to implement data extensions with.
* <p>
* The class implementing this interface should be annotated with {@link com.djrapitops.plan.extension.annotation.PluginInfo}.
* If the extension is given to Plan API without the annotation it will be rejected.
* <hr>
* <p>
* Public methods in the class should be annotated with appropriate Provider annotations.
* Provider annotations:
* {@link com.djrapitops.plan.extension.annotation.BooleanProvider} for {@code boolean} values and conditions for {@link com.djrapitops.plan.extension.annotation.Conditional}.
* {@link com.djrapitops.plan.extension.annotation.NumberProvider} for {@code long} values. (Use this for integers by casting to long) Has option for formatting.
* {@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.
* <hr>
* <p>
* Methods can have one of the following as method parameters:
* {@code UUID playerUUID} - UUID of the player the data is about
* {@code String playerName} - Name of the player the data is about
* {@link Group group} - Provided group the data is about (In case a group needs additional information)
* nothing - The data is interpreted to be about the server.
* <hr>
* <p>
* The name of the method will be used as an identifier in the database, so that a single provider does not duplicate entries.
* Only first 50 characters of the method name are stored.
* If you need to change a method name add a class annotation with the old method name: {@link com.djrapitops.plan.extension.annotation.InvalidateMethod}
* <p>
* Some additional annotations are available for controlling appearance of the results:
* {@link com.djrapitops.plan.extension.annotation.Conditional} A {@code boolean} returned by {@link com.djrapitops.plan.extension.annotation.BooleanProvider} has to be {@code true} for this method to be called.
* {@link com.djrapitops.plan.extension.annotation.Tab} The value of this provider should be placed on a tab with a specific name
* {@link com.djrapitops.plan.extension.annotation.TabInfo} Optional Structure information about a tab
* {@link com.djrapitops.plan.extension.annotation.TabOrder} Optional information about preferred tab
* <hr>
* <p>
* Method calls are asynchronous. You can control when the calls are made via {@link DataExtension#callExtensionMethodsOn()} and {@link Caller}.
* <p>
* You can check against implementation violations by using {@link com.djrapitops.plan.extension.extractor.ExtensionExtractor#validateAnnotations()} in your Unit Tests.
* <p>
* Implementation violations:
* - No {@link com.djrapitops.plan.extension.annotation.PluginInfo} class annotation
* - Class contains no public methods with Provider annotations
* - Class contains private method with Provider annotation
* - Non-primitive return type when primitive is required (eg. Boolean instead of boolean)
* - Method doesn't have correct parameters (see above)
* - {@link com.djrapitops.plan.extension.annotation.BooleanProvider} is annotated with a {@link com.djrapitops.plan.extension.annotation.Conditional} that requires same condition the provider provides.
* - {@link com.djrapitops.plan.extension.annotation.Conditional} without a {@link com.djrapitops.plan.extension.annotation.BooleanProvider} that provides value for the condition
* - Annotation variable is over 50 characters (Or 150 if description)
* - Method name is over 50 characters (Used as an identifier for storage)
*
* @author Rsl1122
* @see com.djrapitops.plan.extension.annotation.PluginInfo Required Annotation
* @see CallEvents for method call event types.
*/
public interface DataExtension {
/**
* Determines when DataExtension methods are called automatically by Plan.
* <p>
* Override this method to determine more suitable call times for your plugin.
* You can also use {@link Caller} to update manually.
* <p>
* If an empty array is supplied the DataExtension methods are not called by Plan automatically.
*
* @return Event types that will trigger method calls to the DataExtension.
* @see CallEvents for details when the methods are called.
*/
default CallEvents[] callExtensionMethodsOn() {
return new CallEvents[]{
CallEvents.PLAYER_JOIN,
CallEvents.PLAYER_LEAVE,
CallEvents.SERVER_EXTENSION_REGISTER
};
}
}

View File

@ -0,0 +1,77 @@
/*
* 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;
import java.util.ArrayList;
import java.util.List;
/**
* Enum representing big elements of a plugin.
* <p>
* Used for determining in which order elements are placed on a {@link com.djrapitops.plan.extension.annotation.Tab} by
* using {@link com.djrapitops.plan.extension.annotation.TabInfo}.
*
* @author Rsl1122
*/
public enum ElementOrder {
/**
* Represents text - value pair box.
*/
VALUES,
/**
* Represents tables.
*/
TABLE,
/**
* Represents graphs.
*/
GRAPH;
public static String serialize(ElementOrder[] order) {
StringBuilder builder = new StringBuilder();
int length = order.length;
for (int i = 0; i < length; i++) {
builder.append(order[i].name());
if (i < length - 1) {
builder.append(',');
}
}
return builder.toString();
}
public static ElementOrder[] deserialize(String serializedOrder) {
if (serializedOrder == null || serializedOrder.isEmpty()) {
return null;
}
String[] split = serializedOrder.split(",");
List<ElementOrder> order = new ArrayList<>();
for (String elementName : split) {
try {
ElementOrder element = valueOf(elementName);
order.add(element);
} catch (IllegalArgumentException ignore) {
/* Has been deleted */
}
}
return order.toArray(new ElementOrder[0]);
}
}

View File

@ -0,0 +1,84 @@
/*
* 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;
import com.djrapitops.plan.extension.extractor.ExtensionExtractor;
import java.util.Optional;
/**
* Interface for registering {@link DataExtension}s.
* <p>
* Obtaining instance:
* - Obtain instance with {@link ExtensionService#getInstance()}.
* - Make sure to catch a possible NoClassDefFoundError in case Plan is not installed
* - Catch IllegalStateException in case ExtensionService is not enabled
* <p>
* Registering {@link DataExtension}:
* - Register your {@link DataExtension} with {@link ExtensionService#register(DataExtension)}
* - Catch a possible IllegalArgumentException in case the DataExtension implementation is invalid.
*
* @author Rsl1122
*/
public interface ExtensionService {
/**
* Obtain instance of ExtensionService.
*
* @return ExtensionService implementation.
* @throws NoClassDefFoundError If Plan is not installed and this class can not be found or if older Plan version is installed.
* @throws IllegalStateException If Plan is installed, but not enabled.
*/
static ExtensionService getInstance() {
return Optional.ofNullable(ExtensionServiceHolder.service)
.orElseThrow(() -> new IllegalStateException("ExtensionService has not been initialised yet."));
}
/**
* Register your {@link DataExtension} implementation.
* <p>
* You can use {@link ExtensionExtractor#validateAnnotations()} in your Unit Tests to prevent IllegalArgumentExceptions here at runtime.
*
* @param extension Your DataExtension implementation, see {@link DataExtension} for requirements.
* @return Optional {@link Caller} that can be used to call for data update in Plan database manually - If the Optional is not present the user has disabled the extension in Plan config.
* @throws IllegalArgumentException If an implementation violation is found.
*/
Optional<Caller> register(DataExtension extension);
/**
* Unregister your {@link DataExtension} implementation.
* <p>
* This method should be used if calling methods on the DataExtension suddenly becomes unavailable, due to
* plugin disable for example.
*
* @param extension Your DataExtension implementation that was registered before.
*/
void unregister(DataExtension extension);
class ExtensionServiceHolder {
static ExtensionService service;
private ExtensionServiceHolder() {
/* Static variable holder */
}
static void set(ExtensionService service) {
ExtensionServiceHolder.service = service;
}
}
}

View File

@ -0,0 +1,51 @@
/*
* 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;
import java.util.Optional;
/**
* Enum for determining additional formatter for a value given by a {@link com.djrapitops.plan.extension.annotation.NumberProvider}.
*
* @author Rsl1122
*/
public enum FormatType {
/**
* Formats a long value (Epoch ms) to a readable timestamp, year is important.
*/
DATE_YEAR,
/**
* Formats a long value (Epoch ms) to a readable timestamp, second is important.
*/
DATE_SECOND,
/**
* Formats a long value (ms) to a readable format.
*/
TIME_MILLISECONDS,
/**
* Applies no formatting to the value.
*/
NONE;
public static Optional<FormatType> getByName(String name) {
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}}

View File

@ -0,0 +1,35 @@
/*
* 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;
/**
* Method parameter for providing values about a group with provider annotations.
* <p>
* Usage Example: {@code @StringProvider String provideStringAboutGroup(Group group)}
* <p>
* Group names of users are provided with {@code @GroupProvider String[] provideGroups(UUID playerUUID)}
* {@code Group} parameter methods are not called without knowledge of a group name.
* <p>
* This method parameter is used since it is not possible to differentiate String playerName and String groupName.
*
* @author Rsl1122
*/
public interface Group {
String getGroupName();
}

View File

@ -0,0 +1,115 @@
/*
* 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.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a boolean value about a Player.
* <p>
* Usage: {@code @BooleanProvider boolean method(UUID playerUUID)}
* <p>
* The provided boolean can be used as a condition for calls to other Provider
* methods by defining the name of the condition and using {@link Conditional}
* on the other method with a Provider annotation.
* <p>
* For example:
* {@code @BooleanProvider(condition="example") boolean condition(UUID playerUUID);}
* {@code @Conditional("example") @NumberProvider long someValue(UUID playerUUID);}
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BooleanProvider {
/**
* Text displayed before the value, limited to 50 characters.
* <p>
* Should inform the user what the value represents, for example
* "Banned", "Has Island"
*
* @return String of max 50 characters, remainder will be clipped.
*/
String text();
/**
* Display-priority of the value, highest value is placed top most.
* <p>
* Two values with same priority may appear in a random order.
*
* @return Priority between 0 and {@code Integer.MAX_VALUE}.
*/
int priority() default 0;
/**
* Text displayed when hovering over the value, limited to 150 characters.
* <p>
* Should be used to clarify what the value is if not self evident, for example
* text: "Boat", description: "Whether or not the player is on a boat."
*
* @return String of max 150 characters, remainder will be clipped.
*/
String description() default "";
/**
* Name of the {@link Conditional} condition limited to 50 characters.
*
* @return Case sensitive string of max 50 characters.
*/
String conditionName() default "";
/**
* Should the result of this method be hidden from the user.
*
* @return true if the value should not be displayed on the page.
*/
boolean hidden() default false;
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Color preference of the plugin.
* <p>
* This color will be set as the default color to use for plugin's elements.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color iconColor() default Color.NONE;
}

View File

@ -0,0 +1,54 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method Annotation to determine that a method can not be called unless a condition is fulfilled.
* <p>
* Condition information is provided with {@link com.djrapitops.plan.extension.annotation.data.BooleanProvider}.
* If {@link com.djrapitops.plan.extension.annotation.BooleanProvider} for the condition is not specified the
* method tagged with this annotation will not be called, (Condition is assumed false).
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Conditional {
/**
* Name of the condition limited to 50 characters.
*
* @return Case sensitive string of max 50 characters.
*/
String value();
/**
* Reverse the condition.
* <p>
* Example:
* - Method with {@code Conditional("expires", negated = true)} will only be called when the condition "expires" is false.
*
* @return {@code false} by default.
*/
boolean negated() default false;
}

View File

@ -0,0 +1,94 @@
/*
* 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.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a double value about a Player.
* <p>
* Usage: {@code @DoubleProvider double method(UUID playerUUID)}
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoubleProvider {
/**
* Text displayed before the value, limited to 50 characters.
* <p>
* Should inform the user what the value represents, for example
* "Distance from spawn", "Balance"
*
* @return String of max 50 characters, remainder will be clipped.
*/
String text();
/**
* Display-priority of the value, highest value is placed top most.
* <p>
* Two values with same priority may appear in a random order.
*
* @return Priority between 0 and {@code Integer.MAX_VALUE}.
*/
int priority() default 0;
/**
* Text displayed when hovering over the value, limited to 150 characters.
* <p>
* Should be used to clarify what the value is if not self evident, for example
* text: "Balance", description: "Economy balance of the player"
*
* @return String of max 150 characters, remainder will be clipped.
*/
String description() default "";
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Color preference of the plugin.
* <p>
* This color will be set as the default color to use for plugin's elements.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color iconColor() default Color.NONE;
}

View File

@ -0,0 +1,50 @@
/*
* 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 java.lang.annotation.*;
/**
* Annotation used to invalidate old method values.
* <p>
* The name of the methods are used as an identifier in the database, so that a single provider does not duplicate entries.
* Only first 50 characters of the method name are stored.
* If you need to change a method name add this class annotation with the old method name.
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(InvalidateMethod.Multiple.class)
public @interface InvalidateMethod {
/**
* Name of the old method, values of which should be removed from the database.
*
* @return Name of the old method, case sensitive. Only first 50 characters are used.
*/
String value();
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Multiple {
InvalidateMethod[] value();
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.FormatType;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a long (64bit number) value about a Player.
* <p>
* If you want to return int values, use this provider with a long as
* return type of the method.
* <p>
* Usage: {@code @NumberProvider long method(UUID playerUUID)}
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NumberProvider {
/**
* Text displayed before the value, limited to 50 characters.
* <p>
* Should inform the user what the value represents, for example
* "Owned Chickens", "Claimed Blocks"
*
* @return String of max 50 characters, remainder will be clipped.
*/
String text();
/**
* Display-priority of the value, highest value is placed top most.
* <p>
* Two values with same priority may appear in a random order.
*
* @return Priority between 0 and {@code Integer.MAX_VALUE}.
*/
int priority() default 0;
/**
* Text displayed when hovering over the value, limited to 150 characters.
* <p>
* Should be used to clarify what the value is if not self evident, for example
* text: "Fished", description: "How long the player has fished for"
*
* @return String of max 150 characters, remainder will be clipped.
*/
String description() default "";
/**
* Apply special formatting to the value before presentation.
*
* @return {@link FormatType} that best represents the long value.
* @see FormatType for available formatters.
*/
FormatType format() default FormatType.NONE;
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Color preference of the plugin.
* <p>
* This color will be set as the default color to use for plugin's elements.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color iconColor() default Color.NONE;
}

View File

@ -0,0 +1,97 @@
/*
* 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.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a double (Percentage) about a Player.
* <p>
* Usage: {@code @PercentageProvider double method(UUID playerUUID)}
* <p>
* The returned value should be between (or one of) 0.0 (0%) and 1.0 (100%).
* Other values will be ignored.
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PercentageProvider {
/**
* Text displayed before the value, limited to 50 characters.
* <p>
* Should inform the user what the value represents, for example
* "Health", "Power"
*
* @return String of max 50 characters, remainder will be clipped.
*/
String text();
/**
* Display-priority of the value, highest value is placed top most.
* <p>
* Two values with same priority may appear in a random order.
*
* @return Priority between 0 and {@code Integer.MAX_VALUE}.
*/
int priority() default 0;
/**
* Text displayed when hovering over the value, limited to 150 characters.
* <p>
* Should be used to clarify what the value is if not self evident, for example
* text: "Power", description: "Faction power, affects ability of faction to perform actions. Regenerates"
*
* @return String of max 150 characters, remainder will be clipped.
*/
String description() default "";
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Color preference of the plugin.
* <p>
* This color will be set as the default color to use for plugin's elements.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color iconColor() default Color.NONE;
}

View File

@ -0,0 +1,70 @@
/*
* 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.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Class Annotation for informing Plan about a plugin.
*
* @author Rsl1122
* @see TabOrder to determine preferred tab ordering if you use {@link Tab}s.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PluginInfo {
/**
* Name of the plugin, limited to 50 characters.
*
* @return String of max 50 characters, remainder will be clipped.
*/
String name();
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "cube";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Color preference of the plugin.
* <p>
* This color will be set as the default color to use for plugin's elements.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color color() default Color.NONE;
}

View File

@ -0,0 +1,106 @@
/*
* 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.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method annotation to provide a String value about a Player.
* <p>
* Usage: {@code @StringProvider String method(UUID playerUUID)}
* <p>
* The returned value is limited to 100 characters, remainder will be clipped.
* <p>
* If the value is a player name, provide value for playerName=true.
* This will allow linking between pages.
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface StringProvider {
/**
* Text displayed before the value, limited to 50 characters.
* <p>
* Should inform the user what the value represents, for example
* "Town Name", "Pet Name"
*
* @return String of max 50 characters, remainder will be clipped.
*/
String text();
/**
* Display-priority of the value, highest value is placed top most.
* <p>
* Two values with same priority may appear in a random order.
*
* @return Priority between 0 and {@code Integer.MAX_VALUE}.
*/
int priority() default 0;
/**
* Text displayed when hovering over the value, limited to 150 characters.
* <p>
* Should be used to clarify what the value is if not self evident, for example
* text: "Power", description: "Faction power, affects ability of faction to perform actions. Regenerates"
*
* @return String of max 150 characters, remainder will be clipped.
*/
String description() default "";
/**
* Determine if this value represents a Player name, for example a mayor of a town.
*
* @return {@code true} if the name can be used as a link to another player's page.
*/
boolean playerName() default false;
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Color preference of the plugin.
* <p>
* This color will be set as the default color to use for plugin's elements.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color iconColor() default Color.NONE;
}

View File

@ -0,0 +1,44 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Method Annotation for determining Tab the given element should appear on.
* <p>
* If not specified Plan places the item on the default tab.
*
* @author Rsl1122
* @see TabInfo if you want to determine an icon or element order for a Tab.
* @see TabOrder to determine preferred tab ordering if you use Tabs.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Tab {
/**
* Name of the tab to place this item on.
*
* @return Tab name, limited to 50 characters.
*/
String value();
}

View File

@ -0,0 +1,74 @@
/*
* 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.ElementOrder;
import com.djrapitops.plan.extension.icon.Family;
import java.lang.annotation.*;
/**
* Class Annotation that allows determining an Icon and {@link ElementOrder} of a tab.
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(TabInfo.Multiple.class)
public @interface TabInfo {
/**
* Name of the tab this information is about.
*
* @return Tab name, limited to 50 characters.
*/
String tab();
/**
* Name of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "circle";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* Order preference for the large elements of a tab.
* <p>
* If an ordering is not present they will be added to the end in the default order.
* If a duplicate ordering exists only the first will be used for determining the order.
*
* @return ElementOrders in the order that they want to be displayed in.
*/
ElementOrder[] elementOrder();
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Multiple {
TabInfo[] value();
}
}

View File

@ -0,0 +1,45 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Class Annotation for informing Plan about plugin's tab order preference.
* <p>
* If tab order is not defined alphabetical order is used.
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TabOrder {
/**
* Order preference of different {@link Tab}s.
* <p>
* If a tab is defined that is not present it is ignored.
* If a tab is not defined but is present alphabetical order is used.
*
* @return Names of the defined tabs (case sensitive).
*/
String[] value();
}

View File

@ -0,0 +1,360 @@
/*
* 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 com.djrapitops.plan.extension.annotation.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
/**
* Implementation detail, for extracting methods from {@link com.djrapitops.plan.extension.DataExtension}.
* <p>
* This class can be used for testing validity of annotation implementations
* in your unit tests to avoid runtime errors. {@link ExtensionExtractor#validateAnnotations()}
*
* @author Rsl1122
*/
public final class ExtensionExtractor {
private final DataExtension extension;
private final String extensionName;
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 static final String WAS_OVER_50_CHARACTERS = "' was over 50 characters.";
public ExtensionExtractor(DataExtension extension) {
this.extension = extension;
extensionName = extension.getClass().getSimpleName();
}
/**
* Use this method in an unit test to validate your DataExtension.
*
* @throws IllegalArgumentException If an implementation error is found.
*/
public void validateAnnotations() {
extractAnnotationInformation();
if (!warnings.isEmpty()) {
throw new IllegalArgumentException("Warnings: " + warnings.toString());
}
}
private <T extends Annotation> Optional<T> getClassAnnotation(Class<T> ofClass) {
return Optional.ofNullable(extension.getClass().getAnnotation(ofClass));
}
private Method[] getMethods() {
return extension.getClass().getDeclaredMethods();
}
public void extractAnnotationInformation() {
extractPluginInfo();
extractInvalidMethods();
extractMethodAnnotations();
validateMethodAnnotations();
validateConditionals();
extractTabInfo();
}
private void extractMethodAnnotations() {
methodAnnotations = new MethodAnnotations();
for (Method method : getMethods()) {
if (!Modifier.isPublic(method.getModifiers())) {
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));
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));
}
if (methodAnnotations.isEmpty()) {
throw new IllegalArgumentException(extensionName + " class had no methods annotated with a Provider annotation");
}
}
private <T> void validateReturnType(Method method, Class<T> expectedType) {
Class<?> returnType = method.getReturnType();
if (!expectedType.isAssignableFrom(returnType)) {
throw new IllegalArgumentException(extensionName + "." + method.getName() + " has invalid return type. was: " + returnType.getName() + ", expected: " + expectedType.getName());
}
}
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);
}
}
private void validateMethodArguments(Method method, boolean parameterIsRequired, Class... parameterOptions) {
Class<?>[] parameterTypes = method.getParameterTypes();
// Possible parameters for the methods:
// UUID playerUUID, String playerName, Group group, none
int parameters = parameterTypes.length;
if (parameterIsRequired && parameters == 0) {
// Does not have parameters, but one is required
throw new IllegalArgumentException(extensionName + "." + method.getName() + " requires one of " + Arrays.toString(parameterOptions) + " as a parameter.");
} else if (parameters == 0) {
// Has no parameters & it is acceptable.
return;
}
if (parameters > 1) {
// Has too many parameters
throw new IllegalArgumentException(extensionName + "." + method.getName() + " has too many parameters, only one of " + Arrays.toString(parameterOptions) + " is required as a parameter.");
}
Class<?> methodParameter = parameterTypes[0];
boolean validParameter = false;
for (Class option : parameterOptions) {
if (option.equals(methodParameter)) {
validParameter = true;
}
}
if (!validParameter) {
// Has invalid parameter
throw new IllegalArgumentException(extensionName + "." + method.getName() + " has invalid parameter: '" + methodParameter.getName() + "' one of " + Arrays.toString(parameterOptions) + " is required as a parameter.");
}
// Has valid parameter & it is acceptable.
}
private void validateMethodAnnotations() {
validateBooleanProviderAnnotations();
validateNumberProviderAnnotations();
validateDoubleProviderAnnotations();
validatePercentageProviderAnnotations();
validateStringProviderAnnotations();
}
private void validateBooleanProviderAnnotations() {
for (Map.Entry<Method, BooleanProvider> booleanProvider : methodAnnotations.getMethodAnnotations(BooleanProvider.class).entrySet()) {
Method method = booleanProvider.getKey();
BooleanProvider annotation = booleanProvider.getValue();
validateReturnType(method, boolean.class);
validateMethodAnnotationPropertyLength(annotation.text(), "text", 50, method);
validateMethodAnnotationPropertyLength(annotation.description(), "description", 150, method);
validateMethodAnnotationPropertyLength(annotation.conditionName(), "conditionName", 50, method);
validateMethodArguments(method, false, UUID.class, String.class, Group.class);
String condition = MethodAnnotations.get(method, Conditional.class).map(Conditional::value).orElse(null);
if (annotation.conditionName().equals(condition)) {
warnings.add(extensionName + "." + method.getName() + " can not be conditional of itself. required condition: " + condition + ", provided condition: " + annotation.conditionName());
}
if (annotation.conditionName().isEmpty() && annotation.hidden()) {
throw new IllegalArgumentException(extensionName + "." + method.getName() + " can not be 'hidden' without a 'conditionName'");
}
}
}
private void validateNumberProviderAnnotations() {
for (Map.Entry<Method, NumberProvider> numberProvider : methodAnnotations.getMethodAnnotations(NumberProvider.class).entrySet()) {
Method method = numberProvider.getKey();
NumberProvider annotation = numberProvider.getValue();
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> numberProvider : methodAnnotations.getMethodAnnotations(DoubleProvider.class).entrySet()) {
Method method = numberProvider.getKey();
DoubleProvider annotation = numberProvider.getValue();
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> numberProvider : methodAnnotations.getMethodAnnotations(PercentageProvider.class).entrySet()) {
Method method = numberProvider.getKey();
PercentageProvider annotation = numberProvider.getValue();
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> numberProvider : methodAnnotations.getMethodAnnotations(StringProvider.class).entrySet()) {
Method method = numberProvider.getKey();
StringProvider annotation = numberProvider.getValue();
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 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,
BooleanProvider.class, DoubleProvider.class, NumberProvider.class,
PercentageProvider.class, StringProvider.class
)) {
throw new IllegalArgumentException(extensionName + "." + conditionalMethod.getName() + " did not have any associated Provider for Conditional.");
}
}
}
private void extractPluginInfo() {
pluginInfo = getClassAnnotation(PluginInfo.class).orElseThrow(() -> new IllegalArgumentException("Given class had no PluginInfo annotation"));
if (pluginInfo.name().length() > 50) {
warnings.add(extensionName + " PluginInfo 'name' was over 50 characters.");
}
}
private void extractTabInfo() {
tabInformation = new ArrayList<>();
getClassAnnotation(TabInfo.Multiple.class).ifPresent(tabs -> {
for (TabInfo tabInfo : tabs.value()) {
String tabName = tabInfo.tab();
// Length restriction check
if (tabName.length() > 50) {
warnings.add(extensionName + " tabName '" + tabName + WAS_OVER_50_CHARACTERS);
}
tabInformation.add(tabInfo);
}
});
tabOrder = getClassAnnotation(TabOrder.class).orElse(null);
Map<Method, Tab> tabs = this.methodAnnotations.getMethodAnnotations(Tab.class);
Set<String> tabNames = tabs.values().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.");
}
if (!tabNames.contains(tabName)) {
warnings.add(extensionName + " has TabInfo for " + tabName + ", but it is not used.");
}
}
// Check Tab name lengths
for (Map.Entry<Method, Tab> tab : tabs.entrySet()) {
String tabName = tab.getValue().value();
if (tabName.length() > 50) {
warnings.add(extensionName + "." + tab.getKey().getName() + " Tab '" + tabName + "' name was over 50 characters.");
}
}
}
private void extractInvalidMethods() {
invalidMethods = new ArrayList<>();
getClassAnnotation(InvalidateMethod.Multiple.class).ifPresent(tabs -> {
for (InvalidateMethod tabInfo : tabs.value()) {
String methodName = tabInfo.value();
// Length restriction check
if (methodName.length() > 50) {
warnings.add(extensionName + " invalidated method '" + methodName + WAS_OVER_50_CHARACTERS);
}
invalidMethods.add(tabInfo);
}
});
}
public List<String> getWarnings() {
return warnings;
}
public PluginInfo getPluginInfo() {
return pluginInfo;
}
public Optional<TabOrder> getTabOrder() {
return Optional.ofNullable(tabOrder);
}
public List<TabInfo> getTabInformation() {
return tabInformation != null ? tabInformation : Collections.emptyList();
}
public MethodAnnotations getMethodAnnotations() {
return methodAnnotations;
}
public List<InvalidateMethod> getInvalidateMethodAnnotations() {
return invalidMethods != null ? invalidMethods : Collections.emptyList();
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* Implementation detail, utility class for handling method annotations.
*
* @author Rsl1122
*/
public class MethodAnnotations {
private final Map<Class, Map<Method, Annotation>> byAnnotationType;
public MethodAnnotations() {
byAnnotationType = new HashMap<>();
}
public static boolean hasAnyOf(Method method, Class... annotationClasses) {
for (Annotation annotation : method.getAnnotations()) {
for (Class annotationClass : annotationClasses) {
if (annotationClass.isAssignableFrom(annotation.getClass())) {
return true;
}
}
}
return false;
}
public static <T extends Annotation> Optional<T> get(Method from, Class<T> ofClass) {
return Optional.ofNullable(from.getAnnotation(ofClass));
}
public <T extends Annotation> void put(Method method, Class<T> annotationClass, T annotation) {
Map<Method, Annotation> methods = byAnnotationType.getOrDefault(annotationClass, new HashMap<>());
methods.put(method, annotation);
byAnnotationType.put(annotationClass, methods);
}
public <T extends Annotation> Map<Method, T> getMethodAnnotations(Class<T> ofType) {
return (Map<Method, T>) byAnnotationType.getOrDefault(ofType, new HashMap<>());
}
public <T extends Annotation> Collection<T> getAnnotations(Class<T> ofType) {
return getMethodAnnotations(ofType).values();
}
public boolean isEmpty() {
return byAnnotationType.isEmpty();
}
@Override
public String toString() {
return "MethodAnnotations{" + byAnnotationType + '}';
}
}

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.icon;
import java.util.Optional;
/**
* Enum to determine what color to use for some element.
*
* @author Rsl1122
*/
public enum Color {
RED,
PINK,
PURPLE,
DEEP_PURPLE,
INDIGO,
BLUE,
LIGHT_BLUE,
CYAN,
TEAL,
GREEN,
LIGHT_GREEN,
LIME,
YELLOW,
AMBER,
ORANGE,
DEEP_ORANGE,
BROWN,
GREY,
BLUE_GREY,
BLACK,
NONE;
public static Optional<Color> getByName(String name) {
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}}

View File

@ -0,0 +1,38 @@
/*
* 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.icon;
import java.util.Optional;
/**
* Enum to determine font-awesome icon family.
*
* @author Rsl1122
*/
public enum Family {
SOLID,
REGULAR,
BRAND;
public static Optional<Family> getByName(String name) {
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.icon;
/**
* Object that represents an icon on the website.
* <p>
* See https://fontawesome.com/icons?d=gallery&m=free for icons and their {@link Family}.
*
* @author Rsl1122
*/
public class Icon {
private Family type;
private String name;
private Color color;
private Icon() {
type = Family.SOLID;
color = Color.NONE;
}
public Icon(Family type, String name, Color color) {
this.type = type;
this.name = name;
this.color = color;
}
public static Builder called(String name) {
return new Builder().called(name);
}
public static Builder of(Family type) {
return new Builder().of(type);
}
public static Builder of(Color color) {
return new Builder().of(color);
}
public Family getFamily() {
return type;
}
public String getName() {
return name;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
@Override
public String toString() {
return "Icon{" + type.name() + ", '" + name + '\'' + ", " + color.name() + '}';
}
public static class Builder {
private final Icon icon;
Builder() {
this.icon = new Icon();
}
public Builder called(String name) {
icon.name = name;
return this;
}
public Builder of(Color color) {
icon.color = color;
return this;
}
public Builder of(Family type) {
icon.type = type;
return this;
}
public Icon build() {
if (icon.name == null) {
throw new IllegalStateException("'name' was not defined yet!");
}
return icon;
}
}
}

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.extractor;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.annotation.*;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Tests for different validations of ExtensionExtractor.
* <p>
* This Test class contains only INVALID implementations of the DataExtension API.
*
* @author Rsl1122
*/
@RunWith(JUnitPlatform.class)
class ExtensionExtractorTest {
@Test
void pluginInfoIsRequired() {
class Extension implements DataExtension {}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Given class had no PluginInfo annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void providerMethodsAreRequired() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension class had no methods annotated with a Provider annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void publicProviderMethodsAreRequired() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@BooleanProvider(text = "Banned")
private boolean method(UUID playerUUID) {
return false;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension class had no methods annotated with a Provider annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void pluginInfoNameOver50Chars() {
@PluginInfo(name = "five five five five five five five five five five -")
class Extension implements DataExtension {
@BooleanProvider(text = "Required Provider")
public boolean method() {
return false;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Warnings: [Extension PluginInfo 'name' was over 50 characters.]", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void booleanProviderMustReturnBoolean() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@BooleanProvider(text = "Banned")
public String method(UUID playerUUID) {
return "false";
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.String, expected: boolean", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void booleanProviderMustReturnPrimitiveBoolean() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@BooleanProvider(text = "Banned")
public Boolean method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.Boolean, expected: boolean", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void numberProviderMustReturnPrimitiveLong() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@NumberProvider(text = "Achievements")
public Long method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.Long, expected: long", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void doubleProviderMustReturnPrimitiveDouble() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@DoubleProvider(text = "Money")
public Double method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.Double, expected: double", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void percentageProviderMustReturnPrimitiveDouble() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@PercentageProvider(text = "Achievements awarded")
public Double method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.Double, expected: double", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void stringProviderMustReturnString() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@StringProvider(text = "Town")
public Double method(UUID playerUUID) {
return null;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid return type. was: java.lang.Double, expected: java.lang.String", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void booleanProviderCanNotSupplyItsOwnConditional() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("hasJoined")
@BooleanProvider(text = "Banned", conditionName = "hasJoined")
public boolean method(UUID playerUUID) {
return false;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Warnings: [Extension.method can not be conditional of itself. required condition: hasJoined, provided condition: hasJoined]", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
}
@Test
void conditionalMethodRequiresProvider() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("hasJoined")
public boolean method(UUID playerUUID) {
return false;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
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")
class Extension implements DataExtension {
@Conditional("hasJoined")
@BooleanProvider(text = "Banned", conditionName = "isBanned")
public boolean method(Integer invalid) {
return false;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
assertEquals("Extension.method has invalid parameter: 'java.lang.Integer' 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 methodHasTooManyParameters() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@Conditional("hasJoined")
@BooleanProvider(text = "Banned", conditionName = "isBanned")
public boolean method(String playerName, UUID playerUUID) {
return false;
}
}
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
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());
}
}

View File

@ -4,13 +4,13 @@ dependencies {
compile "com.djrapitops:AbstractPluginFramework-bukkit:$abstractPluginFrameworkVersion"
compile "org.bstats:bstats-bukkit:$bstatsVersion"
compileOnly "com.destroystokyo.paper:paper-api:$paperVersion"
compileOnly "org.spigotmc:spigot-api:$spigotVersion"
compileOnly "org.bukkit:bukkit:$bukkitVersion"
// compileOnly "org.spigotmc:spigot-api:$spigotVersion"
// compileOnly "org.bukkit:bukkit:$bukkitVersion"
compileOnly "com.destroystokyo.paper:paper-api:$paperVersion"
testCompile "com.destroystokyo.paper:paper-api:$paperVersion"
testCompile "org.spigotmc:spigot-api:$spigotVersion"
testCompile "org.bukkit:bukkit:$bukkitVersion"
// testCompile "org.spigotmc:spigot-api:$spigotVersion"
// testCompile "org.bukkit:bukkit:$bukkitVersion"
testCompile "com.destroystokyo.paper:paper-api:$paperVersion"
testCompile project(path: ":common", configuration: 'testArtifacts')
}

View File

@ -100,7 +100,7 @@ public class CommandListener implements Listener {
try {
command = plugin.getServer().getCommandMap().getCommand(commandName);
} catch (NoSuchMethodError ignored) {
/* Ignored, Bukkit 1.8 has no such method */
/* Ignored, Bukkit 1.8 has no such method. This method is from Paper */
}
}
return command;

View File

@ -20,6 +20,8 @@ import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.store.objects.Nickname;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.transactions.events.*;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionServiceImplementation;
import com.djrapitops.plan.system.cache.GeolocationCache;
import com.djrapitops.plan.system.cache.NicknameCache;
import com.djrapitops.plan.system.cache.SessionCache;
@ -57,6 +59,7 @@ public class PlayerOnlineListener implements Listener {
private final Processing processing;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ExtensionServiceImplementation extensionService;
private final GeolocationCache geolocationCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
@ -70,6 +73,7 @@ public class PlayerOnlineListener implements Listener {
Processing processing,
ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionServiceImplementation extensionService,
GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
@ -81,6 +85,7 @@ public class PlayerOnlineListener implements Listener {
this.processing = processing;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
@ -139,11 +144,11 @@ public class PlayerOnlineListener implements Listener {
private void actOnJoinEvent(PlayerJoinEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
UUID playerUUID = player.getUniqueId();
UUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
AFKListener.AFK_TRACKER.performedAction(uuid, time);
AFKListener.AFK_TRACKER.performedAction(playerUUID, time);
String world = player.getWorld().getName();
String gm = player.getGameMode().name();
@ -159,21 +164,29 @@ public class PlayerOnlineListener implements Listener {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new GeoInfoStoreTransaction(uuid, address, time, geolocationCache::getCountry)
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
database.executeTransaction(new PlayerServerRegisterTransaction(uuid, player::getFirstPlayed, playerName, serverUUID));
sessionCache.cacheSession(uuid, new Session(uuid, serverUUID, time, world, gm))
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID, player::getFirstPlayed, playerName, serverUUID));
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverUUID, time, world, gm))
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));
database.executeTransaction(new NicknameStoreTransaction(
uuid, new Nickname(displayName, time, serverUUID),
(playerUUID, name) -> name.equals(nicknameCache.getDisplayName(playerUUID))
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> name.equals(nicknameCache.getDisplayName(uuid))
));
processing.submitNonCritical(processors.info().playerPageUpdateProcessor(uuid));
processing.submitNonCritical(processors.info().playerPageUpdateProcessor(playerUUID));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
}
@EventHandler(priority = EventPriority.NORMAL)
public void beforePlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
}
@EventHandler(priority = EventPriority.MONITOR)

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.system.tasks;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.ShutdownHook;
import com.djrapitops.plan.db.tasks.DBCleanTask;
import com.djrapitops.plan.extension.ExtensionServerMethodCallerTask;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.system.tasks.bukkit.BukkitTPSCountTimer;
@ -50,6 +51,7 @@ public class BukkitTaskSystem extends ServerTaskSystem {
private final PingCountTimerBukkit pingCountTimer;
private final ConfigStoreTask configStoreTask;
private final DBCleanTask dbCleanTask;
private final ExtensionServerMethodCallerTask extensionServerMethodCallerTask;
@Inject
public BukkitTaskSystem(
@ -65,7 +67,8 @@ public class BukkitTaskSystem extends ServerTaskSystem {
LogsFolderCleanTask logsFolderCleanTask,
PlayersPageRefreshTask playersPageRefreshTask,
ConfigStoreTask configStoreTask,
DBCleanTask dbCleanTask
DBCleanTask dbCleanTask,
ExtensionServerMethodCallerTask extensionServerMethodCallerTask
) {
super(
runnableFactory,
@ -80,6 +83,7 @@ public class BukkitTaskSystem extends ServerTaskSystem {
this.pingCountTimer = pingCountTimer;
this.configStoreTask = configStoreTask;
this.dbCleanTask = dbCleanTask;
this.extensionServerMethodCallerTask = extensionServerMethodCallerTask;
}
@Override
@ -88,20 +92,24 @@ public class BukkitTaskSystem extends ServerTaskSystem {
try {
plugin.registerListener(pingCountTimer);
long startDelay = TimeAmount.toTicks(config.get(TimeSettings.PING_SERVER_ENABLE_DELAY), TimeUnit.MILLISECONDS);
registerTask("PingCountTimer", pingCountTimer)
.runTaskTimer(startDelay, 40L);
// +40 ticks / 2 seconds so that update check task runs first.
long storeDelay = TimeAmount.toTicks(config.get(TimeSettings.CONFIG_UPDATE_INTERVAL), TimeUnit.MILLISECONDS) + 40;
registerTask("Config Store Task", configStoreTask).runTaskLaterAsynchronously(storeDelay);
registerTask("DB Clean Task", dbCleanTask).runTaskTimerAsynchronously(
TimeAmount.toTicks(20, TimeUnit.SECONDS),
TimeAmount.toTicks(config.get(TimeSettings.CLEAN_DATABASE_PERIOD), TimeUnit.MILLISECONDS)
);
registerTask(pingCountTimer).runTaskTimer(startDelay, 40L);
} catch (ExceptionInInitializerError | NoClassDefFoundError ignore) {
// Running CraftBukkit
}
// +40 ticks / 2 seconds so that update check task runs first.
long storeDelay = TimeAmount.toTicks(config.get(TimeSettings.CONFIG_UPDATE_INTERVAL), TimeUnit.MILLISECONDS) + 40;
registerTask(configStoreTask).runTaskLaterAsynchronously(storeDelay);
registerTask(dbCleanTask).runTaskTimerAsynchronously(
TimeAmount.toTicks(20, TimeUnit.SECONDS),
TimeAmount.toTicks(config.get(TimeSettings.CLEAN_DATABASE_PERIOD), TimeUnit.MILLISECONDS)
);
long extensionRefreshPeriod = TimeAmount.toTicks(config.get(TimeSettings.EXTENSION_DATA_REFRESH_PERIOD), TimeUnit.MILLISECONDS);
registerTask(extensionServerMethodCallerTask).runTaskTimerAsynchronously(
TimeAmount.toTicks(30, TimeUnit.SECONDS), extensionRefreshPeriod
);
shutdownHook.register();
}

View File

@ -46,6 +46,7 @@ public class PaperTPSCountTimer extends BukkitTPSCountTimer {
try {
tps = plugin.getServer().getTPS()[0];
} catch (NoSuchMethodError e) {
// This method is from Paper
return super.getTPS(diff, now, cpuUsage, usedMemory, entityCount, chunksLoaded, playersOnline, freeDiskSpace);
}

View File

@ -175,6 +175,7 @@ public class PingCountTimerBukkit extends AbsRunnable implements Listener {
private int getPing(Player player) {
if (PING_METHOD_AVAILABLE) {
// This method is from Paper
return player.spigot().getPing();
}

View File

@ -20,6 +20,8 @@ import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.transactions.events.GeoInfoStoreTransaction;
import com.djrapitops.plan.db.access.transactions.events.PlayerRegisterTransaction;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionServiceImplementation;
import com.djrapitops.plan.system.cache.GeolocationCache;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.database.DBSystem;
@ -38,6 +40,7 @@ import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import javax.inject.Inject;
import java.net.InetAddress;
@ -54,6 +57,7 @@ public class PlayerOnlineListener implements Listener {
private final Processors processors;
private final Processing processing;
private final DBSystem dbSystem;
private final ExtensionServiceImplementation extensionService;
private final GeolocationCache geolocationCache;
private final SessionCache sessionCache;
private final ServerInfo serverInfo;
@ -65,6 +69,7 @@ public class PlayerOnlineListener implements Listener {
Processors processors,
Processing processing,
DBSystem dbSystem,
ExtensionServiceImplementation extensionService,
GeolocationCache geolocationCache,
SessionCache sessionCache,
ServerInfo serverInfo,
@ -74,18 +79,19 @@ public class PlayerOnlineListener implements Listener {
this.processors = processors;
this.processing = processing;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.geolocationCache = geolocationCache;
this.sessionCache = sessionCache;
this.serverInfo = serverInfo;
this.errorHandler = errorHandler;
}
@EventHandler
@EventHandler(priority = EventPriority.HIGHEST)
public void onPostLogin(PostLoginEvent event) {
try {
ProxiedPlayer player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String name = player.getName();
String playerName = player.getName();
InetAddress address = player.getAddress().getAddress();
long time = System.currentTimeMillis();
@ -99,15 +105,24 @@ public class PlayerOnlineListener implements Listener {
);
}
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, name));
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName));
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID()));
} catch (Exception e) {
errorHandler.log(L.WARN, this.getClass(), e);
}
}
@EventHandler
@EventHandler(priority = EventPriority.NORMAL)
public void beforeLogout(PlayerDisconnectEvent event) {
ProxiedPlayer player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onLogout(PlayerDisconnectEvent event) {
try {
ProxiedPlayer player = event.getPlayer();
@ -121,7 +136,7 @@ public class PlayerOnlineListener implements Listener {
}
}
@EventHandler
@EventHandler(priority = EventPriority.HIGHEST)
public void onServerSwitch(ServerSwitchEvent event) {
try {
ProxiedPlayer player = event.getPlayer();

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.system.tasks;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.db.tasks.DBCleanTask;
import com.djrapitops.plan.extension.ExtensionServerMethodCallerTask;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.system.tasks.bungee.BungeeTPSCountTimer;
@ -47,6 +48,7 @@ public class BungeeTaskSystem extends TaskSystem {
private final PlayersPageRefreshTask playersPageRefreshTask;
private final NetworkConfigStoreTask networkConfigStoreTask;
private final DBCleanTask dbCleanTask;
private final ExtensionServerMethodCallerTask extensionServerMethodCallerTask;
@Inject
public BungeeTaskSystem(
@ -59,7 +61,8 @@ public class BungeeTaskSystem extends TaskSystem {
LogsFolderCleanTask logsFolderCleanTask,
PlayersPageRefreshTask playersPageRefreshTask,
NetworkConfigStoreTask networkConfigStoreTask,
DBCleanTask dbCleanTask
DBCleanTask dbCleanTask,
ExtensionServerMethodCallerTask extensionServerMethodCallerTask
) {
super(runnableFactory, bungeeTPSCountTimer);
this.plugin = plugin;
@ -71,6 +74,7 @@ public class BungeeTaskSystem extends TaskSystem {
this.playersPageRefreshTask = playersPageRefreshTask;
this.networkConfigStoreTask = networkConfigStoreTask;
this.dbCleanTask = dbCleanTask;
this.extensionServerMethodCallerTask = extensionServerMethodCallerTask;
}
@Override
@ -85,18 +89,23 @@ public class BungeeTaskSystem extends TaskSystem {
plugin.registerListener(pingCountTimer);
long startDelay = TimeAmount.toTicks(config.get(TimeSettings.PING_SERVER_ENABLE_DELAY), TimeUnit.MILLISECONDS);
runnableFactory.create("PingCountTimer", pingCountTimer).runTaskTimer(startDelay, PingCountTimerBungee.PING_INTERVAL);
registerTask(pingCountTimer).runTaskTimer(startDelay, PingCountTimerBungee.PING_INTERVAL);
registerTask(playersPageRefreshTask)
.runTaskTimerAsynchronously(TimeAmount.toTicks(5L, TimeUnit.MINUTES), TimeAmount.toTicks(5L, TimeUnit.MINUTES));
// +40 ticks / 2 seconds so that update check task runs first.
long storeDelay = TimeAmount.toTicks(config.get(TimeSettings.CONFIG_UPDATE_INTERVAL), TimeUnit.MILLISECONDS) + 40;
registerTask("Config Store Task", networkConfigStoreTask).runTaskLaterAsynchronously(storeDelay);
registerTask(networkConfigStoreTask).runTaskLaterAsynchronously(storeDelay);
registerTask("DB Clean Task", dbCleanTask).runTaskTimerAsynchronously(
registerTask(dbCleanTask).runTaskTimerAsynchronously(
TimeAmount.toTicks(20, TimeUnit.SECONDS),
TimeAmount.toTicks(config.get(TimeSettings.CLEAN_DATABASE_PERIOD), TimeUnit.MILLISECONDS)
);
long extensionRefreshPeriod = TimeAmount.toTicks(config.get(TimeSettings.EXTENSION_DATA_REFRESH_PERIOD), TimeUnit.MILLISECONDS);
registerTask(extensionServerMethodCallerTask).runTaskTimerAsynchronously(
TimeAmount.toTicks(30, TimeUnit.SECONDS), extensionRefreshPeriod
);
}
}

View File

@ -1,5 +1,7 @@
dependencies {
compile "com.djrapitops:AbstractPluginFramework-api:$abstractPluginFrameworkVersion"
compile project(path: ":api", configuration: 'shadow')
compile project(path: ":extensions", configuration: 'shadow')
compile "com.djrapitops:PlanPluginBridge:$planPluginBridgeVersion"
compile "org.apache.httpcomponents:httpclient:$httpClientVersion"
compile "org.apache.commons:commons-text:$commonsTextVersion"
@ -11,6 +13,8 @@ dependencies {
compile "org.slf4j:slf4j-api:$slf4jVersion"
compile "com.maxmind.geoip2:geoip2:$geoIpVersion"
compileOnly "com.google.guava:guava:$guavaVersion"
testCompile project(path: ":api", configuration: 'shadow')
}
shadowJar {

View File

@ -95,7 +95,7 @@ public class TableContainer {
Serializable value = row[i];
Formatter formatter = formatters[i];
body.append("<td").append(formatter != null ? " data-order=\"" + value + "\">" : ">");
body.append(formatter != null ? formatter.apply(value) : value);
body.append(formatter != null ? formatter.apply(value) : (value != null ? value : '-'));
}
body.append("</td>");
} catch (ClassCastException | ArrayIndexOutOfBoundsException e) {

View File

@ -38,8 +38,11 @@ public class PluginsConfigSection {
}
public boolean hasSection(PluginData dataSource) {
return hasSection(dataSource.getSourcePlugin());
}
public boolean hasSection(String pluginName) {
ConfigNode section = getPluginsSection();
String pluginName = dataSource.getSourcePlugin();
return section.getNode(pluginName + ".Enabled").isPresent();
}
@ -49,8 +52,11 @@ public class PluginsConfigSection {
}
public void createSection(PluginData dataSource) throws IOException {
createSection(dataSource.getSourcePlugin());
}
public void createSection(String pluginName) throws IOException {
ConfigNode section = getPluginsSection();
String pluginName = dataSource.getSourcePlugin();
section.set(pluginName + ".Enabled", true);
section.sort();
@ -58,9 +64,11 @@ public class PluginsConfigSection {
}
public boolean isEnabled(PluginData dataSource) {
ConfigNode section = getPluginsSection();
return isEnabled(dataSource.getSourcePlugin());
}
String pluginName = dataSource.getSourcePlugin();
public boolean isEnabled(String pluginName) {
ConfigNode section = getPluginsSection();
return section.getBoolean(pluginName + ".Enabled");
}
}

View File

@ -37,6 +37,7 @@ import com.djrapitops.plan.utilities.html.graphs.bar.BarGraph;
import com.djrapitops.plan.utilities.html.graphs.line.PingGraph;
import com.djrapitops.plan.utilities.html.graphs.pie.WorldPie;
import com.djrapitops.plan.utilities.html.graphs.stack.StackGraph;
import com.djrapitops.plan.utilities.html.pages.AnalysisPluginTabs;
import com.djrapitops.plan.utilities.html.structure.Accordions;
import com.djrapitops.plan.utilities.html.structure.AnalysisPluginsTabContentCreator;
import com.djrapitops.plan.utilities.html.structure.RecentLoginList;
@ -488,13 +489,14 @@ public class AnalysisContainer extends DynamicDataContainer {
private void addPluginSuppliers() {
// TODO Refactor into a system that supports running the analysis on Bungee
Key<String[]> navAndTabs = new Key<>(new Type<String[]>() {
}, "NAV_AND_TABS");
Key<String[]> navAndTabs = new Key<>(new Type<String[]>() {}, "NAV_AND_TABS");
Key<AnalysisPluginTabs> pluginTabs = new Key<>(AnalysisPluginTabs.class, "PLUGIN_TABS");
putCachingSupplier(navAndTabs, () -> pluginsTabContentCreator.createContent(
this, getValue(AnalysisKeys.PLAYERS_MUTATOR).orElse(new PlayersMutator(new ArrayList<>()))
));
putSupplier(AnalysisKeys.PLUGINS_TAB_NAV, () -> getUnsafe(navAndTabs)[0]);
putSupplier(AnalysisKeys.PLUGINS_TAB, () -> getUnsafe(navAndTabs)[1]);
putCachingSupplier(pluginTabs, () -> new AnalysisPluginTabs(serverContainer.getValue(ServerKeys.EXTENSION_DATA).orElse(new ArrayList<>()), formatters));
putSupplier(AnalysisKeys.PLUGINS_TAB_NAV, () -> getUnsafe(pluginTabs).getNav() + getUnsafe(navAndTabs)[0]);
putSupplier(AnalysisKeys.PLUGINS_TAB, () -> getUnsafe(pluginTabs).getTabs() + getUnsafe(navAndTabs)[1]);
}
@Singleton

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.data.store.Type;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.objects.DateObj;
import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
import java.util.List;
import java.util.Map;
@ -63,4 +64,5 @@ public class ServerKeys {
public static final Key<DateObj<Integer>> ALL_TIME_PEAK_PLAYERS = new Key<>(new Type<DateObj<Integer>>() {}, "all_time_peak_players");
public static final Key<DateObj<Integer>> RECENT_PEAK_PLAYERS = new Key<>(new Type<DateObj<Integer>>() {}, "recent_peak_players");
public static final Key<Map<String, Integer>> COMMAND_USAGE = new Key<>(new Type<Map<String, Integer>>() {}, "command_usage");
public static final Key<List<ExtensionServerData>> EXTENSION_DATA = new Key<>(new Type<List<ExtensionServerData>>() {}, "extension_data");
}

View File

@ -26,6 +26,7 @@ import com.djrapitops.plan.db.access.transactions.init.CreateIndexTransaction;
import com.djrapitops.plan.db.access.transactions.init.CreateTablesTransaction;
import com.djrapitops.plan.db.access.transactions.init.OperationCriticalTransaction;
import com.djrapitops.plan.db.patches.*;
import com.djrapitops.plan.system.DebugChannels;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
@ -219,6 +220,7 @@ public abstract class SQLDB extends AbstractDatabase {
return CompletableFuture.supplyAsync(() -> {
accessLock.checkAccess(transaction);
logger.getDebugLogger().logOn(DebugChannels.SQL, "Executing: " + transaction.getClass().getSimpleName());
transaction.executeTransaction(this);
return CompletableFuture.completedFuture(null);
}, getTransactionExecutor()).handle(errorHandler(origin));

View File

@ -27,6 +27,7 @@ import com.djrapitops.plan.db.access.queries.ServerAggregateQueries;
import com.djrapitops.plan.db.access.queries.objects.ServerQueries;
import com.djrapitops.plan.db.access.queries.objects.TPSQueries;
import com.djrapitops.plan.db.access.queries.objects.WorldTimesQueries;
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerDataQuery;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.info.server.Server;
@ -95,6 +96,8 @@ public class ServerContainerQuery implements Query<ServerContainer> {
container.putCachingSupplier(ServerKeys.MOB_KILL_COUNT, () -> SessionsMutator.forContainer(container).toMobKillCount());
container.putCachingSupplier(ServerKeys.DEATH_COUNT, () -> SessionsMutator.forContainer(container).toDeathCount());
container.putCachingSupplier(ServerKeys.EXTENSION_DATA, () -> db.query(new ExtensionServerDataQuery(serverUUID)));
return container;
}
}

View File

@ -141,9 +141,11 @@ public abstract class Transaction {
protected void executeOther(Transaction transaction) {
transaction.db = db;
transaction.dbType = dbType;
transaction.connection = this.connection;
transaction.performOperations();
transaction.connection = null;
transaction.dbType = null;
transaction.db = null;
}

View File

@ -44,6 +44,12 @@ public class RemoveEverythingTransaction extends Transaction {
clearTable(TPSTable.TABLE_NAME);
clearTable(SecurityTable.TABLE_NAME);
clearTable(ServerTable.TABLE_NAME);
clearTable(ExtensionPlayerValueTable.TABLE_NAME);
clearTable(ExtensionServerValueTable.TABLE_NAME);
clearTable(ExtensionProviderTable.TABLE_NAME);
clearTable(ExtensionTabTable.TABLE_NAME);
clearTable(ExtensionPluginTable.TABLE_NAME);
clearTable(ExtensionIconTable.TABLE_NAME);
}
private void clearTable(String tableName) {

View File

@ -27,6 +27,7 @@ import com.djrapitops.plan.db.access.transactions.commands.RemovePlayerTransacti
import com.djrapitops.plan.db.sql.tables.PingTable;
import com.djrapitops.plan.db.sql.tables.SessionsTable;
import com.djrapitops.plan.db.sql.tables.TPSTable;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalResultsTransaction;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plugin.api.TimeAmount;
@ -73,6 +74,9 @@ public class CleanTransaction extends Transaction {
execute(cleanTPSTable(allTimePeak.orElse(-1)));
execute(cleanPingTable());
// Clean DataExtension data
executeOther(new RemoveUnsatisfiedConditionalResultsTransaction());
int removed = cleanOldPlayers();
if (removed > 0) {
logger.info(locale.getString(PluginLang.DB_NOTIFY_CLEAN, removed));

View File

@ -45,5 +45,13 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
execute(WorldTimesTable.createTableSQL(dbType));
execute(SecurityTable.createTableSQL(dbType));
execute(SettingsTable.createTableSQL(dbType));
// DataExtension tables
execute(ExtensionIconTable.createTableSQL(dbType));
execute(ExtensionPluginTable.createTableSQL(dbType));
execute(ExtensionTabTable.createTableSQL(dbType));
execute(ExtensionProviderTable.createTableSQL(dbType));
execute(ExtensionPlayerValueTable.createTableSQL(dbType));
execute(ExtensionServerValueTable.createTableSQL(dbType));
}
}

View File

@ -29,7 +29,11 @@ public class Sql {
public static final String GROUP_BY = " GROUP BY ";
public static final String ORDER_BY = " ORDER BY ";
public static final String INNER_JOIN = " INNER JOIN ";
public static final String LEFT_JOIN = " LEFT JOIN ";
public static final String AND = " AND ";
public static final String OR = " OR ";
public static final String IS_NULL = " IS NULL";
public static final String IS_NOT_NULL = " IS NOT NULL";
private Sql() {
throw new IllegalStateException("Variable Class");

View File

@ -0,0 +1,74 @@
/*
* 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.db.sql.tables;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.parsing.CreateTableParser;
import com.djrapitops.plan.db.sql.parsing.Sql;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import com.djrapitops.plan.extension.icon.Icon;
import org.apache.commons.lang3.StringUtils;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Table information about 'plan_extension_icons'.
*
* @author Rsl1122
*/
public class ExtensionIconTable {
public static final String TABLE_NAME = "plan_extension_icons";
public static final String ID = "id";
public static final String ICON_NAME = "name";
public static final String FAMILY = "family";
public static final String COLOR = "color";
public static final String STATEMENT_SELECT_ICON_ID = "(" + SELECT + ID +
FROM + TABLE_NAME +
WHERE + ICON_NAME + "=?" +
AND + FAMILY + "=?" +
AND + COLOR + "=?)";
public static void set3IconValuesToStatement(PreparedStatement statement, Icon icon) throws SQLException {
set3IconValuesToStatement(statement, 1, icon);
}
public static void set3IconValuesToStatement(PreparedStatement statement, int parameterIndex, Icon icon) throws SQLException {
statement.setString(parameterIndex, StringUtils.truncate(icon.getName(), 50));
statement.setString(parameterIndex + 1, icon.getFamily().name());
statement.setString(parameterIndex + 2, icon.getColor().name());
}
private ExtensionIconTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableParser.create(TABLE_NAME, dbType)
.column(ID, INT).primaryKey()
.column(ICON_NAME, Sql.varchar(50)).notNull().defaultValue("'question'")
.column(FAMILY, Sql.varchar(15)).notNull().defaultValue("'" + Family.SOLID.name() + "'")
.column(COLOR, Sql.varchar(25)).notNull().defaultValue("'" + Color.NONE.name() + "'")
.build();
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.db.sql.tables;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.parsing.CreateTableParser;
import com.djrapitops.plan.db.sql.parsing.Sql;
/**
* Table information about 'plan_extension_user_values'.
*
* @author Rsl1122
*/
public class ExtensionPlayerValueTable {
public static final String TABLE_NAME = "plan_extension_user_values";
public static final String ID = "id";
public static final String PROVIDER_ID = "provider_id";
public static final String USER_UUID = "uuid";
public static final String BOOLEAN_VALUE = "boolean_value";
public static final String DOUBLE_VALUE = "double_value";
public static final String PERCENTAGE_VALUE = "percentage_value";
public static final String LONG_VALUE = "long_value";
public static final String STRING_VALUE = "string_value";
public static final String GROUP_VALUE = "group_value";
private ExtensionPlayerValueTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableParser.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(BOOLEAN_VALUE, Sql.BOOL)
.column(DOUBLE_VALUE, Sql.DOUBLE)
.column(PERCENTAGE_VALUE, Sql.DOUBLE)
.column(LONG_VALUE, Sql.LONG)
.column(STRING_VALUE, Sql.varchar(50))
.column(GROUP_VALUE, Sql.varchar(50))
.column(USER_UUID, Sql.varchar(36)).notNull()
.column(PROVIDER_ID, Sql.INT).notNull()
.foreignKey(PROVIDER_ID, ExtensionProviderTable.TABLE_NAME, ExtensionProviderTable.ID)
.build();
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.db.sql.tables;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.parsing.CreateTableParser;
import com.djrapitops.plan.db.sql.parsing.Sql;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Table information about 'plan_extension_plugins'.
*
* @author Rsl1122
*/
public class ExtensionPluginTable {
public static final String TABLE_NAME = "plan_extension_plugins";
public static final String ID = "id";
public static final String PLUGIN_NAME = "name";
public static final String LAST_UPDATED = "last_updated";
public static final String SERVER_UUID = "server_uuid";
public static final String ICON_ID = "icon_id";
public static final String STATEMENT_SELECT_PLUGIN_ID = "(" + SELECT + ID +
FROM + TABLE_NAME +
WHERE + PLUGIN_NAME + "=?" +
AND + SERVER_UUID + "=?)";
public static void set2PluginValuesToStatement(PreparedStatement statement, int parameterIndex, String pluginName, UUID serverUUID) throws SQLException {
statement.setString(parameterIndex, pluginName);
statement.setString(parameterIndex + 1, serverUUID.toString());
}
private ExtensionPluginTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableParser.create(TABLE_NAME, dbType)
.column(ID, INT).primaryKey()
.column(PLUGIN_NAME, Sql.varchar(50)).notNull()
.column(LAST_UPDATED, LONG).notNull()
.column(SERVER_UUID, Sql.varchar(36)).notNull()
.column(ICON_ID, INT).notNull()
.foreignKey(ICON_ID, ExtensionIconTable.TABLE_NAME, ExtensionIconTable.ID)
.build();
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.db.sql.tables;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.parsing.CreateTableParser;
import com.djrapitops.plan.db.sql.parsing.Sql;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Table information about 'plan_extension_providers'.
*
* @author Rsl1122
*/
public class ExtensionProviderTable {
public static final String TABLE_NAME = "plan_extension_providers";
public static final String ID = "id";
public static final String PROVIDER_NAME = "name";
public static final String TEXT = "text"; // Can be null
public static final String DESCRIPTION = "description"; // Can be null
public static final String PRIORITY = "priority";
public static final String GROUPABLE = "groupable"; // default false
public static final String CONDITION = "condition_name"; // Can be null, related to @Conditional
public static final String PLUGIN_ID = "plugin_id";
public static final String ICON_ID = "icon_id";
public static final String TAB_ID = "tab_id"; // Can be null, related to @Tab
public static final String HIDDEN = "hidden"; // default false, related to @BooleanProvider
public static final String PROVIDED_CONDITION = "provided_condition"; // Can be null, related to @BooleanProvider
public static final String FORMAT_TYPE = "format_type"; // Can be null, related to @NumberProvider
public static final String IS_PLAYER_NAME = "player_name"; // default false, related to @StringProvider
public static final String STATEMENT_SELECT_PROVIDER_ID = "(" + SELECT + ID + FROM + TABLE_NAME +
WHERE + PROVIDER_NAME + "=?" +
AND + PLUGIN_ID + "=" + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ")";
public static void set3PluginValuesToStatement(PreparedStatement statement, int parameterIndex, String providerName, String pluginName, UUID serverUUID) throws SQLException {
statement.setString(parameterIndex, providerName);
ExtensionPluginTable.set2PluginValuesToStatement(statement, parameterIndex + 1, pluginName, serverUUID);
}
private ExtensionProviderTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableParser.create(TABLE_NAME, dbType)
.column(ID, INT).primaryKey()
.column(PROVIDER_NAME, Sql.varchar(50)).notNull()
.column(TEXT, Sql.varchar(50)).notNull()
.column(DESCRIPTION, Sql.varchar(150))
.column(PRIORITY, INT).notNull().defaultValue("0")
.column(GROUPABLE, BOOL).notNull().defaultValue(false)
.column(CONDITION, Sql.varchar(54)) // 50 + 4 for "not_"
.column(PROVIDED_CONDITION, Sql.varchar(50))
.column(FORMAT_TYPE, Sql.varchar(25))
.column(HIDDEN, BOOL).notNull().defaultValue(false)
.column(IS_PLAYER_NAME, BOOL).notNull().defaultValue(false)
.column(PLUGIN_ID, INT).notNull()
.column(ICON_ID, INT).notNull()
.column(TAB_ID, INT)
.foreignKey(PLUGIN_ID, ExtensionPluginTable.TABLE_NAME, ExtensionPluginTable.ID)
.foreignKey(ICON_ID, ExtensionIconTable.TABLE_NAME, ExtensionIconTable.ID)
.foreignKey(TAB_ID, ExtensionTabTable.TABLE_NAME, ExtensionTabTable.ID)
.build();
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.db.sql.tables;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.parsing.CreateTableParser;
import com.djrapitops.plan.db.sql.parsing.Sql;
/**
* Table information about 'plan_extension_server_values'.
*
* @author Rsl1122
*/
public class ExtensionServerValueTable {
public static final String TABLE_NAME = "plan_extension_server_values";
public static final String ID = "id";
public static final String PROVIDER_ID = "provider_id";
// Server UUID can be figured out by limiting by Providers.
public static final String BOOLEAN_VALUE = "boolean_value";
public static final String DOUBLE_VALUE = "double_value";
public static final String PERCENTAGE_VALUE = "percentage_value";
public static final String LONG_VALUE = "long_value";
public static final String STRING_VALUE = "string_value";
public static final String GROUP_VALUE = "group_value";
private ExtensionServerValueTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableParser.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(BOOLEAN_VALUE, Sql.BOOL)
.column(DOUBLE_VALUE, Sql.DOUBLE)
.column(PERCENTAGE_VALUE, Sql.DOUBLE)
.column(LONG_VALUE, Sql.LONG)
.column(STRING_VALUE, Sql.varchar(50))
.column(GROUP_VALUE, Sql.varchar(50))
.column(PROVIDER_ID, Sql.INT).notNull()
.foreignKey(PROVIDER_ID, ExtensionProviderTable.TABLE_NAME, ExtensionProviderTable.ID)
.build();
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.db.sql.tables;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.parsing.CreateTableParser;
import com.djrapitops.plan.db.sql.parsing.Sql;
import com.djrapitops.plan.extension.ElementOrder;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Table information about 'plan_extension_tabs'.
*
* @author Rsl1122
*/
public class ExtensionTabTable {
public static final String TABLE_NAME = "plan_extension_tabs";
public static final String ID = "id";
public static final String TAB_NAME = "name";
public static final String ELEMENT_ORDER = "element_order";
public static final String TAB_PRIORITY = "tab_priority";
public static final String PLUGIN_ID = "plugin_id";
public static final String ICON_ID = "icon_id";
public static final String STATEMENT_SELECT_TAB_ID = "(" + SELECT + ID +
FROM + TABLE_NAME +
WHERE + TAB_NAME + "=?" +
AND + PLUGIN_ID + "=" + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ")";
public static void set3TabValuesToStatement(PreparedStatement statement, int parameterIndex, String tabName, String pluginName, UUID serverUUID) throws SQLException {
statement.setString(parameterIndex, tabName);
ExtensionPluginTable.set2PluginValuesToStatement(statement, parameterIndex + 1, pluginName, serverUUID);
}
private ExtensionTabTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableParser.create(TABLE_NAME, dbType)
.column(ID, INT).primaryKey()
.column(TAB_NAME, Sql.varchar(50)).notNull()
.column(ELEMENT_ORDER, Sql.varchar(100)).notNull().defaultValue("'" + ElementOrder.serialize(ElementOrder.values()) + "'")
.column(TAB_PRIORITY, INT).notNull()
.column(PLUGIN_ID, INT).notNull()
.column(ICON_ID, INT).notNull()
.foreignKey(PLUGIN_ID, ExtensionPluginTable.TABLE_NAME, ExtensionPluginTable.ID)
.foreignKey(ICON_ID, ExtensionIconTable.TABLE_NAME, ExtensionIconTable.ID)
.build();
}
}

View File

@ -0,0 +1,43 @@
/*
* 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;
import com.djrapitops.plugin.task.AbsRunnable;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Task for updating {@link DataExtension} server values periodically.
*
* @author Rsl1122
*/
@Singleton
public class ExtensionServerMethodCallerTask extends AbsRunnable {
private final ExtensionServiceImplementation extensionServiceImplementation;
@Inject
public ExtensionServerMethodCallerTask(ExtensionServiceImplementation extensionServiceImplementation) {
this.extensionServiceImplementation = extensionServiceImplementation;
}
@Override
public void run() {
extensionServiceImplementation.updateServerValues(CallEvents.SERVER_PERIODICAL);
}
}

View File

@ -0,0 +1,184 @@
/*
* 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;
import com.djrapitops.plan.data.plugin.PluginsConfigSection;
import com.djrapitops.plan.extension.implementation.CallerImplementation;
import com.djrapitops.plan.extension.implementation.DataProviderExtractor;
import com.djrapitops.plan.extension.implementation.ExtensionRegister;
import com.djrapitops.plan.extension.implementation.providers.gathering.ProviderValueGatherer;
import com.djrapitops.plan.system.DebugChannels;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Implementation for {@link ExtensionService}.
*
* @author Rsl1122
*/
@Singleton
public class ExtensionServiceImplementation implements ExtensionService {
private final PlanConfig config;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private final ExtensionRegister extensionRegister;
private final PluginLogger logger;
private final ErrorHandler errorHandler;
private final Map<String, ProviderValueGatherer> extensionGatherers;
@Inject
public ExtensionServiceImplementation(
PlanConfig config,
DBSystem dbSystem,
ServerInfo serverInfo,
ExtensionRegister extensionRegister,
PluginLogger logger,
ErrorHandler errorHandler
) {
this.config = config;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
this.extensionRegister = extensionRegister;
this.logger = logger;
this.errorHandler = errorHandler;
extensionGatherers = new HashMap<>();
ExtensionService.ExtensionServiceHolder.set(this);
}
public void enable() {
extensionRegister.registerBuiltInExtensions();
}
@Override
public Optional<Caller> register(DataExtension extension) {
DataProviderExtractor extractor = new DataProviderExtractor(extension);
String pluginName = extractor.getPluginName();
if (shouldNotAllowRegistration(pluginName)) return Optional.empty();
for (String warning : extractor.getWarnings()) {
logger.warn("DataExtension API implementation mistake for " + pluginName + ": " + warning);
}
ProviderValueGatherer gatherer = new ProviderValueGatherer(extension, extractor, dbSystem, serverInfo, logger);
gatherer.storeExtensionInformation();
extensionGatherers.put(pluginName, gatherer);
updateServerValues(gatherer, CallEvents.SERVER_EXTENSION_REGISTER);
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, pluginName + " extension registered.");
return Optional.of(new CallerImplementation(gatherer, this));
}
@Override
public void unregister(DataExtension extension) {
DataProviderExtractor extractor = new DataProviderExtractor(extension);
String pluginName = extractor.getPluginName();
if (extensionGatherers.remove(pluginName) != null) {
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, pluginName + " extension unregistered.");
}
}
private boolean shouldNotAllowRegistration(String pluginName) {
PluginsConfigSection pluginsConfig = config.getPluginsConfigSection();
if (!pluginsConfig.hasSection(pluginName)) {
try {
pluginsConfig.createSection(pluginName);
} catch (IOException e) {
errorHandler.log(L.ERROR, this.getClass(), e);
logger.warn("Could not register DataExtension for " + pluginName + " due to " + e.toString());
return true;
}
}
if (!pluginsConfig.isEnabled(pluginName)) {
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, pluginName + " extension disabled in the config.");
return true;
}
return false; // Should register.
}
public void updatePlayerValues(UUID playerUUID, String playerName, CallEvents event) {
for (ProviderValueGatherer gatherer : extensionGatherers.values()) {
updatePlayerValues(gatherer, playerUUID, playerName, event);
}
}
public void updatePlayerValues(ProviderValueGatherer gatherer, UUID playerUUID, String playerName, CallEvents event) {
if (!gatherer.canCallEvent(event)) {
return;
}
try {
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, "Gathering values for: " + playerName);
gatherer.updateValues(playerUUID, playerName);
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, "Gathering completed: " + playerName);
} catch (Exception | NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
logger.warn(gatherer.getPluginName() + " ran into (but failed safely) " + e.getClass().getSimpleName() +
" when updating value for '" + playerName +
"', (You can disable integration with setting 'Plugins." + gatherer.getPluginName() + ".Enabled')" +
" reason: '" + e.getMessage() +
"', stack trace to follow:");
errorHandler.log(L.WARN, gatherer.getClass(), e);
}
}
public void updateServerValues(CallEvents event) {
for (ProviderValueGatherer gatherer : extensionGatherers.values()) {
updateServerValues(gatherer, event);
}
}
public void updateServerValues(ProviderValueGatherer gatherer, CallEvents event) {
if (!gatherer.canCallEvent(event)) {
return;
}
try {
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, "Gathering values for server");
gatherer.updateValues();
logger.getDebugLogger().logOn(DebugChannels.DATA_EXTENSIONS, "Gathering completed for server");
} catch (Exception | NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
logger.warn(gatherer.getPluginName() + " ran into (but failed safely) " + e.getClass().getSimpleName() +
" when updating value for server" +
", (You can disable integration with setting 'Plugins." + gatherer.getPluginName() + ".Enabled')" +
" reason: '" + e.getMessage() +
"', stack trace to follow:");
errorHandler.log(L.WARN, gatherer.getClass(), e);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.Caller;
import com.djrapitops.plan.extension.ExtensionServiceImplementation;
import com.djrapitops.plan.extension.implementation.providers.gathering.ProviderValueGatherer;
import com.djrapitops.plugin.utilities.Verify;
import java.util.UUID;
/**
* Implementation for {@link Caller} interface.
*
* @author Rsl1122
*/
public class CallerImplementation implements Caller {
private final ProviderValueGatherer gatherer;
private final ExtensionServiceImplementation extensionServiceImplementation;
public CallerImplementation(ProviderValueGatherer gatherer, ExtensionServiceImplementation extensionServiceImplementation) {
this.gatherer = gatherer;
this.extensionServiceImplementation = extensionServiceImplementation;
}
@Override
public void updatePlayerData(UUID playerUUID, String playerName) throws IllegalArgumentException {
Verify.nullCheck(playerUUID, () -> new IllegalArgumentException("'playerUUID' can not be null!"));
Verify.nullCheck(playerName, () -> new IllegalArgumentException("'playerName' can not be null!"));
extensionServiceImplementation.updatePlayerValues(gatherer, playerUUID, playerName, CallEvents.MANUAL);
}
@Override
public void updateServerData() {
extensionServiceImplementation.updateServerValues(gatherer, CallEvents.MANUAL);
}
}

View File

@ -0,0 +1,160 @@
/*
* 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;
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.icon.Color;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.providers.*;
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.stream.Collectors;
/**
* Extracts objects that can be used to obtain data to store in the database.
* <p>
* Goal of this class is to abstract away DataExtension API annotations so that they will not be needed outside when calling methods.
*
* @author Rsl1122
*/
public class DataProviderExtractor {
private ExtensionExtractor extensionExtractor;
private DataProviders dataProviders;
/**
* Create a DataProviderExtractor.
*
* @param extension DataExtension to extract information from.
* @throws IllegalArgumentException If something is badly wrong with the specified extension class annotations.
*/
public DataProviderExtractor(DataExtension extension) {
extensionExtractor = new ExtensionExtractor(extension);
extensionExtractor.extractAnnotationInformation();
dataProviders = new DataProviders();
extractAllDataProviders();
}
public String getPluginName() {
return extensionExtractor.getPluginInfo().name();
}
public Icon getPluginIcon() {
PluginInfo pluginInfo = extensionExtractor.getPluginInfo();
return new Icon(pluginInfo.iconFamily(), pluginInfo.iconName(), pluginInfo.color());
}
public Collection<TabInformation> getPluginTabs() {
Map<String, TabInfo> tabInformation = extensionExtractor.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 extensionExtractor.getMethodAnnotations().getAnnotations(Tab.class).stream()
.map(Tab::value)
.distinct()
.map(tabName -> {
Optional<TabInfo> tabInfo = Optional.ofNullable(tabInformation.get(tabName));
return new TabInformation(
tabName,
tabInfo.map(info -> new Icon(info.iconFamily(), info.iconName(), Color.NONE)).orElse(null),
tabInfo.map(TabInfo::elementOrder).orElse(null),
order.getOrDefault(tabName, 100)
);
}).collect(Collectors.toList());
}
private Map<String, Integer> orderToMap(String[] order) {
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < order.length; i++) {
map.put(order[i], i);
}
return map;
}
public Optional<String[]> getTabOrder() {
return extensionExtractor.getTabOrder().map(TabOrder::value);
}
public Collection<String> getInvalidatedMethods() {
return extensionExtractor.getInvalidateMethodAnnotations().stream()
.map(InvalidateMethod::value)
.collect(Collectors.toSet());
}
public DataProviders getDataProviders() {
return dataProviders;
}
private void extractAllDataProviders() {
PluginInfo pluginInfo = extensionExtractor.getPluginInfo();
MethodAnnotations methodAnnotations = extensionExtractor.getMethodAnnotations();
Map<Method, Tab> tabs = methodAnnotations.getMethodAnnotations(Tab.class);
Map<Method, Conditional> conditions = methodAnnotations.getMethodAnnotations(Conditional.class);
extractDataProviders(pluginInfo, tabs, conditions, BooleanProvider.class, BooleanDataProvider::placeToDataProviders);
extractDataProviders(pluginInfo, tabs, conditions, DoubleProvider.class, DoubleDataProvider::placeToDataProviders);
extractDataProviders(pluginInfo, tabs, conditions, PercentageProvider.class, PercentageDataProvider::placeToDataProviders);
extractDataProviders(pluginInfo, tabs, conditions, NumberProvider.class, NumberDataProvider::placeToDataProviders);
extractDataProviders(pluginInfo, tabs, conditions, StringProvider.class, StringDataProvider::placeToDataProviders);
}
private <T extends Annotation> void extractDataProviders(PluginInfo pluginInfo, Map<Method, Tab> tabs, Map<Method, Conditional> conditions, Class<T> ofKind, DataProviderFactory<T> factory) {
for (Map.Entry<Method, T> entry : extensionExtractor.getMethodAnnotations().getMethodAnnotations(ofKind).entrySet()) {
Method method = entry.getKey();
T annotation = entry.getValue();
Optional<Conditional> conditional = Optional.ofNullable(conditions.get(method));
Optional<Tab> tab = Optional.ofNullable(tabs.get(method));
factory.placeToDataProviders(
dataProviders, method, annotation,
conditional.orElse(null),
tab.map(Tab::value).orElse(null),
pluginInfo.name()
);
}
}
public Collection<String> getWarnings() {
return extensionExtractor.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
);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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;
import com.djrapitops.plan.extension.Group;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* Utility enum for determining what kind of parameters a provider method used.
* <p>
* This also allows figuring out where to save method results.
*
* @author Rsl1122
*/
public enum MethodType {
PLAYER_UUID,
PLAYER_NAME,
GROUP,
SERVER;
public static MethodType forMethod(Method method) {
int parameterCount = method.getParameterCount();
if (parameterCount == 0) {
return SERVER;
}
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> firstParameter = parameterTypes[0];
if (UUID.class.equals(firstParameter)) {
return PLAYER_UUID;
} else if (String.class.equals(firstParameter)) {
return PLAYER_NAME;
} else if (Group.class.equals(firstParameter)) {
return GROUP;
}
throw new IllegalArgumentException(method.getDeclaringClass() + " method " + method.getName() + " had invalid parameters.");
}
}

View File

@ -0,0 +1,57 @@
/*
* 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;
import com.djrapitops.plan.extension.annotation.Conditional;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.results.ExtensionDescriptive;
import org.apache.commons.lang3.StringUtils;
import java.util.Optional;
/**
* Represents the annotation information provided on a method.
*
* @author Rsl1122
*/
public class ProviderInformation extends ExtensionDescriptive {
private final String pluginName;
private final String tab; // can be null
private final Conditional condition; // can be null
public ProviderInformation(
String pluginName, String name, String text, String description, Icon icon, int priority, String tab, Conditional condition
) {
super(name, text, description, icon, priority);
this.pluginName = pluginName;
this.tab = tab;
this.condition = condition;
}
public String getPluginName() {
return StringUtils.truncate(pluginName, 50);
}
public Optional<String> getTab() {
return tab == null || tab.isEmpty() ? Optional.empty() : Optional.of(StringUtils.truncate(tab, 50));
}
public Optional<String> getCondition() {
return condition == null || condition.value().isEmpty() ? Optional.empty() : Optional.of((condition.negated() ? "not_" : "") + StringUtils.truncate(condition.value(), 50));
}
}

View File

@ -0,0 +1,85 @@
/*
* 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;
import com.djrapitops.plan.extension.ElementOrder;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plugin.utilities.ArrayUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents a tab of {@link com.djrapitops.plan.extension.DataExtension} defined by {@link com.djrapitops.plan.extension.annotation.Tab} and
* {@link com.djrapitops.plan.extension.annotation.TabInfo} annotations.
*
* @author Rsl1122
*/
public class TabInformation {
private final String tabName;
private final Icon icon; // can be null
private ElementOrder[] elementOrder; // can be null / miss values
private int tabPriority;
public TabInformation(String tabName, Icon icon, ElementOrder[] elementOrder, int tabPriority) {
this.tabName = tabName;
this.icon = icon;
this.elementOrder = elementOrder;
this.tabPriority = tabPriority;
}
public String getTabName() {
return StringUtils.truncate(tabName, 50);
}
public static Icon defaultIcon() {
return new Icon(Family.SOLID, "circle", Color.NONE);
}
public Icon getTabIcon() {
return icon != null ? icon : defaultIcon();
}
public int getTabPriority() {
return tabPriority;
}
public Optional<ElementOrder[]> getTabElementOrder() {
if (elementOrder == null) {
return Optional.empty();
}
ElementOrder[] possibleValues = ElementOrder.values();
if (elementOrder.length < possibleValues.length) {
addMissingElements(possibleValues);
}
return Optional.of(elementOrder);
}
private void addMissingElements(ElementOrder[] possibleValues) {
for (ElementOrder possibleValue : possibleValues) {
if (Arrays.binarySearch(elementOrder, possibleValue) < 0) {
elementOrder = ArrayUtil.merge(elementOrder, new ElementOrder[]{possibleValue});
}
}
}
}

View File

@ -0,0 +1,79 @@
/*
* 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 org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
import java.util.Optional;
/**
* Represents a DataExtension API method annotated with {@link BooleanProvider} annotation.
* <p>
* Used to obtain data to place in the database.
*
* @author Rsl1122
*/
public class BooleanDataProvider extends DataProvider<Boolean> {
private final String providedCondition;
private final boolean hidden;
private BooleanDataProvider(ProviderInformation providerInformation, MethodWrapper<Boolean> method, String providedCondition, boolean hidden) {
super(providerInformation, method);
this.providedCondition = providedCondition;
this.hidden = hidden;
}
public static void placeToDataProviders(
DataProviders dataProviders, Method method, BooleanProvider annotation,
Conditional condition, String tab, String pluginName
) {
MethodWrapper<Boolean> methodWrapper = new MethodWrapper<>(method, Boolean.class);
Icon providerIcon = new Icon(annotation.iconFamily(), annotation.iconName(), annotation.iconColor());
ProviderInformation providerInformation = new ProviderInformation(
pluginName, method.getName(), annotation.text(), annotation.description(), providerIcon, annotation.priority(), tab, condition
);
dataProviders.put(new BooleanDataProvider(providerInformation, methodWrapper, annotation.conditionName(), annotation.hidden()));
}
public static Optional<String> getProvidedCondition(DataProvider<Boolean> provider) {
if (provider instanceof BooleanDataProvider) {
return ((BooleanDataProvider) provider).getProvidedCondition();
}
return Optional.empty();
}
public Optional<String> getProvidedCondition() {
return providedCondition == null || providedCondition.isEmpty() ? Optional.empty() : Optional.of(StringUtils.truncate(providedCondition, 50));
}
public static boolean isHidden(DataProvider<Boolean> provider) {
return provider instanceof BooleanDataProvider && ((BooleanDataProvider) provider).isHidden();
}
public boolean isHidden() {
return hidden;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.ProviderInformation;
/**
* Abstract representation of all values a Provider annotation provides.
*
* @author Rsl1122
*/
public abstract class DataProvider<T> {
private final ProviderInformation providerInformation;
private final MethodWrapper<T> method;
public DataProvider(ProviderInformation providerInformation, MethodWrapper<T> method) {
this.providerInformation = providerInformation;
this.method = method;
}
public MethodWrapper<T> getMethod() {
return method;
}
public ProviderInformation getProviderInformation() {
return providerInformation;
}
}

View File

@ -0,0 +1,80 @@
/*
* 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 java.util.*;
/**
* Group class for handling multiple different types of {@link DataProvider}s.
*
* @author Rsl1122
*/
public class DataProviders {
private Map<MethodType, Map<Class, List<DataProvider>>> byMethodType;
public DataProviders() {
byMethodType = new EnumMap<>(MethodType.class);
}
public <T> void put(DataProvider<T> provider) {
MethodWrapper<T> method = provider.getMethod();
MethodType methodType = method.getMethodType();
Class<T> resultType = method.getResultType();
Map<Class, List<DataProvider>> byParameterType = byMethodType.getOrDefault(methodType, new HashMap<>());
List<DataProvider> dataProviders = byParameterType.getOrDefault(resultType, new ArrayList<>());
dataProviders.add(provider);
byParameterType.put(resultType, dataProviders);
byMethodType.put(methodType, byParameterType);
}
public <T> List<DataProvider<T>> getPlayerMethodsByType(Class<T> returnType) {
Map<Class, List<DataProvider>> providersAcceptingUUID = byMethodType.getOrDefault(MethodType.PLAYER_UUID, new HashMap<>());
Map<Class, List<DataProvider>> providersAcceptingName = byMethodType.getOrDefault(MethodType.PLAYER_NAME, new HashMap<>());
List<DataProvider<T>> byReturnType = new ArrayList<>();
for (DataProvider dataProvider : providersAcceptingUUID.getOrDefault(returnType, Collections.emptyList())) {
byReturnType.add((DataProvider<T>) dataProvider);
}
for (DataProvider dataProvider : providersAcceptingName.getOrDefault(returnType, Collections.emptyList())) {
byReturnType.add((DataProvider<T>) dataProvider);
}
return byReturnType;
}
public <T> List<DataProvider<T>> getServerMethodsByType(Class<T> returnType) {
List<DataProvider<T>> byReturnType = new ArrayList<>();
for (DataProvider dataProvider : byMethodType.getOrDefault(MethodType.SERVER, new HashMap<>()).getOrDefault(returnType, Collections.emptyList())) {
byReturnType.add((DataProvider<T>) dataProvider);
}
return byReturnType;
}
public <T> List<DataProvider<T>> getGroupMethodsByType(Class<T> returnType) {
List<DataProvider<T>> byReturnType = new ArrayList<>();
for (DataProvider dataProvider : byMethodType.getOrDefault(MethodType.GROUP, new HashMap<>()).getOrDefault(returnType, Collections.emptyList())) {
byReturnType.add((DataProvider<T>) dataProvider);
}
return byReturnType;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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;
/**
* Represents a DataExtension API method annotated with {@link DoubleProvider} annotation.
* <p>
* Used to obtain data to place in the database.
*
* @author Rsl1122
*/
public class DoubleDataProvider extends DataProvider<Double> {
private DoubleDataProvider(ProviderInformation providerInformation, MethodWrapper<Double> methodWrapper) {
super(providerInformation, methodWrapper);
}
public static void placeToDataProviders(
DataProviders dataProviders, Method method, DoubleProvider annotation,
Conditional condition, String tab, String pluginName
) {
MethodWrapper<Double> methodWrapper = new MethodWrapper<>(method, Double.class);
Icon providerIcon = new Icon(annotation.iconFamily(), annotation.iconName(), annotation.iconColor());
ProviderInformation providerInformation = new ProviderInformation(
pluginName, method.getName(), annotation.text(), annotation.description(), providerIcon, annotation.priority(), tab, condition
);
dataProviders.put(new DoubleDataProvider(providerInformation, methodWrapper));
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.DataExtension;
import com.djrapitops.plan.extension.Group;
import com.djrapitops.plan.extension.implementation.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* Wrap a Method so that it is easier to call.
*
* @author Rsl1122
*/
public class MethodWrapper<T> {
private final Method method;
private final Class<T> resultType;
private MethodType methodType;
public MethodWrapper(Method method, Class<T> resultType) {
this.method = method;
this.resultType = resultType;
methodType = MethodType.forMethod(this.method);
}
public T callMethod(DataExtension extension, UUID playerUUID, String playerName) throws InvocationTargetException, IllegalAccessException {
if (methodType != MethodType.PLAYER_NAME && methodType != MethodType.PLAYER_UUID) {
throw new IllegalStateException(method.getDeclaringClass() + " method " + method.getName() + " is not GROUP method.");
}
return callMethod(extension, playerUUID, playerName, null);
}
public T callMethod(DataExtension extension, Group group) throws InvocationTargetException, IllegalAccessException {
if (methodType != MethodType.GROUP) {
throw new IllegalStateException(method.getDeclaringClass() + " method " + method.getName() + " is not GROUP method.");
}
return callMethod(extension, null, null, group);
}
public T callMethod(DataExtension extension) throws InvocationTargetException, IllegalAccessException {
if (methodType != MethodType.SERVER) {
throw new IllegalStateException(method.getDeclaringClass() + " method " + method.getName() + " is not SERVER method.");
}
return callMethod(extension, null, null, null);
}
public T callMethod(DataExtension extension, UUID playerUUID, String playerName, Group group) throws InvocationTargetException, IllegalAccessException {
switch (methodType) {
case SERVER:
return resultType.cast(method.invoke(extension));
case PLAYER_UUID:
return resultType.cast(method.invoke(extension, playerUUID));
case PLAYER_NAME:
return resultType.cast(method.invoke(extension, playerName));
case GROUP:
return resultType.cast(method.invoke(extension, group));
default:
throw new IllegalArgumentException(method.getDeclaringClass() + " method " + method.getName() + " had invalid parameters.");
}
}
public String getMethodName() {
return method.getName();
}
public MethodType getMethodType() {
return methodType;
}
public Class<T> getResultType() {
return resultType;
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.FormatType;
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;
/**
* Represents a DataExtension API method annotated with {@link NumberProvider} annotation.
* <p>
* Used to obtain data to place in the database.
*
* @author Rsl1122
*/
public class NumberDataProvider extends DataProvider<Long> {
private final FormatType formatType;
private NumberDataProvider(ProviderInformation providerInformation, MethodWrapper<Long> methodWrapper, FormatType formatType) {
super(providerInformation, methodWrapper);
this.formatType = formatType;
}
public static void placeToDataProviders(
DataProviders dataProviders, Method method, NumberProvider annotation,
Conditional condition, String tab, String pluginName
) {
MethodWrapper<Long> methodWrapper = new MethodWrapper<>(method, Long.class);
Icon providerIcon = new Icon(annotation.iconFamily(), annotation.iconName(), annotation.iconColor());
ProviderInformation providerInformation = new ProviderInformation(
pluginName, method.getName(), annotation.text(), annotation.description(), providerIcon, annotation.priority(), tab, condition
);
dataProviders.put(new NumberDataProvider(providerInformation, methodWrapper, annotation.format()));
}
public static FormatType getFormatType(DataProvider<Long> provider) {
if (provider instanceof NumberDataProvider) {
return ((NumberDataProvider) provider).getFormatType();
}
return FormatType.NONE;
}
public FormatType getFormatType() {
return formatType;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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;
/**
* Represents a DataExtension API method annotated with {@link PercentageProvider} annotation.
* <p>
* Used to obtain data to place in the database.
*
* @author Rsl1122
*/
public class PercentageDataProvider extends DataProvider<Double> {
private PercentageDataProvider(ProviderInformation providerInformation, MethodWrapper<Double> methodWrapper) {
super(providerInformation, methodWrapper);
}
public static void placeToDataProviders(
DataProviders dataProviders, Method method, PercentageProvider annotation,
Conditional condition, String tab, String pluginName
) {
MethodWrapper<Double> methodWrapper = new MethodWrapper<>(method, Double.class);
Icon providerIcon = new Icon(annotation.iconFamily(), annotation.iconName(), annotation.iconColor());
ProviderInformation providerInformation = new ProviderInformation(
pluginName, method.getName(), annotation.text(), annotation.description(), providerIcon, annotation.priority(), tab, condition
);
dataProviders.put(new PercentageDataProvider(providerInformation, methodWrapper));
}
}

View File

@ -0,0 +1,68 @@
/*
* 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;
/**
* Represents a DataExtension API method annotated with {@link StringProvider} annotation.
* <p>
* Used to obtain data to place in the database.
*
* @author Rsl1122
*/
public class StringDataProvider extends DataProvider<String> {
private final boolean playerName;
private StringDataProvider(ProviderInformation providerInformation, MethodWrapper<String> methodWrapper, boolean playerName) {
super(providerInformation, methodWrapper);
this.playerName = playerName;
}
public static void placeToDataProviders(
DataProviders dataProviders, Method method, StringProvider annotation,
Conditional condition, String tab, String pluginName
) {
MethodWrapper<String> methodWrapper = new MethodWrapper<>(method, String.class);
Icon providerIcon = new Icon(annotation.iconFamily(), annotation.iconName(), annotation.iconColor());
ProviderInformation providerInformation = new ProviderInformation(
pluginName, method.getName(), annotation.text(), annotation.description(), providerIcon, annotation.priority(), tab, condition
);
boolean playerName = annotation.playerName();
dataProviders.put(new StringDataProvider(providerInformation, methodWrapper, playerName));
}
public static boolean isPlayerName(DataProvider<String> provider) {
if (provider instanceof StringDataProvider) {
return ((StringDataProvider) provider).isPlayerName();
}
return false;
}
public boolean isPlayerName() {
return playerName;
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.db.Database;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.BooleanDataProvider;
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.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreBooleanProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerBooleanResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerBooleanResultTransaction;
import com.djrapitops.plugin.logging.console.PluginLogger;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Gathers BooleanProvider method data.
*
* @author Rsl1122
*/
class BooleanProviderValueGatherer {
private final String pluginName;
private final DataExtension extension;
private final UUID serverUUID;
private final Database database;
private final DataProviders dataProviders;
private final PluginLogger logger;
BooleanProviderValueGatherer(
String pluginName, DataExtension extension,
UUID serverUUID, Database database,
DataProviders dataProviders,
PluginLogger logger
) {
this.pluginName = pluginName;
this.extension = extension;
this.serverUUID = serverUUID;
this.database = database;
this.dataProviders = dataProviders;
this.logger = logger;
}
Conditions gatherBooleanDataOfPlayer(UUID playerUUID, String playerName) {
Conditions conditions = new Conditions();
List<DataProvider<Boolean>> unsatisifiedProviders = 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, playerUUID, playerName);
BiFunction<MethodWrapper<Boolean>, Boolean, Transaction> storeTrancationCreator = (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, storeTrancationCreator, conditions, unsatisifiedProviders);
// Remove now satisfied Providers so that they are not called again
unsatisifiedProviders.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>> unsatisifiedProviders = 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);
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, unsatisifiedProviders);
// Remove now satisfied Providers so that they are not called again
unsatisifiedProviders.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>> unsatisifiedProviders
) {
Set<DataProvider<Boolean>> satisfied = new HashSet<>();
for (DataProvider<Boolean> booleanProvider : unsatisifiedProviders) {
ProviderInformation providerInformation = booleanProvider.getProviderInformation();
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
// Condition required by the BooleanProvider is not satisfied
continue;
}
Optional<String> providedCondition = BooleanDataProvider.getProvidedCondition(booleanProvider);
boolean hidden = BooleanDataProvider.isHidden(booleanProvider);
MethodWrapper<Boolean> method = booleanProvider.getMethod();
Boolean result = getMethodResult(
methodCaller.apply(method),
throwable -> pluginName + " has invalid implementation, method " + method.getMethodName() + " threw exception: " + throwable.toString()
);
if (result == null) {
// Error during method call
satisfied.add(booleanProvider); // Prevents further attempts to call this provider for this player.
continue;
}
if (providedCondition.isPresent()) {
if (result) {
// The condition was fulfilled (true) for this player.
conditions.conditionFulfilled(providedCondition.get());
} else {
// The negated condition was fulfilled (false) for this player.
conditions.conditionFulfilled("not_" + providedCondition.get());
}
}
satisfied.add(booleanProvider); // Prevents further attempts to call this provider for this player.
database.executeTransaction(new StoreIconTransaction(providerInformation.getIcon()));
database.executeTransaction(new StoreBooleanProviderTransaction(booleanProvider, providedCondition.orElse(null), hidden, serverUUID));
database.executeTransaction(storeTransactionCreator.apply(method, result));
}
return satisfied;
}
private <T> T getMethodResult(Callable<T> callable, Function<Throwable, String> errorMsg) {
try {
return callable.call();
} catch (Exception | NoSuchFieldError | NoSuchMethodError e) {
logger.warn(errorMsg.apply(e));
return null;
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
/**
* Utility object for managing conditions.
*
* @author Rsl1122
*/
public class Conditions {
private final Set<String> fulfilledConditions;
public Conditions() {
this.fulfilledConditions = new HashSet<>();
}
public boolean isNotFulfilled(String condition) {
return !fulfilledConditions.contains(condition);
}
public void conditionFulfilled(String condition) {
fulfilledConditions.add(condition);
}
}

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.implementation.providers.gathering;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.extension.DataExtension;
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.PercentageDataProvider;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreDoubleProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerDoubleResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerPercentageResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerDoubleResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerPercentageResultTransaction;
import com.djrapitops.plugin.logging.console.PluginLogger;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Gathers DoubleProvider and PercentageProvider method data.
*
* @author Rsl1122
*/
class DoubleAndPercentageProviderValueGatherer {
private final String pluginName;
private final DataExtension extension;
private final UUID serverUUID;
private final Database database;
private final DataProviders dataProviders;
private final PluginLogger logger;
DoubleAndPercentageProviderValueGatherer(
String pluginName, DataExtension extension,
UUID serverUUID, Database database,
DataProviders dataProviders,
PluginLogger logger
) {
this.pluginName = pluginName;
this.extension = extension;
this.serverUUID = serverUUID;
this.database = database;
this.dataProviders = dataProviders;
this.logger = logger;
}
void gatherDoubleDataOfPlayer(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<Double>, Callable<Double>> methodCaller = method -> () -> method.callMethod(extension, playerUUID, playerName);
BiFunction<MethodWrapper<Double>, Double, Transaction> percStoreTransactionCreator = (method, result) -> new StorePlayerPercentageResultTransaction(pluginName, serverUUID, method.getMethodName(), playerUUID, result);
BiFunction<MethodWrapper<Double>, Double, Transaction> doubleStoreTransactionCreator = (method, result) -> new StorePlayerDoubleResultTransaction(pluginName, serverUUID, method.getMethodName(), playerUUID, result);
for (DataProvider<Double> doubleProvider : dataProviders.getPlayerMethodsByType(Double.class)) {
gatherDoubleDataOfProvider(methodCaller, percStoreTransactionCreator, doubleStoreTransactionCreator, conditions, doubleProvider);
}
}
void gatherDoubleDataOfServer(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<Double>, Callable<Double>> methodCaller = method -> () -> method.callMethod(extension);
BiFunction<MethodWrapper<Double>, Double, Transaction> percStoreTransactionCreator = (method, result) -> new StoreServerPercentageResultTransaction(pluginName, serverUUID, method.getMethodName(), result);
BiFunction<MethodWrapper<Double>, Double, Transaction> doubleStoreTransactionCreator = (method, result) -> new StoreServerDoubleResultTransaction(pluginName, serverUUID, method.getMethodName(), result);
for (DataProvider<Double> doubleProvider : dataProviders.getServerMethodsByType(Double.class)) {
gatherDoubleDataOfProvider(methodCaller, percStoreTransactionCreator, doubleStoreTransactionCreator, conditions, doubleProvider);
}
}
private void gatherDoubleDataOfProvider(
Function<MethodWrapper<Double>, Callable<Double>> methodCaller,
BiFunction<MethodWrapper<Double>, Double, Transaction> percStoreTransactionCreator,
BiFunction<MethodWrapper<Double>, Double, Transaction> doubleStoreTransactionCreator,
Conditions conditions, DataProvider<Double> doubleProvider
) {
ProviderInformation providerInformation = doubleProvider.getProviderInformation();
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
return;
}
MethodWrapper<Double> method = doubleProvider.getMethod();
Double result = getMethodResult(
methodCaller.apply(method),
throwable -> pluginName + " has invalid implementation, method " + method.getMethodName() + " threw exception: " + throwable.toString()
);
if (result == null) {
return;
}
database.executeTransaction(new StoreIconTransaction(providerInformation.getIcon()));
database.executeTransaction(new StoreDoubleProviderTransaction(doubleProvider, serverUUID));
if (doubleProvider instanceof PercentageDataProvider) {
database.executeTransaction(percStoreTransactionCreator.apply(method, result));
} else {
database.executeTransaction(doubleStoreTransactionCreator.apply(method, result));
}
}
private <T> T getMethodResult(Callable<T> callable, Function<Throwable, String> errorMsg) {
try {
return callable.call();
} catch (Exception | NoSuchFieldError | NoSuchMethodError e) {
logger.warn(errorMsg.apply(e));
return null;
}
}
}

View File

@ -0,0 +1,126 @@
/*
* 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.db.Database;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.extension.DataExtension;
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.DataProviders;
import com.djrapitops.plan.extension.implementation.providers.MethodWrapper;
import com.djrapitops.plan.extension.implementation.providers.NumberDataProvider;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreNumberProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerNumberResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerNumberResultTransaction;
import com.djrapitops.plugin.logging.console.PluginLogger;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Gathers NumberProvider method data.
*
* @author Rsl1122
*/
class NumberProviderValueGatherer {
private final String pluginName;
private final DataExtension extension;
private final UUID serverUUID;
private final Database database;
private final DataProviders dataProviders;
private final PluginLogger logger;
NumberProviderValueGatherer(
String pluginName, DataExtension extension,
UUID serverUUID, Database database,
DataProviders dataProviders,
PluginLogger logger
) {
this.pluginName = pluginName;
this.extension = extension;
this.serverUUID = serverUUID;
this.database = database;
this.dataProviders = dataProviders;
this.logger = logger;
}
void gatherNumberDataOfPlayer(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<Long>, Callable<Long>> methodCaller = method -> () -> method.callMethod(extension, playerUUID, playerName);
BiFunction<MethodWrapper<Long>, Long, Transaction> storeTransactionCreator = (method, result) -> new StorePlayerNumberResultTransaction(pluginName, serverUUID, method.getMethodName(), playerUUID, result);
for (DataProvider<Long> numberProvider : dataProviders.getPlayerMethodsByType(Long.class)) {
gatherNumberDataOfProvider(methodCaller, storeTransactionCreator, conditions, numberProvider);
}
}
void gatherNumberDataOfServer(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<Long>, Callable<Long>> methodCaller = method -> () -> method.callMethod(extension);
BiFunction<MethodWrapper<Long>, Long, Transaction> storeTransactionCreator = (method, result) -> new StoreServerNumberResultTransaction(pluginName, serverUUID, method.getMethodName(), result);
for (DataProvider<Long> numberProvider : dataProviders.getServerMethodsByType(Long.class)) {
gatherNumberDataOfProvider(methodCaller, storeTransactionCreator, conditions, numberProvider);
}
}
private void gatherNumberDataOfProvider(
Function<MethodWrapper<Long>, Callable<Long>> methodCaller,
BiFunction<MethodWrapper<Long>, Long, Transaction> storeTransactionCreator,
Conditions conditions, DataProvider<Long> numberProvider
) {
ProviderInformation providerInformation = numberProvider.getProviderInformation();
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
return;
}
MethodWrapper<Long> method = numberProvider.getMethod();
Long result = getMethodResult(
methodCaller.apply(method),
throwable -> pluginName + " has invalid implementation, method " + method.getMethodName() + " threw exception: " + throwable.toString()
);
if (result == null) {
return;
}
FormatType formatType = NumberDataProvider.getFormatType(numberProvider);
database.executeTransaction(new StoreIconTransaction(providerInformation.getIcon()));
database.executeTransaction(new StoreNumberProviderTransaction(numberProvider, formatType, serverUUID));
database.executeTransaction(storeTransactionCreator.apply(method, result));
}
private <T> T getMethodResult(Callable<T> callable, Function<Throwable, String> errorMsg) {
try {
return callable.call();
} catch (Exception | NoSuchFieldError | NoSuchMethodError e) {
logger.warn(errorMsg.apply(e));
return null;
}
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.db.Database;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.implementation.DataProviderExtractor;
import com.djrapitops.plan.extension.implementation.TabInformation;
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.results.RemoveInvalidResultsTransaction;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plugin.logging.console.PluginLogger;
import java.util.UUID;
/**
* Object that can be called to place data about players to the database.
*
* @author Rsl1122
*/
public class ProviderValueGatherer {
private final CallEvents[] callEvents;
private final DataProviderExtractor extractor;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private BooleanProviderValueGatherer booleanGatherer;
private NumberProviderValueGatherer numberGatherer;
private DoubleAndPercentageProviderValueGatherer doubleAndPercentageGatherer;
private StringProviderValueGatherer stringGatherer;
public ProviderValueGatherer(
DataExtension extension,
DataProviderExtractor extractor,
DBSystem dbSystem,
ServerInfo serverInfo,
PluginLogger logger
) {
this.callEvents = extension.callExtensionMethodsOn();
this.extractor = extractor;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
booleanGatherer = new BooleanProviderValueGatherer(
extractor.getPluginName(), extension,
serverInfo.getServerUUID(), dbSystem.getDatabase(),
extractor.getDataProviders(), logger
);
numberGatherer = new NumberProviderValueGatherer(
extractor.getPluginName(), extension,
serverInfo.getServerUUID(), dbSystem.getDatabase(),
extractor.getDataProviders(), logger
);
doubleAndPercentageGatherer = new DoubleAndPercentageProviderValueGatherer(
extractor.getPluginName(), extension,
serverInfo.getServerUUID(), dbSystem.getDatabase(),
extractor.getDataProviders(), logger
);
stringGatherer = new StringProviderValueGatherer(
extractor.getPluginName(), extension,
serverInfo.getServerUUID(), dbSystem.getDatabase(),
extractor.getDataProviders(), logger
);
}
public boolean canCallEvent(CallEvents event) {
if (event == CallEvents.MANUAL) {
return true;
}
for (CallEvents accepted : callEvents) {
if (event == accepted) {
return true;
}
}
return false;
}
public String getPluginName() {
return extractor.getPluginName();
}
public void storeExtensionInformation() {
String pluginName = extractor.getPluginName();
Icon pluginIcon = extractor.getPluginIcon();
long time = System.currentTimeMillis();
UUID serverUUID = serverInfo.getServerUUID();
Database database = dbSystem.getDatabase();
database.executeTransaction(new StoreIconTransaction(pluginIcon));
database.executeTransaction(new StorePluginTransaction(pluginName, time, serverUUID, pluginIcon));
for (TabInformation tab : extractor.getPluginTabs()) {
database.executeTransaction(new StoreIconTransaction(tab.getTabIcon()));
database.executeTransaction(new StoreTabInformationTransaction(pluginName, serverUUID, tab));
}
database.executeTransaction(new RemoveInvalidResultsTransaction(pluginName, serverUUID, extractor.getInvalidatedMethods()));
}
public void updateValues(UUID playerUUID, String playerName) {
Conditions conditions = booleanGatherer.gatherBooleanDataOfPlayer(playerUUID, playerName);
numberGatherer.gatherNumberDataOfPlayer(playerUUID, playerName, conditions);
doubleAndPercentageGatherer.gatherDoubleDataOfPlayer(playerUUID, playerName, conditions);
stringGatherer.gatherStringDataOfPlayer(playerUUID, playerName, conditions);
}
public void updateValues() {
Conditions conditions = booleanGatherer.gatherBooleanDataOfServer();
numberGatherer.gatherNumberDataOfServer(conditions);
doubleAndPercentageGatherer.gatherDoubleDataOfServer(conditions);
stringGatherer.gatherStringDataOfServer(conditions);
}
}

View File

@ -0,0 +1,128 @@
/*
* 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.db.Database;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.extension.DataExtension;
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.StringDataProvider;
import com.djrapitops.plan.extension.implementation.storage.transactions.StoreIconTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.providers.StoreStringProviderTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StorePlayerStringResultTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.StoreServerStringResultTransaction;
import com.djrapitops.plugin.logging.console.PluginLogger;
import org.apache.commons.lang3.StringUtils;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Gathers StringProvider method data.
*
* @author Rsl1122
*/
class StringProviderValueGatherer {
private final String pluginName;
private final DataExtension extension;
private final UUID serverUUID;
private final Database database;
private final DataProviders dataProviders;
private final PluginLogger logger;
StringProviderValueGatherer(
String pluginName, DataExtension extension,
UUID serverUUID, Database database,
DataProviders dataProviders,
PluginLogger logger
) {
this.pluginName = pluginName;
this.extension = extension;
this.serverUUID = serverUUID;
this.database = database;
this.dataProviders = dataProviders;
this.logger = logger;
}
void gatherStringDataOfPlayer(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<String>, Callable<String>> methodCaller = method -> () -> method.callMethod(extension, playerUUID, playerName);
BiFunction<MethodWrapper<String>, String, Transaction> storeTransactionCreator = (method, result) -> new StorePlayerStringResultTransaction(pluginName, serverUUID, method.getMethodName(), playerUUID, result);
for (DataProvider<String> stringProvider : dataProviders.getPlayerMethodsByType(String.class)) {
gatherStringDataOfProvider(methodCaller, storeTransactionCreator, conditions, stringProvider);
}
}
void gatherStringDataOfServer(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<String>, Callable<String>> methodCaller = method -> () -> method.callMethod(extension);
BiFunction<MethodWrapper<String>, String, Transaction> storeTransactionCreator = (method, result) -> new StoreServerStringResultTransaction(pluginName, serverUUID, method.getMethodName(), result);
for (DataProvider<String> stringProvider : dataProviders.getServerMethodsByType(String.class)) {
gatherStringDataOfProvider(methodCaller, storeTransactionCreator, conditions, stringProvider);
}
}
private void gatherStringDataOfProvider(
Function<MethodWrapper<String>, Callable<String>> methodCaller,
BiFunction<MethodWrapper<String>, String, Transaction> storeTransactionCreator,
Conditions conditions,
DataProvider<String> stringProvider
) {
ProviderInformation providerInformation = stringProvider.getProviderInformation();
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent() && conditions.isNotFulfilled(condition.get())) {
return;
}
MethodWrapper<String> method = stringProvider.getMethod();
String result = getMethodResult(
methodCaller.apply(method),
throwable -> pluginName + " has invalid implementation, method " + method.getMethodName() + " threw exception: " + throwable.toString()
);
if (result == null) {
return;
}
result = StringUtils.truncate(result, 50);
database.executeTransaction(new StoreIconTransaction(providerInformation.getIcon()));
database.executeTransaction(new StoreStringProviderTransaction(stringProvider, StringDataProvider.isPlayerName(stringProvider), serverUUID));
database.executeTransaction(storeTransactionCreator.apply(method, result));
}
private <T> T getMethodResult(Callable<T> callable, Function<Throwable, String> errorMsg) {
try {
return callable.call();
} catch (Exception | NoSuchFieldError | NoSuchMethodError e) {
logger.warn(errorMsg.apply(e));
return null;
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.results;
/**
* Represents boolean data returned by a BooleanProvider method.
*
* @author Rsl1122
*/
public class ExtensionBooleanData implements ExtensionData {
private ExtensionDescriptive descriptive;
private boolean value;
public ExtensionBooleanData(ExtensionDescriptive descriptive, boolean value) {
this.descriptive = descriptive;
this.value = value;
}
public ExtensionDescriptive getDescriptive() {
return descriptive;
}
public String getFormattedValue() {
return value ? "Yes" : "No";
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.results;
/**
* Represents a data-point given by a Provider method of a DataExtension.
*
* @author Rsl1122
*/
public interface ExtensionData {
/**
* Get Descriptive information about the data point.
*
* @return a {@link ExtensionDescriptive}.
*/
ExtensionDescriptive getDescriptive();
}

View File

@ -0,0 +1,87 @@
/*
* 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.results;
import com.djrapitops.plan.extension.icon.Icon;
import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
import java.util.Optional;
/**
* Describes information about an extension value given by a Provider method.
*
* @author Rsl1122
*/
public class ExtensionDescriptive implements Comparable<ExtensionDescriptive> {
private final String name;
private final String text;
private final String description; // can be null
private final Icon icon;
private final int priority;
public ExtensionDescriptive(String name, String text, String description, Icon icon, int priority) {
this.name = name;
this.text = text;
this.description = description;
this.icon = icon;
this.priority = priority;
}
public String getName() {
return StringUtils.truncate(name, 50);
}
public String getText() {
return StringUtils.truncate(text, 50);
}
public Optional<String> getDescription() {
return description == null || description.isEmpty() ? Optional.empty() : Optional.of(StringUtils.truncate(description, 150));
}
public Icon getIcon() {
return icon;
}
public int getPriority() {
return priority;
}
@Override
public int compareTo(ExtensionDescriptive other) {
return Integer.compare(other.priority, this.priority); // Higher is first
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ExtensionDescriptive)) return false;
ExtensionDescriptive that = (ExtensionDescriptive) o;
return priority == that.priority &&
name.equals(that.name) &&
text.equals(that.text) &&
Objects.equals(description, that.description) &&
icon.equals(that.icon);
}
@Override
public int hashCode() {
return Objects.hash(name, text, description, icon, priority);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.results;
import com.djrapitops.plan.utilities.formatting.Formatter;
/**
* Represents double data returned by a DoubleProvider or PercentageProvider method.
*
* @author Rsl1122
*/
public class ExtensionDoubleData implements ExtensionData {
private ExtensionDescriptive descriptive;
private double value;
public ExtensionDoubleData(ExtensionDescriptive descriptive, double value) {
this.descriptive = descriptive;
this.value = value;
}
public ExtensionDescriptive getDescriptive() {
return descriptive;
}
public String getFormattedValue(Formatter<Double> formatter) {
return formatter.apply(value);
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.results;
import com.djrapitops.plan.extension.icon.Icon;
/**
* Information about a DataExtension stored in the database.
*
* @author Rsl1122
*/
public class ExtensionInformation {
private final int id;
private final String pluginName;
private final Icon icon;
public ExtensionInformation(int id, String pluginName, Icon icon) {
this.id = id;
this.pluginName = pluginName;
this.icon = icon;
}
public int getId() {
return id;
}
public String getPluginName() {
return pluginName;
}
public Icon getIcon() {
return icon;
}
@Override
public String toString() {
return '{' + "id=" + id + ", pluginName='" + pluginName + '\'' + '}';
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.results;
import com.djrapitops.plan.extension.FormatType;
import com.djrapitops.plan.utilities.formatting.Formatter;
/**
* Represents double data returned by a DoubleProvider or PercentageProvider method.
*
* @author Rsl1122
*/
public class ExtensionNumberData implements ExtensionData {
private final ExtensionDescriptive descriptive;
private final FormatType formatType;
private final long value;
public ExtensionNumberData(ExtensionDescriptive descriptive, FormatType formatType, long value) {
this.descriptive = descriptive;
this.formatType = formatType;
this.value = value;
}
public ExtensionDescriptive getDescriptive() {
return descriptive;
}
public FormatType getFormatType() {
return formatType;
}
public String getFormattedValue(Formatter<Long> formatter) {
return formatter.apply(value);
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.results;
import com.djrapitops.plan.api.PlanAPI;
import com.djrapitops.plan.utilities.html.Html;
/**
* Represents double data returned by a DoubleProvider or PercentageProvider method.
*
* @author Rsl1122
*/
public class ExtensionStringData implements ExtensionData {
private final ExtensionDescriptive descriptive;
private final boolean playerName;
private final String value;
public ExtensionStringData(ExtensionDescriptive descriptive, boolean playerName, String value) {
this.descriptive = descriptive;
this.playerName = playerName;
this.value = value;
}
public ExtensionDescriptive getDescriptive() {
return descriptive;
}
public String getFormattedValue() {
String withColors = Html.swapColorCodesToSpan(value);
return !playerName ? withColors : Html.LINK.parse(PlanAPI.getInstance().getPlayerInspectPageLink(value), withColors);
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.results;
import com.djrapitops.plan.extension.implementation.TabInformation;
import java.util.*;
import java.util.stream.Collectors;
/**
* Represents data on an extension tab.
*
* @author Rsl1122
*/
public class ExtensionTabData implements Comparable<ExtensionTabData> {
private final TabInformation tabInformation; // Can be null in case where no tab was defined for provider.
private final Map<String, ExtensionBooleanData> booleanData;
private final Map<String, ExtensionDoubleData> doubleData;
private final Map<String, ExtensionDoubleData> percentageData;
private final Map<String, ExtensionNumberData> numberData;
private final Map<String, ExtensionStringData> stringData;
private List<String> order;
// Table and Graph data will be added later.
public ExtensionTabData(TabInformation tabInformation) {
this.tabInformation = tabInformation;
booleanData = new HashMap<>();
doubleData = new HashMap<>();
percentageData = new HashMap<>();
numberData = new HashMap<>();
stringData = new HashMap<>();
}
public TabInformation getTabInformation() {
return tabInformation;
}
public List<String> getValueOrder() {
return order;
}
public Optional<ExtensionBooleanData> getBoolean(String providerName) {
return Optional.ofNullable(booleanData.get(providerName));
}
public Optional<ExtensionDoubleData> getDouble(String providerName) {
return Optional.ofNullable(doubleData.get(providerName));
}
public Optional<ExtensionDoubleData> getPercentage(String providerName) {
return Optional.ofNullable(percentageData.get(providerName));
}
public Optional<ExtensionNumberData> getNumber(String providerName) {
return Optional.ofNullable(numberData.get(providerName));
}
public Optional<ExtensionStringData> getString(String providerName) {
return Optional.ofNullable(stringData.get(providerName));
}
@Override
public int compareTo(ExtensionTabData other) {
return Integer.compare(this.tabInformation.getTabPriority(), other.tabInformation.getTabPriority()); // Lower is first
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ExtensionTabData)) return false;
ExtensionTabData that = (ExtensionTabData) o;
return tabInformation.equals(that.tabInformation) &&
order.equals(that.order);
}
@Override
public int hashCode() {
return Objects.hash(tabInformation, order);
}
public void combine(ExtensionTabData other) {
this.booleanData.putAll(other.booleanData);
this.doubleData.putAll(other.doubleData);
this.percentageData.putAll(other.percentageData);
this.numberData.putAll(other.numberData);
this.stringData.putAll(other.stringData);
}
public static class Factory {
private final ExtensionTabData data;
public Factory(TabInformation tabInformation) {
data = new ExtensionTabData(tabInformation);
}
public Factory putBooleanData(ExtensionBooleanData extensionBooleanData) {
data.booleanData.put(extensionBooleanData.getDescriptive().getName(), extensionBooleanData);
return this;
}
public Factory putDoubleData(ExtensionDoubleData extensionDoubleData) {
data.doubleData.put(extensionDoubleData.getDescriptive().getName(), extensionDoubleData);
return this;
}
public Factory putPercentageData(ExtensionDoubleData extensionDoubleData) {
data.percentageData.put(extensionDoubleData.getDescriptive().getName(), extensionDoubleData);
return this;
}
public Factory putNumberData(ExtensionNumberData extensionNumberData) {
data.numberData.put(extensionNumberData.getDescriptive().getName(), extensionNumberData);
return this;
}
public Factory putStringData(ExtensionStringData extensionStringData) {
data.stringData.put(extensionStringData.getDescriptive().getName(), extensionStringData);
return this;
}
private void createOrderingList() {
List<ExtensionDescriptive> descriptives = new ArrayList<>();
data.booleanData.values().stream().map(ExtensionData::getDescriptive).forEach(descriptives::add);
data.doubleData.values().stream().map(ExtensionData::getDescriptive).forEach(descriptives::add);
data.percentageData.values().stream().map(ExtensionData::getDescriptive).forEach(descriptives::add);
data.numberData.values().stream().map(ExtensionData::getDescriptive).forEach(descriptives::add);
data.stringData.values().stream().map(ExtensionData::getDescriptive).forEach(descriptives::add);
data.order = descriptives.stream().sorted()
.map(ExtensionDescriptive::getName)
.distinct()// Method names are usually different, but in case someone had same method name with different parameters.
.collect(Collectors.toList());
}
public ExtensionTabData build() {
createOrderingList();
return data;
}
}
@Override
public String toString() {
return "ExtensionTabData{" +
"available=" + order +
'}';
}
}

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.implementation.results.player;
import com.djrapitops.plan.extension.implementation.results.ExtensionInformation;
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Represents data of a single extension about a player.
*
* @author Rsl1122
*/
public class ExtensionPlayerData implements Comparable<ExtensionPlayerData> {
private final int pluginID;
private ExtensionInformation extensionInformation;
private List<ExtensionTabData> tabs;
private ExtensionPlayerData(int pluginID) {
this.pluginID = pluginID;
tabs = new ArrayList<>();
}
public int getPluginID() {
return pluginID;
}
public ExtensionInformation getExtensionInformation() {
return extensionInformation;
}
public boolean hasOnlyGenericTab() {
return tabs.size() == 1 && tabs.get(0).getTabInformation().getTabName().isEmpty();
}
public List<ExtensionTabData> getTabs() {
return tabs;
}
@Override
public int compareTo(ExtensionPlayerData o) {
return String.CASE_INSENSITIVE_ORDER.compare(this.extensionInformation.getPluginName(), o.extensionInformation.getPluginName());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ExtensionPlayerData)) return false;
ExtensionPlayerData that = (ExtensionPlayerData) o;
return pluginID == that.pluginID &&
extensionInformation.equals(that.extensionInformation) &&
tabs.equals(that.tabs);
}
@Override
public int hashCode() {
return Objects.hash(pluginID, extensionInformation, tabs);
}
public static class Factory {
private final ExtensionPlayerData data;
public Factory(int pluginId) {
data = new ExtensionPlayerData(pluginId);
}
public Factory setInformation(ExtensionInformation information) {
if (information.getId() != data.pluginID) {
throw new IllegalArgumentException("ID mismatch, wanted id: " + data.pluginID + " but got " + information);
}
data.extensionInformation = information;
return this;
}
public Factory addTab(ExtensionTabData tab) {
data.tabs.add(tab);
return this;
}
public ExtensionPlayerData build() {
Collections.sort(data.tabs);
return data;
}
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.results.server;
import com.djrapitops.plan.extension.implementation.results.ExtensionInformation;
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
import java.util.*;
/**
* Represents data of a single extension about a server.
*
* @author Rsl1122
*/
public class ExtensionServerData implements Comparable<ExtensionServerData> {
private final int pluginID;
private ExtensionInformation extensionInformation;
private Map<String, ExtensionTabData> tabs;
private ExtensionServerData(int pluginID) {
this.pluginID = pluginID;
tabs = new HashMap<>();
}
public int getPluginID() {
return pluginID;
}
public ExtensionInformation getExtensionInformation() {
return extensionInformation;
}
public boolean hasOnlyGenericTab() {
return tabs.size() == 1 && tabs.containsKey("");
}
public List<ExtensionTabData> getTabs() {
List<ExtensionTabData> tabList = new ArrayList<>(tabs.values());
Collections.sort(tabList);
return tabList;
}
@Override
public int compareTo(ExtensionServerData o) {
return String.CASE_INSENSITIVE_ORDER.compare(this.extensionInformation.getPluginName(), o.extensionInformation.getPluginName());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ExtensionServerData)) return false;
ExtensionServerData that = (ExtensionServerData) o;
return pluginID == that.pluginID &&
Objects.equals(extensionInformation, that.extensionInformation) &&
Objects.equals(tabs, that.tabs);
}
@Override
public int hashCode() {
return Objects.hash(pluginID, extensionInformation, tabs);
}
public static class Factory {
private final ExtensionServerData data;
public Factory(int pluginId) {
data = new ExtensionServerData(pluginId);
}
public Factory combine(Factory with) {
if (with != null) {
for (ExtensionTabData tab : with.build().getTabs()) {
Optional<ExtensionTabData> found = getTab(tab.getTabInformation().getTabName());
if (found.isPresent()) {
found.get().combine(tab);
} else {
addTab(tab);
}
}
}
return this;
}
public Factory setInformation(ExtensionInformation information) {
if (information.getId() != data.pluginID) {
throw new IllegalArgumentException("ID mismatch, wanted id: " + data.pluginID + " but got " + information);
}
data.extensionInformation = information;
return this;
}
public Factory addTab(ExtensionTabData tab) {
data.tabs.put(tab.getTabInformation().getTabName(), tab);
return this;
}
public Optional<ExtensionTabData> getTab(String tabName) {
return Optional.ofNullable(data.tabs.get(tabName));
}
public ExtensionServerData build() {
return data;
}
}
}

View File

@ -0,0 +1,199 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.extension.ElementOrder;
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.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.results.ExtensionDescriptive;
import com.djrapitops.plan.extension.implementation.results.ExtensionDoubleData;
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Query aggregated boolean values from player value table.
* <p>
* Returns Map: PluginID - ExtensionServerData.Factory.
* <p>
* How it is done:
* - Combines three queries, one that selects true boolean count, one that selects boolean value count and one that selects provider information.
* - Data query is sorted into a multi-map: PluginID - Tab Name - Tab Data
* - (Tab Name can be empty.)
* - Multi-map is sorted into ExtensionPlayerData objects by PluginID, one per ID
* <p>
* There are multiple data extraction methods to make extracting the value query easier.
*
* @author Rsl1122
*/
public class ExtensionAggregateBooleansQuery implements Query<Map<Integer, ExtensionServerData.Factory>> {
private final UUID serverUUID;
public ExtensionAggregateBooleansQuery(UUID serverUUID) {
this.serverUUID = serverUUID;
}
@Override
public Map<Integer, ExtensionServerData.Factory> executeQuery(SQLDB db) {
String selectTrueBooleans = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",COUNT(1) as positive" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String selectBooleanCount = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",COUNT(1) as total" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + IS_NOT_NULL +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String sql = SELECT +
"b1.total as total," +
"b2.positive as positive," +
"p1." + ExtensionProviderTable.PLUGIN_ID + " as plugin_id," +
"p1." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," +
"p1." + ExtensionProviderTable.TEXT + " as text," +
"p1." + ExtensionProviderTable.DESCRIPTION + " as description," +
"p1." + ExtensionProviderTable.PRIORITY + " as provider_priority," +
"p1." + ExtensionProviderTable.IS_PLAYER_NAME + " as is_player_name," +
"t1." + ExtensionTabTable.TAB_NAME + " as tab_name," +
"t1." + ExtensionTabTable.TAB_PRIORITY + " as tab_priority," +
"t1." + ExtensionTabTable.ELEMENT_ORDER + " as element_order," +
"i1." + ExtensionIconTable.ICON_NAME + " as provider_icon_name," +
"i1." + ExtensionIconTable.FAMILY + " as provider_icon_family," +
"i1." + ExtensionIconTable.COLOR + " as provider_icon_color," +
"i2." + ExtensionIconTable.ICON_NAME + " as tab_icon_name," +
"i2." + ExtensionIconTable.FAMILY + " as tab_icon_family," +
"i2." + ExtensionIconTable.COLOR + " as tab_icon_color" +
FROM + '(' + selectBooleanCount + ") b1" +
INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " p1 on p1." + ExtensionProviderTable.ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " e1 on p1." + ExtensionProviderTable.PLUGIN_ID + "=e1." + ExtensionPluginTable.ID +
LEFT_JOIN + '(' + selectTrueBooleans + ") b2 on b2." + ExtensionPlayerValueTable.PROVIDER_ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
LEFT_JOIN + ExtensionTabTable.TABLE_NAME + " t1 on t1." + ExtensionTabTable.ID + "=p1." + ExtensionProviderTable.TAB_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i1 on i1." + ExtensionIconTable.ID + "=p1." + ExtensionProviderTable.ICON_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i2 on i2." + ExtensionIconTable.ID + "=p1." + ExtensionTabTable.ICON_ID +
WHERE + ExtensionPluginTable.SERVER_UUID + "=?" +
AND + "p1." + ExtensionProviderTable.HIDDEN + "=?";
return db.query(new QueryStatement<Map<Integer, ExtensionServerData.Factory>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // selectTrueBooleans parameter
statement.setString(2, serverUUID.toString());
statement.setBoolean(3, false); // Don't select hidden values
}
@Override
public Map<Integer, ExtensionServerData.Factory> processResults(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = extractTabDataByPluginID(set);
return ExtensionServerDataQuery.flatMapToServerData(tabDataByPluginID);
}
});
}
private Map<Integer, Map<String, ExtensionTabData.Factory>> extractTabDataByPluginID(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = new HashMap<>();
while (set.next()) {
int pluginID = set.getInt("plugin_id");
Map<String, ExtensionTabData.Factory> tabData = tabDataByPluginID.getOrDefault(pluginID, new HashMap<>());
String tabName = Optional.ofNullable(set.getString("tab_name")).orElse("");
ExtensionTabData.Factory inMap = tabData.get(tabName);
ExtensionTabData.Factory extensionTab = inMap != null ? inMap : extractTab(tabName, set, tabData);
ExtensionDescriptive extensionDescriptive = extractDescriptive(set);
extractAndPutDataTo(extensionTab, extensionDescriptive, set);
tabData.put(tabName, extensionTab);
tabDataByPluginID.put(pluginID, tabData);
}
return tabDataByPluginID;
}
private void extractAndPutDataTo(ExtensionTabData.Factory extensionTab, ExtensionDescriptive descriptive, ResultSet set) throws SQLException {
double percentageValue = percentage(set.getInt("positive"), set.getInt("total"));
extensionTab.putPercentageData(new ExtensionDoubleData(descriptive, percentageValue));
}
private double percentage(double first, double second) {
if (first == 0.0 || second == 0.0) {
return 0.0;
}
return first / second;
}
private ExtensionDescriptive extractDescriptive(ResultSet set) throws SQLException {
String name = set.getString("provider_name") + "_aggregate";
String text = set.getString(ExtensionProviderTable.TEXT) + " / Players";
String description = set.getString(ExtensionProviderTable.DESCRIPTION);
int priority = set.getInt("provider_priority");
String iconName = set.getString("provider_icon_name");
Family family = Family.getByName(set.getString("provider_icon_family")).orElse(Family.SOLID);
Color color = Color.getByName(set.getString("provider_icon_color")).orElse(Color.NONE);
Icon icon = new Icon(family, iconName, color);
return new ExtensionDescriptive(name, text, description, icon, priority);
}
private ExtensionTabData.Factory extractTab(String tabName, ResultSet set, Map<String, ExtensionTabData.Factory> tabData) throws SQLException {
Optional<Integer> tabPriority = Optional.of(set.getInt("tab_priority"));
if (set.wasNull()) {
tabPriority = Optional.empty();
}
Optional<ElementOrder[]> elementOrder = Optional.ofNullable(set.getString(ExtensionTabTable.ELEMENT_ORDER)).map(ElementOrder::deserialize);
Icon tabIcon = extractTabIcon(set);
return tabData.getOrDefault(tabName, new ExtensionTabData.Factory(new TabInformation(
tabName,
tabIcon,
elementOrder.orElse(ElementOrder.values()),
tabPriority.orElse(100)
)));
}
private Icon extractTabIcon(ResultSet set) throws SQLException {
Optional<String> iconName = Optional.ofNullable(set.getString("tab_icon_name"));
if (iconName.isPresent()) {
Family iconFamily = Family.getByName(set.getString("tab_icon_family")).orElse(Family.SOLID);
Color iconColor = Color.getByName(set.getString("tab_icon_color")).orElse(Color.NONE);
return new Icon(iconFamily, iconName.get(), iconColor);
} else {
return TabInformation.defaultIcon();
}
}
}

View File

@ -0,0 +1,195 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.extension.ElementOrder;
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.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.results.ExtensionDescriptive;
import com.djrapitops.plan.extension.implementation.results.ExtensionDoubleData;
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Query aggregated boolean values from player value table.
* <p>
* Returns Map: PluginID - ExtensionServerData.Factory.
* <p>
* How it is done:
* - Combines three queries, one that selects true boolean count, one that selects boolean value count and one that selects provider information.
* - Data query is sorted into a multi-map: PluginID - Tab Name - Tab Data
* - (Tab Name can be empty.)
* - Multi-map is sorted into ExtensionPlayerData objects by PluginID, one per ID
* <p>
* There are multiple data extraction methods to make extracting the value query easier.
*
* @author Rsl1122
*/
public class ExtensionAggregateDoublesQuery implements Query<Map<Integer, ExtensionServerData.Factory>> {
private final UUID serverUUID;
public ExtensionAggregateDoublesQuery(UUID serverUUID) {
this.serverUUID = serverUUID;
}
@Override
public Map<Integer, ExtensionServerData.Factory> executeQuery(SQLDB db) {
String selectDoubleAverage = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",AVG(" + ExtensionPlayerValueTable.DOUBLE_VALUE + ") as average" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.DOUBLE_VALUE + IS_NOT_NULL +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String selectDoubleTotal = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",SUM(" + ExtensionPlayerValueTable.DOUBLE_VALUE + ") as total" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.DOUBLE_VALUE + IS_NOT_NULL +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String sql = SELECT +
"b1.total as total," +
"b2.average as average," +
"p1." + ExtensionProviderTable.PLUGIN_ID + " as plugin_id," +
"p1." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," +
"p1." + ExtensionProviderTable.TEXT + " as text," +
"p1." + ExtensionProviderTable.DESCRIPTION + " as description," +
"p1." + ExtensionProviderTable.PRIORITY + " as provider_priority," +
"p1." + ExtensionProviderTable.IS_PLAYER_NAME + " as is_player_name," +
"t1." + ExtensionTabTable.TAB_NAME + " as tab_name," +
"t1." + ExtensionTabTable.TAB_PRIORITY + " as tab_priority," +
"t1." + ExtensionTabTable.ELEMENT_ORDER + " as element_order," +
"i1." + ExtensionIconTable.ICON_NAME + " as provider_icon_name," +
"i1." + ExtensionIconTable.FAMILY + " as provider_icon_family," +
"i1." + ExtensionIconTable.COLOR + " as provider_icon_color," +
"i2." + ExtensionIconTable.ICON_NAME + " as tab_icon_name," +
"i2." + ExtensionIconTable.FAMILY + " as tab_icon_family," +
"i2." + ExtensionIconTable.COLOR + " as tab_icon_color" +
FROM + '(' + selectDoubleTotal + ") b1" +
INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " p1 on p1." + ExtensionProviderTable.ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " e1 on p1." + ExtensionProviderTable.PLUGIN_ID + "=e1." + ExtensionPluginTable.ID +
LEFT_JOIN + '(' + selectDoubleAverage + ") b2 on b2." + ExtensionPlayerValueTable.PROVIDER_ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
LEFT_JOIN + ExtensionTabTable.TABLE_NAME + " t1 on t1." + ExtensionTabTable.ID + "=p1." + ExtensionProviderTable.TAB_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i1 on i1." + ExtensionIconTable.ID + "=p1." + ExtensionProviderTable.ICON_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i2 on i2." + ExtensionIconTable.ID + "=p1." + ExtensionTabTable.ICON_ID +
WHERE + ExtensionPluginTable.SERVER_UUID + "=?" +
AND + "p1." + ExtensionProviderTable.HIDDEN + "=?";
return db.query(new QueryStatement<Map<Integer, ExtensionServerData.Factory>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
statement.setBoolean(2, false); // Don't select hidden values
}
@Override
public Map<Integer, ExtensionServerData.Factory> processResults(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = extractTabDataByPluginID(set);
return ExtensionServerDataQuery.flatMapToServerData(tabDataByPluginID);
}
});
}
private Map<Integer, Map<String, ExtensionTabData.Factory>> extractTabDataByPluginID(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = new HashMap<>();
while (set.next()) {
int pluginID = set.getInt("plugin_id");
Map<String, ExtensionTabData.Factory> tabData = tabDataByPluginID.getOrDefault(pluginID, new HashMap<>());
String tabName = Optional.ofNullable(set.getString("tab_name")).orElse("");
ExtensionTabData.Factory inMap = tabData.get(tabName);
ExtensionTabData.Factory extensionTab = inMap != null ? inMap : extractTab(tabName, set, tabData);
ExtensionDescriptive extensionDescriptive = extractDescriptive(set);
extractAndPutDataTo(extensionTab, extensionDescriptive, set);
tabData.put(tabName, extensionTab);
tabDataByPluginID.put(pluginID, tabData);
}
return tabDataByPluginID;
}
private void extractAndPutDataTo(ExtensionTabData.Factory extensionTab, ExtensionDescriptive descriptive, ResultSet set) throws SQLException {
extensionTab.putDoubleData(new ExtensionDoubleData(modifiedDescriptive(descriptive, "_avg", "Average "), set.getDouble("average")));
extensionTab.putDoubleData(new ExtensionDoubleData(modifiedDescriptive(descriptive, "_total", "Total "), set.getDouble("total")));
}
private ExtensionDescriptive modifiedDescriptive(ExtensionDescriptive descriptive, String appendToName, String appendToText) {
return new ExtensionDescriptive(descriptive.getName() + appendToName, appendToText + descriptive.getText(), descriptive.getDescription().orElse(null), descriptive.getIcon(), descriptive.getPriority());
}
private ExtensionDescriptive extractDescriptive(ResultSet set) throws SQLException {
String name = set.getString("provider_name");
String text = set.getString(ExtensionProviderTable.TEXT);
String description = set.getString(ExtensionProviderTable.DESCRIPTION);
int priority = set.getInt("provider_priority");
String iconName = set.getString("provider_icon_name");
Family family = Family.getByName(set.getString("provider_icon_family")).orElse(Family.SOLID);
Color color = Color.getByName(set.getString("provider_icon_color")).orElse(Color.NONE);
Icon icon = new Icon(family, iconName, color);
return new ExtensionDescriptive(name, text, description, icon, priority);
}
private ExtensionTabData.Factory extractTab(String tabName, ResultSet set, Map<String, ExtensionTabData.Factory> tabData) throws SQLException {
Optional<Integer> tabPriority = Optional.of(set.getInt("tab_priority"));
if (set.wasNull()) {
tabPriority = Optional.empty();
}
Optional<ElementOrder[]> elementOrder = Optional.ofNullable(set.getString(ExtensionTabTable.ELEMENT_ORDER)).map(ElementOrder::deserialize);
Icon tabIcon = extractTabIcon(set);
return tabData.getOrDefault(tabName, new ExtensionTabData.Factory(new TabInformation(
tabName,
tabIcon,
elementOrder.orElse(ElementOrder.values()),
tabPriority.orElse(100)
)));
}
private Icon extractTabIcon(ResultSet set) throws SQLException {
Optional<String> iconName = Optional.ofNullable(set.getString("tab_icon_name"));
if (iconName.isPresent()) {
Family iconFamily = Family.getByName(set.getString("tab_icon_family")).orElse(Family.SOLID);
Color iconColor = Color.getByName(set.getString("tab_icon_color")).orElse(Color.NONE);
return new Icon(iconFamily, iconName.get(), iconColor);
} else {
return TabInformation.defaultIcon();
}
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.extension.ElementOrder;
import com.djrapitops.plan.extension.FormatType;
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.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.results.ExtensionDescriptive;
import com.djrapitops.plan.extension.implementation.results.ExtensionNumberData;
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Query aggregated boolean values from player value table.
* <p>
* Returns Map: PluginID - ExtensionServerData.Factory.
* <p>
* How it is done:
* - Combines three queries, one that selects true boolean count, one that selects boolean value count and one that selects provider information.
* - Data query is sorted into a multi-map: PluginID - Tab Name - Tab Data
* - (Tab Name can be empty.)
* - Multi-map is sorted into ExtensionPlayerData objects by PluginID, one per ID
* <p>
* There are multiple data extraction methods to make extracting the value query easier.
*
* @author Rsl1122
*/
public class ExtensionAggregateNumbersQuery implements Query<Map<Integer, ExtensionServerData.Factory>> {
private final UUID serverUUID;
public ExtensionAggregateNumbersQuery(UUID serverUUID) {
this.serverUUID = serverUUID;
}
@Override
public Map<Integer, ExtensionServerData.Factory> executeQuery(SQLDB db) {
String selectNumberAverage = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",AVG(" + ExtensionPlayerValueTable.LONG_VALUE + ") as average" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.LONG_VALUE + IS_NOT_NULL +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String selectNumberTotal = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",SUM(" + ExtensionPlayerValueTable.LONG_VALUE + ") as total" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.LONG_VALUE + IS_NOT_NULL +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String sql = SELECT +
"b1.total as total," +
"b2.average as average," +
"p1." + ExtensionProviderTable.PLUGIN_ID + " as plugin_id," +
"p1." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," +
"p1." + ExtensionProviderTable.TEXT + " as text," +
"p1." + ExtensionProviderTable.DESCRIPTION + " as description," +
"p1." + ExtensionProviderTable.PRIORITY + " as provider_priority," +
"p1." + ExtensionProviderTable.FORMAT_TYPE + " as format_type," +
"p1." + ExtensionProviderTable.IS_PLAYER_NAME + " as is_player_name," +
"t1." + ExtensionTabTable.TAB_NAME + " as tab_name," +
"t1." + ExtensionTabTable.TAB_PRIORITY + " as tab_priority," +
"t1." + ExtensionTabTable.ELEMENT_ORDER + " as element_order," +
"i1." + ExtensionIconTable.ICON_NAME + " as provider_icon_name," +
"i1." + ExtensionIconTable.FAMILY + " as provider_icon_family," +
"i1." + ExtensionIconTable.COLOR + " as provider_icon_color," +
"i2." + ExtensionIconTable.ICON_NAME + " as tab_icon_name," +
"i2." + ExtensionIconTable.FAMILY + " as tab_icon_family," +
"i2." + ExtensionIconTable.COLOR + " as tab_icon_color" +
FROM + '(' + selectNumberTotal + ") b1" +
INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " p1 on p1." + ExtensionProviderTable.ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " e1 on p1." + ExtensionProviderTable.PLUGIN_ID + "=e1." + ExtensionPluginTable.ID +
LEFT_JOIN + '(' + selectNumberAverage + ") b2 on b2." + ExtensionPlayerValueTable.PROVIDER_ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
LEFT_JOIN + ExtensionTabTable.TABLE_NAME + " t1 on t1." + ExtensionTabTable.ID + "=p1." + ExtensionProviderTable.TAB_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i1 on i1." + ExtensionIconTable.ID + "=p1." + ExtensionProviderTable.ICON_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i2 on i2." + ExtensionIconTable.ID + "=p1." + ExtensionTabTable.ICON_ID +
WHERE + ExtensionPluginTable.SERVER_UUID + "=?" +
AND + "p1." + ExtensionProviderTable.HIDDEN + "=?" +
AND + "p1." + ExtensionProviderTable.FORMAT_TYPE + "!=?" +
AND + "p1." + ExtensionProviderTable.FORMAT_TYPE + "!=?";
return db.query(new QueryStatement<Map<Integer, ExtensionServerData.Factory>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
statement.setBoolean(2, false); // Don't select hidden values
statement.setString(3, FormatType.DATE_YEAR.name());
statement.setString(4, FormatType.DATE_SECOND.name());
}
@Override
public Map<Integer, ExtensionServerData.Factory> processResults(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = extractTabDataByPluginID(set);
return ExtensionServerDataQuery.flatMapToServerData(tabDataByPluginID);
}
});
}
private Map<Integer, Map<String, ExtensionTabData.Factory>> extractTabDataByPluginID(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = new HashMap<>();
while (set.next()) {
int pluginID = set.getInt("plugin_id");
Map<String, ExtensionTabData.Factory> tabData = tabDataByPluginID.getOrDefault(pluginID, new HashMap<>());
String tabName = Optional.ofNullable(set.getString("tab_name")).orElse("");
ExtensionTabData.Factory inMap = tabData.get(tabName);
ExtensionTabData.Factory extensionTab = inMap != null ? inMap : extractTab(tabName, set, tabData);
ExtensionDescriptive extensionDescriptive = extractDescriptive(set);
extractAndPutDataTo(extensionTab, extensionDescriptive, set);
tabData.put(tabName, extensionTab);
tabDataByPluginID.put(pluginID, tabData);
}
return tabDataByPluginID;
}
private void extractAndPutDataTo(ExtensionTabData.Factory extensionTab, ExtensionDescriptive descriptive, ResultSet set) throws SQLException {
FormatType formatType = FormatType.getByName(set.getString(ExtensionProviderTable.FORMAT_TYPE)).orElse(FormatType.NONE);
extensionTab.putNumberData(new ExtensionNumberData(modifiedDescriptive(descriptive, "_avg", "Average "), formatType, set.getLong("average")));
extensionTab.putNumberData(new ExtensionNumberData(modifiedDescriptive(descriptive, "_total", "Total "), formatType, set.getLong("total")));
}
private ExtensionDescriptive modifiedDescriptive(ExtensionDescriptive descriptive, String appendToName, String appendToText) {
return new ExtensionDescriptive(descriptive.getName() + appendToName, appendToText + descriptive.getText(), descriptive.getDescription().orElse(null), descriptive.getIcon(), descriptive.getPriority());
}
private ExtensionDescriptive extractDescriptive(ResultSet set) throws SQLException {
String name = set.getString("provider_name");
String text = set.getString(ExtensionProviderTable.TEXT);
String description = set.getString(ExtensionProviderTable.DESCRIPTION);
int priority = set.getInt("provider_priority");
String iconName = set.getString("provider_icon_name");
Family family = Family.getByName(set.getString("provider_icon_family")).orElse(Family.SOLID);
Color color = Color.getByName(set.getString("provider_icon_color")).orElse(Color.NONE);
Icon icon = new Icon(family, iconName, color);
return new ExtensionDescriptive(name, text, description, icon, priority);
}
private ExtensionTabData.Factory extractTab(String tabName, ResultSet set, Map<String, ExtensionTabData.Factory> tabData) throws SQLException {
Optional<Integer> tabPriority = Optional.of(set.getInt("tab_priority"));
if (set.wasNull()) {
tabPriority = Optional.empty();
}
Optional<ElementOrder[]> elementOrder = Optional.ofNullable(set.getString(ExtensionTabTable.ELEMENT_ORDER)).map(ElementOrder::deserialize);
Icon tabIcon = extractTabIcon(set);
return tabData.getOrDefault(tabName, new ExtensionTabData.Factory(new TabInformation(
tabName,
tabIcon,
elementOrder.orElse(ElementOrder.values()),
tabPriority.orElse(100)
)));
}
private Icon extractTabIcon(ResultSet set) throws SQLException {
Optional<String> iconName = Optional.ofNullable(set.getString("tab_icon_name"));
if (iconName.isPresent()) {
Family iconFamily = Family.getByName(set.getString("tab_icon_family")).orElse(Family.SOLID);
Color iconColor = Color.getByName(set.getString("tab_icon_color")).orElse(Color.NONE);
return new Icon(iconFamily, iconName.get(), iconColor);
} else {
return TabInformation.defaultIcon();
}
}
}

View File

@ -0,0 +1,185 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.extension.ElementOrder;
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.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.results.ExtensionDescriptive;
import com.djrapitops.plan.extension.implementation.results.ExtensionDoubleData;
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Query aggregated boolean values from player value table.
* <p>
* Returns Map: PluginID - ExtensionServerData.Factory.
* <p>
* How it is done:
* - Combines three queries, one that selects true boolean count, one that selects boolean value count and one that selects provider information.
* - Data query is sorted into a multi-map: PluginID - Tab Name - Tab Data
* - (Tab Name can be empty.)
* - Multi-map is sorted into ExtensionPlayerData objects by PluginID, one per ID
* <p>
* There are multiple data extraction methods to make extracting the value query easier.
*
* @author Rsl1122
*/
public class ExtensionAggregatePercentagesQuery implements Query<Map<Integer, ExtensionServerData.Factory>> {
private final UUID serverUUID;
public ExtensionAggregatePercentagesQuery(UUID serverUUID) {
this.serverUUID = serverUUID;
}
@Override
public Map<Integer, ExtensionServerData.Factory> executeQuery(SQLDB db) {
String selectPercentageAverage = SELECT +
ExtensionPlayerValueTable.PROVIDER_ID +
",AVG(" + ExtensionPlayerValueTable.PERCENTAGE_VALUE + ") as average" +
FROM + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.PERCENTAGE_VALUE + IS_NOT_NULL +
GROUP_BY + ExtensionPlayerValueTable.PROVIDER_ID;
String sql = SELECT +
"b1.average as average," +
"p1." + ExtensionProviderTable.PLUGIN_ID + " as plugin_id," +
"p1." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," +
"p1." + ExtensionProviderTable.TEXT + " as text," +
"p1." + ExtensionProviderTable.DESCRIPTION + " as description," +
"p1." + ExtensionProviderTable.PRIORITY + " as provider_priority," +
"p1." + ExtensionProviderTable.IS_PLAYER_NAME + " as is_player_name," +
"t1." + ExtensionTabTable.TAB_NAME + " as tab_name," +
"t1." + ExtensionTabTable.TAB_PRIORITY + " as tab_priority," +
"t1." + ExtensionTabTable.ELEMENT_ORDER + " as element_order," +
"i1." + ExtensionIconTable.ICON_NAME + " as provider_icon_name," +
"i1." + ExtensionIconTable.FAMILY + " as provider_icon_family," +
"i1." + ExtensionIconTable.COLOR + " as provider_icon_color," +
"i2." + ExtensionIconTable.ICON_NAME + " as tab_icon_name," +
"i2." + ExtensionIconTable.FAMILY + " as tab_icon_family," +
"i2." + ExtensionIconTable.COLOR + " as tab_icon_color" +
FROM + '(' + selectPercentageAverage + ") b1" +
INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " p1 on p1." + ExtensionProviderTable.ID + "=b1." + ExtensionPlayerValueTable.PROVIDER_ID +
INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " e1 on p1." + ExtensionProviderTable.PLUGIN_ID + "=e1." + ExtensionPluginTable.ID +
LEFT_JOIN + ExtensionTabTable.TABLE_NAME + " t1 on t1." + ExtensionTabTable.ID + "=p1." + ExtensionProviderTable.TAB_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i1 on i1." + ExtensionIconTable.ID + "=p1." + ExtensionProviderTable.ICON_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i2 on i2." + ExtensionIconTable.ID + "=p1." + ExtensionTabTable.ICON_ID +
WHERE + ExtensionPluginTable.SERVER_UUID + "=?" +
AND + "p1." + ExtensionProviderTable.HIDDEN + "=?";
return db.query(new QueryStatement<Map<Integer, ExtensionServerData.Factory>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
statement.setBoolean(2, false); // Don't select hidden values
}
@Override
public Map<Integer, ExtensionServerData.Factory> processResults(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = extractTabDataByPluginID(set);
return ExtensionServerDataQuery.flatMapToServerData(tabDataByPluginID);
}
});
}
private Map<Integer, Map<String, ExtensionTabData.Factory>> extractTabDataByPluginID(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = new HashMap<>();
while (set.next()) {
int pluginID = set.getInt("plugin_id");
Map<String, ExtensionTabData.Factory> tabData = tabDataByPluginID.getOrDefault(pluginID, new HashMap<>());
String tabName = Optional.ofNullable(set.getString("tab_name")).orElse("");
ExtensionTabData.Factory inMap = tabData.get(tabName);
ExtensionTabData.Factory extensionTab = inMap != null ? inMap : extractTab(tabName, set, tabData);
ExtensionDescriptive extensionDescriptive = extractDescriptive(set);
extractAndPutDataTo(extensionTab, extensionDescriptive, set);
tabData.put(tabName, extensionTab);
tabDataByPluginID.put(pluginID, tabData);
}
return tabDataByPluginID;
}
private void extractAndPutDataTo(ExtensionTabData.Factory extensionTab, ExtensionDescriptive descriptive, ResultSet set) throws SQLException {
extensionTab.putPercentageData(new ExtensionDoubleData(modifiedDescriptive(descriptive, "_avg", "Average "), set.getDouble("average")));
}
private ExtensionDescriptive modifiedDescriptive(ExtensionDescriptive descriptive, String appendToName, String appendToText) {
return new ExtensionDescriptive(descriptive.getName() + appendToName, appendToText + descriptive.getText(), descriptive.getDescription().orElse(null), descriptive.getIcon(), descriptive.getPriority());
}
private ExtensionDescriptive extractDescriptive(ResultSet set) throws SQLException {
String name = set.getString("provider_name");
String text = set.getString(ExtensionProviderTable.TEXT);
String description = set.getString(ExtensionProviderTable.DESCRIPTION);
int priority = set.getInt("provider_priority");
String iconName = set.getString("provider_icon_name");
Family family = Family.getByName(set.getString("provider_icon_family")).orElse(Family.SOLID);
Color color = Color.getByName(set.getString("provider_icon_color")).orElse(Color.NONE);
Icon icon = new Icon(family, iconName, color);
return new ExtensionDescriptive(name, text, description, icon, priority);
}
private ExtensionTabData.Factory extractTab(String tabName, ResultSet set, Map<String, ExtensionTabData.Factory> tabData) throws SQLException {
Optional<Integer> tabPriority = Optional.of(set.getInt("tab_priority"));
if (set.wasNull()) {
tabPriority = Optional.empty();
}
Optional<ElementOrder[]> elementOrder = Optional.ofNullable(set.getString(ExtensionTabTable.ELEMENT_ORDER)).map(ElementOrder::deserialize);
Icon tabIcon = extractTabIcon(set);
return tabData.getOrDefault(tabName, new ExtensionTabData.Factory(new TabInformation(
tabName,
tabIcon,
elementOrder.orElse(ElementOrder.values()),
tabPriority.orElse(100)
)));
}
private Icon extractTabIcon(ResultSet set) throws SQLException {
Optional<String> iconName = Optional.ofNullable(set.getString("tab_icon_name"));
if (iconName.isPresent()) {
Family iconFamily = Family.getByName(set.getString("tab_icon_family")).orElse(Family.SOLID);
Color iconColor = Color.getByName(set.getString("tab_icon_color")).orElse(Color.NONE);
return new Icon(iconFamily, iconName.get(), iconColor);
} else {
return TabInformation.defaultIcon();
}
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryAllStatement;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
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.implementation.results.ExtensionInformation;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Queries for information about DataExtensions stored in the database.
*
* @author Rsl1122
*/
public class ExtensionInformationQueries {
private ExtensionInformationQueries() {
/* Static method class */
}
public static Query<List<ExtensionInformation>> extensionsOfServer(UUID serverUUID) {
String sql = SELECT +
ExtensionPluginTable.TABLE_NAME + '.' + ExtensionPluginTable.ID + " as id," +
ExtensionPluginTable.TABLE_NAME + '.' + ExtensionPluginTable.PLUGIN_NAME + " as plugin_name," +
ExtensionIconTable.TABLE_NAME + '.' + ExtensionIconTable.ICON_NAME + " as icon_name," +
ExtensionIconTable.COLOR + ',' +
ExtensionIconTable.FAMILY +
FROM + ExtensionPluginTable.TABLE_NAME +
INNER_JOIN + ExtensionIconTable.TABLE_NAME + " on " +
ExtensionPluginTable.ICON_ID + "=" + ExtensionIconTable.TABLE_NAME + '.' + ExtensionIconTable.ID +
WHERE + ExtensionPluginTable.SERVER_UUID + "=?";
return new QueryStatement<List<ExtensionInformation>>(sql, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
}
@Override
public List<ExtensionInformation> processResults(ResultSet set) throws SQLException {
List<ExtensionInformation> information = new ArrayList<>();
while (set.next()) {
information.add(extractExtensionInformationFromQuery(set));
}
return information;
}
};
}
private static ExtensionInformation extractExtensionInformationFromQuery(ResultSet set) throws SQLException {
int id = set.getInt("id");
String pluginName = set.getString("plugin_name");
String iconName = set.getString("icon_name");
Family iconFamily = Family.getByName(set.getString(ExtensionIconTable.FAMILY)).orElse(Family.SOLID);
Color color = Color.getByName(set.getString(ExtensionIconTable.COLOR)).orElse(Color.NONE);
Icon icon = new Icon(iconFamily, iconName, color);
return new ExtensionInformation(id, pluginName, icon);
}
public static Query<Map<UUID, List<ExtensionInformation>>> allExtensions() {
String sql = SELECT +
ExtensionPluginTable.TABLE_NAME + '.' + ExtensionPluginTable.ID + " as id," +
ExtensionPluginTable.TABLE_NAME + '.' + ExtensionPluginTable.PLUGIN_NAME + " as plugin_name," +
ExtensionPluginTable.SERVER_UUID + ',' +
ExtensionIconTable.TABLE_NAME + '.' + ExtensionIconTable.ICON_NAME + " as icon_name," +
ExtensionIconTable.COLOR + ',' +
ExtensionIconTable.FAMILY +
FROM + ExtensionPluginTable.TABLE_NAME +
INNER_JOIN + ExtensionIconTable.TABLE_NAME + " on " +
ExtensionPluginTable.ICON_ID + "=" + ExtensionIconTable.TABLE_NAME + '.' + ExtensionIconTable.ID;
return new QueryAllStatement<Map<UUID, List<ExtensionInformation>>>(sql, 100) {
@Override
public Map<UUID, List<ExtensionInformation>> processResults(ResultSet set) throws SQLException {
Map<UUID, List<ExtensionInformation>> byServerUUID = new HashMap<>();
while (set.next()) {
UUID serverUUID = UUID.fromString(set.getString(ExtensionPluginTable.SERVER_UUID));
List<ExtensionInformation> information = byServerUUID.getOrDefault(serverUUID, new ArrayList<>());
information.add(extractExtensionInformationFromQuery(set));
byServerUUID.put(serverUUID, information);
}
return byServerUUID;
}
};
}
}

View File

@ -0,0 +1,245 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPlayerValueTable;
import com.djrapitops.plan.db.sql.tables.ExtensionProviderTable;
import com.djrapitops.plan.db.sql.tables.ExtensionTabTable;
import com.djrapitops.plan.extension.ElementOrder;
import com.djrapitops.plan.extension.FormatType;
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.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.results.*;
import com.djrapitops.plan.extension.implementation.results.player.ExtensionPlayerData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Query all ExtensionPlayerData by Server UUIDs.
* <p>
* Returns Map: Server UUID - List of ExtensionPlayerData.
* <p>
* How it is done:
* - Two queries are run, one that fetches all extensions and one that fetches all data of the player.
* - Data query is sorted into a multi-map: PluginID - Tab Name - Tab Data
* - (Tab Name can be empty.)
* - Multi-map is sorted into ExtensionPlayerData objects by PluginID, one per ID
* - This map is sorted into final Map: Server UUID - List of ExtensionPlayerData at the highest level.
* <p>
* There are multiple data extraction methods to make extracting the value query easier.
*
* @author Rsl1122
*/
public class ExtensionPlayerDataQuery implements Query<Map<UUID, List<ExtensionPlayerData>>> {
private final UUID playerUUID;
public ExtensionPlayerDataQuery(UUID playerUUID) {
this.playerUUID = playerUUID;
}
@Override
public Map<UUID, List<ExtensionPlayerData>> executeQuery(SQLDB db) {
Map<UUID, List<ExtensionInformation>> extensionsByServerUUID = db.query(ExtensionInformationQueries.allExtensions());
Map<Integer, ExtensionPlayerData.Factory> extensionDataByPluginID = db.query(fetchIncompletePlayerDataByPluginID());
return flatMapByServerUUID(extensionsByServerUUID, extensionDataByPluginID);
}
private Map<UUID, List<ExtensionPlayerData>> flatMapByServerUUID(Map<UUID, List<ExtensionInformation>> extensionsByServerUUID, Map<Integer, ExtensionPlayerData.Factory> extensionDataByPluginID) {
Map<UUID, List<ExtensionPlayerData>> extensionDataByServerUUID = new HashMap<>();
for (Map.Entry<UUID, List<ExtensionInformation>> entry : extensionsByServerUUID.entrySet()) {
UUID serverUUID = entry.getKey();
for (ExtensionInformation extensionInformation : entry.getValue()) {
ExtensionPlayerData.Factory data = extensionDataByPluginID.get(extensionInformation.getId());
if (data == null) {
continue;
}
List<ExtensionPlayerData> list = extensionDataByServerUUID.getOrDefault(serverUUID, new ArrayList<>());
list.add(data.setInformation(extensionInformation).build());
extensionDataByServerUUID.put(serverUUID, list);
}
}
return extensionDataByServerUUID;
}
private Query<Map<Integer, ExtensionPlayerData.Factory>> fetchIncompletePlayerDataByPluginID() {
String sql = SELECT +
"v1." + ExtensionPlayerValueTable.BOOLEAN_VALUE + " as boolean_value," +
"v1." + ExtensionPlayerValueTable.DOUBLE_VALUE + " as double_value," +
"v1." + ExtensionPlayerValueTable.PERCENTAGE_VALUE + " as percentage_value," +
"v1." + ExtensionPlayerValueTable.LONG_VALUE + " as long_value," +
"v1." + ExtensionPlayerValueTable.STRING_VALUE + " as string_value," +
"p1." + ExtensionProviderTable.PLUGIN_ID + " as plugin_id," +
"p1." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," +
"p1." + ExtensionProviderTable.TEXT + " as text," +
"p1." + ExtensionProviderTable.DESCRIPTION + " as description," +
"p1." + ExtensionProviderTable.PRIORITY + " as provider_priority," +
"p1." + ExtensionProviderTable.FORMAT_TYPE + " as format_type," +
"p1." + ExtensionProviderTable.IS_PLAYER_NAME + " as is_player_name," +
"t1." + ExtensionTabTable.TAB_NAME + " as tab_name," +
"t1." + ExtensionTabTable.TAB_PRIORITY + " as tab_priority," +
"t1." + ExtensionTabTable.ELEMENT_ORDER + " as element_order," +
"i1." + ExtensionIconTable.ICON_NAME + " as provider_icon_name," +
"i1." + ExtensionIconTable.FAMILY + " as provider_icon_family," +
"i1." + ExtensionIconTable.COLOR + " as provider_icon_color," +
"i2." + ExtensionIconTable.ICON_NAME + " as tab_icon_name," +
"i2." + ExtensionIconTable.FAMILY + " as tab_icon_family," +
"i2." + ExtensionIconTable.COLOR + " as tab_icon_color" +
FROM + ExtensionPlayerValueTable.TABLE_NAME + " v1" +
INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " p1 on p1." + ExtensionProviderTable.ID + "=v1." + ExtensionPlayerValueTable.PROVIDER_ID +
LEFT_JOIN + ExtensionTabTable.TABLE_NAME + " t1 on t1." + ExtensionTabTable.ID + "=p1." + ExtensionProviderTable.TAB_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i1 on i1." + ExtensionIconTable.ID + "=p1." + ExtensionProviderTable.ICON_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i2 on i2." + ExtensionIconTable.ID + "=p1." + ExtensionTabTable.ICON_ID +
WHERE + ExtensionPlayerValueTable.USER_UUID + "=?" +
AND + "p1." + ExtensionProviderTable.HIDDEN + "=?";
return new QueryStatement<Map<Integer, ExtensionPlayerData.Factory>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, playerUUID.toString());
statement.setBoolean(2, false); // Don't select hidden values
}
@Override
public Map<Integer, ExtensionPlayerData.Factory> processResults(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = extractTabDataByPluginID(set);
return flatMapToPlayerData(tabDataByPluginID);
}
};
}
private Map<Integer, ExtensionPlayerData.Factory> flatMapToPlayerData(Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID) {
Map<Integer, ExtensionPlayerData.Factory> dataByPluginID = new HashMap<>();
for (Map.Entry<Integer, Map<String, ExtensionTabData.Factory>> entry : tabDataByPluginID.entrySet()) {
Integer pluginID = entry.getKey();
ExtensionPlayerData.Factory data = dataByPluginID.getOrDefault(pluginID, new ExtensionPlayerData.Factory(pluginID));
for (ExtensionTabData.Factory tabData : entry.getValue().values()) {
data.addTab(tabData.build());
}
dataByPluginID.put(pluginID, data);
}
return dataByPluginID;
}
private Map<Integer, Map<String, ExtensionTabData.Factory>> extractTabDataByPluginID(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = new HashMap<>();
while (set.next()) {
int pluginID = set.getInt("plugin_id");
Map<String, ExtensionTabData.Factory> tabData = tabDataByPluginID.getOrDefault(pluginID, new HashMap<>());
String tabName = Optional.ofNullable(set.getString("tab_name")).orElse("");
ExtensionTabData.Factory inMap = tabData.get(tabName);
ExtensionTabData.Factory extensionTab = inMap != null ? inMap : extractTab(tabName, set, tabData);
ExtensionDescriptive extensionDescriptive = extractDescriptive(set);
extractAndPutDataTo(extensionTab, extensionDescriptive, set);
tabData.put(tabName, extensionTab);
tabDataByPluginID.put(pluginID, tabData);
}
return tabDataByPluginID;
}
private void extractAndPutDataTo(ExtensionTabData.Factory extensionTab, ExtensionDescriptive descriptive, ResultSet set) throws SQLException {
boolean booleanValue = set.getBoolean(ExtensionPlayerValueTable.BOOLEAN_VALUE);
if (!set.wasNull()) {
extensionTab.putBooleanData(new ExtensionBooleanData(descriptive, booleanValue));
return;
}
double doubleValue = set.getDouble(ExtensionPlayerValueTable.DOUBLE_VALUE);
if (!set.wasNull()) {
extensionTab.putDoubleData(new ExtensionDoubleData(descriptive, doubleValue));
return;
}
double percentageValue = set.getDouble(ExtensionPlayerValueTable.PERCENTAGE_VALUE);
if (!set.wasNull()) {
extensionTab.putPercentageData(new ExtensionDoubleData(descriptive, percentageValue));
return;
}
long numberValue = set.getLong(ExtensionPlayerValueTable.LONG_VALUE);
if (!set.wasNull()) {
FormatType formatType = FormatType.getByName(set.getString(ExtensionProviderTable.FORMAT_TYPE)).orElse(FormatType.NONE);
extensionTab.putNumberData(new ExtensionNumberData(descriptive, formatType, numberValue));
return;
}
String stringValue = set.getString(ExtensionPlayerValueTable.STRING_VALUE);
if (stringValue != null) {
boolean isPlayerName = set.getBoolean("is_player_name");
extensionTab.putStringData(new ExtensionStringData(descriptive, isPlayerName, stringValue));
}
}
private ExtensionDescriptive extractDescriptive(ResultSet set) throws SQLException {
String name = set.getString("provider_name");
String text = set.getString(ExtensionProviderTable.TEXT);
String description = set.getString(ExtensionProviderTable.DESCRIPTION);
int priority = set.getInt("provider_priority");
String iconName = set.getString("provider_icon_name");
Family family = Family.getByName(set.getString("provider_icon_family")).orElse(Family.SOLID);
Color color = Color.getByName(set.getString("provider_icon_color")).orElse(Color.NONE);
Icon icon = new Icon(family, iconName, color);
return new ExtensionDescriptive(name, text, description, icon, priority);
}
private ExtensionTabData.Factory extractTab(String tabName, ResultSet set, Map<String, ExtensionTabData.Factory> tabData) throws SQLException {
Optional<Integer> tabPriority = Optional.of(set.getInt("tab_priority"));
if (set.wasNull()) {
tabPriority = Optional.empty();
}
Optional<ElementOrder[]> elementOrder = Optional.ofNullable(set.getString(ExtensionTabTable.ELEMENT_ORDER)).map(ElementOrder::deserialize);
Icon tabIcon = extractTabIcon(set);
return tabData.getOrDefault(tabName, new ExtensionTabData.Factory(new TabInformation(
tabName,
tabIcon,
elementOrder.orElse(ElementOrder.values()),
tabPriority.orElse(100)
)));
}
private Icon extractTabIcon(ResultSet set) throws SQLException {
Optional<String> iconName = Optional.ofNullable(set.getString("tab_icon_name"));
if (iconName.isPresent()) {
Family iconFamily = Family.getByName(set.getString("tab_icon_family")).orElse(Family.SOLID);
Color iconColor = Color.getByName(set.getString("tab_icon_color")).orElse(Color.NONE);
return new Icon(iconFamily, iconName.get(), iconColor);
} else {
return TabInformation.defaultIcon();
}
}
}

View File

@ -0,0 +1,264 @@
/*
* 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.storage.queries;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.extension.ElementOrder;
import com.djrapitops.plan.extension.FormatType;
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.implementation.TabInformation;
import com.djrapitops.plan.extension.implementation.results.*;
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Query ExtensionServerData of a server.
* <p>
* Returns List of ExtensionServerData.
* <p>
* How it is done:
* - Two queries are run, one that fetches all extensions and one that fetches all data of the player.
* - Data query is sorted into a multi-map: PluginID - Tab Name - Tab Data
* - (Tab Name can be empty.)
* - Multi-map is sorted into ExtensionServerData objects by PluginID, one per ID
* - This map is combined with similar maps that contain aggregated player values
* - This map is sorted into List of ExtensionPlayerData at the highest level.
* <p>
* There are multiple data extraction methods to make extracting the value query easier.
*
* @author Rsl1122
*/
public class ExtensionServerDataQuery implements Query<List<ExtensionServerData>> {
private final UUID serverUUID;
public ExtensionServerDataQuery(UUID serverUUID) {
this.serverUUID = serverUUID;
}
static Map<Integer, ExtensionServerData.Factory> flatMapToServerData(Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID) {
Map<Integer, ExtensionServerData.Factory> dataByPluginID = new HashMap<>();
for (Map.Entry<Integer, Map<String, ExtensionTabData.Factory>> entry : tabDataByPluginID.entrySet()) {
Integer pluginID = entry.getKey();
ExtensionServerData.Factory data = dataByPluginID.getOrDefault(pluginID, new ExtensionServerData.Factory(pluginID));
for (ExtensionTabData.Factory tabData : entry.getValue().values()) {
data.addTab(tabData.build());
}
dataByPluginID.put(pluginID, data);
}
return dataByPluginID;
}
@Override
public List<ExtensionServerData> executeQuery(SQLDB db) {
List<ExtensionInformation> extensionsOfServer = db.query(ExtensionInformationQueries.extensionsOfServer(serverUUID));
Map<Integer, ExtensionServerData.Factory> extensionDataByPluginID = db.query(fetchIncompleteServerDataByPluginID());
combine(extensionDataByPluginID, db.query(new ExtensionAggregateBooleansQuery(serverUUID)));
combine(extensionDataByPluginID, db.query(new ExtensionAggregateDoublesQuery(serverUUID)));
combine(extensionDataByPluginID, db.query(new ExtensionAggregateNumbersQuery(serverUUID)));
combine(extensionDataByPluginID, db.query(new ExtensionAggregatePercentagesQuery(serverUUID)));
return combineWithExtensionInfo(extensionsOfServer, extensionDataByPluginID);
}
private void combine(
Map<Integer, ExtensionServerData.Factory> extensionDataByPluginID,
Map<Integer, ExtensionServerData.Factory> aggregates
) {
for (Map.Entry<Integer, ExtensionServerData.Factory> entry : aggregates.entrySet()) {
Integer pluginID = entry.getKey();
ExtensionServerData.Factory data = entry.getValue();
ExtensionServerData.Factory found = extensionDataByPluginID.get(pluginID);
if (found == null) {
extensionDataByPluginID.put(pluginID, data);
} else {
found.combine(data);
}
}
}
private List<ExtensionServerData> combineWithExtensionInfo(
List<ExtensionInformation> extensionsOfServer,
Map<Integer, ExtensionServerData.Factory> extensionDataByPluginID
) {
List<ExtensionServerData> extensionServerData = new ArrayList<>();
for (ExtensionInformation extensionInformation : extensionsOfServer) {
ExtensionServerData.Factory data = extensionDataByPluginID.get(extensionInformation.getId());
if (data == null) {
continue;
}
extensionServerData.add(data.setInformation(extensionInformation).build());
}
return extensionServerData;
}
private Query<Map<Integer, ExtensionServerData.Factory>> fetchIncompleteServerDataByPluginID() {
String sql = SELECT +
"v1." + ExtensionServerValueTable.BOOLEAN_VALUE + " as boolean_value," +
"v1." + ExtensionServerValueTable.DOUBLE_VALUE + " as double_value," +
"v1." + ExtensionServerValueTable.PERCENTAGE_VALUE + " as percentage_value," +
"v1." + ExtensionServerValueTable.LONG_VALUE + " as long_value," +
"v1." + ExtensionServerValueTable.STRING_VALUE + " as string_value," +
"p1." + ExtensionProviderTable.PLUGIN_ID + " as plugin_id," +
"p1." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," +
"p1." + ExtensionProviderTable.TEXT + " as text," +
"p1." + ExtensionProviderTable.DESCRIPTION + " as description," +
"p1." + ExtensionProviderTable.PRIORITY + " as provider_priority," +
"p1." + ExtensionProviderTable.FORMAT_TYPE + " as format_type," +
"p1." + ExtensionProviderTable.IS_PLAYER_NAME + " as is_player_name," +
"t1." + ExtensionTabTable.TAB_NAME + " as tab_name," +
"t1." + ExtensionTabTable.TAB_PRIORITY + " as tab_priority," +
"t1." + ExtensionTabTable.ELEMENT_ORDER + " as element_order," +
"i1." + ExtensionIconTable.ICON_NAME + " as provider_icon_name," +
"i1." + ExtensionIconTable.FAMILY + " as provider_icon_family," +
"i1." + ExtensionIconTable.COLOR + " as provider_icon_color," +
"i2." + ExtensionIconTable.ICON_NAME + " as tab_icon_name," +
"i2." + ExtensionIconTable.FAMILY + " as tab_icon_family," +
"i2." + ExtensionIconTable.COLOR + " as tab_icon_color" +
FROM + ExtensionServerValueTable.TABLE_NAME + " v1" +
INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " p1 on p1." + ExtensionProviderTable.ID + "=v1." + ExtensionServerValueTable.PROVIDER_ID +
INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " e1 on p1." + ExtensionProviderTable.PLUGIN_ID + "=e1." + ExtensionPluginTable.ID +
LEFT_JOIN + ExtensionTabTable.TABLE_NAME + " t1 on t1." + ExtensionTabTable.ID + "=p1." + ExtensionProviderTable.TAB_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i1 on i1." + ExtensionIconTable.ID + "=p1." + ExtensionProviderTable.ICON_ID +
LEFT_JOIN + ExtensionIconTable.TABLE_NAME + " i2 on i2." + ExtensionIconTable.ID + "=p1." + ExtensionTabTable.ICON_ID +
WHERE + ExtensionPluginTable.SERVER_UUID + "=?" +
AND + "p1." + ExtensionProviderTable.HIDDEN + "=?";
return new QueryStatement<Map<Integer, ExtensionServerData.Factory>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
statement.setBoolean(2, false); // Don't select hidden values
}
@Override
public Map<Integer, ExtensionServerData.Factory> processResults(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = extractTabDataByPluginID(set);
return flatMapToServerData(tabDataByPluginID);
}
};
}
private Map<Integer, Map<String, ExtensionTabData.Factory>> extractTabDataByPluginID(ResultSet set) throws SQLException {
Map<Integer, Map<String, ExtensionTabData.Factory>> tabDataByPluginID = new HashMap<>();
while (set.next()) {
int pluginID = set.getInt("plugin_id");
Map<String, ExtensionTabData.Factory> tabData = tabDataByPluginID.getOrDefault(pluginID, new HashMap<>());
String tabName = Optional.ofNullable(set.getString("tab_name")).orElse("");
ExtensionTabData.Factory inMap = tabData.get(tabName);
ExtensionTabData.Factory extensionTab = inMap != null ? inMap : extractTab(tabName, set, tabData);
ExtensionDescriptive extensionDescriptive = extractDescriptive(set);
extractAndPutDataTo(extensionTab, extensionDescriptive, set);
tabData.put(tabName, extensionTab);
tabDataByPluginID.put(pluginID, tabData);
}
return tabDataByPluginID;
}
private void extractAndPutDataTo(ExtensionTabData.Factory extensionTab, ExtensionDescriptive descriptive, ResultSet set) throws SQLException {
boolean booleanValue = set.getBoolean(ExtensionServerValueTable.BOOLEAN_VALUE);
if (!set.wasNull()) {
extensionTab.putBooleanData(new ExtensionBooleanData(descriptive, booleanValue));
return;
}
double doubleValue = set.getDouble(ExtensionServerValueTable.DOUBLE_VALUE);
if (!set.wasNull()) {
extensionTab.putDoubleData(new ExtensionDoubleData(descriptive, doubleValue));
return;
}
double percentageValue = set.getDouble(ExtensionServerValueTable.PERCENTAGE_VALUE);
if (!set.wasNull()) {
extensionTab.putPercentageData(new ExtensionDoubleData(descriptive, percentageValue));
return;
}
long numberValue = set.getLong(ExtensionServerValueTable.LONG_VALUE);
if (!set.wasNull()) {
FormatType formatType = FormatType.getByName(set.getString(ExtensionProviderTable.FORMAT_TYPE)).orElse(FormatType.NONE);
extensionTab.putNumberData(new ExtensionNumberData(descriptive, formatType, numberValue));
return;
}
String stringValue = set.getString(ExtensionServerValueTable.STRING_VALUE);
if (stringValue != null) {
boolean isPlayerName = set.getBoolean("is_player_name");
extensionTab.putStringData(new ExtensionStringData(descriptive, isPlayerName, stringValue));
}
}
private ExtensionDescriptive extractDescriptive(ResultSet set) throws SQLException {
String name = set.getString("provider_name");
String text = set.getString(ExtensionProviderTable.TEXT);
String description = set.getString(ExtensionProviderTable.DESCRIPTION);
int priority = set.getInt("provider_priority");
String iconName = set.getString("provider_icon_name");
Family family = Family.getByName(set.getString("provider_icon_family")).orElse(Family.SOLID);
Color color = Color.getByName(set.getString("provider_icon_color")).orElse(Color.NONE);
Icon icon = new Icon(family, iconName, color);
return new ExtensionDescriptive(name, text, description, icon, priority);
}
private ExtensionTabData.Factory extractTab(String tabName, ResultSet set, Map<String, ExtensionTabData.Factory> tabData) throws SQLException {
Optional<Integer> tabPriority = Optional.of(set.getInt("tab_priority"));
if (set.wasNull()) {
tabPriority = Optional.empty();
}
Optional<ElementOrder[]> elementOrder = Optional.ofNullable(set.getString(ExtensionTabTable.ELEMENT_ORDER)).map(ElementOrder::deserialize);
Icon tabIcon = extractTabIcon(set);
return tabData.getOrDefault(tabName, new ExtensionTabData.Factory(new TabInformation(
tabName,
tabIcon,
elementOrder.orElse(ElementOrder.values()),
tabPriority.orElse(100)
)));
}
private Icon extractTabIcon(ResultSet set) throws SQLException {
Optional<String> iconName = Optional.ofNullable(set.getString("tab_icon_name"));
if (iconName.isPresent()) {
Family iconFamily = Family.getByName(set.getString("tab_icon_family")).orElse(Family.SOLID);
Color iconColor = Color.getByName(set.getString("tab_icon_color")).orElse(Color.NONE);
return new Icon(iconFamily, iconName.get(), iconColor);
} else {
return TabInformation.defaultIcon();
}
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.storage.transactions;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.HasMoreThanZeroQueryStatement;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.extension.icon.Icon;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Transaction to store an Icon to the database.
*
* @author Rsl1122
*/
public class StoreIconTransaction extends Transaction {
private final Icon icon;
public StoreIconTransaction(Icon icon) {
this.icon = icon;
}
@Override
protected void performOperations() {
if (!query(isIconStored())) {
execute(insertIcon());
}
}
private Executable insertIcon() {
String sql = "INSERT INTO " + ExtensionIconTable.TABLE_NAME + "(" +
ExtensionIconTable.ICON_NAME + "," +
ExtensionIconTable.FAMILY + "," +
ExtensionIconTable.COLOR +
") VALUES (?,?,?)";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ExtensionIconTable.set3IconValuesToStatement(statement, icon);
}
};
}
private Query<Boolean> isIconStored() {
String sql = SELECT + "COUNT(1) as c" +
FROM + ExtensionIconTable.TABLE_NAME +
WHERE + ExtensionIconTable.ICON_NAME + "=?" +
AND + ExtensionIconTable.FAMILY + "=?" +
AND + ExtensionIconTable.COLOR + "=?";
return new HasMoreThanZeroQueryStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ExtensionIconTable.set3IconValuesToStatement(statement, icon);
}
};
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.storage.transactions;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.extension.icon.Icon;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
/**
* Transaction to update command usage information in the database.
*
* @author Rsl1122
*/
public class StorePluginTransaction extends Transaction {
private final String pluginName;
private final long time;
private final UUID serverUUID;
private final Icon icon;
public StorePluginTransaction(String pluginName, long time, UUID serverUUID, Icon icon) {
this.pluginName = pluginName;
this.time = time;
this.serverUUID = serverUUID;
this.icon = icon;
}
@Override
protected void performOperations() {
execute(storePlugin());
}
private Executable storePlugin() {
return connection -> {
if (!updatePlugin().execute(connection)) {
return insertPlugin().execute(connection);
}
return false;
};
}
private Executable updatePlugin() {
String sql = "UPDATE " + ExtensionPluginTable.TABLE_NAME +
" SET " +
ExtensionPluginTable.LAST_UPDATED + "=?," +
ExtensionPluginTable.ICON_ID + "=" + ExtensionIconTable.STATEMENT_SELECT_ICON_ID +
WHERE + ExtensionPluginTable.PLUGIN_NAME + "=?" +
AND + ExtensionPluginTable.SERVER_UUID + "=?";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, time);
ExtensionIconTable.set3IconValuesToStatement(statement, 2, icon);
statement.setString(5, pluginName);
statement.setString(6, serverUUID.toString());
}
};
}
private Executable insertPlugin() {
String sql = "INSERT INTO " + ExtensionPluginTable.TABLE_NAME + "(" +
ExtensionPluginTable.PLUGIN_NAME + "," +
ExtensionPluginTable.LAST_UPDATED + "," +
ExtensionPluginTable.SERVER_UUID + "," +
ExtensionPluginTable.ICON_ID +
") VALUES (?,?,?," + ExtensionIconTable.STATEMENT_SELECT_ICON_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, pluginName);
statement.setLong(2, time);
statement.setString(3, serverUUID.toString());
ExtensionIconTable.set3IconValuesToStatement(statement, 4, icon);
}
};
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.storage.transactions;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.db.sql.tables.ExtensionTabTable;
import com.djrapitops.plan.extension.ElementOrder;
import com.djrapitops.plan.extension.implementation.TabInformation;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
/**
* Transaction for storing {@link TabInformation}s.
*
* @author Rsl1122
*/
public class StoreTabInformationTransaction extends Transaction {
private final String pluginName;
private final UUID serverUUID;
private final TabInformation tabInformation;
public StoreTabInformationTransaction(String pluginName, UUID serverUUID, TabInformation tabInformation) {
this.pluginName = pluginName;
this.serverUUID = serverUUID;
this.tabInformation = tabInformation;
}
@Override
protected void performOperations() {
execute(storeTab());
}
private Executable storeTab() {
return connection -> {
if (!updateTab().execute(connection)) {
return insertTab().execute(connection);
}
return false;
};
}
private Executable updateTab() {
String sql = "UPDATE " + ExtensionTabTable.TABLE_NAME +
" SET " +
ExtensionTabTable.TAB_PRIORITY + "=?," +
ExtensionTabTable.ELEMENT_ORDER + "=?," +
ExtensionTabTable.ICON_ID + "=" + ExtensionIconTable.STATEMENT_SELECT_ICON_ID + "," +
WHERE + ExtensionTabTable.PLUGIN_ID + "=" + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID +
AND + ExtensionTabTable.TAB_NAME + "=?";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setInt(1, tabInformation.getTabPriority());
statement.setString(2, tabInformation.getTabElementOrder().map(ElementOrder::serialize).orElse(null));
ExtensionIconTable.set3IconValuesToStatement(statement, 3, tabInformation.getTabIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 6, pluginName, serverUUID);
statement.setString(8, tabInformation.getTabName());
}
};
}
private Executable insertTab() {
String sql = "INSERT INTO " + ExtensionTabTable.TABLE_NAME + "(" +
ExtensionTabTable.TAB_NAME + "," +
ExtensionTabTable.ELEMENT_ORDER + "," +
ExtensionTabTable.TAB_PRIORITY + "," +
ExtensionTabTable.ICON_ID +
ExtensionTabTable.PLUGIN_ID +
") VALUES (?,?,?," + ExtensionIconTable.STATEMENT_SELECT_ICON_ID + "," + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, tabInformation.getTabName());
statement.setString(2, tabInformation.getTabElementOrder().map(ElementOrder::serialize).orElse(null));
statement.setInt(3, tabInformation.getTabPriority());
ExtensionIconTable.set3IconValuesToStatement(statement, 4, tabInformation.getTabIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 7, pluginName, serverUUID);
}
};
}
}

View File

@ -0,0 +1,162 @@
/*
* 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.storage.transactions.providers;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.db.sql.tables.ExtensionTabTable;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
import static com.djrapitops.plan.db.sql.tables.ExtensionProviderTable.*;
/**
* Transaction to store information about a {@link com.djrapitops.plan.extension.implementation.providers.BooleanDataProvider}.
*
* @author Rsl1122
*/
public class StoreBooleanProviderTransaction extends Transaction {
private final String providedCondition;
private final boolean hidden;
private final UUID serverUUID;
private final ProviderInformation providerInformation;
public StoreBooleanProviderTransaction(DataProvider<Boolean> booleanProvider, String providedCondition, boolean hidden, UUID serverUUID) {
this.providedCondition = providedCondition;
this.hidden = hidden;
this.serverUUID = serverUUID;
this.providerInformation = booleanProvider.getProviderInformation();
}
@Override
protected void performOperations() {
execute(storeProvider());
}
private Executable storeProvider() {
return connection -> {
if (!updateProvider().execute(connection)) {
return insertProvider().execute(connection);
}
return false;
};
}
private Executable updateProvider() {
String sql = "UPDATE " + TABLE_NAME +
" SET " +
TEXT + "=?," +
DESCRIPTION + "=?," +
PRIORITY + "=?," +
CONDITION + "=?," +
PROVIDED_CONDITION + "=?," +
TAB_ID + '=' + ExtensionTabTable.STATEMENT_SELECT_TAB_ID + ',' +
ICON_ID + '=' + ExtensionIconTable.STATEMENT_SELECT_ICON_ID + ',' +
HIDDEN + "=?" +
WHERE + PLUGIN_ID + '=' + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID +
AND + PROVIDER_NAME + "=?";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(2, description.get());
} else {
statement.setNull(2, Types.VARCHAR);
}
statement.setInt(3, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(4, condition.get());
} else {
statement.setNull(4, Types.VARCHAR);
}
if (providedCondition != null) {
statement.setString(5, providedCondition);
} else {
statement.setNull(5, Types.VARCHAR);
}
ExtensionTabTable.set3TabValuesToStatement(statement, 6, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 9, providerInformation.getIcon());
statement.setBoolean(12, hidden);
ExtensionPluginTable.set2PluginValuesToStatement(statement, 13, providerInformation.getPluginName(), serverUUID);
statement.setString(15, providerInformation.getName());
}
};
}
private Executable insertProvider() {
String sql = "INSERT INTO " + TABLE_NAME + '(' +
PROVIDER_NAME + ',' +
TEXT + ',' +
DESCRIPTION + ',' +
PRIORITY + ',' +
CONDITION + ',' +
PROVIDED_CONDITION + ',' +
HIDDEN + ',' +
TAB_ID + ',' +
ICON_ID + ',' +
PLUGIN_ID +
") VALUES (?,?,?,?,?,?,?," +
ExtensionTabTable.STATEMENT_SELECT_TAB_ID + ',' +
ExtensionIconTable.STATEMENT_SELECT_ICON_ID + ',' +
ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ')';
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getName());
statement.setString(2, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(3, description.get());
} else {
statement.setNull(3, Types.VARCHAR);
}
statement.setInt(4, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(5, condition.get());
} else {
statement.setNull(5, Types.VARCHAR);
}
if (providedCondition != null) {
statement.setString(6, providedCondition);
} else {
statement.setNull(6, Types.VARCHAR);
}
statement.setBoolean(7, hidden);
ExtensionTabTable.set3TabValuesToStatement(statement, 8, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 11, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 14, providerInformation.getPluginName(), serverUUID);
}
};
}
}

View File

@ -0,0 +1,146 @@
/*
* 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.storage.transactions.providers;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.db.sql.tables.ExtensionTabTable;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
import static com.djrapitops.plan.db.sql.tables.ExtensionProviderTable.*;
/**
* Transaction to store information about a dobule {@link DataProvider}.
* <p>
* Includes:
* {@link com.djrapitops.plan.extension.implementation.providers.DoubleDataProvider}.
* {@link com.djrapitops.plan.extension.implementation.providers.PercentageDataProvider}.
*
* @author Rsl1122
*/
public class StoreDoubleProviderTransaction extends Transaction {
private final UUID serverUUID;
private final ProviderInformation providerInformation;
public StoreDoubleProviderTransaction(DataProvider<Double> provider, UUID serverUUID) {
this.serverUUID = serverUUID;
this.providerInformation = provider.getProviderInformation();
}
@Override
protected void performOperations() {
execute(storeProvider());
}
private Executable storeProvider() {
return connection -> {
if (!updateProvider().execute(connection)) {
return insertProvider().execute(connection);
}
return false;
};
}
private Executable updateProvider() {
String sql = "UPDATE " + TABLE_NAME +
" SET " +
TEXT + "=?," +
DESCRIPTION + "=?," +
PRIORITY + "=?," +
CONDITION + "=?," +
TAB_ID + "=" + ExtensionTabTable.STATEMENT_SELECT_TAB_ID + "," +
ICON_ID + "=" + ExtensionIconTable.STATEMENT_SELECT_ICON_ID +
WHERE + PLUGIN_ID + "=" + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID +
AND + PROVIDER_NAME + "=?";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(2, description.get());
} else {
statement.setNull(2, Types.VARCHAR);
}
statement.setInt(3, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(4, condition.get());
} else {
statement.setNull(4, Types.VARCHAR);
}
ExtensionTabTable.set3TabValuesToStatement(statement, 5, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 8, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 11, providerInformation.getPluginName(), serverUUID);
statement.setString(13, providerInformation.getName());
}
};
}
private Executable insertProvider() {
String sql = "INSERT INTO " + TABLE_NAME + "(" +
PROVIDER_NAME + "," +
TEXT + "," +
DESCRIPTION + "," +
PRIORITY + "," +
CONDITION + "," +
TAB_ID + "," +
ICON_ID + "," +
PLUGIN_ID +
") VALUES (?,?,?,?,?," +
ExtensionTabTable.STATEMENT_SELECT_TAB_ID + "," +
ExtensionIconTable.STATEMENT_SELECT_ICON_ID + "," +
ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getName());
statement.setString(2, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(3, description.get());
} else {
statement.setNull(3, Types.VARCHAR);
}
statement.setInt(4, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(5, condition.get());
} else {
statement.setNull(5, Types.VARCHAR);
}
ExtensionTabTable.set3TabValuesToStatement(statement, 6, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 9, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 12, providerInformation.getPluginName(), serverUUID);
}
};
}
}

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.storage.transactions.providers;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.db.sql.tables.ExtensionTabTable;
import com.djrapitops.plan.extension.FormatType;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
import static com.djrapitops.plan.db.sql.tables.ExtensionProviderTable.*;
/**
* Transaction to store information about a {@link com.djrapitops.plan.extension.implementation.providers.NumberDataProvider}.
*
* @author Rsl1122
*/
@SuppressWarnings("Duplicates")
public class StoreNumberProviderTransaction extends Transaction {
private final FormatType formatType;
private final UUID serverUUID;
private final ProviderInformation providerInformation;
public StoreNumberProviderTransaction(DataProvider<Long> provider, FormatType formatType, UUID serverUUID) {
this.formatType = formatType;
this.serverUUID = serverUUID;
this.providerInformation = provider.getProviderInformation();
}
@Override
protected void performOperations() {
execute(storeProvider());
}
private Executable storeProvider() {
return connection -> {
if (!updateProvider().execute(connection)) {
return insertProvider().execute(connection);
}
return false;
};
}
private Executable updateProvider() {
String sql = "UPDATE " + TABLE_NAME + " SET " +
TEXT + "=?," +
DESCRIPTION + "=?," +
PRIORITY + "=?," +
CONDITION + "=?," +
FORMAT_TYPE + "=?," +
TAB_ID + "=" + ExtensionTabTable.STATEMENT_SELECT_TAB_ID + "," +
ICON_ID + "=" + ExtensionIconTable.STATEMENT_SELECT_ICON_ID +
WHERE + PLUGIN_ID + "=" + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID +
AND + PROVIDER_NAME + "=?";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(2, description.get());
} else {
statement.setNull(2, Types.VARCHAR);
}
statement.setInt(3, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(4, condition.get());
} else {
statement.setNull(4, Types.VARCHAR);
}
statement.setString(5, formatType.name());
ExtensionTabTable.set3TabValuesToStatement(statement, 6, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 9, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 12, providerInformation.getPluginName(), serverUUID);
statement.setString(14, providerInformation.getName());
}
};
}
private Executable insertProvider() {
String sql = "INSERT INTO " + TABLE_NAME + "(" +
PROVIDER_NAME + "," +
TEXT + "," +
DESCRIPTION + "," +
PRIORITY + "," +
CONDITION + "," +
FORMAT_TYPE + "," +
TAB_ID + "," +
ICON_ID + "," +
PLUGIN_ID +
") VALUES (?,?,?,?,?,?," +
ExtensionTabTable.STATEMENT_SELECT_TAB_ID + "," +
ExtensionIconTable.STATEMENT_SELECT_ICON_ID + "," +
ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getName());
statement.setString(2, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(3, description.get());
} else {
statement.setNull(3, Types.VARCHAR);
}
statement.setInt(4, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(5, condition.get());
} else {
statement.setNull(5, Types.VARCHAR);
}
statement.setString(6, formatType.name());
ExtensionTabTable.set3TabValuesToStatement(statement, 7, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 10, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 13, providerInformation.getPluginName(), serverUUID);
}
};
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.storage.transactions.providers;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionIconTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.db.sql.tables.ExtensionTabTable;
import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.DataProvider;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Optional;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
import static com.djrapitops.plan.db.sql.tables.ExtensionProviderTable.*;
/**
* Transaction to store information about a {@link com.djrapitops.plan.extension.implementation.providers.StringDataProvider}.
*
* @author Rsl1122
*/
public class StoreStringProviderTransaction extends Transaction {
private final boolean playerName;
private final UUID serverUUID;
private ProviderInformation providerInformation;
public StoreStringProviderTransaction(DataProvider<String> provider, boolean playerName, UUID serverUUID) {
this.playerName = playerName;
this.serverUUID = serverUUID;
providerInformation = provider.getProviderInformation();
}
@Override
protected void performOperations() {
execute(storeProvider());
}
private Executable storeProvider() {
return connection -> {
if (!updateProvider().execute(connection)) {
return insertProvider().execute(connection);
}
return false;
};
}
private Executable updateProvider() {
String sql = "UPDATE " + TABLE_NAME +
" SET " +
TEXT + "=?," +
DESCRIPTION + "=?," +
PRIORITY + "=?," +
CONDITION + "=?," +
IS_PLAYER_NAME + "=?," +
TAB_ID + "=" + ExtensionTabTable.STATEMENT_SELECT_TAB_ID + "," +
ICON_ID + "=" + ExtensionIconTable.STATEMENT_SELECT_ICON_ID +
WHERE + PLUGIN_ID + "=" + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID +
AND + PROVIDER_NAME + "=?";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(2, description.get());
} else {
statement.setNull(2, Types.VARCHAR);
}
statement.setInt(3, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(4, condition.get());
} else {
statement.setNull(4, Types.VARCHAR);
}
statement.setBoolean(5, playerName);
ExtensionTabTable.set3TabValuesToStatement(statement, 6, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 9, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 12, providerInformation.getPluginName(), serverUUID);
statement.setString(14, providerInformation.getName());
}
};
}
private Executable insertProvider() {
String sql = "INSERT INTO " + TABLE_NAME + "(" +
PROVIDER_NAME + "," +
TEXT + "," +
DESCRIPTION + "," +
PRIORITY + "," +
CONDITION + "," +
IS_PLAYER_NAME + "," +
TAB_ID + "," +
ICON_ID + "," +
PLUGIN_ID +
") VALUES (?,?,?,?,?,?," +
ExtensionTabTable.STATEMENT_SELECT_TAB_ID + "," +
ExtensionIconTable.STATEMENT_SELECT_ICON_ID + "," +
ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, providerInformation.getName());
statement.setString(2, providerInformation.getText());
Optional<String> description = providerInformation.getDescription();
if (description.isPresent()) {
statement.setString(3, description.get());
} else {
statement.setNull(3, Types.VARCHAR);
}
statement.setInt(4, providerInformation.getPriority());
Optional<String> condition = providerInformation.getCondition();
if (condition.isPresent()) {
statement.setString(5, condition.get());
} else {
statement.setNull(5, Types.VARCHAR);
}
statement.setBoolean(6, playerName);
ExtensionTabTable.set3TabValuesToStatement(statement, 7, providerInformation.getTab().orElse("No Tab"), providerInformation.getPluginName(), serverUUID);
ExtensionIconTable.set3IconValuesToStatement(statement, 10, providerInformation.getIcon());
ExtensionPluginTable.set2PluginValuesToStatement(statement, 13, providerInformation.getPluginName(), serverUUID);
}
};
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.storage.transactions.results;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionPlayerValueTable;
import com.djrapitops.plan.db.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.db.sql.tables.ExtensionProviderTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
/**
* Transaction to remove method results that correspond to {@link com.djrapitops.plan.extension.annotation.InvalidateMethod} annotations.
*
* @author Rsl1122
*/
public class RemoveInvalidResultsTransaction extends Transaction {
private final String pluginName;
private final UUID serverUUID;
private final Collection<String> invalidatedMethods;
public RemoveInvalidResultsTransaction(String pluginName, UUID serverUUID, Collection<String> invalidatedMethods) {
this.pluginName = pluginName;
this.serverUUID = serverUUID;
this.invalidatedMethods = invalidatedMethods;
}
@Override
protected void performOperations() {
for (String invalidatedMethod : invalidatedMethods) {
execute(deleteInvalidMethodResults(invalidatedMethod));
execute(deleteInvalidMethodProvider(invalidatedMethod));
}
}
private Executable deleteInvalidMethodResults(String invalidMethod) {
String sql = "DELETE FROM " + ExtensionPlayerValueTable.TABLE_NAME +
WHERE + ExtensionPlayerValueTable.PROVIDER_ID + "=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID;
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ExtensionProviderTable.set3PluginValuesToStatement(statement, 1, invalidMethod, pluginName, serverUUID);
}
};
}
private Executable deleteInvalidMethodProvider(String invalidMethod) {
String sql = "DELETE FROM " + ExtensionProviderTable.TABLE_NAME +
WHERE + ExtensionProviderTable.PROVIDER_NAME + "=?" +
AND + ExtensionProviderTable.PLUGIN_ID + ExtensionPluginTable.STATEMENT_SELECT_PLUGIN_ID;
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, invalidMethod);
ExtensionPluginTable.set2PluginValuesToStatement(statement, 2, pluginName, serverUUID);
}
};
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.storage.transactions.results;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionPlayerValueTable;
import com.djrapitops.plan.db.sql.tables.ExtensionProviderTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
/**
* Transaction to remove older results that violate an updated condition value.
* <p>
* How it works:
* - Select all fulfilled conditions for all players (conditionName when true and not_conditionName when false)
* - Left join with player value & provider tables when uuids match, and when condition matches a condition in the query above.
* - Filter the join query for values where the condition did not match any provided condition in the join (Is null)
* - Delete all player values with IDs that are returned by the left join query after filtering
*
* @author Rsl1122
*/
public class RemoveUnsatisfiedConditionalResultsTransaction extends Transaction {
@Override
protected void performOperations() {
execute(deleteUnsatisfied());
}
private Executable deleteUnsatisfied() {
String reversedCondition = dbType == DBType.SQLITE ? "'not_' || " + ExtensionProviderTable.PROVIDED_CONDITION : "CONCAT('not_'," + ExtensionProviderTable.PROVIDED_CONDITION + ')';
String providerTable = ExtensionProviderTable.TABLE_NAME;
String playerValueTable = ExtensionPlayerValueTable.TABLE_NAME;
String selectSatisfiedPositiveConditions = SELECT +
ExtensionProviderTable.PROVIDED_CONDITION + ',' +
ExtensionPlayerValueTable.USER_UUID +
FROM + providerTable +
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
String selectSatisfiedNegativeConditions = SELECT +
reversedCondition + " as " + ExtensionProviderTable.PROVIDED_CONDITION + ',' +
ExtensionPlayerValueTable.USER_UUID +
FROM + providerTable +
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
// Query contents: Set of provided_conditions
String selectSatisfiedConditions = '(' + selectSatisfiedPositiveConditions + " UNION " + selectSatisfiedNegativeConditions + ") q1";
// Query contents:
// id | uuid | q1.uuid | condition | q1.provided_condition
// -- | ---- | ------- | --------- | ---------------------
// 1 | ... | ... | A | A Satisfied condition
// 2 | ... | ... | not_B | not_B Satisfied condition
// 3 | ... | ... | NULL | NULL Satisfied condition
// 4 | ... | ... | B | NULL Unsatisfied condition, filtered to these in WHERE clause.
// 5 | ... | ... | not_C | NULL Unsatisfied condition
String selectUnsatisfiedValueIDs = SELECT + playerValueTable + '.' + ExtensionPlayerValueTable.ID +
FROM + providerTable +
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
" on (" +
playerValueTable + '.' + ExtensionPlayerValueTable.USER_UUID +
"=q1." + ExtensionPlayerValueTable.USER_UUID +
AND + ExtensionProviderTable.CONDITION +
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
')' +
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
// Nested query here is required because MySQL limits update statements with nested queries:
// The nested query creates a temporary table that bypasses the same table query-update limit.
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
String sql = "DELETE FROM " + playerValueTable +
WHERE + ExtensionPlayerValueTable.ID + " IN (" + SELECT + ExtensionPlayerValueTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // Select provided conditions with 'true' value
statement.setBoolean(2, false); // Select negated conditions with 'false' value
}
};
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.storage.transactions.results;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionProviderTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
import static com.djrapitops.plan.db.sql.tables.ExtensionPlayerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.BooleanDataProvider}.
*
* @author Rsl1122
*/
public class StorePlayerBooleanResultTransaction extends Transaction {
private final String pluginName;
private final UUID serverUUID;
private final String providerName;
private final UUID playerUUID;
private final boolean value;
public StorePlayerBooleanResultTransaction(String pluginName, UUID serverUUID, String providerName, UUID playerUUID, boolean value) {
this.pluginName = pluginName;
this.serverUUID = serverUUID;
this.providerName = providerName;
this.playerUUID = playerUUID;
this.value = value;
}
@Override
protected void performOperations() {
execute(storeValue());
}
private Executable storeValue() {
return connection -> {
if (!updateValue().execute(connection)) {
return insertValue().execute(connection);
}
return false;
};
}
private Executable updateValue() {
String sql = "UPDATE " + TABLE_NAME +
" SET " +
BOOLEAN_VALUE + "=?" +
WHERE + USER_UUID + "=?" +
AND + PROVIDER_ID + "=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID;
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, value);
statement.setString(2, playerUUID.toString());
ExtensionProviderTable.set3PluginValuesToStatement(statement, 3, providerName, pluginName, serverUUID);
}
};
}
private Executable insertValue() {
String sql = "INSERT INTO " + TABLE_NAME + "(" +
BOOLEAN_VALUE + "," +
USER_UUID + "," +
PROVIDER_ID +
") VALUES (?,?," + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, value);
statement.setString(2, playerUUID.toString());
ExtensionProviderTable.set3PluginValuesToStatement(statement, 3, providerName, pluginName, serverUUID);
}
};
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.storage.transactions.results;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Executable;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.sql.tables.ExtensionProviderTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import static com.djrapitops.plan.db.sql.parsing.Sql.AND;
import static com.djrapitops.plan.db.sql.parsing.Sql.WHERE;
import static com.djrapitops.plan.db.sql.tables.ExtensionPlayerValueTable.*;
/**
* Transaction to store method result of a {@link com.djrapitops.plan.extension.implementation.providers.DoubleDataProvider}.
*
* @author Rsl1122
*/
public class StorePlayerDoubleResultTransaction extends Transaction {
private final String pluginName;
private final UUID serverUUID;
private final String providerName;
private final UUID playerUUID;
private final double value;
public StorePlayerDoubleResultTransaction(String pluginName, UUID serverUUID, String providerName, UUID playerUUID, double value) {
this.pluginName = pluginName;
this.serverUUID = serverUUID;
this.providerName = providerName;
this.playerUUID = playerUUID;
this.value = value;
}
@Override
protected void performOperations() {
execute(storeValue());
}
private Executable storeValue() {
return connection -> {
if (!updateValue().execute(connection)) {
return insertValue().execute(connection);
}
return false;
};
}
private Executable updateValue() {
String sql = "UPDATE " + TABLE_NAME +
" SET " +
DOUBLE_VALUE + "=?" +
WHERE + USER_UUID + "=?" +
AND + PROVIDER_ID + "=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID;
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setDouble(1, value);
statement.setString(2, playerUUID.toString());
ExtensionProviderTable.set3PluginValuesToStatement(statement, 3, providerName, pluginName, serverUUID);
}
};
}
private Executable insertValue() {
String sql = "INSERT INTO " + TABLE_NAME + "(" +
DOUBLE_VALUE + "," +
USER_UUID + "," +
PROVIDER_ID +
") VALUES (?,?," + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID + ")";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setDouble(1, value);
statement.setString(2, playerUUID.toString());
ExtensionProviderTable.set3PluginValuesToStatement(statement, 3, providerName, pluginName, serverUUID);
}
};
}
}

Some files were not shown because too many files have changed in this diff Show More