Removed project files

This commit is contained in:
Rsl1122 2019-08-05 12:09:15 +03:00
parent 219d819d2d
commit 5590717c39
1052 changed files with 0 additions and 164957 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

View File

@ -1,41 +0,0 @@
plugins {
id "com.jfrog.bintray" version "1.8.4"
}
ext.apiVersion = '0.0.6'
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

@ -1,70 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.capability;
import java.util.Optional;
/**
* List of different capabilities current version provides.
* <p>
* The enum is package private to restrict direct access. This is to avoid NoClassDefFoundError in case
* a wanted Capability is not provided in an earlier version.
* <p>
* Use {@link CapabilityService#hasCapability(String)} with name of a Capability to figure out if an API is available.
* Example usage: {@code CapabilityService.getInstance().hasCapability("DATA_EXTENSION_VALUES")}.
* <p>
* If a capability is not available, attempting to use the capability might lead to exceptions.
*
* @author Rsl1122
*/
enum Capability {
/**
* ExtensionService, DataExtension API base package, PluginInfo, Conditional, Tab, TabInfo, TabOrder and BooleanProvider, DoubleProvider, PercentageProvider, NumberProvider, StringProvider annotations.
*/
DATA_EXTENSION_VALUES,
/**
* DataExtension API table package, TableProvider, Table and Table.Factory
*/
DATA_EXTENSION_TABLES,
/**
* DataExtension API addition, allows throwing {@link com.djrapitops.plan.extension.NotReadyException} inside a Provider method when your API is not ready for a method call.
*/
DATA_EXTENSION_NOT_READY_EXCEPTION,
/**
* DataExtension API addition, parameter {@code showInPlayerTable} for BooleanProvider, DoubleProvider, PercentageProvider, NumberProvider, StringProvider annotations.
* <p>
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
*/
DATA_EXTENSION_SHOW_IN_PLAYER_TABLE,
/**
*
*/
QUERY_API;
static Optional<Capability> getByName(String name) {
if (name == null) {
return Optional.empty();
}
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
}

View File

@ -1,76 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.capability;
import java.util.Optional;
import java.util.function.Consumer;
/**
* Service for figuring out provided API capabilities.
* <p>
* {@link CapabilityService#registerEnableListener(Consumer)} to be notified of Plan reloads
* {@link CapabilityService#hasCapability(String)} to check if a capability is available.
* <p>
* See {@link Capability} for list of capabilities provided by the current version.
*
* @author Rsl1122
*/
public interface CapabilityService {
/**
* Obtain instance of CapabilityService.
*
* @return CapabilityService 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 CapabilityService getInstance() {
return Optional.ofNullable(CapabilityServiceHolder.service)
.orElseThrow(() -> new IllegalStateException("CapabilityService has not been initialised yet."));
}
/**
* Register a method to be called when Plan reloads.
*
* @param isEnabledListener The boolean given to the method tells if Plan has enabled successfully.
*/
void registerEnableListener(Consumer<Boolean> isEnabledListener);
/**
* Check if the API on the current version provides a capability.
*
* @param capabilityName Name of a capability
* @return true if the capability is available.
* @see Capability for different capabilityNames.
*/
default boolean hasCapability(String capabilityName) {
return Capability.getByName(capabilityName).isPresent();
}
class CapabilityServiceHolder {
static CapabilityService service;
private CapabilityServiceHolder() {
/* Static variable holder */
}
static void set(CapabilityService service) {
CapabilityServiceHolder.service = service;
}
}
}

View File

@ -1,65 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
/**
* 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

@ -1,50 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
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

@ -1,93 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
/**
* 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

@ -1,77 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
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 graphs.
*/
GRAPH,
/**
* Represents tables.
*/
TABLE;
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

@ -1,84 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
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

@ -1,54 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
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) {
if (name == null) {
return Optional.empty();
}
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}}

View File

@ -1,35 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
/**
* 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

@ -1,37 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension;
/**
* Exception to throw inside DataExtension if a method is not ready to be called (Data is not available etc).
* <p>
* This Exception will not cause Plan to "yell" about the exception.
* <p>
* Requires Capability#DATA_EXTENSION_NOT_READY_EXCEPTION.
*
* @author Rsl1122
*/
public class NotReadyException extends IllegalStateException {
/**
* Construct the exception.
* <p>
* The Exception is not logged (Fails silently) so no message is available.
*/
public NotReadyException() {
}
}

View File

@ -1,124 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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;
/**
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
* <p>
* If {@link BooleanProvider#hidden()} is {@code true} then this value will not be shown in the table regardless of the value of this parameter.
*
* @return false by default.
*/
boolean showInPlayerTable() default false;
}

View File

@ -1,54 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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.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

@ -1,100 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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;
/**
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
*
* @return false by default.
*/
boolean showInPlayerTable() default false;
}

View File

@ -1,50 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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

@ -1,112 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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;
/**
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
*
* @return false by default.
*/
boolean showInPlayerTable() default false;
}

View File

@ -1,103 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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;
/**
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
*
* @return false by default.
*/
boolean showInPlayerTable() default false;
}

View File

@ -1,70 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "cube";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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

@ -1,112 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "question";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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;
/**
* When the parameter is set to {@code true} the value from this Provider is shown on a table alongside players.
*
* @return false by default.
*/
boolean showInPlayerTable() default false;
}

View File

@ -1,44 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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

@ -1,74 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 (select 'free')) for icons and their {@link Family}.
*
* @return Name of the icon, if name is not valid no icon is shown.
*/
String iconName() default "circle";
/**
* Family of Font Awesome icon.
* <p>
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
*
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
*/
Family iconFamily() default Family.SOLID;
/**
* 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

@ -1,45 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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

@ -1,49 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.annotation;
import com.djrapitops.plan.extension.icon.Color;
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 Table.
* <p>
* Usage: {@code @TableProvider Table method(UUID playerUUID)}
* <p>
* Tables about players can have up to 4 columns.
* Tables about server can have up to 5 columns.
* <p>
* It is recommended to place each table on their own tab with a {@link Tab} annotation on the same method.
*
* @author Rsl1122
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TableProvider {
/**
* Determine the color of the table header.
*
* @return Preferred color. If none are specified defaults are used.
*/
Color tableColor() default Color.NONE;
}

View File

@ -1,373 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.extractor;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.Group;
import com.djrapitops.plan.extension.annotation.*;
import com.djrapitops.plan.extension.table.Table;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
/**
* 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().getMethods();
}
public void extractAnnotationInformation() {
extractPluginInfo();
extractInvalidMethods();
extractMethodAnnotations();
validateMethodAnnotations();
validateConditionals();
extractTabInfo();
}
private void extractMethodAnnotations() {
methodAnnotations = new MethodAnnotations();
for (Method method : getMethods()) {
int modifiers = method.getModifiers();
if (!Modifier.isPublic(modifiers)
|| Modifier.isStatic(modifiers)) {
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));
MethodAnnotations.get(method, TableProvider.class).ifPresent(annotation -> methodAnnotations.put(method, TableProvider.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();
validateTableProviderAnnotations();
}
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 validateTableProviderAnnotations() {
for (Method method : methodAnnotations.getMethodAnnotations(TableProvider.class).keySet()) {
validateReturnType(method, Table.class);
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

@ -1,76 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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

@ -1,59 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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) {
if (name == null) {
return Optional.empty();
}
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}}

View File

@ -1,41 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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) {
if (name == null) {
return Optional.empty();
}
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
}

View File

@ -1,107 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.icon;
/**
* Object that represents an icon on the website.
* <p>
* See https://fontawesome.com/icons (select '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 Icon setColor(Color color) {
this.color = color;
return this;
}
@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

@ -1,213 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.table;
import com.djrapitops.plan.extension.ElementOrder;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Icon;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Object for giving Plan table data.
* <p>
* Usage: {@code Table.builder().columnOne("columnName", new Icon(...)).addRow("Your", "Row", "Data").build()}
* <p>
* Tables about players can have up to 4 columns.
* Tables about server can have up to 5 columns.
* <p>
* Icon colors are ignored.
* <p>
* If a row has more values than the column limit, they are ignored.
* If a row has less values than table columns, a '-' is displayed to distinguish a missing value.
* <p>
* If a table has no columns or rows, it is ignored.
*
* @author Rsl1122
* @see com.djrapitops.plan.extension.annotation.TableProvider for associated annotation.
*/
public final class Table {
private final String[] columns;
private final Icon[] icons;
private final List<Object[]> rows;
private Table() {
/* Tables are constructed with the factory. */
columns = new String[5];
icons = new Icon[5];
rows = new ArrayList<>();
}
/**
* Create a new Table Factory.
*
* @return a new Table Factory.
*/
public static Table.Factory builder() {
return new Table.Factory();
}
public String[] getColumns() {
return columns;
}
public int getMaxColumnSize() {
int columnCount = 0;
for (String column : columns) {
if (column == null) {
break; // Prevent having one null column between two columns
}
columnCount++;
}
return columnCount;
}
public Icon[] getIcons() {
return icons;
}
public List<Object[]> getRows() {
return rows;
}
/**
* Factory for creating new {@link Table} objects.
*/
public static final class Factory {
private final Table building;
// These variable are related to implementation, and is used when the table is being fetched from the database.
Color color; // Table color is defined with TableProvider annotation.
String tableName; // tableName is defined by method name annotated by TableProvider.
String tabName; // Tab name is defined with Tab annotation
int tabPriority; // Tab priority is defined with TabOrder annotation
ElementOrder[] tabOrder; // Tab element order is defined with TabInfo annotation
Icon tabIcon; // Tab icon is defined with TabInfo annotation
private Factory() {
building = new Table();
}
private Factory column(int indx, String columnName, Icon icon) {
building.columns[indx] = columnName;
building.icons[indx] = icon != null ? Icon.called(icon.getName()).of(icon.getFamily()).build() : Icon.called("question").build();
return this;
}
/**
* Set first column name and icon.
*
* @param columnName Name of the column.
* @param icon Icon of the column, color is ignored.
* @return Factory.
*/
public Factory columnOne(String columnName, Icon icon) {
return column(0, columnName, icon);
}
/**
* Set second column name and icon.
*
* @param columnName Name of the column.
* @param icon Icon of the column, color is ignored.
* @return Factory.
*/
public Factory columnTwo(String columnName, Icon icon) {
return column(1, columnName, icon);
}
/**
* Set third column name and icon.
*
* @param columnName Name of the column.
* @param icon Icon of the column, color is ignored.
* @return Factory.
*/
public Factory columnThree(String columnName, Icon icon) {
return column(2, columnName, icon);
}
/**
* Set fourth column name and icon.
*
* @param columnName Name of the column.
* @param icon Icon of the column, color is ignored.
* @return Factory.
*/
public Factory columnFour(String columnName, Icon icon) {
return column(3, columnName, icon);
}
/**
* Set fifth column name and icon.
*
* @param columnName Name of the column.
* @param icon Icon of the column, color is ignored.
* @return Factory.
*/
public Factory columnFive(String columnName, Icon icon) {
return column(4, columnName, icon);
}
/**
* Add a row of values to the table.
*
* @param values One value per column you have defined, {@code Object#toString()} will be called on the objects.
* @return Factory.
* @throws IllegalArgumentException If given varargs for 'values' is null.
*/
public Factory addRow(Object... values) {
if (values == null) {
throw new IllegalArgumentException("'values' for Table#addRow can not be null!");
}
if (values.length == 0 || areAllValuesNull(values)) {
return this; // Ignore row when all values are null or no values are present.
}
building.rows.add(Arrays.copyOf(values, 5));
return this;
}
private boolean areAllValuesNull(Object[] values) {
boolean allNull = true;
for (Object value : values) {
if (value != null) {
allNull = false;
break;
}
}
return allNull;
}
/**
* Finish building the table.
*
* @return Finished Table object.
*/
public Table build() {
return building;
}
}
}

View File

@ -1,64 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.query;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/**
* Class that allows performing most commonly wanted queries.
* <p>
* This exists so that SQL does not necessarily need to be written.
* Obtain an instance from {@link QueryService}.
*
* @author Rsl1122
*/
public interface CommonQueries {
/**
* Fetch playtime of a player on a server.
* <p>
* Returns 0 for any non existing players or servers.
*
* @param playerUUID UUID of the player.
* @param serverUUID UUID of the Plan server.
* @param after Data after this Epoch ms should be fetched
* @param before Data before this Epoch ms should be fetched
* @return Milliseconds the player has played with the defined parameters.
*/
long fetchPlaytime(UUID playerUUID, UUID serverUUID, long after, long before);
/**
* Fetch last seen Epoch ms for a player on a server.
*
* @param playerUUID UUID of the player.
* @param serverUUID UUID of the Plan server.
* @return Epoch ms the player was last seen, 0 if player has not played on server.
*/
long fetchLastSeen(UUID playerUUID, UUID serverUUID);
Set<UUID> fetchServerUUIDs();
Optional<UUID> fetchUUIDOf(String playerName);
Optional<String> fetchNameOf(UUID playerUUID);
boolean doesDBHaveTable(String table);
boolean doesDBHaveTableColumn(String table, String column);
}

View File

@ -1,163 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.query;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.function.Consumer;
/**
* Service for Query API.
* <p>
* Requires Capability QUERY_API
*
* @author Rsl1122
*/
public interface QueryService {
/**
* Obtain instance of QueryService.
*
* @return QueryService 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 QueryService getInstance() {
return Optional.ofNullable(QueryService.QueryServiceHolder.service)
.orElseThrow(() -> new IllegalStateException("QueryService has not been initialised yet."));
}
/**
* Get what kind of database is in use.
*
* @return H2, SQLITE or MYSQL
* @throws IllegalStateException If database has not been initialized (Plugin failed to enable)
*/
String getDBType();
/**
* Perform a query against Plan database.
* <p>
* Blocks thread until query is complete.
*
* @param sql SQL String to execute, can contain parameterized queries ({@code ?}).
* @param performQuery set your parameters to the PreparedStatement and execute the query, return results.
* @param <T> Type of results.
* @return The object returned by {@code results}.
* @throws IllegalStateException If something goes wrong with the query. SQLException might be as cause.
*/
<T> T query(
String sql,
ThrowingFunction<PreparedStatement, T> performQuery
) throws IllegalStateException;
/**
* Execute SQL against Plan database.
* <p>
* Does not block thread, SQL is executed in a single transaction to the database.
* <p>
* Differs from {@link QueryService#query(String, ThrowingFunction)} in that no results are returned.
*
* @param sql SQL String to execute, can contain parameterized queries ({@code ?}).
* @param performStatement set your parameters to the PreparedStatement and execute the statement.
* @return A Future that tells when the transaction has completed. Blocks thread if Future#get is called.
* @throws IllegalStateException If something goes wrong with the query. SQLException might be as cause.
*/
Future<?> execute(
String sql,
ThrowingConsumer<PreparedStatement> performStatement
) throws IllegalStateException;
/**
* Used for getting notified about removal of player data.
* <p>
* SQL for removing this player's data should be executed when this occurs.
* <p>
* Example usage:
* {@code subscribeToPlayerRemoveEvent(playerUUID -> { do stuff })}
*
* @param eventListener Functional interface that is called on the event.
*/
void subscribeToPlayerRemoveEvent(Consumer<UUID> eventListener);
/**
* Used for getting notified about removal of ALL data.
* <p>
* SQL for removing all extra tables (and data) should be performed
* <p>
* Example usage:
* {@code subscribeDataClearEvent(() -> { do stuff })}
*
* @param eventListener Functional interface that is called on the event.
*/
void subscribeDataClearEvent(VoidFunction eventListener);
/**
* Get the UUID of this server.
*
* @return Optinal of the server UUID, empty if server did not start properly.
*/
Optional<UUID> getServerUUID();
/**
* Perform some commonly wanted queries.
*
* @return {@link CommonQueries} implementation.
* @throws IllegalStateException If database has not been initialized (Plugin failed to enable)
*/
CommonQueries getCommonQueries();
/**
* See https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
*/
@FunctionalInterface
interface ThrowingConsumer<T> {
void accept(T t) throws SQLException;
}
/**
* See https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
*/
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws SQLException;
}
/**
* See https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
*/
@FunctionalInterface
interface VoidFunction {
void apply();
}
class QueryServiceHolder {
static QueryService service;
private QueryServiceHolder() {
/* Static variable holder */
}
static void set(QueryService service) {
QueryService.QueryServiceHolder.service = service;
}
}
}

View File

