Verified Commit 5e86232e authored by Yannick Schinko's avatar Yannick Schinko
Browse files

Added common code

Just a basic version so far.
Mostly just stripped down from AuraBan
parent eee54fe0
......@@ -9,10 +9,6 @@ configurations {
}
repositories {
maven {
name "mikroskeem"
url "https://repo.wut.ee/repository/mikroskeem-repo"
}
maven {
name "sponge"
url "https://repo.spongepowered.org/maven"
......@@ -27,10 +23,9 @@ dependencies {
}
// Normal dependencies
api "org.slf4j:slf4j-api:${slf4j_version}"
api "com.github.ben-manes.caffeine:caffeine:${caffeine_version}"
api "org.spongepowered:configurate-hocon:${configurate_version}"
api "com.h2database:h2:${h2_version}"
api "com.zaxxer:HikariCP:${hikariCP_version}"
// Shadow dependencies
shadow("team.aura_dev.lib.multiplatformcore:MultiPlatformCore:${multiPlatformCore_version}") {
......@@ -38,8 +33,6 @@ dependencies {
}
// Test dependencies
testImplementation "ch.vorburger.mariaDB4j:mariaDB4j:2.4.0"
testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:2.5.1"
testRuntimeOnly "org.slf4j:slf4j-nop:${slf4j_version}"
// SLF4J files
......
......@@ -12,8 +12,6 @@ useRootValues=true
caffeine_version=2.8.0
configurate_version=3.6.1
h2_version=1.4.200
hikariCP_version=3.4.1
multiPlatformCore_version=1.2.1.+
slf4j_version=1.7.25
......
package team.aura_dev.aurasudo.platform.common;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import lombok.Getter;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import team.aura_dev.aurasudo.api.AuraSudoApi;
import team.aura_dev.aurasudo.platform.common.config.ConfigLoader;
import team.aura_dev.aurasudo.platform.common.dependency.RuntimeDependencies;
import team.aura_dev.aurasudo.platform.common.player.PlayerManagerCommon;
import team.aura_dev.lib.multiplatformcore.DependencyClassLoader;
import team.aura_dev.lib.multiplatformcore.dependency.RuntimeDependency;
import team.aura_dev.lib.multiplatformcore.download.DependencyDownloader;
@SuppressFBWarnings(
value = {"JLM_JSR166_UTILCONCURRENT_MONITORENTER", "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"},
justification = "Code is generated by lombok which means I don't have any influence on it.")
public abstract class AuraSudoBase implements AuraSudoApi, AuraSudoBaseBootstrap {
public static final Logger logger = LoggerFactory.getLogger(NAME);
@Getter private static AuraSudoBase instance = null;
@Getter protected PlayerManagerCommon playerManager;
@Getter protected final DependencyClassLoader classLoader;
@Getter protected final Path configDir;
@Getter protected final Path libsDir;
@Getter protected final DependencyDownloader dependencyDownloader;
@Getter(lazy = true)
private final List<String> asciiBanner = generateAsciiBanner();
protected ConfigLoader configLoader;
protected AuraSudoBase(DependencyClassLoader classLoader, Path configDir) {
if (instance != null) {
throw new IllegalStateException("AuraBan has already been initialized!");
}
instance = this;
this.classLoader = classLoader;
this.configDir = configDir;
this.libsDir = configDir.resolve("libs");
this.dependencyDownloader = new DependencyDownloader(classLoader, libsDir);
}
public abstract String getBasePlatform();
public abstract String getPlatformVariant();
public String getFullPlatform() {
return getBasePlatform() + " - " + getPlatformVariant();
}
public Path getConfigFile() {
return getConfigDir().resolve(ID + ".conf");
}
public Collection<RuntimeDependency> getEarlyDependencies() {
final List<RuntimeDependency> dependencies = new LinkedList<>();
// We need Configurate for the config
dependencies.add(RuntimeDependencies.CONFIGURATE_HOCON);
// We don't need to download dependencies already present
dependencies.removeAll(getPlatformDependencies());
return dependencies;
}
public Collection<RuntimeDependency> getDependencies() {
final List<RuntimeDependency> dependencies = new LinkedList<>();
// We need caffeine as a loading cache in several classes
dependencies.add(RuntimeDependencies.CAFFEINE);
// We don't need to download dependencies already present
dependencies.removeAll(getPlatformDependencies());
return dependencies;
}
/**
* This method returns a {@link Collection} of all the dependencies that are already present on
* the target platform.<br>
* This allows making sure that they are not downloaded unnecessarily.
*
* @return a {@link Collection} of already present dependencies
*/
public Collection<RuntimeDependency> getPlatformDependencies() {
return Collections.emptyList();
}
protected abstract PlayerManagerCommon generatePlayerManager();
protected abstract void registerEventListeners();
// ============================================================================================
// Actual plugin functionality starts here
// ============================================================================================
@SneakyThrows
public final void preInitPlugin() {
logger.info("Preinitializing " + NAME + " Version " + VERSION);
if (VERSION.contains("SNAPSHOT")) {
logger.warn("WARNING! This is a snapshot version!");
logger.warn("Use at your own risk!");
} else if (VERSION.contains("DEV")) {
logger.info("This is a unreleased development version!");
logger.info("Things might not work properly!");
}
logger.info("Downloading early dependencies");
dependencyDownloader.downloadAndInjectInClasspath(getEarlyDependencies());
configLoader = new ConfigLoader(this);
configLoader.loadConfig();
}
@SneakyThrows
public final void initPlugin() {
if (configLoader.getConfig().getGeneral().getBannerEnabled()) {
// Get logger without name to nicely print the banner
final Logger bannerLogger = LoggerFactory.getLogger("");
// Print ASCII banner
getAsciiBanner().forEach(bannerLogger::info);
}
logger.info("Initializing " + NAME + " Version " + VERSION);
logger.info("Downloading dependencies");
dependencyDownloader.downloadAndInjectInClasspath(getDependencies());
this.playerManager = generatePlayerManager();
logger.info("Registering Event Listeners");
registerEventListeners();
// TODO
}
// Private helper methods
// Generated with http://www.patorjk.com/software/taag/#p=display&f=Straight&t=AuraSudo
private List<String> generateAsciiBanner() {
return Arrays.asList(
" §9 §6 __",
" §9 /\\ _ _ §6(_ _| _ §9AuraSudo §6v" + VERSION,
" §9/--\\|_|| (_|§6__)|_|(_|(_) §8Proudly running on " + getFullPlatform(),
"");
}
}
package team.aura_dev.aurasudo.platform.common;
public interface AuraSudoBaseBootstrap {
public void preInitPlugin();
public void initPlugin();
}
package team.aura_dev.aurasudo.platform.common;
import java.nio.file.Path;
import team.aura_dev.lib.multiplatformcore.MultiProjectSLF4JBootstrapper;
public class AuraSudoBootstrapper extends MultiProjectSLF4JBootstrapper<AuraSudoBaseBootstrap> {
public static final String ID = "@id@";
public static final String NAME = "@name@";
public static final String VERSION = "@version@";
public static final String DESCRIPTION = "@description@";
public static final String URL = "https://github.com/AuraDevelopmentTeam/AuraSudo";
public static final String AUTHOR = "The_BrainStone";
public AuraSudoBootstrapper() {
super(AuraSudoBaseBootstrap.class);
}
@Override
protected String getPackageName() {
return "@group@";
}
/**
* Checks if SLF4J is present and loads it if not.<br>
* {@code slf4jVersion} defaults to @slf4jVersion@
*
* @param libsPath Where to unpack the jar files to
* @param type Which type of the slf4j-plugin-xxx to use
* @see #checkAndLoadSLF4J(Path, String, String)
*/
public void checkAndLoadSLF4JPlugin(Path libsPath, String type) {
checkAndLoadSLF4J(libsPath, "@slf4jVersion@", "plugin-" + type + "-@slf4jPluginVersion@");
}
}
package team.aura_dev.aurasudo.platform.common.config;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import ninja.leaping.configurate.objectmapping.Setting;
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
@ConfigSerializable
@Getter
public class Config {
@Setting private General general = new General();
@Setting private Commands command = new Commands();
@ConfigSerializable
@Getter
public static class General {
@Setting(comment = "Whether to show the colorful startup banner or not")
private boolean bannerEnabled = true;
}
@ConfigSerializable
@Getter
public static class Commands {
@Setting private Sudo sudo = new Sudo();
@ConfigSerializable
@Getter
public static class Sudo {
@Setting(comment = "A list of aliases for this command.")
private List<String> aliases = Arrays.asList("sudo", "adminmode", "staffmode", "am", "sm");
}
}
}
package team.aura_dev.aurasudo.platform.common.config;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.reflect.TypeToken;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.NonNull;
import ninja.leaping.configurate.ConfigurationOptions;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.loader.HeaderMode;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import team.aura_dev.aurasudo.platform.common.AuraSudoBase;
/** This class is required because it references Configurate classes, which are loaded later. */
public class ConfigLoader {
private final AuraSudoBase plugin;
private ConfigurationLoader<CommentedConfigurationNode> loader;
@Getter @NonNull private Config config;
public ConfigLoader(AuraSudoBase plugin) {
this.plugin = plugin;
this.loader = getConfigLoader();
}
private ConfigurationLoader<CommentedConfigurationNode> getConfigLoader() {
return HoconConfigurationLoader.builder()
.setDefaultOptions(
ConfigurationOptions.defaults()
.setHeader(
"######################################################################################################################### #\n"
+ "+-----------------------------------------------------------------------------------------------------------------------+ #\n"
+ getBanner()
+ "| | #\n"
+ "| Source Code: https://github.com/AuraDevelopmentTeam/AuraSudo/ | #\n"
+ "| Bug Reports: https://github.com/AuraDevelopmentTeam/AuraSudo/issues | #\n"
+ "| Wiki: https://aura-dev.team/documentation/AuraSudo | #\n"
+ "| | #\n"
+ "| New options ARE added to this file automatically. Removing a setting will have it be regenerated with its default | #\n"
+ "| value. This config format (HOCON: https://github.com/lightbend/config/blob/master/HOCON.md) is very lenient, so | #\n"
+ "| so don't worry about messing up the formatting or anything. After every reload the config will be nice and | #\n"
+ "| formatted. You should be able to find existing highlighter settings for your favorite text editor, so you can have | #\n"
+ "| nice syntax highlighing like for other config formats like YAML, JSON or TOML. | #\n"
+ "+-----------------------------------------------------------------------------------------------------------------------+ #\n"
+ "######################################################################################################################### #"))
.setHeaderMode(HeaderMode.PRESET)
// These two are commented out because those are the defaults anyways. Additionally
// SpongeForge relocates HOCON classes, so we can't reference them directly anyways.
//
// .setRenderOptions(ConfigRenderOptions.defaults().setOriginComments(false).setJson(false))
// .setParseOptions(ConfigParseOptions.defaults())
.setPath(plugin.getConfigFile())
.build();
}
/**
* This method takes the plugin banner, removes the color codes, pads it left and right with
* spaces and lastly adds the banner box ASCII art borders to it, so it fits in the block up
* above.<br>
* This is done so that we can have the dynamic banner in the config.
*
* <p>While this sounds a fair bit simpler than it actually is this method is quite long and
* complex.<br>
* Feel free to try to improve it if it bothers you.
*
* @return A version of the plugin banner suitable for the config.
*/
@VisibleForTesting
String getBanner() {
// Constants
final int lineLength = 115;
final char[] spaces = new char[lineLength];
Arrays.fill(spaces, ' ');
final Pattern colorCode = Pattern.compile("§[0-9a-fk-or]", Pattern.CASE_INSENSITIVE);
// Variables
List<String> bannerLines = new ArrayList<>(plugin.getAsciiBanner());
int maxLength = 0;
String result;
StringBuilder builder;
// First pass:
// Remove color codes
for (int i = 0; i < bannerLines.size(); ++i) {
result = colorCode.matcher(bannerLines.get(i)).replaceAll("");
bannerLines.set(i, result);
if (maxLength < result.length()) {
maxLength = result.length();
}
}
// The banner starts with two spaces, let's use this to compensate
maxLength += 2;
// Store these values, so we don't have to calculate them again
final int remainingLength = lineLength - maxLength;
final int paddingBefore = remainingLength / 2;
final int paddingAfter = remainingLength - paddingBefore;
// Second pass:
// Add space paddings
for (int i = 0; i < bannerLines.size(); ++i) {
result = bannerLines.get(i);
builder =
new StringBuilder(lineLength)
.append(spaces, 0, paddingBefore)
.append(result)
.append(spaces, 0, maxLength - result.length())
.append(spaces, 0, paddingAfter);
bannerLines.set(i, builder.toString());
}
// Join the string
final StringJoiner joiner = new StringJoiner(" | #\n| ", "| ", " | #\n");
for (final String line : bannerLines) {
joiner.add(line);
}
return joiner.toString();
}
public void loadConfig() throws IOException, ObjectMappingException {
final TypeToken<Config> configToken = TypeToken.of(Config.class);
AuraSudoBase.logger.debug("Loading config...");
CommentedConfigurationNode node = loader.load();
config = node.<Config>getValue(configToken, Config::new);
AuraSudoBase.logger.debug("Saving/Formatting config...");
node.setValue(configToken, config);
loader.save(node);
}
}
package team.aura_dev.aurasudo.platform.common.dependency;
import lombok.experimental.UtilityClass;
import team.aura_dev.lib.multiplatformcore.dependency.RuntimeDependency;
import team.aura_dev.lib.multiplatformcore.dependency.RuntimeDependency.Maven;
@UtilityClass
public class RuntimeDependencies {
////////////////////////////////////////////////////////
// Config
////////////////////////////////////////////////////////
public static final RuntimeDependency CONFIGURATE_HOCON =
RuntimeDependency.builder(
"org.spongepowered",
"configurate-hocon",
"3.6.1",
"6395403afce7b9bbf4e26ef74c13da9a",
"e3f199dbd91de753a70f63606f530fdb8644bbd5")
.maven(Maven.SPONGE)
.transitive()
.exclusion("com.google.code.findbugs:jsr305")
.exclusion("com.google.errorprone:error_prone_annotations")
.exclusion("com.google.j2objc:j2objc-annotations")
// org.codehaus.mojo gets relocated. That's why we need to make sure we don't have a
// literal "org.codehaus.mojo" in any strings
.exclusion("org.Codehaus.mojo:animal-sniffer-annotations".toLowerCase())
.build();
////////////////////////////////////////////////////////
// Utilities
////////////////////////////////////////////////////////
public static final RuntimeDependency CAFFEINE =
RuntimeDependency.builder(
"com.github.ben-manes.caffeine",
"caffeine",
"2.8.0",
"d6dbff7e409b1c2ad88930e2c220ea13",
"6000774d7f8412ced005a704188ced78beeed2bb")
.build();
}
config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
package team.aura_dev.aurasudo.platform.common.player;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.UUID;
import javax.annotation.Nonnull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import team.aura_dev.aurasudo.api.player.PlayerData;
/**
* Simple class to represent players in a platform independent way.
*
* <p>If the platform offers a way to display a display name (like with added prefixes/suffixes or a
* nickname) then it needs to override this class.
*/
@SuppressFBWarnings(
value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE",
justification = "Code generated by lombok.")
@Data
@Getter(onMethod = @__({@Nonnull}))
@EqualsAndHashCode(of = "uuid")
public class PlayerDataCommon implements PlayerData {
@NonNull protected final UUID uuid;
@NonNull protected final String playerName;
@NonNull protected boolean sudoActive = false;
/**
* A nice name for the player.<br>
* Can be overridden to allow showing of prefixes and nicknames.
*
* @return a nicer variant of the player name
*/
@Override
@Nonnull
public String getDisplayName() {
return playerName;
}
@Override
public boolean getSudoActive() {
return sudoActive;
}
public void setSudoActive(boolean active) {
sudoActive = active;
}
}
package team.aura_dev.aurasudo.platform.common.player;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import lombok.Data;
import lombok.NonNull;
import team.aura_dev.aurasudo.api.player.PlayerData;
import team.aura_dev.aurasudo.api.player.PlayerManager;
public abstract class PlayerManagerCommon implements PlayerManager {
protected final LoadingCache<UUID, Optional<PlayerData>> playerCache;
protected PlayerManagerCommon() {
playerCache =
Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(this::generatePlayerData);
}
protected abstract Optional<PlayerData> generatePlayerData(@Nonnull @NonNull UUID uuid);
@SuppressFBWarnings(
value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
justification = "SpotBugs is incorrect in this case")
@Override
public Optional<PlayerData> getPlayerData(@Nonnull @NonNull UUID uuid) {
return playerCache.get(uuid);
}
@Override
public PlayerData fromNativePlayer(@Nonnull @NonNull Object player)
throws IllegalArgumentException {
final BasePlayerData playerData = nativePlayerToBasePlayerData(player);