@ -1,257 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.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 tableProviderMustReturnTable() {
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@TableProvider
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: com.djrapitops.plan.extension.table.Table", 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

@ -1,153 +0,0 @@
// Aggregate Javadocs
buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.netflix.nebula:gradle-aggregate-javadocs-plugin:2.2.+'
}
}
plugins {
id "java"
id "jacoco"
id "checkstyle"
id "org.sonarqube" version "2.7"
id "net.ltgt.apt" version "0.21"
id "net.ltgt.apt-idea" version "0.21"
id "com.github.johnrengelman.shadow" version "5.1.0"
}
apply plugin: 'nebula-aggregate-javadocs'
allprojects {
wrapper.gradleVersion = "5.0"
group "com.djrapitops"
version "4.9.1"
test {
useJUnitPlatform()
testLogging {
events "passed", "failed"
exceptionFormat "full"
}
}
// Fix for UTF-8 files showing with wrong encoding when compiled on Windows machines.
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
}
subprojects {
// Build plugins
apply plugin: "java"
apply plugin: "maven-publish"
apply plugin: "net.ltgt.apt" // Annotation processing plugin
apply plugin: "net.ltgt.apt-idea" // Annotation processing IntelliJ IDEA configuration plugin
apply plugin: "com.github.johnrengelman.shadow"
// Report plugins
apply plugin: "checkstyle"
apply plugin: "jacoco"
sourceCompatibility = 1.8
targetCompatibility = 1.8
ext.daggerVersion = "2.24"
ext.daggerCompilerVersion = "2.24"
ext.abstractPluginFrameworkVersion = "3.4.1"
ext.planPluginBridgeVersion = "4.9.0-R0.3"
ext.bukkitVersion = "1.12.2-R0.1-SNAPSHOT"
ext.spigotVersion = "1.12.2-R0.1-SNAPSHOT"
ext.paperVersion = "1.12.2-R0.1-SNAPSHOT"
ext.spongeVersion = "7.1.0"
ext.bungeeVersion = "1.12-SNAPSHOT"
ext.velocityVersion = "1.0-SNAPSHOT"
ext.redisBungeeVersion = "0.3.8-SNAPSHOT"
ext.httpClientVersion = "4.5.9"
ext.commonsTextVersion = "1.7"
ext.htmlCompressorVersion = "1.5.2"
ext.caffeineVersion = "2.7.0"
ext.h2Version = "1.4.199"
ext.mysqlVersion = "8.0.17"
ext.hikariVersion = "3.3.1"
ext.slf4jVersion = "1.7.26"
ext.geoIpVersion = "2.12.0"
ext.guavaVersion = "28.0-jre"
ext.bstatsVersion = "1.4"
repositories {
mavenCentral()
maven { // Spigot Repository
url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/"
}
maven { // Paper Repository
url = "https://papermc.io/repo/repository/maven-public/"
}
maven { // Sponge Repository
url = "https://repo.spongepowered.org/maven"
}
maven { // BungeeCord Repository
url = "https://oss.sonatype.org/content/repositories/snapshots"
}
maven { // RedisBungee Repository
url = "http://repo.md-5.net/content/repositories/snapshots/"
}
maven { // Velocity Repository
url = "https://repo.velocitypowered.com/snapshots/"
}
maven { // bStats Repository
url = "https://repo.codemc.org/repository/maven-public"
}
maven { // PlanPluginBridge Repository
url = "https://dl.bintray.com/rsl1122/Plan-repository"
}
}
dependencies {
// Dependency Injection used across the project
compile "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerCompilerVersion"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerCompilerVersion"
// Test Tooling Dependencies
testCompile "org.junit.jupiter:junit-jupiter-engine:5.5.1" // JUnit 5
testCompile "org.junit.platform:junit-platform-runner:1.5.1" // JUnit 4 runner for JUnit 5 tests
testCompile "org.junit.vintage:junit-vintage-engine:5.5.1" // JUnit 4 compatibility for JUnit 5
testCompile "org.junit.jupiter:junit-jupiter-params:5.5.1" // JUnit 5, parameterized tests
testCompile "org.mockito:mockito-core:3.0.0" // Mockito Core
testCompile "org.mockito:mockito-junit-jupiter:3.0.0" // Mockito JUnit 5 Extension
testCompile "org.seleniumhq.selenium:selenium-java:3.141.59" // Selenium (Browser tests)
testCompile "com.jayway.awaitility:awaitility:1.7.0" // Awaitility (Concurrent wait conditions)
// Testing dependencies required by Plan
testCompile "org.xerial:sqlite-jdbc:3.28.0" // SQLite
testCompile "mysql:mysql-connector-java:$mysqlVersion" // MySQL
}
configurations {
testArtifacts.extendsFrom testRuntime
}
// Test classes available to other modules
task testJar(type: Jar) {
classifier "test"
from sourceSets.test.output
}
artifacts {
testArtifacts testJar
}
checkstyle {
configFile rootProject.file('config/checkstyle/checkstyle.xml')
}
}
sonarqube {
properties {
property "sonar.projectName", "Player Analytics"
property "sonar.projectKey", "com.djrapitops:Plan"
}
}

View File

@ -1,23 +0,0 @@
dependencies {
compile project(path: ":common", configuration: 'shadow')
compile "com.djrapitops:AbstractPluginFramework-bukkit:$abstractPluginFrameworkVersion"
compile "org.bstats:bstats-bukkit:$bstatsVersion"
// compileOnly "org.spigotmc:spigot-api:$spigotVersion"
// compileOnly "org.bukkit:bukkit:$bukkitVersion"
compileOnly "com.destroystokyo.paper:paper-api:$paperVersion"
// testCompile "org.spigotmc:spigot-api:$spigotVersion"
// testCompile "org.bukkit:bukkit:$bukkitVersion"
testCompile "com.destroystokyo.paper:paper-api:$paperVersion"
testCompile project(path: ":common", configuration: 'testArtifacts')
}
shadowJar {
configurations = [project.configurations.compile]
relocate 'org.bstats', 'com.djrapitops.plan.utilities.metrics'
relocate 'org.slf4j', 'plan.org.slf4j'
}

View File

@ -1,52 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plugin.api.Check;
import org.bstats.bukkit.Metrics;
public class BStatsBukkit {
private final Plan plugin;
private Metrics metrics;
public BStatsBukkit(Plan plugin) {
this.plugin = plugin;
}
public void registerMetrics() {
if (metrics == null) {
metrics = new Metrics(plugin);
}
registerConfigSettingGraphs();
}
private void registerConfigSettingGraphs() {
String serverType = plugin.getServer().getName();
if ("CraftBukkit".equals(serverType) && Check.isSpigotAvailable()) {
serverType = "Spigot";
}
String databaseType = plugin.getSystem().getDatabaseSystem().getDatabase().getType().getName();
addStringSettingPie("server_type", serverType);
addStringSettingPie("database_type", databaseType);
}
protected void addStringSettingPie(String id, String setting) {
metrics.addCustomChart(new Metrics.SimplePie(id, () -> setting));
}
}

View File

@ -1,64 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.utilities.java.Reflection;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* ServerShutdownSave implementation for Bukkit based servers.
*
* @author Rsl1122
*/
@Singleton
public class BukkitServerShutdownSave extends ServerShutdownSave {
@Inject
public BukkitServerShutdownSave(
Locale locale,
DBSystem dbSystem,
PluginLogger logger,
ErrorHandler errorHandler
) {
super(locale, dbSystem, logger, errorHandler);
}
@Override
protected boolean checkServerShuttingDownStatus() {
try {
return performCheck();
} catch (Exception | NoClassDefFoundError | NoSuchFieldError e) {
logger.debug("Server shutdown check failed, using JVM ShutdownHook instead. Error: " + e.toString());
return false; // ShutdownHook handles save in case this fails upon plugin disable.
}
}
private boolean performCheck() {
// Special thanks to Fuzzlemann for figuring out the methods required for this check.
// https://github.com/Rsl1122/Plan-PlayerAnalytics/issues/769#issuecomment-433898242
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
Object minecraftServer = Reflection.getField(minecraftServerClass, "SERVER", minecraftServerClass).get(null);
return Reflection.getField(minecraftServerClass, "isStopped", boolean.class).get(minecraftServer);
}
}

View File

@ -1,172 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.command.PlanCommand;
import com.djrapitops.plan.command.commands.RegisterCommandFilter;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plan.system.settings.theme.PlanColorScheme;
import com.djrapitops.plugin.BukkitPlugin;
import com.djrapitops.plugin.benchmarking.Benchmark;
import com.djrapitops.plugin.command.ColorScheme;
import com.djrapitops.plugin.task.AbsRunnable;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Main class for Bukkit that manages the plugin.
*
* @author Rsl1122
*/
public class Plan extends BukkitPlugin implements PlanPlugin {
private PlanSystem system;
private Locale locale;
private ServerShutdownSave serverShutdownSave;
@Override
public void onEnable() {
PlanBukkitComponent component = DaggerPlanBukkitComponent.builder().plan(this).build();
try {
timings.start("Enable");
system = component.system();
serverShutdownSave = component.serverShutdownSave();
locale = system.getLocaleSystem().getLocale();
system.enable();
registerMetrics();
logger.debug("Verbose debug messages are enabled.");
String benchTime = " (" + timings.end("Enable").map(Benchmark::toDurationString).orElse("-") + ")";
logger.info(locale.getString(PluginLang.ENABLED) + benchTime);
} catch (AbstractMethodError e) {
logger.error("Plugin ran into AbstractMethodError - Server restart is required. Likely cause is updating the jar without a restart.");
} catch (EnableException e) {
logger.error("----------------------------------------");
logger.error("Error: " + e.getMessage());
logger.error("----------------------------------------");
logger.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /plan reload");
onDisable();
} catch (Exception e) {
Logger.getGlobal().log(Level.SEVERE, this.getClass().getSimpleName() + "-v" + getVersion(), e);
logger.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /plan reload");
logger.error("This error should be reported at https://github.com/Rsl1122/Plan-PlayerAnalytics/issues");
onDisable();
}
PlanCommand command = component.planCommand();
command.registerCommands();
registerCommand("plan", command);
new RegisterCommandFilter().registerFilter();
if (system != null) {
system.getProcessing().submitNonCritical(() -> system.getListenerSystem().callEnableEvent(this));
}
}
private void registerMetrics() {
Plan plugin = this;
// Spigot 1.14 requires Sync events to be fired from a server thread.
// Registering a service fires a sync event, and bStats registers a service,
// so this has to be run on the server thread.
runnableFactory.create("Register Metrics task", new AbsRunnable() {
@Override
public void run() {
new BStatsBukkit(plugin).registerMetrics();
}
}).runTask();
}
@Override
public ColorScheme getColorScheme() {
return PlanColorScheme.create(system.getConfigSystem().getConfig(), logger);
}
/**
* Disables the plugin.
*/
@Override
public void onDisable() {
if (serverShutdownSave != null) {
serverShutdownSave.performSave();
}
if (system != null) {
system.disable();
}
logger.info(locale != null ? locale.getString(PluginLang.DISABLED) : PluginLang.DISABLED.getDefault());
}
@Override
public String getVersion() {
return getDescription().getVersion();
}
@Override
public void onReload() {
// Nothing to be done, systems are disabled
}
@Override
public boolean isReloading() {
return reloading;
}
/**
* @deprecated Deprecated due to use of APF Config
*/
@Override
@Deprecated
public void reloadConfig() {
throw new IllegalStateException("This method should be used on this plugin. Use onReload() instead");
}
/**
* @deprecated Deprecated due to use of APF Config
*/
@Override
@Deprecated
public FileConfiguration getConfig() {
throw new IllegalStateException("This method should be used on this plugin. Use getMainConfig() instead");
}
/**
* @deprecated Deprecated due to use of APF Config
*/
@Override
@Deprecated
public void saveConfig() {
throw new IllegalStateException("This method should be used on this plugin. Use getMainConfig().save() instead");
}
/**
* @deprecated Deprecated due to use of APF Config
*/
@Override
@Deprecated
public void saveDefaultConfig() {
throw new IllegalStateException("This method should be used on this plugin.");
}
@Override
public PlanSystem getSystem() {
return system;
}
}

View File

@ -1,66 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.command.PlanCommand;
import com.djrapitops.plan.modules.APFModule;
import com.djrapitops.plan.modules.FilesModule;
import com.djrapitops.plan.modules.ServerSuperClassBindingModule;
import com.djrapitops.plan.modules.SystemObjectProvidingModule;
import com.djrapitops.plan.modules.bukkit.BukkitPlanModule;
import com.djrapitops.plan.modules.bukkit.BukkitServerPropertiesModule;
import com.djrapitops.plan.modules.bukkit.BukkitSuperClassBindingModule;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.pluginbridge.plan.PluginBridgeModule;
import dagger.BindsInstance;
import dagger.Component;
import javax.inject.Singleton;
/**
* Dagger Component that constructs the plugin systems running on Bukkit.
*
* @author Rsl1122
*/
@Singleton
@Component(modules = {
BukkitPlanModule.class,
SystemObjectProvidingModule.class,
APFModule.class,
FilesModule.class,
BukkitServerPropertiesModule.class,
ServerSuperClassBindingModule.class,
BukkitSuperClassBindingModule.class,
PluginBridgeModule.Bukkit.class
})
public interface PlanBukkitComponent {
PlanCommand planCommand();
PlanSystem system();
ServerShutdownSave serverShutdownSave();
@Component.Builder
interface Builder {
@BindsInstance
Builder plan(Plan plan);
PlanBukkitComponent build();
}
}

View File

@ -1,58 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.api.events;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* Event that is called when Plan is enabled.
* <p>
* This includes, but might not be limited to:
* - First time the plugin enables successfully
* - Plan is reloaded
* - Bukkit-BungeeCord setup updates settings
* - Plan is enabled after it was disabled
* <p>
* {@code event.isPlanSystemEnabled()} can be called to determine if the enable was successful.
* It is not guaranteed that this event is called when the plugin fails to enable properly.
*
* @author Rsl1122
*/
public class PlanBukkitEnableEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final boolean enabled;
public PlanBukkitEnableEvent(boolean enabled) {
super(true);
this.enabled = enabled;
}
public static HandlerList getHandlerList() {
return handlers;
}
public boolean isPlanSystemEnabled() {
return enabled;
}
@Override
public HandlerList getHandlers() {
return PlanBukkitEnableEvent.getHandlerList();
}
}

View File

@ -1,99 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.command.commands;
import com.google.common.collect.ImmutableSet;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.filter.AbstractFilter;
import org.apache.logging.log4j.message.Message;
import java.util.Set;
/**
* Filters out WebUser registration command logs.
*
* @author Rsl1122
*/
public class RegisterCommandFilter extends AbstractFilter {
private final Set<String> censoredCommands = ImmutableSet.of("/plan web register", "/plan webuser register", "/plan register");
public void registerFilter() {
Logger logger = (Logger) LogManager.getRootLogger();
logger.addFilter(this);
}
@Override
public Result filter(LogEvent event) {
if (event == null) {
return Result.NEUTRAL;
}
return validateMessage(event.getMessage());
}
@Override
public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
return validateMessage(msg);
}
@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) {
return validateMessage(msg);
}
@Override
public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
if (msg == null) {
return Result.NEUTRAL;
}
return validateMessage(msg.toString());
}
private Result validateMessage(Message message) {
if (message == null) {
return Result.NEUTRAL;
}
return validateMessage(message.getFormattedMessage());
}
private Result validateMessage(String message) {
if (message == null) {
return Result.NEUTRAL;
}
return commandShouldBeCensored(message)
? Result.DENY
: Result.NEUTRAL;
}
private boolean commandShouldBeCensored(String message) {
return message != null
&& (message.toLowerCase().contains("issued server command:")
&& shouldBeCensored(message));
}
private boolean shouldBeCensored(String message) {
return message != null && censoredCommands.stream().anyMatch(message::contains);
}
}

View File

@ -1,42 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bukkit;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.command.PlanCommand;
import com.djrapitops.plugin.command.CommandNode;
import dagger.Binds;
import dagger.Module;
import javax.inject.Named;
/**
* Dagger module for binding Plan instance.
*
* @author Rsl1122
*/
@Module
public interface BukkitPlanModule {
@Binds
PlanPlugin bindPlanPlugin(Plan plugin);
@Binds
@Named("mainCommand")
CommandNode bindMainCommand(PlanCommand command);
}

View File

@ -1,40 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bukkit;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.system.info.server.properties.BukkitServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
/**
* Dagger module for Bukkit ServerProperties.
*
* @author Rsl1122
*/
@Module
public class BukkitServerPropertiesModule {
@Provides
@Singleton
ServerProperties provideServerProperties(Plan plugin) {
return new BukkitServerProperties(plugin.getServer());
}
}

View File

@ -1,65 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bukkit;
import com.djrapitops.plan.BukkitServerShutdownSave;
import com.djrapitops.plan.ServerShutdownSave;
import com.djrapitops.plan.system.database.BukkitDBSystem;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.importing.BukkitImportSystem;
import com.djrapitops.plan.system.importing.ImportSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.info.server.ServerServerInfo;
import com.djrapitops.plan.system.listeners.BukkitListenerSystem;
import com.djrapitops.plan.system.listeners.ListenerSystem;
import com.djrapitops.plan.system.settings.BukkitConfigSystem;
import com.djrapitops.plan.system.settings.ConfigSystem;
import com.djrapitops.plan.system.tasks.BukkitTaskSystem;
import com.djrapitops.plan.system.tasks.TaskSystem;
import dagger.Binds;
import dagger.Module;
/**
* Module for binding Bukkit specific classes to the interface implementations.
*
* @author Rsl1122
*/
@Module
public interface BukkitSuperClassBindingModule {
@Binds
ServerInfo bindBukkitServerInfo(ServerServerInfo serverServerInfo);
@Binds
DBSystem bindBukkitDatabaseSystem(BukkitDBSystem dbSystem);
@Binds
ConfigSystem bindBukkitConfigSystem(BukkitConfigSystem bukkitConfigSystem);
@Binds
TaskSystem bindBukkitTaskSystem(BukkitTaskSystem bukkitTaskSystem);
@Binds
ListenerSystem bindBukkitListenerSystem(BukkitListenerSystem bukkitListenerSystem);
@Binds
ImportSystem bindImportSystem(BukkitImportSystem bukkitImportSystem);
@Binds
ServerShutdownSave bindBukkitServerShutdownSave(BukkitServerShutdownSave bukkitServerShutdownSave);
}

View File

@ -1,68 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.database;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.db.H2DB;
import com.djrapitops.plan.db.MySQLDB;
import com.djrapitops.plan.db.SQLiteDB;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DatabaseSettings;
import com.djrapitops.plugin.benchmarking.Timings;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Bukkit Database system that initializes SQLite and MySQL database objects.
*
* @author Rsl1122
*/
@Singleton
public class BukkitDBSystem extends DBSystem {
private final PlanConfig config;
@Inject
public BukkitDBSystem(
Locale locale,
MySQLDB mySQLDB,
SQLiteDB.Factory sqLiteDB,
H2DB.Factory h2DB,
PlanConfig config,
PluginLogger logger,
Timings timings,
ErrorHandler errorHandler
) {
super(locale, sqLiteDB, h2DB, logger, timings, errorHandler);
this.config = config;
databases.add(mySQLDB);
databases.add(h2DB.usingDefaultFile());
databases.add(sqLiteDB.usingDefaultFile());
}
@Override
public void enable() throws EnableException {
String dbType = config.get(DatabaseSettings.TYPE).toLowerCase().trim();
db = getActiveDatabaseByName(dbType);
super.enable();
}
}

View File

@ -1,45 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.importing;
import com.djrapitops.plan.system.importing.importers.OfflinePlayerImporter;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* ImportSystem implementation for Bukkit.
*
* @author Rsl1122
*/
@Singleton
public class BukkitImportSystem extends ImportSystem {
private final OfflinePlayerImporter offlinePlayerImporter;
@Inject
public BukkitImportSystem(
OfflinePlayerImporter offlinePlayerImporter
) {
this.offlinePlayerImporter = offlinePlayerImporter;
}
@Override
void registerImporters() {
registerImporter(offlinePlayerImporter);
}
}

View File

@ -1,235 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.importing.data;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.system.DebugChannels;
import com.djrapitops.plugin.api.utility.UUIDFetcher;
import com.djrapitops.plugin.benchmarking.Timings;
import org.bukkit.OfflinePlayer;
import java.util.*;
import java.util.stream.Collectors;
/**
* UserImportRefiner attempts to find any crucial information that is missing.
*
* - Finds UUIDs if only name is present.
* - Finds Names if only UUID is present.
* - Removes any importers that do not have any identifiers.
*
* @author Fuzzlemann
*/
public class BukkitUserImportRefiner {
private final Plan plugin;
private final Timings timings;
private final boolean onlineMode;
private final List<UserImportData> importers = new ArrayList<>();
private final Map<UserImportData, String> missingUUIDs = new HashMap<>();
private final Map<UserImportData, String> missingNames = new HashMap<>();
private final Map<UserImportData, String> foundUUIDs = new HashMap<>();
private final Map<UserImportData, String> foundNames = new HashMap<>();
public BukkitUserImportRefiner(Plan plugin, List<UserImportData> importers) {
this.plugin = plugin;
this.timings = plugin.getTimings();
this.importers.addAll(importers);
onlineMode = plugin.getServer().getOnlineMode();
}
public List<UserImportData> refineData() {
String benchmarkName = "Refining UserImportData";
timings.start(benchmarkName);
processMissingIdentifiers();
timings.end(DebugChannels.IMPORTING, benchmarkName);
return importers;
}
private void processMissingIdentifiers() {
String benchmarkName = "Processing missing identifiers";
timings.start(benchmarkName);
List<UserImportData> invalidData = new ArrayList<>();
importers.parallelStream().forEach(importer -> {
String name = importer.getName();
UUID uuid = importer.getUuid();
boolean nameNull = name == null;
boolean uuidNull = uuid == null;
if (nameNull && uuidNull) {
invalidData.add(importer);
} else if (nameNull) {
missingNames.put(importer, uuid.toString());
} else if (uuidNull) {
missingUUIDs.put(importer, name);
}
});
importers.removeAll(invalidData);
processMissingUUIDs();
processMissingNames();
timings.end(DebugChannels.IMPORTING, benchmarkName);
}
private void processMissingUUIDs() {
String benchmarkName = "Processing missing UUIDs";
timings.start(benchmarkName);
if (onlineMode) {
addMissingUUIDsOverFetcher();
addMissingUUIDsOverOfflinePlayer();
} else {
addMissingUUIDsOverOfflinePlayer();
addMissingUUIDsOverFetcher();
}
foundUUIDs.entrySet().parallelStream()
.forEach(entry -> {
UserImportData userImportData = entry.getKey();
UUID uuid = UUID.fromString(entry.getValue());
userImportData.setUuid(uuid);
});
importers.removeAll(missingUUIDs.keySet());
timings.end(DebugChannels.IMPORTING, benchmarkName);
}
private void addMissingUUIDsOverFetcher() {
UUIDFetcher uuidFetcher = new UUIDFetcher(new ArrayList<>(missingUUIDs.values()));
Map<String, String> result;
try {
result = uuidFetcher.call().entrySet().parallelStream()
.collect(Collectors.toMap(entry -> entry.getValue().toString(), Map.Entry::getKey));
} catch (Exception e) {
return;
}
addFoundUUIDs(result);
}
private void addMissingUUIDsOverOfflinePlayer() {
Map<String, String> result = new HashMap<>();
for (String name : missingUUIDs.values()) {
String uuid = getUuidByOfflinePlayer(name);
if (uuid == null) {
continue;
}
result.put(name, uuid);
}
addFoundUUIDs(result);
}
private void addFoundUUIDs(Map<String, String> foundUUIDs) {
List<UserImportData> found = new ArrayList<>();
missingUUIDs.entrySet().parallelStream().forEach(entry -> {
UserImportData importer = entry.getKey();
String name = entry.getValue();
String uuid = foundUUIDs.get(name);
this.foundUUIDs.put(importer, uuid);
found.add(importer);
});
missingUUIDs.keySet().removeAll(found);
}
@SuppressWarnings("deprecation")
private String getUuidByOfflinePlayer(String name) {
OfflinePlayer player = plugin.getServer().getOfflinePlayer(name);
if (!player.hasPlayedBefore()) {
return null;
}
return player.getUniqueId().toString();
}
private void processMissingNames() {
String benchmarkNames = "Processing missing names";
timings.start(benchmarkNames);
findMissingNames();
foundNames.entrySet().parallelStream().forEach(entry -> entry.getKey().setName(entry.getValue()));
importers.removeAll(missingNames.keySet());
timings.end(DebugChannels.IMPORTING, benchmarkNames);
}
private void findMissingNames() {
Map<String, String> result = new HashMap<>();
missingNames.values().parallelStream().forEach(uuid -> {
String name = getNameByOfflinePlayer(uuid);
result.put(uuid, name);
});
addFoundNames(result);
}
private void addFoundNames(Map<String, String> foundNames) {
List<UserImportData> found = new ArrayList<>();
missingNames.entrySet().parallelStream().forEach(entry -> {
UserImportData importer = entry.getKey();
String uuid = entry.getValue();
String name = foundNames.get(uuid);
this.foundNames.put(importer, name);
found.add(importer);
});
missingNames.keySet().removeAll(found);
}
private String getNameByOfflinePlayer(String uuid) {
OfflinePlayer player = plugin.getServer().getOfflinePlayer(UUID.fromString(uuid));
if (!player.hasPlayedBefore()) {
return null;
}
return player.getName();
}
}

View File

@ -1,215 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.importing.importers;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.data.container.BaseUser;
import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.container.UserInfo;
import com.djrapitops.plan.data.store.objects.Nickname;
import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.queries.LargeStoreQueries;
import com.djrapitops.plan.db.access.queries.objects.UserIdentifierQueries;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.system.cache.GeolocationCache;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.importing.data.BukkitUserImportRefiner;
import com.djrapitops.plan.system.importing.data.ServerImportData;
import com.djrapitops.plan.system.importing.data.UserImportData;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plugin.utilities.Verify;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Generic importer for user data into Plan on the Bukkit platform.
*
* @author Fuzzlemann
*/
public abstract class BukkitImporter implements Importer {
protected final Supplier<UUID> serverUUID;
private final GeolocationCache geolocationCache;
private final DBSystem dbSystem;
private final String name;
private final Plan plugin;
protected BukkitImporter(
Plan plugin,
GeolocationCache geolocationCache,
DBSystem dbSystem,
ServerInfo serverInfo,
String name
) {
this.geolocationCache = geolocationCache;
this.dbSystem = dbSystem;
this.serverUUID = serverInfo::getServerUUID;
this.name = name;
this.plugin = plugin;
}
@Deprecated
public List<String> getNames() {
return new ArrayList<>();
}
@Override
public String getName() {
return name;
}
public abstract ServerImportData getServerImportData();
public abstract List<UserImportData> getUserImportData();
@Override
public final void processImport() {
ExecutorService service = Executors.newCachedThreadPool();
try {
service.submit(this::processServerData);
service.submit(this::processUserData);
} finally {
shutdownService(service);
}
}
private void processServerData() {
ServerImportData serverImportData = getServerImportData();
if (serverImportData == null) {
return;
}
dbSystem.getDatabase().executeTransaction(new Transaction() {
@Override
protected void performOperations() {
execute(LargeStoreQueries.storeAllTPSData(Collections.singletonMap(serverUUID.get(), serverImportData.getTpsData())));
execute(LargeStoreQueries.storeAllCommandUsageData(Collections.singletonMap(serverUUID.get(), serverImportData.getCommandUsages())));
}
});
}
private void processUserData() {
List<UserImportData> userImportData = getUserImportData();
if (Verify.isEmpty(userImportData)) {
return;
}
BukkitUserImportRefiner userImportRefiner = new BukkitUserImportRefiner(plugin, userImportData);
userImportData = userImportRefiner.refineData();
Database db = dbSystem.getDatabase();
Set<UUID> existingUUIDs = db.query(UserIdentifierQueries.fetchAllPlayerUUIDs());
Set<UUID> existingUserInfoTableUUIDs = db.query(UserIdentifierQueries.fetchPlayerUUIDsOfServer(serverUUID.get()));
Map<UUID, BaseUser> users = new HashMap<>();
List<UserInfo> userInfo = new ArrayList<>();
Map<UUID, List<Nickname>> nickNames = new HashMap<>();
List<Session> sessions = new ArrayList<>();
Map<UUID, List<GeoInfo>> geoInfo = new HashMap<>();
userImportData.parallelStream().forEach(data -> {
UUID uuid = data.getUuid();
if (!existingUUIDs.contains(uuid)) {
users.put(uuid, toBaseUser(data));
}
if (!existingUserInfoTableUUIDs.contains(uuid)) {
userInfo.add(toUserInfo(data));
}
nickNames.put(uuid, data.getNicknames());
geoInfo.put(uuid, convertGeoInfo(data));
sessions.add(toSession(data));
});
db.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
execute(LargeStoreQueries.storeAllCommonUserInformation(users.values()));
execute(LargeStoreQueries.storeAllSessionsWithKillAndWorldData(sessions));
Map<UUID, List<UserInfo>> userInformation = Collections.singletonMap(serverUUID.get(), userInfo);
execute(LargeStoreQueries.storePerServerUserInformation(userInformation));
execute(LargeStoreQueries.storeAllNicknameData(Collections.singletonMap(serverUUID.get(), nickNames)));
execute(LargeStoreQueries.storeAllGeoInformation(geoInfo));
}
});
}
private void shutdownService(ExecutorService service) {
service.shutdown();
try {
if (!service.awaitTermination(20, TimeUnit.MINUTES)) {
service.shutdownNow();
}
} catch (InterruptedException e) {
service.shutdownNow();
Thread.currentThread().interrupt();
}
}
private BaseUser toBaseUser(UserImportData userImportData) {
UUID playerUUID = userImportData.getUuid();
String playerName = userImportData.getName();
long registered = userImportData.getRegistered();
int timesKicked = userImportData.getTimesKicked();
return new BaseUser(playerUUID, playerName, registered, timesKicked);
}
private UserInfo toUserInfo(UserImportData userImportData) {
UUID uuid = userImportData.getUuid();
long registered = userImportData.getRegistered();
boolean op = userImportData.isOp();
boolean banned = userImportData.isBanned();
return new UserInfo(uuid, serverUUID.get(), registered, op, banned);
}
private Session toSession(UserImportData userImportData) {
int mobKills = userImportData.getMobKills();
int deaths = userImportData.getDeaths();
Session session = new Session(0, userImportData.getUuid(), serverUUID.get(), 0L, 0L, mobKills, deaths, 0);
session.setPlayerKills(userImportData.getKills());
session.setWorldTimes(new WorldTimes(userImportData.getWorldTimes()));
return session;
}
private List<GeoInfo> convertGeoInfo(UserImportData userImportData) {
long date = System.currentTimeMillis();
return userImportData.getIps().parallelStream()
.map(ip -> {
String geoLoc = geolocationCache.getCountry(ip);
return new GeoInfo(ip, geoLoc, date);
}).collect(Collectors.toList());
}
}

View File

@ -1,82 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.importing.importers;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.system.cache.GeolocationCache;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.importing.data.ServerImportData;
import com.djrapitops.plan.system.importing.data.UserImportData;
import com.djrapitops.plan.system.info.server.ServerInfo;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* @author Fuzzlemann
*/
@Singleton
public class OfflinePlayerImporter extends BukkitImporter {
@Inject
public OfflinePlayerImporter(
Plan plugin,
GeolocationCache geolocationCache,
DBSystem dbSystem,
ServerInfo serverInfo
) {
super(plugin, geolocationCache, dbSystem, serverInfo, "offline");
}
@Override
public ServerImportData getServerImportData() {
return null;
}
@Override
public List<UserImportData> getUserImportData() {
List<UserImportData> dataList = new ArrayList<>();
Set<OfflinePlayer> operators = Bukkit.getOperators();
Set<OfflinePlayer> banned = Bukkit.getBannedPlayers();
Arrays.stream(Bukkit.getOfflinePlayers()).parallel().forEach(player -> {
UserImportData.UserImportDataBuilder builder = UserImportData.builder(serverUUID.get());
builder.name(player.getName())
.uuid(player.getUniqueId())
.registered(player.getFirstPlayed());
if (operators.contains(player)) {
builder.op();
}
if (banned.contains(player)) {
builder.banned();
}
dataList.add(builder.build());
});
return dataList;
}
}

View File

@ -1,40 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.info.server.properties;
import org.bukkit.Server;
/**
* ServerProperties for Bukkit.
*
* @author Rsl1122
*/
public class BukkitServerProperties extends ServerProperties {
public BukkitServerProperties(Server server) {
super(
server.getName(),
server.getPort(),
server.getVersion(),
server.getBukkitVersion(),
server::getIp,
server.getMaxPlayers(),
() -> server.getOnlinePlayers().size()
);
}
}

View File

@ -1,92 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.api.events.PlanBukkitEnableEvent;
import com.djrapitops.plan.capability.CapabilityServiceImplementation;
import com.djrapitops.plan.system.listeners.bukkit.*;
import com.djrapitops.plan.system.status.Status;
import org.bukkit.Bukkit;
import org.bukkit.event.HandlerList;
import javax.inject.Inject;
public class BukkitListenerSystem extends ListenerSystem {
private final Plan plugin;
private final Status status;
private final PlayerOnlineListener playerOnlineListener;
private final ChatListener chatListener;
private final GameModeChangeListener gamemodeChangeListener;
private final WorldChangeListener worldChangeListener;
private final CommandListener commandListener;
private final DeathEventListener deathEventListener;
private final AFKListener afkListener;
@Inject
public BukkitListenerSystem(Plan plugin,
Status status,
PlayerOnlineListener playerOnlineListener,
ChatListener chatListener,
GameModeChangeListener gamemodeChangeListener,
WorldChangeListener worldChangeListener,
CommandListener commandListener,
DeathEventListener deathEventListener,
AFKListener afkListener
) {
this.plugin = plugin;
this.status = status;
this.playerOnlineListener = playerOnlineListener;
this.chatListener = chatListener;
this.gamemodeChangeListener = gamemodeChangeListener;
this.worldChangeListener = worldChangeListener;
this.commandListener = commandListener;
this.deathEventListener = deathEventListener;
this.afkListener = afkListener;
}
@Override
protected void registerListeners() {
plugin.registerListener(
playerOnlineListener,
chatListener,
gamemodeChangeListener,
worldChangeListener,
commandListener,
deathEventListener,
afkListener
);
status.setCountKicks(true);
}
@Override
protected void unregisterListeners() {
HandlerList.unregisterAll(plugin);
}
@Override
public void callEnableEvent(PlanPlugin plugin) {
boolean isEnabled = plugin.isSystemEnabled();
PlanBukkitEnableEvent event = new PlanBukkitEnableEvent(isEnabled);
Bukkit.getServer().getPluginManager().callEvent(event);
CapabilityServiceImplementation.notifyAboutEnable(isEnabled);
}
}

View File

@ -1,111 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
import com.djrapitops.plan.system.afk.AFKTracker;
import com.djrapitops.plan.system.settings.Permissions;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Listener that keeps track of actions that are not considered being AFK.
* <p>
* Additional Listener calls in PlayerOnlineListener to avoid having HIGHEST priority listeners.
*
* @author Rsl1122
* @see PlayerOnlineListener
*/
public class AFKListener implements Listener {
// Static so that /reload does not cause afk tracking to fail.
static AFKTracker AFK_TRACKER;
private final Map<UUID, Boolean> ignorePermissionInfo;
private final ErrorHandler errorHandler;
@Inject
public AFKListener(PlanConfig config, ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
this.ignorePermissionInfo = new HashMap<>();
AFKListener.assignAFKTracker(config);
}
private static void assignAFKTracker(PlanConfig config) {
if (AFK_TRACKER == null) {
AFK_TRACKER = new AFKTracker(config);
}
}
private void event(PlayerEvent event) {
try {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
long time = System.currentTimeMillis();
Boolean ignored = ignorePermissionInfo.get(uuid);
if (ignored == null) {
ignored = player.hasPermission(Permissions.IGNORE_AFK.getPermission());
}
if (ignored) {
AFK_TRACKER.hasIgnorePermission(uuid);
ignorePermissionInfo.put(uuid, true);
} else {
ignorePermissionInfo.put(uuid, false);
}
AFK_TRACKER.performedAction(uuid, time);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onMove(PlayerMoveEvent event) {
event(event);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerChat(AsyncPlayerChatEvent event) {
event(event);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
event(event);
boolean isAfkCommand = event.getMessage().substring(1).toLowerCase().startsWith("afk");
if (isAfkCommand) {
UUID uuid = event.getPlayer().getUniqueId();
AFK_TRACKER.usedAfkCommand(uuid, System.currentTimeMillis());
}
}
}

View File

@ -1,84 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
import com.djrapitops.plan.data.store.objects.Nickname;
import com.djrapitops.plan.db.access.transactions.events.NicknameStoreTransaction;
import com.djrapitops.plan.system.cache.NicknameCache;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import javax.inject.Inject;
import java.util.UUID;
/**
* Event Listener for AsyncPlayerChatEvents.
*
* @author Rsl1122
*/
public class ChatListener implements Listener {
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final NicknameCache nicknameCache;
private final ErrorHandler errorHandler;
@Inject
public ChatListener(
ServerInfo serverInfo,
DBSystem dbSystem,
NicknameCache nicknameCache,
ErrorHandler errorHandler
) {
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.nicknameCache = nicknameCache;
this.errorHandler = errorHandler;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChat(AsyncPlayerChatEvent event) {
if (event.isCancelled()) {
return;
}
try {
actOnChatEvent(event);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void actOnChatEvent(AsyncPlayerChatEvent event) {
long time = System.currentTimeMillis();
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
String displayName = player.getDisplayName();
dbSystem.getDatabase().executeTransaction(new NicknameStoreTransaction(
uuid, new Nickname(displayName, time, serverInfo.getServerUUID()),
(playerUUID, name) -> name.equals(nicknameCache.getDisplayName(playerUUID))
));
}
}

View File

@ -1,108 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.db.access.transactions.events.CommandStoreTransaction;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.settings.Permissions;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DataGatheringSettings;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.command.Command;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import javax.inject.Inject;
/**
* Event Listener for PlayerCommandPreprocessEvents.
*
* @author Rsl1122
*/
public class CommandListener implements Listener {
private final Plan plugin;
private final PlanConfig config;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ErrorHandler errorHandler;
@Inject
public CommandListener(
Plan plugin,
PlanConfig config,
ServerInfo serverInfo,
DBSystem dbSystem,
ErrorHandler errorHandler
) {
this.plugin = plugin;
this.config = config;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.errorHandler = errorHandler;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
boolean hasIgnorePermission = event.getPlayer().hasPermission(Permissions.IGNORE_COMMAND_USE.getPermission());
if (event.isCancelled() || hasIgnorePermission) {
return;
}
try {
actOnCommandEvent(event);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void actOnCommandEvent(PlayerCommandPreprocessEvent event) {
String commandName = event.getMessage().substring(1).split(" ")[0].toLowerCase();
boolean logUnknownCommands = config.isTrue(DataGatheringSettings.LOG_UNKNOWN_COMMANDS);
boolean combineCommandAliases = config.isTrue(DataGatheringSettings.COMBINE_COMMAND_ALIASES);
if (!logUnknownCommands || combineCommandAliases) {
Command command = getBukkitCommand(commandName);
if (command == null) {
if (!logUnknownCommands) {
return;
}
} else if (combineCommandAliases) {
commandName = command.getName();
}
}
dbSystem.getDatabase().executeTransaction(new CommandStoreTransaction(serverInfo.getServerUUID(), commandName));
}
private Command getBukkitCommand(String commandName) {
Command command = plugin.getServer().getPluginCommand(commandName);
if (command == null) {
try {
command = plugin.getServer().getCommandMap().getCommand(commandName);
} catch (NoSuchMethodError ignored) {
/* Ignored, Bukkit 1.8 has no such method. This method is from Paper */
}
}
return command;
}
}

View File

@ -1,157 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.processing.Processing;
import com.djrapitops.plan.system.processing.processors.player.MobKillProcessor;
import com.djrapitops.plan.system.processing.processors.player.PlayerKillProcessor;
import com.djrapitops.plan.utilities.formatting.EntityNameFormatter;
import com.djrapitops.plan.utilities.formatting.ItemNameFormatter;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.Material;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.projectiles.ProjectileSource;
import javax.inject.Inject;
import java.util.UUID;
/**
* Event Listener for EntityDeathEvents.
*
* @author Rsl1122
*/
public class DeathEventListener implements Listener {
private final Processing processing;
private final ErrorHandler errorHandler;
@Inject
public DeathEventListener(
Processing processing,
ErrorHandler errorHandler
) {
this.processing = processing;
this.errorHandler = errorHandler;
}
@SuppressWarnings("deprecation")
@EventHandler(priority = EventPriority.MONITOR)
public void onDeath(EntityDeathEvent event) {
long time = System.currentTimeMillis();
LivingEntity dead = event.getEntity();
if (dead instanceof Player) {
// Process Death
SessionCache.getCachedSession(dead.getUniqueId()).ifPresent(Session::died);
}
try {
EntityDamageEvent entityDamageEvent = dead.getLastDamageCause();
if (!(entityDamageEvent instanceof EntityDamageByEntityEvent)) {
return;
}
EntityDamageByEntityEvent entityDamageByEntityEvent = (EntityDamageByEntityEvent) entityDamageEvent;
Entity killerEntity = entityDamageByEntityEvent.getDamager();
UUID uuid = dead instanceof Player ? dead.getUniqueId() : null;
handleKill(time, uuid, killerEntity);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void handleKill(long time, UUID victimUUID, Entity killerEntity) {
Runnable processor = null;
if (killerEntity instanceof Player) {
processor = handlePlayerKill(time, victimUUID, (Player) killerEntity);
} else if (killerEntity instanceof Tameable) {
processor = handlePetKill(time, victimUUID, (Tameable) killerEntity);
} else if (killerEntity instanceof Projectile) {
processor = handleProjectileKill(time, victimUUID, (Projectile) killerEntity);
}
if (processor != null) {
processing.submit(processor);
}
}
private Runnable handlePlayerKill(long time, UUID victimUUID, Player killer) {
Material itemInHand;
try {
itemInHand = killer.getInventory().getItemInMainHand().getType();
} catch (NoSuchMethodError e) {
try {
itemInHand = killer.getInventory().getItemInHand().getType(); // Support for non dual wielding versions.
} catch (Exception | NoSuchMethodError | NoSuchFieldError e2) {
itemInHand = Material.AIR;
}
}
String weaponName = new ItemNameFormatter().apply(itemInHand.name());
return victimUUID != null
? new PlayerKillProcessor(killer.getUniqueId(), time, victimUUID, weaponName)
: new MobKillProcessor(killer.getUniqueId());
}
private Runnable handlePetKill(long time, UUID victimUUID, Tameable tameable) {
if (!tameable.isTamed()) {
return null;
}
AnimalTamer owner = tameable.getOwner();
if (!(owner instanceof Player)) {
return null;
}
String name;
try {
name = tameable.getType().name();
} catch (NoSuchMethodError oldVersionNoTypesError) {
// getType introduced in 1.9
name = tameable.getClass().getSimpleName();
}
return victimUUID != null
? new PlayerKillProcessor(owner.getUniqueId(), time, victimUUID, new EntityNameFormatter().apply(name))
: new MobKillProcessor(owner.getUniqueId());
}
private Runnable handleProjectileKill(long time, UUID victimUUID, Projectile projectile) {
ProjectileSource source = projectile.getShooter();
if (!(source instanceof Player)) {
return null;
}
Player player = (Player) source;
String projectileName = new EntityNameFormatter().apply(projectile.getType().name());
return victimUUID != null
? new PlayerKillProcessor(player.getUniqueId(), time, victimUUID, projectileName)
: new MobKillProcessor(player.getUniqueId());
}
}

View File

@ -1,87 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.db.access.transactions.events.WorldNameStoreTransaction;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.settings.config.WorldAliasSettings;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import javax.inject.Inject;
import java.util.Optional;
import java.util.UUID;
/**
* Event Listener for PlayerGameModeChangeEvents.
*
* @author Rsl1122
*/
public class GameModeChangeListener implements Listener {
private final WorldAliasSettings worldAliasSettings;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ErrorHandler errorHandler;
@Inject
public GameModeChangeListener(
WorldAliasSettings worldAliasSettings,
ServerInfo serverInfo,
DBSystem dbSystem,
ErrorHandler errorHandler
) {
this.worldAliasSettings = worldAliasSettings;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.errorHandler = errorHandler;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onGameModeChange(PlayerGameModeChangeEvent event) {
if (event.isCancelled()) {
return;
}
try {
actOnEvent(event);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void actOnEvent(PlayerGameModeChangeEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
long time = System.currentTimeMillis();
String gameMode = event.getNewGameMode().name();
String worldName = player.getWorld().getName();
dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName));
worldAliasSettings.addWorld(worldName);
Optional<Session> cachedSession = SessionCache.getCachedSession(uuid);
cachedSession.ifPresent(session -> session.changeState(worldName, gameMode, time));
}
}

View File

@ -1,217 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
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;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.processing.Processing;
import com.djrapitops.plan.system.processing.processors.Processors;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DataGatheringSettings;
import com.djrapitops.plan.system.status.Status;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import javax.inject.Inject;
import java.net.InetAddress;
import java.util.UUID;
/**
* Event Listener for PlayerJoin, PlayerQuit and PlayerKickEvents.
*
* @author Rsl1122
*/
public class PlayerOnlineListener implements Listener {
private final PlanConfig config;
private final Processors processors;
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;
private final ErrorHandler errorHandler;
private final Status status;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processors processors,
Processing processing,
ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionServiceImplementation extensionService,
GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
Status status,
ErrorHandler errorHandler
) {
this.config = config;
this.processors = processors;
this.processing = processing;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.status = status;
this.errorHandler = errorHandler;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLogin(PlayerLoginEvent event) {
try {
PlayerLoginEvent.Result result = event.getResult();
UUID playerUUID = event.getPlayer().getUniqueId();
boolean operator = event.getPlayer().isOp();
boolean banned = result == PlayerLoginEvent.Result.KICK_BANNED;
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, () -> banned));
dbSystem.getDatabase().executeTransaction(new OperatorStatusTransaction(playerUUID, operator));
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
/**
* PlayerKickEvent Listener.
* <p>
* Adds processing information to the ProcessingQueue.
* After KickEvent, the QuitEvent is automatically called.
*
* @param event Fired event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerKick(PlayerKickEvent event) {
try {
if (!status.areKicksCounted() || event.isCancelled()) {
return;
}
UUID uuid = event.getPlayer().getUniqueId();
if (AFKListener.AFK_TRACKER.isAfk(uuid)) {
return;
}
dbSystem.getDatabase().executeTransaction(new KickStoreTransaction(uuid));
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
try {
actOnJoinEvent(event);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void actOnJoinEvent(PlayerJoinEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
UUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
AFKListener.AFK_TRACKER.performedAction(playerUUID, time);
String world = player.getWorld().getName();
String gm = player.getGameMode().name();
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
InetAddress address = player.getAddress().getAddress();
String playerName = player.getName();
String displayName = player.getDisplayName();
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
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(
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> name.equals(nicknameCache.getDisplayName(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)
public void onPlayerQuit(PlayerQuitEvent event) {
try {
actOnQuitEvent(event);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void actOnQuitEvent(PlayerQuitEvent event) {
long time = System.currentTimeMillis();
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
AFKListener.AFK_TRACKER.loggedOut(playerUUID, time);
nicknameCache.removeDisplayName(playerUUID);
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, player::isBanned));
sessionCache.endSession(playerUUID, time)
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new SessionEndTransaction(endedSession)));
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
}
}

View File

@ -1,81 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bukkit;
import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.db.access.transactions.events.WorldNameStoreTransaction;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.settings.config.WorldAliasSettings;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import javax.inject.Inject;
import java.util.Optional;
import java.util.UUID;
public class WorldChangeListener implements Listener {
private final WorldAliasSettings worldAliasSettings;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ErrorHandler errorHandler;
@Inject
public WorldChangeListener(
WorldAliasSettings worldAliasSettings,
ServerInfo serverInfo,
DBSystem dbSystem,
ErrorHandler errorHandler
) {
this.worldAliasSettings = worldAliasSettings;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.errorHandler = errorHandler;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onWorldChange(PlayerChangedWorldEvent event) {
try {
actOnEvent(event);
} catch (Exception e) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
private void actOnEvent(PlayerChangedWorldEvent event) {
long time = System.currentTimeMillis();
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
String worldName = player.getWorld().getName();
String gameMode = player.getGameMode().name();
dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName));
worldAliasSettings.addWorld(worldName);
Optional<Session> cachedSession = SessionCache.getCachedSession(uuid);
cachedSession.ifPresent(session -> session.changeState(worldName, gameMode, time));
}
}

View File

@ -1,125 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.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.DataGatheringSettings;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.system.tasks.bukkit.BukkitTPSCountTimer;
import com.djrapitops.plan.system.tasks.bukkit.PaperTPSCountTimer;
import com.djrapitops.plan.system.tasks.bukkit.PingCountTimerBukkit;
import com.djrapitops.plan.system.tasks.server.BootAnalysisTask;
import com.djrapitops.plan.system.tasks.server.ConfigStoreTask;
import com.djrapitops.plan.system.tasks.server.PeriodicAnalysisTask;
import com.djrapitops.plugin.api.Check;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.task.RunnableFactory;
import org.bukkit.Bukkit;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* TaskSystem responsible for registering tasks for Bukkit.
*
* @author Rsl1122
*/
@Singleton
public class BukkitTaskSystem extends ServerTaskSystem {
private final Plan plugin;
private final ShutdownHook shutdownHook;
private final PingCountTimerBukkit pingCountTimer;
private final ConfigStoreTask configStoreTask;
private final DBCleanTask dbCleanTask;
private final ExtensionServerMethodCallerTask extensionServerMethodCallerTask;
@Inject
public BukkitTaskSystem(
Plan plugin,
PlanConfig config,
ShutdownHook shutdownHook,
RunnableFactory runnableFactory,
PaperTPSCountTimer paperTPSCountTimer,
BukkitTPSCountTimer bukkitTPSCountTimer,
BootAnalysisTask bootAnalysisTask,
PeriodicAnalysisTask periodicAnalysisTask,
PingCountTimerBukkit pingCountTimer,
LogsFolderCleanTask logsFolderCleanTask,
PlayersPageRefreshTask playersPageRefreshTask,
ConfigStoreTask configStoreTask,
DBCleanTask dbCleanTask,
ExtensionServerMethodCallerTask extensionServerMethodCallerTask
) {
super(
runnableFactory,
Check.isPaperAvailable() ? paperTPSCountTimer : bukkitTPSCountTimer,
config,
bootAnalysisTask,
periodicAnalysisTask,
logsFolderCleanTask,
playersPageRefreshTask);
this.plugin = plugin;
this.shutdownHook = shutdownHook;
this.pingCountTimer = pingCountTimer;
this.configStoreTask = configStoreTask;
this.dbCleanTask = dbCleanTask;
this.extensionServerMethodCallerTask = extensionServerMethodCallerTask;
}
@Override
public void enable() {
super.enable();
try {
Long pingDelay = config.get(TimeSettings.PING_SERVER_ENABLE_DELAY);
if (pingDelay < TimeUnit.HOURS.toMillis(1L) && config.get(DataGatheringSettings.PING)) {
plugin.registerListener(pingCountTimer);
long startDelay = TimeAmount.toTicks(pingDelay, 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();
}
@Override
public void disable() {
super.disable();
Optional.ofNullable(Bukkit.getScheduler()).ifPresent(scheduler -> scheduler.cancelTasks(plugin));
}
}

View File

@ -1,154 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.tasks.bukkit;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.container.builders.TPSBuilder;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.tasks.TPSCountTimer;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.World;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.concurrent.TimeUnit;
@Singleton
public class BukkitTPSCountTimer extends TPSCountTimer {
protected final Plan plugin;
private ServerProperties serverProperties;
private long lastCheckNano;
@Inject
public BukkitTPSCountTimer(
Plan plugin,
DBSystem dbSystem,
ServerInfo serverInfo,
ServerProperties serverProperties,
PluginLogger logger,
ErrorHandler errorHandler
) {
super(dbSystem, serverInfo, logger, errorHandler);
this.plugin = plugin;
this.serverProperties = serverProperties;
lastCheckNano = -1;
}
@Override
public void addNewTPSEntry(long nanoTime, long now) {
long diff = nanoTime - lastCheckNano;
lastCheckNano = nanoTime;
if (diff > nanoTime) { // First run's diff = nanoTime + 1, no calc possible.
logger.debug("First run of TPSCountTimer Task.");
return;
}
history.add(calculateTPS(diff, now));
}
/**
* Calculates the TPS
*
* @param diff The time difference between the last run and the new run
* @param now The time right now
* @return the TPS
*/
private TPS calculateTPS(long diff, long now) {
double averageCPUUsage = getCPUUsage();
long usedMemory = getUsedMemory();
long freeDiskSpace = getFreeDiskSpace();
int playersOnline = serverProperties.getOnlinePlayers();
latestPlayersOnline = playersOnline;
int loadedChunks = getLoadedChunks();
int entityCount = getEntityCount();
return getTPS(diff, now, averageCPUUsage, usedMemory, entityCount, loadedChunks, playersOnline, freeDiskSpace);
}
protected TPS getTPS(long diff, long now,
double cpuUsage, long usedMemory,
int entityCount, int chunksLoaded,
int playersOnline, long freeDiskSpace) {
long difference = diff;
if (difference < TimeUnit.SECONDS.toNanos(1L)) { // No tick count above 20
difference = TimeUnit.SECONDS.toNanos(1L);
}
long twentySeconds = TimeUnit.SECONDS.toNanos(20L);
while (difference > twentySeconds) {
// Add 0 TPS since more than 20 ticks has passed.
history.add(TPSBuilder.get()
.date(now)
.tps(0)
.playersOnline(playersOnline)
.usedCPU(cpuUsage)
.usedMemory(usedMemory)
.entities(entityCount)
.chunksLoaded(chunksLoaded)
.freeDiskSpace(freeDiskSpace)
.toTPS());
difference -= twentySeconds;
}
double tpsN = twentySeconds * 1.0 / difference;
return TPSBuilder.get()
.date(now)
.tps(tpsN)
.playersOnline(playersOnline)
.usedCPU(cpuUsage)
.usedMemory(usedMemory)
.entities(entityCount)
.chunksLoaded(chunksLoaded)
.freeDiskSpace(freeDiskSpace)
.toTPS();
}
/**
* Gets the amount of loaded chunks
*
* @return amount of loaded chunks
*/
private int getLoadedChunks() {
int sum = 0;
for (World world : plugin.getServer().getWorlds()) {
sum += world.getLoadedChunks().length;
}
return sum;
}
/**
* Gets the amount of entities on the server for Bukkit / Spigot
*
* @return amount of entities
*/
protected int getEntityCount() {
int sum = 0;
for (World world : plugin.getServer().getWorlds()) {
sum += world.getEntities().size();
}
return sum;
}
}

View File

@ -1,77 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.tasks.bukkit;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.container.builders.TPSBuilder;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import org.bukkit.World;
import javax.inject.Inject;
public class PaperTPSCountTimer extends BukkitTPSCountTimer {
@Inject
public PaperTPSCountTimer(
Plan plugin,
DBSystem dbSystem,
ServerInfo serverInfo,
PluginLogger logger,
ErrorHandler errorHandler
) {
super(plugin, dbSystem, serverInfo, serverInfo.getServerProperties(), logger, errorHandler);
}
@Override
protected TPS getTPS(long diff, long now, double cpuUsage, long usedMemory, int entityCount, int chunksLoaded, int playersOnline, long freeDiskSpace) {
double tps;
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);
}
if (tps > 20) {
tps = 20;
}
return TPSBuilder.get()
.date(now)
.tps(tps)
.playersOnline(playersOnline)
.usedCPU(cpuUsage)
.usedMemory(usedMemory)
.entities(entityCount)
.chunksLoaded(chunksLoaded)
.freeDiskSpace(freeDiskSpace)
.toTPS();
}
@Override
protected int getEntityCount() {
try {
return plugin.getServer().getWorlds().stream().mapToInt(World::getEntityCount).sum();
} catch (BootstrapMethodError | NoSuchMethodError e) {
return super.getEntityCount();
}
}
}

View File

@ -1,220 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.djrapitops.plan.system.tasks.bukkit;
import com.djrapitops.plan.data.store.objects.DateObj;
import com.djrapitops.plan.db.access.transactions.events.PingStoreTransaction;
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.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.utilities.java.Reflection;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.task.AbsRunnable;
import com.djrapitops.plugin.task.RunnableFactory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Task that handles player ping calculation on Bukkit based servers.
* <p>
* Modified PingManager from LagMonitor plugin.
* https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/task/PingManager.java
*
* @author games647
*/
@Singleton
public class PingCountTimerBukkit extends AbsRunnable implements Listener {
//the server is pinging the client every 40 Ticks (2 sec) - so check it then
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178
private static final boolean PING_METHOD_AVAILABLE;
private static final MethodHandle PING_FIELD;
private static final MethodHandle GET_HANDLE_METHOD;
static {
PING_METHOD_AVAILABLE = isPingMethodAvailable();
MethodHandle localHandle = null;
MethodHandle localPing = null;
if (!PING_METHOD_AVAILABLE) {
try {
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
Lookup lookup = MethodHandles.publicLookup();
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
localHandle = lookup.unreflect(getHandleMethod);
localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException reflectiveEx) {
Logger.getGlobal().log(
Level.WARNING,
"Plan: Reflective exception in static initializer of PingCountTimer",
reflectiveEx
);
} catch (IllegalArgumentException e) {
Logger.getGlobal().log(
Level.WARNING,
"Plan: No Ping method handle found - Ping will not be recorded."
);
}
}
GET_HANDLE_METHOD = localHandle;
PING_FIELD = localPing;
}
private final Map<UUID, List<DateObj<Integer>>> playerHistory;
private final PlanConfig config;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private final RunnableFactory runnableFactory;
@Inject
public PingCountTimerBukkit(
PlanConfig config,
DBSystem dbSystem,
ServerInfo serverInfo,
RunnableFactory runnableFactory
) {
this.config = config;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
this.runnableFactory = runnableFactory;
playerHistory = new HashMap<>();
}
private static boolean isPingMethodAvailable() {
try {
//Only available in Paper
Class.forName("org.bukkit.entity.Player$Spigot").getDeclaredMethod("getPing");
return true;
} catch (ClassNotFoundException | NoSuchMethodException noSuchMethodEx) {
return false;
}
}
@Override
public void run() {
long time = System.currentTimeMillis();
Iterator<Map.Entry<UUID, List<DateObj<Integer>>>> iterator = playerHistory.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<UUID, List<DateObj<Integer>>> entry = iterator.next();
UUID uuid = entry.getKey();
List<DateObj<Integer>> history = entry.getValue();
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
int ping = getPing(player);
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}
history.add(new DateObj<>(time, ping));
if (history.size() >= 30) {
dbSystem.getDatabase().executeTransaction(
new PingStoreTransaction(uuid, serverInfo.getServerUUID(), new ArrayList<>(history))
);
history.clear();
}
} else {
iterator.remove();
}
}
}
public void addPlayer(Player player) {
playerHistory.put(player.getUniqueId(), new ArrayList<>());
}
public void removePlayer(Player player) {
playerHistory.remove(player.getUniqueId());
}
private int getPing(Player player) {
if (PING_METHOD_AVAILABLE) {
// This method is from Paper
return player.spigot().getPing();
}
return getReflectionPing(player);
}
private int getReflectionPing(Player player) {
try {
Object entityPlayer = GET_HANDLE_METHOD.invoke(player);
return (int) PING_FIELD.invoke(entityPlayer);
} catch (Exception ex) {
return -1;
} catch (Throwable throwable) {
throw (Error) throwable;
}
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) {
return;
}
runnableFactory.create("Add Player to Ping list", new AbsRunnable() {
@Override
public void run() {
if (player.isOnline()) {
addPlayer(player);
}
}
}).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS));
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
removePlayer(quitEvent.getPlayer());
}
public void clear() {
playerHistory.clear();
}
}

View File

@ -1,432 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.djrapitops.plan.utilities.java;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An utility class that simplifies reflection in Bukkit plugins.
* <p>
* Modified Reflection utility from LagMonitor plugin.
* https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java
*
* @author Kristian
*/
public final class Reflection {
// Deduce the net.minecraft.server.v* package
private static final String OBC_PREFIX = getOBCPrefix();
private static final String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
private static final String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
// Variable replacement
private static final Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
private Reflection() {
// Seal class
}
private static String getOBCPrefix() {
Server server = Bukkit.getServer();
return server != null ? server.getClass().getPackage().getName() : Server.class.getPackage().getName();
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
return getField(getClass(className), name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
// Common method
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
for (final Field field : target.getDeclaredFields()) {
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
field.setAccessible(true);
// A function for retrieving a specific field value
return new FieldAccessor<T>() {
@Override
@SuppressWarnings("unchecked")
public T get(Object target) {
try {
return (T) field.get(target);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access reflection.", e);
}
}
@Override
public void set(Object target, Object value) {
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access reflection.", e);
}
}
@Override
public boolean hasField(Object target) {
// target instanceof DeclaringClass
return field.getDeclaringClass().isAssignableFrom(target.getClass());
}
};
}
}
// Search in parent classes
if (target.getSuperclass() != null) {
return getField(target.getSuperclass(), name, fieldType, index);
}
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
return getTypedMethod(getClass(className), methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
return getTypedMethod(clazz, methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param returnType - the expected return type, or NULL to ignore.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
for (final Method method : clazz.getDeclaredMethods()) {
if ((methodName == null || method.getName().equals(methodName)) && (returnType == null) || method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), params)) {
method.setAccessible(true);
return (target, arguments) -> {
try {
return method.invoke(target, arguments);
} catch (Exception e) {
throw new IllegalStateException("Cannot invoke method " + method, e);
}
};
}
}
// Search in every superclass
if (clazz.getSuperclass() != null) {
return getMethod(clazz.getSuperclass(), methodName, params);
}
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
}
/**
* Search for the first publicly and privately defined constructor of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
return getConstructor(getClass(className), params);
}
/**
* Search for the first publicly and privately defined constructor of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Arrays.equals(constructor.getParameterTypes(), params)) {
constructor.setAccessible(true);
return arguments -> {
try {
return constructor.newInstance(arguments);
} catch (Exception e) {
throw new IllegalStateException("Cannot invoke constructor " + constructor, e);
}
};
}
}
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
}
/**
* Retrieve a class from its full name, without knowing its type on compile time.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @param lookupName - the class name with variables.
* @return The class.
* @see Object#getClass()
*/
public static Class<Object> getUntypedClass(String lookupName) {
@SuppressWarnings({"rawtypes", "unchecked"})
Class<Object> clazz = (Class) getClass(lookupName);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* </p>
* <table border="1" summary="Variables and description">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual package name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @param lookupName - the class name with variables.
* @return The looked up class.
* @throws IllegalArgumentException If a variable or class could not be found.
*/
public static Class<?> getClass(String lookupName) {
return getCanonicalClass(expandVariables(lookupName));
}
/**
* Retrieve a class in the net.minecraft.server.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getMinecraftClass(String name) {
return getCanonicalClass(NMS_PREFIX + '.' + name);
}
/**
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getCraftBukkitClass(String name) {
return getCanonicalClass(OBC_PREFIX + '.' + name);
}
/**
* Retrieve a class by its canonical name.
*
* @param canonicalName - the canonical name.
* @return The class.
*/
private static Class<?> getCanonicalClass(String canonicalName) {
try {
return Class.forName(canonicalName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
}
}
/**
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
*
* @param name - the full name of the class.
* @return The expanded string.
*/
private static String expandVariables(String name) {
StringBuffer output = new StringBuffer();
Matcher matcher = MATCH_VARIABLE.matcher(name);
while (matcher.find()) {
String variable = matcher.group(1);
String replacement;
// Expand all detected variables
if ("nms".equalsIgnoreCase(variable)) {
replacement = NMS_PREFIX;
} else if ("obc".equalsIgnoreCase(variable)) {
replacement = OBC_PREFIX;
} else if ("version".equalsIgnoreCase(variable)) {
replacement = VERSION;
} else {
throw new IllegalArgumentException("Unknown variable: " + variable);
}
// Assume the expanded variables are all packages, and append a dot
if (!replacement.isEmpty() && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') {
replacement += ".";
}
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(output);
return output.toString();
}
/**
* An interface for invoking a specific constructor.
*/
@FunctionalInterface
public interface ConstructorInvoker {
/**
* Invoke a constructor for a specific class.
*
* @param arguments - the arguments to pass to the constructor.
* @return The constructed object.
*/
Object invoke(Object... arguments);
}
/**
* An interface for invoking a specific method.
*/
@FunctionalInterface
public interface MethodInvoker {
/**
* Invoke a method on a specific target object.
*
* @param target - the target object, or NULL for a static method.
* @param arguments - the arguments to pass to the method.
* @return The return value, or NULL if is void.
*/
Object invoke(Object target, Object... arguments);
}
/**
* An interface for retrieving the field content.
*
* @param <T> - field type.
*/
public interface FieldAccessor<T> {
/**
* Retrieve the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @return The value of the field.
*/
T get(Object target);
/**
* Set the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @param value - the new value of the field.
*/
void set(Object target, Object value);
/**
* Determine if the given object has this field.
*
* @param target - the object to test.
* @return TRUE if it does, FALSE otherwise.
*/
boolean hasField(Object target);
}
}

View File

@ -1,106 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.queries.objects.ServerQueries;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.info.server.Server;
import com.djrapitops.plan.system.settings.ConfigSettingKeyTest;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.WebserverSettings;
import com.djrapitops.plan.system.settings.paths.key.Setting;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import rules.BukkitComponentMocker;
import rules.ComponentMocker;
import utilities.OptionalAssert;
import utilities.RandomData;
import java.util.Collection;
import java.util.Optional;
import static org.junit.Assert.assertTrue;
/**
* Test for Bukkit PlanSystem.
*
* @author Rsl1122
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class BukkitSystemTest {
@ClassRule
public static TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public ComponentMocker component = new BukkitComponentMocker(temporaryFolder);
private final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
private PlanSystem system;
@Before
public void prepareSystem() {
system = component.getPlanSystem();
system.getConfigSystem().getConfig()
.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
}
@Test
public void bukkitSystemEnables() throws EnableException {
try {
system.enable();
assertTrue(system.isEnabled());
} finally {
system.disable();
}
}
@Test
public void correctWebAddressInDatabaseAfterEnable() throws EnableException {
try {
system.enable();
Database database = system.getDatabaseSystem().getDatabase();
String expectedAddress = system.getWebServerSystem().getWebServer().getAccessAddress();
Optional<String> found = database.query(ServerQueries.fetchServerMatchingIdentifier(system.getServerInfo().getServerUUID()))
.map(Server::getWebAddress);
OptionalAssert.equals(expectedAddress, found);
} finally {
system.disable();
}
}
@Test
public void bukkitSystemHasDefaultConfigValuesAfterEnable() throws EnableException, IllegalAccessException {
try {
system.enable();
PlanConfig config = system.getConfigSystem().getConfig();
Collection<Setting> serverSettings = ConfigSettingKeyTest.getServerSettings();
ConfigSettingKeyTest.assertValidDefaultValuesForAllSettings(config, serverSettings);
} finally {
system.disable();
}
}
}

View File

@ -1,75 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners;
import com.djrapitops.plan.system.listeners.bukkit.AFKListener;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plugin.logging.console.TestPluginLogger;
import com.djrapitops.plugin.logging.error.ConsoleErrorLogger;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import utilities.TestConstants;
import java.util.concurrent.TimeUnit;
import static org.mockito.Mockito.*;
/**
* Test for {@link AFKListener}
*
* @author Rsl1122
*/
public class AFKListenerTest {
private AFKListener underTest;
@Before
public void setUp() {
PlanConfig config = Mockito.mock(PlanConfig.class);
when(config.get(TimeSettings.AFK_THRESHOLD)).thenReturn(TimeUnit.MINUTES.toMillis(3));
underTest = new AFKListener(config, new ConsoleErrorLogger(new TestPluginLogger()));
}
@Test
public void afkPermissionIsNotCalledMoreThanOnce() {
Player player = mockPlayer();
PlayerMoveEvent event = mockMoveEvent(player);
underTest.onMove(event);
underTest.onMove(event);
verify(player, times(1)).hasPermission(anyString());
}
private PlayerMoveEvent mockMoveEvent(Player player) {
PlayerMoveEvent event = Mockito.mock(PlayerMoveEvent.class);
when(event.getPlayer()).thenReturn(player);
return event;
}
private Player mockPlayer() {
Player player = Mockito.mock(Player.class);
when(player.getUniqueId()).thenReturn(TestConstants.PLAYER_ONE_UUID);
when(player.hasPermission(anyString())).thenReturn(true);
return player;
}
}

View File

@ -1,57 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package rules;
import com.djrapitops.plan.DaggerPlanBukkitComponent;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanBukkitComponent;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.system.PlanSystem;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import utilities.mocks.PlanBukkitMocker;
public class BukkitComponentMocker extends ExternalResource implements ComponentMocker {
private final TemporaryFolder testFolder;
private Plan planMock;
private PlanBukkitComponent component;
public BukkitComponentMocker(TemporaryFolder testFolder) {
this.testFolder = testFolder;
}
@Override
protected void before() throws Throwable {
PlanBukkitMocker mocker = PlanBukkitMocker.setUp()
.withDataFolder(testFolder.newFolder())
.withPluginDescription()
.withResourceFetchingFromJar()
.withServer();
planMock = mocker.getPlanMock();
component = DaggerPlanBukkitComponent.builder().plan(planMock).build();
}
public PlanPlugin getPlanMock() {
return planMock;
}
public PlanSystem getPlanSystem() {
return component.system();
}
}

View File

@ -1,135 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package utilities.mocks;
import com.djrapitops.plan.Plan;
import com.djrapitops.plugin.benchmarking.Timings;
import com.djrapitops.plugin.command.ColorScheme;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.console.TestPluginLogger;
import com.djrapitops.plugin.logging.debug.CombineDebugLogger;
import com.djrapitops.plugin.logging.debug.DebugLogger;
import com.djrapitops.plugin.logging.debug.MemoryDebugLogger;
import com.djrapitops.plugin.logging.error.ConsoleErrorLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import com.djrapitops.plugin.task.RunnableFactory;
import org.bukkit.Server;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.InvalidDescriptionException;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.scheduler.BukkitScheduler;
import org.mockito.Mockito;
import utilities.TestConstants;
import utilities.mocks.objects.TestLogger;
import utilities.mocks.objects.TestRunnableFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import static org.mockito.Mockito.doReturn;
/**
* Mocking Utility for Bukkit version of Plan.
*
* @author Rsl1122
*/
public class PlanBukkitMocker extends Mocker {
private Plan planMock;
private PlanBukkitMocker() {
}
public static PlanBukkitMocker setUp() {
return new PlanBukkitMocker().mockPlugin();
}
private PlanBukkitMocker mockPlugin() {
planMock = Mockito.mock(Plan.class);
super.planMock = planMock;
doReturn(new ColorScheme("§1", "§2", "§3")).when(planMock).getColorScheme();
doReturn("1.0.0").when(planMock).getVersion();
TestLogger testLogger = new TestLogger();
RunnableFactory runnableFactory = new TestRunnableFactory();
PluginLogger testPluginLogger = new TestPluginLogger();
DebugLogger debugLogger = new CombineDebugLogger(new MemoryDebugLogger());
ErrorHandler consoleErrorLogger = new ConsoleErrorLogger(testPluginLogger);
Timings timings = new Timings(debugLogger);
Mockito.doReturn(testLogger).when(planMock).getLogger();
doReturn(runnableFactory).when(planMock).getRunnableFactory();
doReturn(testPluginLogger).when(planMock).getPluginLogger();
doReturn(debugLogger).when(planMock).getDebugLogger();
doReturn(consoleErrorLogger).when(planMock).getErrorHandler();
doReturn(timings).when(planMock).getTimings();
return this;
}
public PlanBukkitMocker withDataFolder(File tempFolder) {
doReturn(tempFolder).when(planMock).getDataFolder();
return this;
}
@Deprecated
public PlanBukkitMocker withLogging() {
return this;
}
public PlanBukkitMocker withPluginDescription() {
try {
File pluginYml = getFile("/plugin.yml");
PluginDescriptionFile description = new PluginDescriptionFile(new FileInputStream(pluginYml));
doReturn(description).when(planMock).getDescription();
} catch (FileNotFoundException | InvalidDescriptionException e) {
System.out.println("Error while setting plugin description");
}
return this;
}
public PlanBukkitMocker withResourceFetchingFromJar() throws IOException {
withPluginFiles();
return this;
}
public PlanBukkitMocker withServer() {
Server serverMock = Mockito.mock(Server.class);
doReturn("").when(serverMock).getIp();
doReturn("Bukkit").when(serverMock).getName();
doReturn("Bukkit").when(serverMock).getServerName();
doReturn(25565).when(serverMock).getPort();
doReturn("1.12.2").when(serverMock).getVersion();
doReturn("32423").when(serverMock).getBukkitVersion();
Mockito.doReturn(TestConstants.BUKKIT_MAX_PLAYERS).when(serverMock).getMaxPlayers();
ConsoleCommandSender sender = Mockito.mock(ConsoleCommandSender.class);
doReturn(sender).when(serverMock).getConsoleSender();
BukkitScheduler bukkitScheduler = Mockito.mock(BukkitScheduler.class);
doReturn(bukkitScheduler).when(serverMock).getScheduler();
doReturn(serverMock).when(planMock).getServer();
return this;
}
public Plan getPlanMock() {
return planMock;
}
}

View File

@ -1,20 +0,0 @@
dependencies {
compile project(path: ":common", configuration: 'shadow')
compile "com.djrapitops:AbstractPluginFramework-bungeecord:$abstractPluginFrameworkVersion"
compile "org.bstats:bstats-bungeecord:$bstatsVersion"
compileOnly "net.md-5:bungeecord-api:$bungeeVersion"
compileOnly "com.imaginarycode.minecraft:RedisBungee:$redisBungeeVersion"
testCompile "net.md-5:bungeecord-api:$bungeeVersion"
testCompile "com.imaginarycode.minecraft:RedisBungee:$redisBungeeVersion"
testCompile project(path: ":common", configuration: 'testArtifacts')
}
shadowJar {
configurations = [project.configurations.compile]
relocate 'org.bstats', 'com.djrapitops.plan.utilities.metrics'
relocate 'org.slf4j', 'plan.org.slf4j'
}

View File

@ -1,58 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.system.info.connection.ConnectionSystem;
import org.bstats.bungeecord.Metrics;
import java.io.Serializable;
public class BStatsBungee {
private final PlanBungee plugin;
private final Database database;
private final ConnectionSystem connectionSystem;
private Metrics metrics;
public BStatsBungee(PlanBungee plugin, Database database, ConnectionSystem connectionSystem) {
this.plugin = plugin;
this.database = database;
this.connectionSystem = connectionSystem;
}
public void registerMetrics() {
if (metrics == null) {
metrics = new Metrics(plugin);
}
registerConfigSettingGraphs();
}
private void registerConfigSettingGraphs() {
String serverType = plugin.getProxy().getName();
String databaseType = database.getType().getName();
addStringSettingPie("server_type", serverType);
addStringSettingPie("database_type", databaseType);
addStringSettingPie("network_servers", connectionSystem.getDataServers().size());
}
protected void addStringSettingPie(String id, Serializable setting) {
metrics.addCustomChart(new Metrics.SimplePie(id, setting::toString));
}
}

View File

@ -1,114 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.command.PlanProxyCommand;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plan.system.settings.theme.PlanColorScheme;
import com.djrapitops.plugin.BungeePlugin;
import com.djrapitops.plugin.command.ColorScheme;
import com.djrapitops.plugin.logging.L;
import java.io.InputStream;
/**
* Bungee Main class.
*
* @author Rsl1122
*/
public class PlanBungee extends BungeePlugin implements PlanPlugin {
private PlanSystem system;
private Locale locale;
@Override
public void onEnable() {
PlanBungeeComponent component = DaggerPlanBungeeComponent.builder().plan(this).build();
try {
system = component.system();
locale = system.getLocaleSystem().getLocale();
system.enable();
new BStatsBungee(
this,
system.getDatabaseSystem().getDatabase(),
system.getInfoSystem().getConnectionSystem()
).registerMetrics();
logger.info(locale.getString(PluginLang.ENABLED));
} catch (AbstractMethodError e) {
logger.error("Plugin ran into AbstractMethodError - Server restart is required. Likely cause is updating the jar without a restart.");
} catch (EnableException e) {
logger.error("----------------------------------------");
logger.error("Error: " + e.getMessage());
logger.error("----------------------------------------");
logger.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /planbungee reload");
onDisable();
} catch (Exception e) {
errorHandler.log(L.CRITICAL, this.getClass(), e);
logger.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /planbungee reload");
logger.error("This error should be reported at https://github.com/Rsl1122/Plan-PlayerAnalytics/issues");
onDisable();
}
PlanProxyCommand command = component.planCommand();
command.registerCommands();
registerCommand("planbungee", command);
if (system != null) {
system.getProcessing().submitNonCritical(() -> system.getListenerSystem().callEnableEvent(this));
}
}
@Override
public void onDisable() {
if (system != null) system.disable();
logger.info(locale.getString(PluginLang.DISABLED));
}
@Override
public String getVersion() {
return super.getDescription().getVersion();
}
@Override
public void onReload() {
// Nothing to be done, systems are disabled
}
@Override
public InputStream getResource(String resource) {
return getResourceAsStream(resource);
}
@Override
public ColorScheme getColorScheme() {
return PlanColorScheme.create(system.getConfigSystem().getConfig(), logger);
}
@Override
public PlanSystem getSystem() {
return system;
}
@Override
public boolean isReloading() {
return reloading;
}
}

View File

@ -1,66 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.command.PlanProxyCommand;
import com.djrapitops.plan.modules.APFModule;
import com.djrapitops.plan.modules.FilesModule;
import com.djrapitops.plan.modules.ProxySuperClassBindingModule;
import com.djrapitops.plan.modules.SystemObjectProvidingModule;
import com.djrapitops.plan.modules.bungee.BungeeCommandModule;
import com.djrapitops.plan.modules.bungee.BungeePlanModule;
import com.djrapitops.plan.modules.bungee.BungeeServerPropertiesModule;
import com.djrapitops.plan.modules.bungee.BungeeSuperClassBindingModule;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.pluginbridge.plan.PluginBridgeModule;
import dagger.BindsInstance;
import dagger.Component;
import javax.inject.Singleton;
/**
* Dagger Component that constructs the plugin systems running on Bungee.
*
* @author Rsl1122
*/
@Singleton
@Component(modules = {
BungeePlanModule.class,
BungeeCommandModule.class,
SystemObjectProvidingModule.class,
APFModule.class,
FilesModule.class,
ProxySuperClassBindingModule.class,
BungeeSuperClassBindingModule.class,
BungeeServerPropertiesModule.class,
PluginBridgeModule.Bungee.class
})
public interface PlanBungeeComponent {
PlanProxyCommand planCommand();
PlanSystem system();
@Component.Builder
interface Builder {
@BindsInstance
Builder plan(PlanBungee plan);
PlanBungeeComponent build();
}
}

View File

@ -1,46 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.api.events;
import net.md_5.bungee.api.plugin.Event;
/**
* Event that is called when Plan is enabled.
* <p>
* This includes, but might not be limited to:
* - First time the plugin enables successfully
* - Plan is reloaded
* - Plan is enabled after it was disabled
* <p>
* {@code event.isPlanSystemEnabled()} can be called to determine if the enable was successful.
* It is not guaranteed that this event is called when the plugin fails to enable properly.
*
* @author Rsl1122
*/
public class PlanBungeeEnableEvent extends Event {
private final boolean enabled;
public PlanBungeeEnableEvent(boolean enabled) {
this.enabled = enabled;
}
public boolean isPlanSystemEnabled() {
return enabled;
}
}

View File

@ -1,32 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bungee;
import dagger.Module;
import dagger.Provides;
import javax.inject.Named;
@Module
public class BungeeCommandModule {
@Provides
@Named("mainCommandName")
String provideMainCommandName() {
return "planbungee";
}
}

View File

@ -1,42 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bungee;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.command.PlanProxyCommand;
import com.djrapitops.plugin.command.CommandNode;
import dagger.Binds;
import dagger.Module;
import javax.inject.Named;
/**
* Dagger module for binding PlanBungee instance.
*
* @author Rsl1122
*/
@Module
public interface BungeePlanModule {
@Binds
PlanPlugin bindPlanPlugin(PlanBungee plugin);
@Binds
@Named("mainCommand")
CommandNode bindMainCommand(PlanProxyCommand command);
}

View File

@ -1,41 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bungee;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.system.info.server.properties.BungeeServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
/**
* Dagger module for Bungee ServerProperties.
*
* @author Rsl1122
*/
@Module
public class BungeeServerPropertiesModule {
@Provides
@Singleton
ServerProperties provideServerProperties(PlanBungee plugin, PlanConfig config) {
return new BungeeServerProperties(plugin.getProxy(), config);
}
}

View File

@ -1,44 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bungee;
import com.djrapitops.plan.system.info.server.BungeeServerInfo;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.listeners.BungeeListenerSystem;
import com.djrapitops.plan.system.listeners.ListenerSystem;
import com.djrapitops.plan.system.tasks.BungeeTaskSystem;
import com.djrapitops.plan.system.tasks.TaskSystem;
import dagger.Binds;
import dagger.Module;
/**
* Module for binding Bungee specific classes to the interface implementations.
*
* @author Rsl1122
*/
@Module
public interface BungeeSuperClassBindingModule {
@Binds
ServerInfo bindBungeeServerInfo(BungeeServerInfo bungeeServerInfo);
@Binds
TaskSystem bindBungeeTaskSystem(BungeeTaskSystem bungeeTaskSystem);
@Binds
ListenerSystem bindBungeeListenerSystem(BungeeListenerSystem bungeeListenerSystem);
}

View File

@ -1,112 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.info.server;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.queries.objects.ServerQueries;
import com.djrapitops.plan.db.access.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.webserver.WebServer;
import com.djrapitops.plugin.logging.console.PluginLogger;
import dagger.Lazy;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
/**
* Manages Server information on the Bungee instance.
*
* @author Rsl1122
*/
@Singleton
public class BungeeServerInfo extends ServerInfo {
private final DBSystem dbSystem;
private final Lazy<WebServer> webServer;
private final PluginLogger logger;
@Inject
public BungeeServerInfo(
ServerProperties serverProperties,
DBSystem dbSystem,
Lazy<WebServer> webServer,
PluginLogger logger
) {
super(serverProperties);
this.dbSystem = dbSystem;
this.webServer = webServer;
this.logger = logger;
}
@Override
public void loadServerInfo() throws EnableException {
checkIfDefaultIP();
try {
Database database = dbSystem.getDatabase();
Optional<Server> proxyInfo = database.query(ServerQueries.fetchProxyServerInformation());
if (proxyInfo.isPresent()) {
server = proxyInfo.get();
updateServerInfo(database);
} else {
server = registerBungeeInfo(database);
}
} catch (DBOpException | ExecutionException e) {
throw new EnableException("Failed to read Server information from Database.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void updateServerInfo(Database db) {
String accessAddress = webServer.get().getAccessAddress();
if (!accessAddress.equals(server.getWebAddress())) {
server.setWebAddress(accessAddress);
db.executeTransaction(new StoreServerInformationTransaction(server));
}
}
private void checkIfDefaultIP() throws EnableException {
String ip = serverProperties.getIp();
if ("0.0.0.0".equals(ip)) {
logger.error("IP setting still 0.0.0.0 - Configure AlternativeIP/IP that connects to the Proxy server.");
logger.info("Player Analytics partially enabled (Use /planbungee to reload config)");
throw new EnableException("IP setting still 0.0.0.0 - Configure AlternativeIP/IP that connects to the Proxy server.");
}
}
private Server registerBungeeInfo(Database db) throws EnableException, ExecutionException, InterruptedException {
UUID serverUUID = generateNewUUID();
String accessAddress = webServer.get().getAccessAddress();
Server proxy = new Server(-1, serverUUID, "BungeeCord", accessAddress, serverProperties.getMaxPlayers());
db.executeTransaction(new StoreServerInformationTransaction(proxy))
.get();
Optional<Server> proxyInfo = db.query(ServerQueries.fetchProxyServerInformation());
if (proxyInfo.isPresent()) {
return proxyInfo.get();
}
throw new EnableException("BungeeCord registration failed (DB)");
}
}

View File

@ -1,43 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.info.server.properties;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.ProxySettings;
import net.md_5.bungee.api.ProxyServer;
/**
* ServerProperties for Bungee.
* <p>
* Supports RedisBungee for Players online getting.
*
* @author Rsl1122
*/
public class BungeeServerProperties extends ServerProperties {
public BungeeServerProperties(ProxyServer server, PlanConfig config) {
super(
"BungeeCord",
-1,
server.getVersion(),
server.getVersion(),
() -> config.get(ProxySettings.IP),
server.getConfig().getPlayerLimit(),
RedisCheck.isClassAvailable() ? new RedisPlayersOnlineSupplier() : server::getOnlineCount
);
}
}

View File

@ -1,36 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.info.server.properties;
import com.djrapitops.plugin.api.Check;
/**
* Utility class for checking if RedisBungee API is available.
*
* @author Rsl1122
*/
public class RedisCheck {
private RedisCheck() {
/* Static method class */
}
public static boolean isClassAvailable() {
return Check.isAvailable("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI");
}
}

View File

@ -1,34 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.info.server.properties;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import java.util.function.IntSupplier;
/**
* Players online supplier when using RedisBungee.
*
* @author Rsl1122
*/
public class RedisPlayersOnlineSupplier implements IntSupplier {
@Override
public int getAsInt() {
return RedisBungee.getApi().getPlayerCount();
}
}

View File

@ -1,55 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.api.events.PlanBungeeEnableEvent;
import com.djrapitops.plan.capability.CapabilityServiceImplementation;
import com.djrapitops.plan.system.listeners.bungee.PlayerOnlineListener;
import javax.inject.Inject;
public class BungeeListenerSystem extends ListenerSystem {
private final PlanBungee plugin;
private PlayerOnlineListener playerOnlineListener;
@Inject
public BungeeListenerSystem(PlanBungee plugin, PlayerOnlineListener playerOnlineListener) {
this.plugin = plugin;
this.playerOnlineListener = playerOnlineListener;
}
@Override
protected void registerListeners() {
plugin.registerListener(playerOnlineListener);
}
@Override
protected void unregisterListeners() {
plugin.getProxy().getPluginManager().unregisterListeners(plugin);
}
@Override
public void callEnableEvent(PlanPlugin plugin) {
boolean isEnabled = plugin.isSystemEnabled();
PlanBungeeEnableEvent event = new PlanBungeeEnableEvent(isEnabled);
((PlanBungee) plugin).getProxy().getPluginManager().callEvent(event);
CapabilityServiceImplementation.notifyAboutEnable(isEnabled);
}
}

View File

@ -1,153 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.listeners.bungee;
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;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.processing.Processing;
import com.djrapitops.plan.system.processing.processors.Processors;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DataGatheringSettings;
import com.djrapitops.plan.system.webserver.cache.PageId;
import com.djrapitops.plan.system.webserver.cache.ResponseCache;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
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;
import java.util.UUID;
/**
* Player Join listener for Bungee.
*
* @author Rsl1122
*/
public class PlayerOnlineListener implements Listener {
private final PlanConfig config;
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;
private final ErrorHandler errorHandler;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processors processors,
Processing processing,
DBSystem dbSystem,
ExtensionServiceImplementation extensionService,
GeolocationCache geolocationCache,
SessionCache sessionCache,
ServerInfo serverInfo,
ErrorHandler errorHandler
) {
this.config = config;
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(priority = EventPriority.HIGHEST)
public void onPostLogin(PostLoginEvent event) {
try {
ProxiedPlayer player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
InetAddress address = player.getAddress().getAddress();
long time = System.currentTimeMillis();
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverInfo.getServerUUID(), time, null, null));
Database database = dbSystem.getDatabase();
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
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(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();
UUID playerUUID = player.getUniqueId();
sessionCache.endSession(playerUUID, System.currentTimeMillis());
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID()));
} catch (Exception e) {
errorHandler.log(L.WARN, this.getClass(), e);
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onServerSwitch(ServerSwitchEvent event) {
try {
ProxiedPlayer player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
long time = System.currentTimeMillis();
// Replaces the current session in the cache.
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverInfo.getServerUUID(), time, null, null));
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
} catch (Exception e) {
errorHandler.log(L.WARN, this.getClass(), e);
}
}
}

View File

@ -1,115 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.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.DataGatheringSettings;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.system.tasks.bungee.BungeeTPSCountTimer;
import com.djrapitops.plan.system.tasks.bungee.PingCountTimerBungee;
import com.djrapitops.plan.system.tasks.proxy.NetworkConfigStoreTask;
import com.djrapitops.plan.system.tasks.proxy.NetworkPageRefreshTask;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.task.RunnableFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.concurrent.TimeUnit;
/**
* TaskSystem responsible for registering tasks for Bungee.
*
* @author Rsl1122
*/
@Singleton
public class BungeeTaskSystem extends TaskSystem {
private final PlanBungee plugin;
private final PlanConfig config;
private final NetworkPageRefreshTask networkPageRefreshTask;
private final PingCountTimerBungee pingCountTimer;
private final LogsFolderCleanTask logsFolderCleanTask;
private final PlayersPageRefreshTask playersPageRefreshTask;
private final NetworkConfigStoreTask networkConfigStoreTask;
private final DBCleanTask dbCleanTask;
private final ExtensionServerMethodCallerTask extensionServerMethodCallerTask;
@Inject
public BungeeTaskSystem(
PlanBungee plugin,
PlanConfig config,
RunnableFactory runnableFactory,
BungeeTPSCountTimer bungeeTPSCountTimer,
NetworkPageRefreshTask networkPageRefreshTask,
PingCountTimerBungee pingCountTimer,
LogsFolderCleanTask logsFolderCleanTask,
PlayersPageRefreshTask playersPageRefreshTask,
NetworkConfigStoreTask networkConfigStoreTask,
DBCleanTask dbCleanTask,
ExtensionServerMethodCallerTask extensionServerMethodCallerTask
) {
super(runnableFactory, bungeeTPSCountTimer);
this.plugin = plugin;
this.config = config;
this.networkPageRefreshTask = networkPageRefreshTask;
this.pingCountTimer = pingCountTimer;
this.logsFolderCleanTask = logsFolderCleanTask;
this.playersPageRefreshTask = playersPageRefreshTask;
this.networkConfigStoreTask = networkConfigStoreTask;
this.dbCleanTask = dbCleanTask;
this.extensionServerMethodCallerTask = extensionServerMethodCallerTask;
}
@Override
public void enable() {
registerTasks();
}
private void registerTasks() {
registerTask(tpsCountTimer).runTaskTimerAsynchronously(1000, TimeAmount.toTicks(1L, TimeUnit.SECONDS));
registerTask(networkPageRefreshTask).runTaskTimerAsynchronously(1500, TimeAmount.toTicks(5L, TimeUnit.MINUTES));
registerTask(logsFolderCleanTask).runTaskLaterAsynchronously(TimeAmount.toTicks(30L, TimeUnit.SECONDS));
Long pingDelay = config.get(TimeSettings.PING_SERVER_ENABLE_DELAY);
if (pingDelay < TimeUnit.HOURS.toMillis(1L) && config.get(DataGatheringSettings.PING)) {
plugin.registerListener(pingCountTimer);
long startDelay = TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS);
registerTask(pingCountTimer).runTaskTimer(startDelay, 40L);
}
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(networkConfigStoreTask).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
);
}
}

View File

@ -1,64 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.system.tasks.bungee;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.container.builders.TPSBuilder;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.tasks.TPSCountTimer;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class BungeeTPSCountTimer extends TPSCountTimer {
private final ServerProperties serverProperties;
@Inject
public BungeeTPSCountTimer(
DBSystem dbSystem,
ServerInfo serverInfo,
ServerProperties serverProperties,
PluginLogger logger,
ErrorHandler errorHandler
) {
super(dbSystem, serverInfo, logger, errorHandler);
this.serverProperties = serverProperties;
}
@Override
public void addNewTPSEntry(long nanoTime, long now) {
int onlineCount = serverProperties.getOnlinePlayers();
TPS tps = TPSBuilder.get()
.date(now)
.playersOnline(onlineCount)
.usedCPU(getCPUUsage())
.usedMemory(getUsedMemory())
.entities(-1)
.chunksLoaded(-1)
.freeDiskSpace(getFreeDiskSpace())
.toTPS();
history.add(tps);
latestPlayersOnline = onlineCount;
}
}

View File

@ -1,142 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.djrapitops.plan.system.tasks.bungee;
import com.djrapitops.plan.data.store.objects.DateObj;
import com.djrapitops.plan.db.access.transactions.events.PingStoreTransaction;
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.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.task.AbsRunnable;
import com.djrapitops.plugin.task.RunnableFactory;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.event.ServerDisconnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Task that handles player ping calculation on Bungee based servers.
*
* @author BrainStone
*/
@Singleton
public class PingCountTimerBungee extends AbsRunnable implements Listener {
private final Map<UUID, List<DateObj<Integer>>> playerHistory;
private final PlanConfig config;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private final RunnableFactory runnableFactory;
@Inject
public PingCountTimerBungee(
PlanConfig config,
DBSystem dbSystem,
ServerInfo serverInfo,
RunnableFactory runnableFactory
) {
this.config = config;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
this.runnableFactory = runnableFactory;
playerHistory = new HashMap<>();
}
@Override
public void run() {
long time = System.currentTimeMillis();
Iterator<Map.Entry<UUID, List<DateObj<Integer>>>> iterator = playerHistory.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<UUID, List<DateObj<Integer>>> entry = iterator.next();
UUID uuid = entry.getKey();
List<DateObj<Integer>> history = entry.getValue();
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid);
if (player != null) {
int ping = getPing(player);
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}
history.add(new DateObj<>(time, ping));
if (history.size() >= 30) {
dbSystem.getDatabase().executeTransaction(
new PingStoreTransaction(uuid, serverInfo.getServerUUID(), new ArrayList<>(history))
);
history.clear();
}
} else {
iterator.remove();
}
}
}
public void addPlayer(ProxiedPlayer player) {
playerHistory.put(player.getUniqueId(), new ArrayList<>());
}
public void removePlayer(ProxiedPlayer player) {
playerHistory.remove(player.getUniqueId());
}
private int getPing(ProxiedPlayer player) {
return player.getPing();
}
@EventHandler
public void onPlayerJoin(ServerConnectedEvent joinEvent) {
ProxiedPlayer player = joinEvent.getPlayer();
Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) {
return;
}
runnableFactory.create("Add Player to Ping list", new AbsRunnable() {
@Override
public void run() {
if (player.isConnected()) {
addPlayer(player);
}
}
}).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS));
}
@EventHandler
public void onPlayerQuit(ServerDisconnectEvent quitEvent) {
removePlayer(quitEvent.getPlayer());
}
public void clear() {
playerHistory.clear();
}
}

View File

@ -1,142 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.db.SQLiteDB;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DatabaseSettings;
import com.djrapitops.plan.system.settings.paths.ProxySettings;
import com.djrapitops.plan.system.settings.paths.WebserverSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import rules.BungeeComponentMocker;
import rules.ComponentMocker;
import utilities.CIProperties;
import utilities.RandomData;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
/**
* Test for Bungee PlanSystem.
*
* @author Rsl1122
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class BungeeSystemTest {
@ClassRule
public static TemporaryFolder temporaryFolder = new TemporaryFolder();
private final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
@Rule
public ComponentMocker component = new BungeeComponentMocker(temporaryFolder);
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void bungeeEnables() throws Exception {
PlanSystem bungeeSystem = component.getPlanSystem();
try {
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
config.set(ProxySettings.IP, "8.8.8.8");
DBSystem dbSystem = bungeeSystem.getDatabaseSystem();
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
dbSystem.setActiveDatabase(db);
bungeeSystem.enable();
assertTrue(bungeeSystem.isEnabled());
} finally {
bungeeSystem.disable();
}
}
@Test
public void bungeeDoesNotEnableWithDefaultIP() throws Exception {
thrown.expect(EnableException.class);
thrown.expectMessage("IP setting still 0.0.0.0 - Configure AlternativeIP/IP that connects to the Proxy server.");
PlanSystem bungeeSystem = component.getPlanSystem();
try {
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
config.set(ProxySettings.IP, "0.0.0.0");
DBSystem dbSystem = bungeeSystem.getDatabaseSystem();
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
dbSystem.setActiveDatabase(db);
bungeeSystem.enable(); // Throws EnableException
} finally {
bungeeSystem.disable();
}
}
@Test
public void testEnableNoMySQL() throws EnableException {
thrown.expect(EnableException.class);
PlanSystem bungeeSystem = component.getPlanSystem();
try {
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
config.set(ProxySettings.IP, "8.8.8.8");
bungeeSystem.enable(); // Throws EnableException
} finally {
bungeeSystem.disable();
}
}
@Test
public void testEnableWithMySQL() throws EnableException {
boolean isCI = Boolean.parseBoolean(System.getenv(CIProperties.IS_CI_SERVICE));
assumeTrue(isCI);
PlanSystem bungeeSystem = component.getPlanSystem();
try {
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
config.set(DatabaseSettings.MYSQL_DATABASE, "Plan");
config.set(DatabaseSettings.MYSQL_USER, "travis");
config.set(DatabaseSettings.MYSQL_PASS, "");
config.set(DatabaseSettings.MYSQL_HOST, "127.0.0.1");
config.set(DatabaseSettings.TYPE, "MySQL");
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
config.set(ProxySettings.IP, "8.8.8.8");
bungeeSystem.enable();
assertTrue(bungeeSystem.isEnabled());
} finally {
bungeeSystem.disable();
}
}
}

View File

@ -1,57 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package rules;
import com.djrapitops.plan.DaggerPlanBungeeComponent;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.PlanBungeeComponent;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.system.PlanSystem;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import utilities.mocks.PlanBungeeMocker;
public class BungeeComponentMocker extends ExternalResource implements ComponentMocker {
private final TemporaryFolder testFolder;
private PlanBungee planMock;
private PlanBungeeComponent component;
public BungeeComponentMocker(TemporaryFolder testFolder) {
this.testFolder = testFolder;
}
@Override
protected void before() throws Throwable {
PlanBungeeMocker mocker = PlanBungeeMocker.setUp()
.withDataFolder(testFolder.newFolder())
.withPluginDescription()
.withResourceFetchingFromJar()
.withProxy();
planMock = mocker.getPlanMock();
component = DaggerPlanBungeeComponent.builder().plan(planMock).build();
}
public PlanPlugin getPlanMock() {
return planMock;
}
public PlanSystem getPlanSystem() {
return component.system();
}
}

View File

@ -1,131 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package utilities.mocks;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plugin.benchmarking.Timings;
import com.djrapitops.plugin.command.ColorScheme;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.console.TestPluginLogger;
import com.djrapitops.plugin.logging.debug.CombineDebugLogger;
import com.djrapitops.plugin.logging.debug.DebugLogger;
import com.djrapitops.plugin.logging.debug.MemoryDebugLogger;
import com.djrapitops.plugin.logging.error.ConsoleErrorLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import com.djrapitops.plugin.task.RunnableFactory;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyConfig;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.api.plugin.PluginManager;
import org.mockito.Mockito;
import utilities.TestConstants;
import utilities.mocks.objects.TestLogger;
import utilities.mocks.objects.TestRunnableFactory;
import java.io.File;
import java.util.HashSet;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
/**
* Mocking Utility for Bungee version of Plan (PlanBungee).
*
* @author Rsl1122
*/
public class PlanBungeeMocker extends Mocker {
private PlanBungee planMock;
private PlanBungeeMocker() {
}
public static PlanBungeeMocker setUp() {
return new PlanBungeeMocker().mockPlugin();
}
private PlanBungeeMocker mockPlugin() {
planMock = Mockito.mock(PlanBungee.class);
super.planMock = planMock;
doReturn(new ColorScheme("§1", "§2", "§3")).when(planMock).getColorScheme();
doReturn("1.0.0").when(planMock).getVersion();
TestLogger testLogger = new TestLogger();
RunnableFactory runnableFactory = new TestRunnableFactory();
PluginLogger testPluginLogger = new TestPluginLogger();
DebugLogger debugLogger = new CombineDebugLogger(new MemoryDebugLogger());
ErrorHandler consoleErrorLogger = new ConsoleErrorLogger(testPluginLogger);
Timings timings = new Timings(debugLogger);
doReturn(testLogger).when(planMock).getLogger();
doReturn(runnableFactory).when(planMock).getRunnableFactory();
doReturn(testPluginLogger).when(planMock).getPluginLogger();
doReturn(debugLogger).when(planMock).getDebugLogger();
doReturn(consoleErrorLogger).when(planMock).getErrorHandler();
doReturn(timings).when(planMock).getTimings();
return this;
}
public PlanBungeeMocker withDataFolder(File tempFolder) {
when(planMock.getDataFolder()).thenReturn(tempFolder);
return this;
}
public PlanBungeeMocker withResourceFetchingFromJar() throws Exception {
withPluginFiles();
return this;
}
@Deprecated
public PlanBungeeMocker withLogging() {
return this;
}
@SuppressWarnings("deprecation")
public PlanBungeeMocker withProxy() {
ProxyServer proxyMock = Mockito.mock(ProxyServer.class);
doReturn("1.12.2").when(proxyMock).getVersion();
CommandSender console = Mockito.mock(CommandSender.class);
doReturn(console).when(proxyMock).getConsole();
ProxyConfig proxyConfig = Mockito.mock(ProxyConfig.class);
doReturn(TestConstants.BUNGEE_MAX_PLAYERS).when(proxyConfig).getPlayerLimit();
doReturn(proxyConfig).when(proxyMock).getConfig();
PluginManager pm = Mockito.mock(PluginManager.class);
doReturn(pm).when(proxyMock).getPluginManager();
doReturn(proxyMock).when(planMock).getProxy();
return this;
}
public PlanBungeeMocker withPluginDescription() {
File pluginYml = getFile("/bungee.yml");
HashSet<String> empty = new HashSet<>();
PluginDescription pluginDescription = new PluginDescription("Plan", "", "9.9.9", "Rsl1122", empty, empty, pluginYml, "");
when(planMock.getDescription()).thenReturn(pluginDescription);
return this;
}
public PlanBungee getPlanMock() {
return planMock;
}
}

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name='Checker'>
<property name="localeCountry" value="EN"/>
<property name="localeLanguage" value="en"/>
<module name='TreeWalker'>
<property name='tabWidth' value='4'/>
<!-- Block Checks -->
<module name='EmptyBlock'/>
<module name='LeftCurly'/>
<module name='NeedBraces'/>
<module name='RightCurly'/>
<module name='AvoidNestedBlocks'/>
<module name="NestedIfDepth">
<property name="max" value="2"/>
</module>
<module name="NestedForDepth">
<property name="max" value="2"/>
</module>
<module name="NestedTryDepth"/>
<!-- Miscellaneous -->
<module name='Indentation'/>
<module name="OneStatementPerLine"/>
<!--- Naming Conventions -->
<module name='ClassTypeParameterName'/>
<module name='ConstantName'/>
<module name='LocalFinalVariableName'/>
<module name='LocalVariableName'/>
<module name='MemberName'/>
<module name='MethodName'/>
<module name='MethodTypeParameterName'/>
<module name='PackageName'>
<property name='format' value='^[a-z]+(\.[a-z][a-z0-9]*)*$'/>
</module>
<module name='ParameterName'/>
<module name='StaticVariableName'/>
<module name='TypeName'/>
<!-- Whitespace -->
<module name='GenericWhitespace'/>
<module name='EmptyForInitializerPad'/>
<module name='EmptyForIteratorPad'/>
<module name='MethodParamPad'/>
<module name='NoWhitespaceAfter'>
<property name='tokens' value='BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS'/>
</module>
<module name='NoWhitespaceBefore'>
<property name='tokens' value='SEMI, DOT, POST_DEC, POST_INC'/>
<property name='allowLineBreaks' value='true'/>
</module>
<module name='ParenPad'/>
<module name='TypecastParenPad'/>
<module name='WhitespaceAfter'/>
<module name='WhitespaceAround'>
<property name='allowEmptyConstructors' value='true'/>
<property name='allowEmptyMethods' value='true'/>
</module>
<!-- Javadoc -->
<module name="JavadocMethod">
<property name="scope" value="public"/>
<property name="allowMissingParamTags" value="false"/>
<property name="allowMissingPropertyJavadoc" value="true"/>
<property name="allowMissingThrowsTags" value="false"/>
<property name="allowMissingReturnTag" value="false"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
</module>
<module name="JavadocStyle">
<property name="scope" value="public"/>
<property name="checkEmptyJavadoc" value="true"/>
</module>
</module>
<!-- File Length -->
<module name='FileLength'>
<property name='max' value='2000'/>
</module>
</module>

View File

@ -1,56 +0,0 @@
dependencies {
compile "com.djrapitops:AbstractPluginFramework-api:$abstractPluginFrameworkVersion"
compile project(":api")
compile project(path: ":extensions", configuration: 'shadow')
compile "com.djrapitops:PlanPluginBridge:$planPluginBridgeVersion"
compile "org.apache.httpcomponents:httpclient:$httpClientVersion"
compile "org.apache.commons:commons-text:$commonsTextVersion"
compile "com.googlecode.htmlcompressor:htmlcompressor:$htmlCompressorVersion"
compile "com.github.ben-manes.caffeine:caffeine:$caffeineVersion"
compile "com.h2database:h2:$h2Version"
compile "mysql:mysql-connector-java:$mysqlVersion"
compile "com.zaxxer:HikariCP:$hikariVersion"
compile "org.slf4j:slf4j-nop:$slf4jVersion"
compile "org.slf4j:slf4j-api:$slf4jVersion"
compile "com.maxmind.geoip2:geoip2:$geoIpVersion"
compileOnly "com.google.guava:guava:$guavaVersion"
testCompile project(":api")
testCompile "com.google.code.gson:gson:2.8.5"
}
shadowJar {
configurations = [project.configurations.compile]
// Exclude these files
exclude "**/*.svg"
exclude "**/*.ttf"
exclude "**/*.woff"
exclude "**/*.eot"
exclude "**/*.woff2"
exclude "**/*.psd"
exclude "**/module-info.class"
exclude 'META-INF/versions/' // Causes Sponge to crash
relocate('org.apache', 'plan.org.apache') {
exclude 'org/apache/logging/**'
}
relocate 'com.maxmind', 'plan.com.maxmind'
relocate 'com.fasterxml', 'plan.com.fasterxml'
relocate 'com.zaxxer', 'plan.com.zaxxer'
relocate 'com.github.benmanes', 'plan.com.github.benmanes'
relocate 'com.googlecode', 'plan.com.googlecode'
relocate 'org.h2', 'plan.org.h2'
relocate 'org.bstats', 'plan.org.bstats'
relocate 'org.slf4j', 'plan.org.slf4j'
// Exclude test dependencies
exclude "org/junit/**/*"
exclude "org/opentest4j/**/*"
exclude "org/checkerframework/**/*"
exclude "org/apiguardian/**/*"
exclude "org/mockito/**/*"
exclude "org/selenium/**/*"
exclude "org/jayway/**/*"
}

View File

@ -1,48 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plugin.IPlugin;
import com.djrapitops.plugin.command.ColorScheme;
import java.io.File;
import java.io.InputStream;
/**
* Abstraction interface for both Plan and PlanBungee.
*
* @author Rsl1122
*/
public interface PlanPlugin extends IPlugin {
@Override
File getDataFolder();
InputStream getResource(String resource);
ColorScheme getColorScheme();
@Override
boolean isReloading();
PlanSystem getSystem();
default boolean isSystemEnabled() {
return getSystem().isEnabled();
}
}

View File

@ -1,135 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.api.exceptions.database.DBInitException;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.store.keys.SessionKeys;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.transactions.events.ServerShutdownTransaction;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
/**
* Class in charge of performing save operations when the server shuts down.
*
* @author Rsl1122
*/
public abstract class ServerShutdownSave {
protected final PluginLogger logger;
private final DBSystem dbSystem;
private final Locale locale;
private final ErrorHandler errorHandler;
private boolean shuttingDown = false;
public ServerShutdownSave(
Locale locale,
DBSystem dbSystem,
PluginLogger logger,
ErrorHandler errorHandler
) {
this.locale = locale;
this.dbSystem = dbSystem;
this.logger = logger;
this.errorHandler = errorHandler;
}
protected abstract boolean checkServerShuttingDownStatus();
public void serverIsKnownToBeShuttingDown() {
shuttingDown = true;
}
public void performSave() {
if (!checkServerShuttingDownStatus() && !shuttingDown) {
return;
}
Map<UUID, Session> activeSessions = SessionCache.getActiveSessions();
if (activeSessions.isEmpty()) {
return;
}
// This check ensures that logging is not attempted on JVM shutdown.
// Underlying Logger might not be available leading to an exception.
if (!shuttingDown) {
logger.info(locale.getString(PluginLang.DISABLED_UNSAVED_SESSIONS));
}
attemptSave(activeSessions);
SessionCache.clear();
}
private void attemptSave(Map<UUID, Session> activeSessions) {
try {
prepareSessionsForStorage(activeSessions, System.currentTimeMillis());
saveActiveSessions(activeSessions);
} catch (DBInitException e) {
errorHandler.log(L.ERROR, this.getClass(), e);
} catch (IllegalStateException ignored) {
/* Database is not initialized */
} finally {
closeDatabase(dbSystem.getDatabase());
}
}
private void saveActiveSessions(Map<UUID, Session> activeSessions) {
Database database = dbSystem.getDatabase();
if (database.getState() == Database.State.CLOSED) {
// Ensure that database is not closed when performing the transaction.
database.init();
}
saveSessions(activeSessions, database);
}
private void prepareSessionsForStorage(Map<UUID, Session> activeSessions, long now) {
for (Session session : activeSessions.values()) {
Optional<Long> end = session.getValue(SessionKeys.END);
if (!end.isPresent()) {
session.endSession(now);
}
}
}
private void saveSessions(Map<UUID, Session> activeSessions, Database database) {
try {
database.executeTransaction(new ServerShutdownTransaction(activeSessions.values()))
.get(); // Ensure that the transaction is executed before shutdown.
} catch (ExecutionException | DBOpException e) {
errorHandler.log(L.ERROR, this.getClass(), e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void closeDatabase(Database database) {
database.close();
}
}

View File

@ -1,67 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Thread that is run when JVM shuts down.
* <p>
* Saves active sessions to the Database (PlayerQuitEvent is not called)
*
* @author Rsl1122
*/
@Singleton
public class ShutdownHook extends Thread {
private static ShutdownHook activated;
private final ServerShutdownSave serverShutdownSave;
@Inject
public ShutdownHook(ServerShutdownSave serverShutdownSave) {
this.serverShutdownSave = serverShutdownSave;
}
private static boolean isActivated() {
return activated != null;
}
private static void activate(ShutdownHook hook) {
activated = hook;
Runtime.getRuntime().addShutdownHook(hook);
}
private static void deactivate() {
Runtime.getRuntime().removeShutdownHook(activated);
activated = null;
}
public void register() {
if (isActivated()) {
deactivate();
}
activate(this);
}
@Override
public void run() {
serverShutdownSave.serverIsKnownToBeShuttingDown();
serverShutdownSave.performSave();
}
}

View File

@ -1,135 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.api;
import com.djrapitops.plan.api.data.PlayerContainer;
import com.djrapitops.plan.api.data.ServerContainer;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.data.plugin.HookHandler;
import com.djrapitops.plan.data.plugin.PluginData;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.queries.containers.ContainerFetchQueries;
import com.djrapitops.plan.db.access.queries.objects.ServerQueries;
import com.djrapitops.plan.db.access.queries.objects.UserIdentifierQueries;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.database.databases.operation.FetchOperations;
import com.djrapitops.plan.system.database.databases.sql.operation.SQLFetchOps;
import com.djrapitops.plan.utilities.uuid.UUIDUtility;
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.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* PlanAPI extension for all implementations.
*
* @author Rsl1122
*/
@Singleton
public class CommonAPI implements PlanAPI {
private final DBSystem dbSystem;
private final UUIDUtility uuidUtility;
private final HookHandler hookHandler;
private final PluginLogger logger;
private final ErrorHandler errorHandler;
@Inject
public CommonAPI(
DBSystem dbSystem,
UUIDUtility uuidUtility,
HookHandler hookHandler,
PluginLogger logger,
ErrorHandler errorHandler
) {
this.dbSystem = dbSystem;
this.uuidUtility = uuidUtility;
this.hookHandler = hookHandler;
this.logger = logger;
this.errorHandler = errorHandler;
PlanAPIHolder.set(this);
}
@Override
public void addPluginDataSource(PluginData pluginData) {
hookHandler.addPluginDataSource(pluginData);
}
@Override
public String getPlayerInspectPageLink(UUID uuid) {
return getPlayerInspectPageLink(getPlayerName(uuid));
}
@Override
public String getPlayerInspectPageLink(String playerName) {
return "../player/" + playerName;
}
@Override
public UUID playerNameToUUID(String playerName) {
return uuidUtility.getUUIDOf(playerName);
}
@Override
public Map<UUID, String> getKnownPlayerNames() {
try {
return queryDB(UserIdentifierQueries.fetchAllPlayerNames());
} catch (DBOpException e) {
errorHandler.log(L.ERROR, this.getClass(), e);
return new HashMap<>();
}
}
@Override
public PlayerContainer fetchPlayerContainer(UUID uuid) {
return new PlayerContainer(queryDB(ContainerFetchQueries.fetchPlayerContainer(uuid)));
}
@Override
public ServerContainer fetchServerContainer(UUID serverUUID) {
return new ServerContainer(queryDB(ContainerFetchQueries.fetchServerContainer(serverUUID)));
}
@Override
public Collection<UUID> fetchServerUUIDs() {
return queryDB(ServerQueries.fetchPlanServerInformation()).keySet();
}
@Override
public String getPlayerName(UUID playerUUID) {
return queryDB(UserIdentifierQueries.fetchPlayerNameOf(playerUUID)).orElse(null);
}
@Override
public FetchOperations fetchFromPlanDB() {
logger.warn("PlanAPI#fetchFromPlanDB has been deprecated and will be removed in the future. Stack trace to follow");
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
logger.warn(element.toString());
}
return new SQLFetchOps(dbSystem.getDatabase());
}
private <T> T queryDB(Query<T> query) {
return dbSystem.getDatabase().query(query);
}
}

View File

@ -1,104 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.api;
import com.djrapitops.plan.api.data.PlayerContainer;
import com.djrapitops.plan.api.data.ServerContainer;
import com.djrapitops.plan.data.plugin.PluginData;
import com.djrapitops.plan.system.database.databases.operation.FetchOperations;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Interface for PlanAPI methods.
*
* @author Rsl1122
*/
public interface PlanAPI {
static PlanAPI getInstance() {
return Optional.ofNullable(PlanAPIHolder.API)
.orElseThrow(() -> new IllegalStateException("PlanAPI has not been initialised yet."));
}
class PlanAPIHolder {
static PlanAPI API;
static void set(PlanAPI api) {
PlanAPIHolder.API = api;
}
private PlanAPIHolder() {
/* Static variable holder */
}
}
/**
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
*/
@Deprecated
void addPluginDataSource(PluginData pluginData);
String getPlayerInspectPageLink(UUID uuid);
String getPlayerInspectPageLink(String playerName);
String getPlayerName(UUID uuid);
UUID playerNameToUUID(String playerName);
Map<UUID, String> getKnownPlayerNames();
/**
* Fetch things from the database.
*
* @return FetchOperations object.
* @deprecated FetchOperations interface is going to removed since it is too rigid.
*/
@Deprecated
FetchOperations fetchFromPlanDB();
/**
* Fetch PlayerContainer from the database.
* <p>
* Blocking operation.
*
* @param uuid UUID of the player.
* @return a {@link PlayerContainer}.
*/
PlayerContainer fetchPlayerContainer(UUID uuid);
/**
* Fetch a ServerContainer from the database.
* <p>
* Blocking operation.
*
* @param serverUUID UUID of the server.
* @return a {@link ServerContainer}.
*/
ServerContainer fetchServerContainer(UUID serverUUID);
/**
* Fetch server UUIDs.
*
* @return All Plan server UUIDs.
*/
Collection<UUID> fetchServerUUIDs();
}

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