diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 52b99bde..731e74af 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -7,6 +7,5 @@ dependencies { tasks { shadowJar { relocate ("de.tr7zw.changeme", "net.momirealms.customfishing.libraries") - relocate ("de.tr7zw.annotations", "net.momirealms.customfishing.libraries.annotations") } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java index f9685e19..1e5d7b1d 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java @@ -34,12 +34,12 @@ public interface FishingCompetition { /** * Stop the fishing competition */ - void stop(); + void stop(boolean triggerEvent); /** * End the fishing competition */ - void end(); + void end(boolean triggerEvent); /** * Check if the fishing competition is ongoing. diff --git a/build.gradle.kts b/build.gradle.kts index ee3cd87f..f489feb5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { - version = "2.0.12.3" + version = "2.1.0" apply() apply(plugin = "java") diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 51899ca4..a3568dfd 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -2,17 +2,23 @@ dependencies { // server compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT") + // packet + compileOnly("com.comphenix.protocol:ProtocolLib:5.1.0") + // command compileOnly("dev.jorel:commandapi-bukkit-core:9.3.0") - // packet - compileOnly("com.comphenix.protocol:ProtocolLib:5.1.0") + // nbt + compileOnly("de.tr7zw:item-nbt-api:2.12.2") + + // bStats + compileOnly("org.bstats:bstats-bukkit:3.0.2") // papi compileOnly("me.clip:placeholderapi:2.11.5") // config - compileOnly("dev.dejvokep:boosted-yaml:1.3.1") + compileOnly("dev.dejvokep:boosted-yaml:1.3.2") // mythic compileOnly("io.lumine:Mythic-Dist:5.3.5") @@ -57,19 +63,13 @@ dependencies { compileOnly(files("libs/zaphkiel-2.0.24.jar")) // api module - implementation(project(":api")) + implementation(project(":api")) { + exclude("de.tr7zw") + } // adventure implementation("net.kyori:adventure-api:4.15.0") - implementation("net.kyori:adventure-platform-bukkit:4.3.1") - implementation("net.kyori:adventure-text-minimessage:4.15.0") - implementation("net.kyori:adventure-text-serializer-legacy:4.15.0") - - // nbt - implementation("de.tr7zw:item-nbt-api:2.12.2") - - // bStats - implementation("org.bstats:bstats-bukkit:3.0.2") + implementation("net.kyori:adventure-platform-bukkit:4.3.2") // local lib implementation(files("libs/BiomeAPI.jar")) @@ -77,9 +77,18 @@ dependencies { tasks { shadowJar { - relocate ("de.tr7zw.changeme", "net.momirealms.customfishing.libraries") - relocate ("de.tr7zw.annotations", "net.momirealms.customfishing.libraries.annotations") + relocate ("org.apache.commons.pool2", "net.momirealms.customfishing.libraries.commonspool2") + relocate ("com.mysql", "net.momirealms.customfishing.libraries.mysql") + relocate ("org.mariadb", "net.momirealms.customfishing.libraries.mariadb") + relocate ("com.zaxxer.hikari", "net.momirealms.customfishing.libraries.hikari") + relocate ("redis.clients.jedis", "net.momirealms.customfishing.libraries.jedis") + relocate ("com.mongodb", "net.momirealms.customfishing.libraries.mongodb") + relocate ("org.bson", "net.momirealms.customfishing.libraries.bson") + relocate ("net.objecthunter.exp4j", "net.momirealms.customfishing.libraries.exp4j") + relocate ("de.tr7zw.changeme", "net.momirealms.customfishing.libraries.changeme") relocate ("net.kyori", "net.momirealms.customfishing.libraries") + relocate ("dev.jorel.commandapi", "net.momirealms.customfishing.libraries.commandapi") + relocate ("dev.dejvokep.boostedyaml", "net.momirealms.customfishing.libraries.boostedyaml") relocate ("org.bstats", "net.momirealms.customfishing.libraries.bstats") relocate ("net.momirealms.biomeapi", "net.momirealms.customfishing.libraries.biomeapi") } diff --git a/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java b/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java index f20d3016..9e12b342 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java @@ -31,7 +31,10 @@ import net.momirealms.customfishing.api.util.ReflectionUtils; import net.momirealms.customfishing.command.CommandManagerImpl; import net.momirealms.customfishing.compatibility.IntegrationManagerImpl; import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.libraries.libraryloader.LibraryLoader; +import net.momirealms.customfishing.libraries.classpath.ReflectionClassPathAppender; +import net.momirealms.customfishing.libraries.dependencies.Dependency; +import net.momirealms.customfishing.libraries.dependencies.DependencyManager; +import net.momirealms.customfishing.libraries.dependencies.DependencyManagerImpl; import net.momirealms.customfishing.mechanic.action.ActionManagerImpl; import net.momirealms.customfishing.mechanic.bag.BagManagerImpl; import net.momirealms.customfishing.mechanic.block.BlockManagerImpl; @@ -66,13 +69,13 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.TimeZone; public class CustomFishingPluginImpl extends CustomFishingPlugin { private static ProtocolManager protocolManager; private CoolDownManager coolDownManager; private ChatCatcherManager chatCatcherManager; + private DependencyManager dependencyManager; public CustomFishingPluginImpl() { super(); @@ -80,7 +83,32 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin { @Override public void onLoad() { - this.loadDependencies(); + this.dependencyManager = new DependencyManagerImpl(this, new ReflectionClassPathAppender(this.getClassLoader())); + this.dependencyManager.loadDependencies(new ArrayList<>( + List.of( + Dependency.GSON, + Dependency.SLF4J_API, + Dependency.SLF4J_SIMPLE, + Dependency.COMMAND_API, + Dependency.BOOSTED_YAML, + Dependency.ADVENTURE_TEXT_MINIMESSAGE, + Dependency.ADVENTURE_LEGACY_SERIALIZER, + Dependency.NBT_API, + Dependency.EXP4J, + Dependency.MYSQL_DRIVER, + Dependency.MARIADB_DRIVER, + Dependency.MONGODB_DRIVER_SYNC, + Dependency.MONGODB_DRIVER_CORE, + Dependency.MONGODB_DRIVER_BSON, + Dependency.JEDIS, + Dependency.COMMONS_POOL_2, + Dependency.H2_DRIVER, + Dependency.SQLITE_DRIVER, + Dependency.BSTATS_BASE, + Dependency.HIKARI, + Dependency.BSTATS_BUKKIT + ) + )); } @Override @@ -208,44 +236,44 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin { * Load plugin dependencies */ private void loadDependencies() { - String mavenRepo = TimeZone.getDefault().getID().startsWith("Asia") ? - "https://maven.aliyun.com/repository/public/" : "https://repo.maven.apache.org/maven2/"; - LibraryLoader.loadDependencies( - "org.apache.commons:commons-pool2:2.12.0", mavenRepo, - "redis.clients:jedis:5.1.0", mavenRepo, - "dev.dejvokep:boosted-yaml:1.3.1", mavenRepo, - "com.zaxxer:HikariCP:5.0.1", mavenRepo, - "net.objecthunter:exp4j:0.4.8", mavenRepo, - "org.mariadb.jdbc:mariadb-java-client:3.3.2", mavenRepo, - "com.mysql:mysql-connector-j:8.3.0", mavenRepo, - "commons-io:commons-io:2.14.0", mavenRepo, - "com.google.code.gson:gson:2.10.1", mavenRepo, - "com.h2database:h2:2.2.224", mavenRepo, - "org.mongodb:mongodb-driver-sync:4.11.1", mavenRepo, - "org.mongodb:mongodb-driver-core:4.11.1", mavenRepo, - "org.mongodb:bson:4.11.1", mavenRepo, - "org.xerial:sqlite-jdbc:3.45.1.0", mavenRepo, - "dev.jorel:commandapi-bukkit-shade:9.3.0", mavenRepo - ); +// String mavenRepo = TimeZone.getDefault().getID().startsWith("Asia") ? +// "https://maven.aliyun.com/repository/public/" : "https://repo.maven.apache.org/maven2/"; +// LibraryLoader.loadDependencies( +// "org.apache.commons:commons-pool2:2.12.0", mavenRepo, +// "redis.clients:jedis:5.1.0", mavenRepo, +// "dev.dejvokep:boosted-yaml:1.3.2", mavenRepo, +// "com.zaxxer:HikariCP:5.0.1", mavenRepo, +// "net.objecthunter:exp4j:0.4.8", mavenRepo, +// "org.mariadb.jdbc:mariadb-java-client:3.3.2", mavenRepo, +// "com.mysql:mysql-connector-j:8.3.0", mavenRepo, +// "commons-io:commons-io:2.14.0", mavenRepo, +// "com.google.code.gson:gson:2.10.1", mavenRepo, +// "com.h2database:h2:2.2.224", mavenRepo, +// "org.mongodb:mongodb-driver-sync:4.11.1", mavenRepo, +// "org.mongodb:mongodb-driver-core:4.11.1", mavenRepo, +// "org.mongodb:bson:4.11.1", mavenRepo, +// "org.xerial:sqlite-jdbc:3.45.1.0", mavenRepo, +// "dev.jorel:commandapi-bukkit-shade:9.3.0", mavenRepo +// ); - String version = getServer().getClass().getPackage().getName().split("\\.")[3]; - String artifact = ""; - switch (version) { - case "v1_17_R1" -> artifact = "r9"; - case "v1_18_R1" -> artifact = "r10"; - case "v1_18_R2" -> artifact = "r11"; - case "v1_19_R1" -> artifact = "r12"; - case "v1_19_R2" -> artifact = "r13"; - case "v1_19_R3" -> artifact = "r15"; - case "v1_20_R1" -> artifact = "r16"; - case "v1_20_R2" -> artifact = "r17"; - case "v1_20_R3" -> artifact = "r18"; - } - LibraryLoader.loadDependencies( - "xyz.xenondevs.invui:invui-core:1.25", "https://repo.xenondevs.xyz/releases/", - "xyz.xenondevs.invui:inventory-access:1.25", "https://repo.xenondevs.xyz/releases/", - String.format("xyz.xenondevs.invui:inventory-access-%s:1.25", artifact), "https://repo.xenondevs.xyz/releases/" - ); +// String version = getServer().getClass().getPackage().getName().split("\\.")[3]; +// String artifact = ""; +// switch (version) { +// case "v1_17_R1" -> artifact = "r9"; +// case "v1_18_R1" -> artifact = "r10"; +// case "v1_18_R2" -> artifact = "r11"; +// case "v1_19_R1" -> artifact = "r12"; +// case "v1_19_R2" -> artifact = "r13"; +// case "v1_19_R3" -> artifact = "r15"; +// case "v1_20_R1" -> artifact = "r16"; +// case "v1_20_R2" -> artifact = "r17"; +// case "v1_20_R3" -> artifact = "r18"; +// } +// LibraryLoader.loadDependencies( +// "xyz.xenondevs.invui:invui-core:1.25", "https://repo.xenondevs.xyz/releases/", +// "xyz.xenondevs.invui:inventory-access:1.25", "https://repo.xenondevs.xyz/releases/", +// String.format("xyz.xenondevs.invui:inventory-access-%s:1.25", artifact), "https://repo.xenondevs.xyz/releases/" +// ); } /** @@ -337,6 +365,10 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin { return chatCatcherManager; } + public DependencyManager getDependencyManager() { + return dependencyManager; + } + /** * Retrieves the ProtocolManager instance used for managing packets. * diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java index eae79a6d..86e23515 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java @@ -64,7 +64,6 @@ public class CommandManagerImpl implements CommandManager { StatisticsCommand.INSTANCE.getStatisticsCommand() ) .register(); - if (plugin.getMarketManager().isEnable()) { new CommandAPICommand("sellfish") .withPermission("customfishing.sellfish") @@ -74,7 +73,6 @@ public class CommandManagerImpl implements CommandManager { }) .register(); } - if (plugin.getBagManager().isEnabled()) { FishingBagCommand.INSTANCE.getBagCommand().register(); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java index c7089acf..75df733f 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java +++ b/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java @@ -79,7 +79,7 @@ public class CompetitionCommand { } else { FishingCompetition competition = CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition(); if (competition != null) { - CustomFishingPlugin.get().getScheduler().runTaskAsync(competition::end); + CustomFishingPlugin.get().getScheduler().runTaskAsync(() -> competition.end(true)); AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_End_Competition); } else { AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_No_Competition_Ongoing); @@ -99,7 +99,9 @@ public class CompetitionCommand { } else { FishingCompetition competition = CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition(); if (competition != null) { - CustomFishingPlugin.get().getScheduler().runTaskAsync(competition::stop); + CustomFishingPlugin.get().getScheduler().runTaskAsync(() -> { + competition.stop(true); + }); AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Stop_Competition); } else { AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_No_Competition_Ongoing); diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java index 4a13b90b..a757e5f7 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java +++ b/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java @@ -19,8 +19,6 @@ package net.momirealms.customfishing.gui.page.file; import net.momirealms.customfishing.adventure.AdventureManagerImpl; import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.Icon; import net.momirealms.customfishing.gui.icon.BackGroundItem; import net.momirealms.customfishing.gui.icon.BackToFolderItem; import net.momirealms.customfishing.gui.icon.ScrollDownItem; @@ -32,10 +30,8 @@ import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.animation.impl.SequentialAnimation; import xyz.xenondevs.invui.gui.Gui; import xyz.xenondevs.invui.gui.ScrollGui; -import xyz.xenondevs.invui.gui.SlotElement; import xyz.xenondevs.invui.gui.structure.Markers; import xyz.xenondevs.invui.item.Item; import xyz.xenondevs.invui.item.ItemProvider; diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/MavenLibraries.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ClassPathAppender.java similarity index 73% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/MavenLibraries.java rename to plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ClassPathAppender.java index 7179e34f..601853a7 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/MavenLibraries.java +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ClassPathAppender.java @@ -1,5 +1,5 @@ /* - * This file is part of helper, licensed under the MIT License. + * This file is part of LuckPerms, licensed under the MIT License. * * Copyright (c) lucko (Luck) * Copyright (c) contributors @@ -23,21 +23,19 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.libraryloader; +package net.momirealms.customfishing.libraries.classpath; -import org.jetbrains.annotations.NotNull; - -import java.lang.annotation.*; +import java.nio.file.Path; /** - * Annotation to indicate the required libraries for a class. + * Interface which allows access to add URLs to the plugin classpath at runtime. */ -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface MavenLibraries { +public interface ClassPathAppender extends AutoCloseable { - @NotNull - MavenLibrary[] value() default {}; + void addJarToClasspath(Path file); + @Override + default void close() { + + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java new file mode 100644 index 00000000..7d8b30ee --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java @@ -0,0 +1,62 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.classpath; + +import net.momirealms.customfishing.libraries.loader.JarInJarClassLoader; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.Path; + +public class JarInJarClassPathAppender implements ClassPathAppender { + private final JarInJarClassLoader classLoader; + + public JarInJarClassPathAppender(ClassLoader classLoader) { + if (!(classLoader instanceof JarInJarClassLoader)) { + throw new IllegalArgumentException("Loader is not a JarInJarClassLoader: " + classLoader.getClass().getName()); + } + this.classLoader = (JarInJarClassLoader) classLoader; + } + + @Override + public void addJarToClasspath(Path file) { + try { + this.classLoader.addJarToClasspath(file.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + this.classLoader.deleteJarResource(); + try { + this.classLoader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ReflectionClassPathAppender.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ReflectionClassPathAppender.java new file mode 100644 index 00000000..5732f66a --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ReflectionClassPathAppender.java @@ -0,0 +1,52 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.classpath; + +import java.net.MalformedURLException; +import java.net.URLClassLoader; +import java.nio.file.Path; + +public class ReflectionClassPathAppender implements ClassPathAppender { + private final URLClassLoaderAccess classLoaderAccess; + + public ReflectionClassPathAppender(ClassLoader classLoader) throws IllegalStateException { + if (classLoader instanceof URLClassLoader) { + this.classLoaderAccess = URLClassLoaderAccess.create((URLClassLoader) classLoader); + } else { + throw new IllegalStateException("ClassLoader is not instance of URLClassLoader"); + } + } + + @Override + public void addJarToClasspath(Path file) { + try { + this.classLoaderAccess.addURL(file.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/URLClassLoaderAccess.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/URLClassLoaderAccess.java similarity index 62% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/URLClassLoaderAccess.java rename to plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/URLClassLoaderAccess.java index 43e6df69..33251a3a 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/URLClassLoaderAccess.java +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/URLClassLoaderAccess.java @@ -1,5 +1,5 @@ /* - * This file is part of helper, licensed under the MIT License. + * This file is part of LuckPerms, licensed under the MIT License. * * Copyright (c) lucko (Luck) * Copyright (c) contributors @@ -23,11 +23,12 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.libraryloader; +package net.momirealms.customfishing.libraries.classpath; -import org.jetbrains.annotations.NotNull; +import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.Collection; @@ -43,8 +44,10 @@ public abstract class URLClassLoaderAccess { * @param classLoader the class loader * @return the access object */ - static URLClassLoaderAccess create(URLClassLoader classLoader) { - if (Unsafe.isSupported()) { + public static URLClassLoaderAccess create(URLClassLoader classLoader) { + if (Reflection.isSupported()) { + return new Reflection(classLoader); + } else if (Unsafe.isSupported()) { return new Unsafe(classLoader); } else { return Noop.INSTANCE; @@ -63,7 +66,48 @@ public abstract class URLClassLoaderAccess { * * @param url the URL to add */ - public abstract void addURL(@NotNull URL url); + public abstract void addURL(@NonNull URL url); + + private static void throwError(Throwable cause) throws UnsupportedOperationException { + throw new UnsupportedOperationException("LuckPerms is unable to inject into the plugin URLClassLoader.\n" + + "You may be able to fix this problem by adding the following command-line argument " + + "directly after the 'java' command in your start script: \n'--add-opens java.base/java.lang=ALL-UNNAMED'", cause); + } + + /** + * Accesses using reflection, not supported on Java 9+. + */ + private static class Reflection extends URLClassLoaderAccess { + private static final Method ADD_URL_METHOD; + + static { + Method addUrlMethod; + try { + addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addUrlMethod.setAccessible(true); + } catch (Exception e) { + addUrlMethod = null; + } + ADD_URL_METHOD = addUrlMethod; + } + + private static boolean isSupported() { + return ADD_URL_METHOD != null; + } + + Reflection(URLClassLoader classLoader) { + super(classLoader); + } + + @Override + public void addURL(@NonNull URL url) { + try { + ADD_URL_METHOD.invoke(super.classLoader, url); + } catch (ReflectiveOperationException e) { + URLClassLoaderAccess.throwError(e); + } + } + } /** * Accesses using sun.misc.Unsafe, supported on Java 9+. @@ -106,6 +150,7 @@ public abstract class URLClassLoaderAccess { unopenedURLs = null; pathURLs = null; } + this.unopenedURLs = unopenedURLs; this.pathURLs = pathURLs; } @@ -117,9 +162,15 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NotNull URL url) { - this.unopenedURLs.add(url); - this.pathURLs.add(url); + public void addURL(@NonNull URL url) { + if (this.unopenedURLs == null || this.pathURLs == null) { + URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs")); + } + + synchronized (this.unopenedURLs) { + this.unopenedURLs.add(url); + this.pathURLs.add(url); + } } } @@ -131,8 +182,8 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NotNull URL url) { - throw new UnsupportedOperationException(); + public void addURL(@NonNull URL url) { + URLClassLoaderAccess.throwError(null); } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java new file mode 100644 index 00000000..3715dff3 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java @@ -0,0 +1,340 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies; + +import com.google.common.collect.ImmutableList; +import net.momirealms.customfishing.libraries.dependencies.relocation.Relocation; +import org.jetbrains.annotations.Nullable; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Locale; + +/** + * The dependencies used by LuckPerms. + */ +public enum Dependency { + + ASM( + "org.ow2.asm", + "asm", + "9.1", + null + ), + ASM_COMMONS( + "org.ow2.asm", + "asm-commons", + "9.1", + null + ), + JAR_RELOCATOR( + "me.lucko", + "jar-relocator", + "1.7", + null + ), + KYORI_OPTION( + "net{}kyori", + "option", + "1.0.0", + null, + Relocation.of("option", "net{}kyori{}option") + ), + ADVENTURE_API( + "net{}kyori", + "adventure-api", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_KEY( + "net{}kyori", + "adventure-key", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_NBT( + "net{}kyori", + "adventure-nbt", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_LEGACY_SERIALIZER( + "net{}kyori", + "adventure-text-serializer-legacy", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_TEXT_LOGGER( + "net{}kyori", + "adventure-text-logger-slf4j", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_GSON( + "net{}kyori", + "adventure-text-serializer-gson", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_GSON_LEGACY( + "net{}kyori", + "adventure-text-serializer-gson-legacy-impl", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_PLATFORM( + "net{}kyori", + "adventure-platform-api", + "4.3.2", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_PLATFORM_BUKKIT( + "net{}kyori", + "adventure-platform-bukkit", + "4.3.2", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_PLATFORM_FACET( + "net{}kyori", + "adventure-platform-facet", + "4.3.2", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_TEXT_MINIMESSAGE( + "net{}kyori", + "adventure-text-minimessage", + "4.15.0", + null, + Relocation.of("adventure", "net{}kyori{}adventure") + ), + COMMAND_API( + "dev{}jorel", + "commandapi-bukkit-shade", + "9.3.0", + null, + Relocation.of("commandapi", "dev{}jorel{}commandapi") + ), + MARIADB_DRIVER( + "org{}mariadb{}jdbc", + "mariadb-java-client", + "3.3.2", + null, + Relocation.of("mariadb", "org{}mariadb") + ), + BOOSTED_YAML( + "dev{}dejvokep", + "boosted-yaml", + "1.3.2", + null, + Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml") + ), + NBT_API( + "de{}tr7zw", + "item-nbt-api", + "2.12.2", + "codemc", + Relocation.of("changeme", "de{}tr7zw{}changeme") + ), + EXP4J( + "net{}objecthunter", + "exp4j", + "0.4.8", + null, + Relocation.of("exp4j", "net{}objecthunter{}exp4j") + ), + MYSQL_DRIVER( + "com{}mysql", + "mysql-connector-j", + "8.3.0", + null, + Relocation.of("mysql", "com{}mysql") + ), + H2_DRIVER( + "com.h2database", + "h2", + "2.2.224", + null + ), + SQLITE_DRIVER( + "org.xerial", + "sqlite-jdbc", + "3.45.1.0", + null + ), + HIKARI( + "com{}zaxxer", + "HikariCP", + "5.0.1", + null, + Relocation.of("hikari", "com{}zaxxer{}hikari") + ), + SLF4J_SIMPLE( + "org.slf4j", + "slf4j-simple", + "2.0.12", + null + ), + SLF4J_API( + "org.slf4j", + "slf4j-api", + "2.0.12", + null + ), + MONGODB_DRIVER_CORE( + "org{}mongodb", + "mongodb-driver-core", + "4.11.1", + null, + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + MONGODB_DRIVER_SYNC( + "org{}mongodb", + "mongodb-driver-sync", + "4.11.1", + null, + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + MONGODB_DRIVER_BSON( + "org{}mongodb", + "bson", + "4.11.1", + null, + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + JEDIS( + "redis{}clients", + "jedis", + "5.1.0", + null, + Relocation.of("jedis", "redis{}clients{}jedis"), + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ), + BSTATS_BASE( + "org{}bstats", + "bstats-base", + "3.0.2", + null, + Relocation.of("bstats", "org{}bstats") + ), + BSTATS_BUKKIT( + "org{}bstats", + "bstats-bukkit", + "3.0.2", + null, + Relocation.of("bstats", "org{}bstats") + ), + COMMONS_POOL_2( + "org.apache.commons", + "commons-pool2", + "2.11.0", + null, + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ), + GSON( + "com.google.code.gson", + "gson", + "2.10.1", + null + ),; + + private final String mavenRepoPath; + private final String version; + private final List relocations; + private final String repo; + + private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; + + Dependency(String groupId, String artifactId, String version, String repo) { + this(groupId, artifactId, version, repo, new Relocation[0]); + } + + Dependency(String groupId, String artifactId, String version, String repo, Relocation... relocations) { + this.mavenRepoPath = String.format(MAVEN_FORMAT, + rewriteEscaping(groupId).replace(".", "/"), + rewriteEscaping(artifactId), + version, + rewriteEscaping(artifactId), + version + ); + this.version = version; + this.relocations = ImmutableList.copyOf(relocations); + this.repo = repo; + } + + private static String rewriteEscaping(String s) { + return s.replace("{}", "."); + } + + public String getFileName(String classifier) { + String name = name().toLowerCase(Locale.ROOT).replace('_', '-'); + String extra = classifier == null || classifier.isEmpty() + ? "" + : "-" + classifier; + + return name + "-" + this.version + extra + ".jar"; + } + + String getMavenRepoPath() { + return this.mavenRepoPath; + } + + public List getRelocations() { + return this.relocations; + } + + /** + * Creates a {@link MessageDigest} suitable for computing the checksums + * of dependencies. + * + * @return the digest + */ + public static MessageDigest createDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + @Nullable + public String getRepo() { + return repo; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyDownloadException.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyDownloadException.java new file mode 100644 index 00000000..f48aa426 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyDownloadException.java @@ -0,0 +1,48 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies; + +/** + * Exception thrown if a dependency cannot be downloaded. + */ +public class DependencyDownloadException extends Exception { + + public DependencyDownloadException() { + + } + + public DependencyDownloadException(String message) { + super(message); + } + + public DependencyDownloadException(String message, Throwable cause) { + super(message, cause); + } + + public DependencyDownloadException(Throwable cause) { + super(cause); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/MavenLibrary.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManager.java similarity index 55% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/MavenLibrary.java rename to plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManager.java index 60ad69c8..2f09a5a0 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/MavenLibrary.java +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManager.java @@ -1,5 +1,5 @@ /* - * This file is part of helper, licensed under the MIT License. + * This file is part of LuckPerms, licensed under the MIT License. * * Copyright (c) lucko (Luck) * Copyright (c) contributors @@ -23,51 +23,31 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.libraryloader; +package net.momirealms.customfishing.libraries.dependencies; -import org.jetbrains.annotations.NotNull; - -import java.lang.annotation.*; +import java.util.Collection; +import java.util.Set; /** - * Annotation to indicate a required library for a class. + * Loads and manages runtime dependencies for the plugin. */ -@Documented -@Repeatable(MavenLibraries.class) -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface MavenLibrary { +public interface DependencyManager extends AutoCloseable { /** - * The group id of the library + * Loads dependencies. * - * @return the group id of the library + * @param dependencies the dependencies to load */ - @NotNull - String groupId(); + void loadDependencies(Collection dependencies); /** - * The artifact id of the library + * Obtains an isolated classloader containing the given dependencies. * - * @return the artifact id of the library + * @param dependencies the dependencies + * @return the classloader */ - @NotNull - String artifactId(); - - /** - * The version of the library - * - * @return the version of the library - */ - @NotNull - String version(); - - /** - * The repo where the library can be obtained from - * - * @return the repo where the library can be obtained from - */ - @NotNull - Repository repo() default @Repository(url = "https://repo1.maven.org/maven2"); + ClassLoader obtainClassLoaderWith(Set dependencies); + @Override + void close(); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManagerImpl.java new file mode 100644 index 00000000..87ecbcf0 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManagerImpl.java @@ -0,0 +1,232 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies; + +import com.google.common.collect.ImmutableSet; +import net.momirealms.customfishing.CustomFishingPluginImpl; +import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.libraries.classpath.ClassPathAppender; +import net.momirealms.customfishing.libraries.dependencies.classloader.IsolatedClassLoader; +import net.momirealms.customfishing.libraries.dependencies.relocation.Relocation; +import net.momirealms.customfishing.libraries.dependencies.relocation.RelocationHandler; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CountDownLatch; + +/** + * Loads and manages runtime dependencies for the plugin. + */ +public class DependencyManagerImpl implements DependencyManager { + + /** A registry containing plugin specific behaviour for dependencies. */ + private final DependencyRegistry registry; + /** The path where library jars are cached. */ + private final Path cacheDirectory; + /** The classpath appender to preload dependencies into */ + private final ClassPathAppender classPathAppender; + /** A map of dependencies which have already been loaded. */ + private final EnumMap loaded = new EnumMap<>(Dependency.class); + /** A map of isolated classloaders which have been created. */ + private final Map, IsolatedClassLoader> loaders = new HashMap<>(); + /** Cached relocation handler instance. */ + private @MonotonicNonNull RelocationHandler relocationHandler = null; + + public DependencyManagerImpl(CustomFishingPluginImpl plugin, ClassPathAppender classPathAppender) { + this.registry = new DependencyRegistry(); + this.cacheDirectory = setupCacheDirectory(plugin); + this.classPathAppender = classPathAppender; + } + + private synchronized RelocationHandler getRelocationHandler() { + if (this.relocationHandler == null) { + this.relocationHandler = new RelocationHandler(this); + } + return this.relocationHandler; + } + + @Override + public ClassLoader obtainClassLoaderWith(Set dependencies) { + ImmutableSet set = ImmutableSet.copyOf(dependencies); + + for (Dependency dependency : dependencies) { + if (!this.loaded.containsKey(dependency)) { + throw new IllegalStateException("Dependency " + dependency + " is not loaded."); + } + } + + synchronized (this.loaders) { + IsolatedClassLoader classLoader = this.loaders.get(set); + if (classLoader != null) { + return classLoader; + } + + URL[] urls = set.stream() + .map(this.loaded::get) + .map(file -> { + try { + return file.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }) + .toArray(URL[]::new); + + classLoader = new IsolatedClassLoader(urls); + this.loaders.put(set, classLoader); + return classLoader; + } + } + + @Override + public void loadDependencies(Collection dependencies) { + CountDownLatch latch = new CountDownLatch(dependencies.size()); + + for (Dependency dependency : dependencies) { + if (this.loaded.containsKey(dependency)) { + latch.countDown(); + continue; + } + + try { + loadDependency(dependency); + } catch (Throwable e) { + new RuntimeException("Unable to load dependency " + dependency.name(), e).printStackTrace(); + } finally { + latch.countDown(); + } + } + + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void loadDependency(Dependency dependency) throws Exception { + if (this.loaded.containsKey(dependency)) { + return; + } + + Path file = remapDependency(dependency, downloadDependency(dependency)); + + this.loaded.put(dependency, file); + + if (this.classPathAppender != null && this.registry.shouldAutoLoad(dependency)) { + this.classPathAppender.addJarToClasspath(file); + } + } + + private Path downloadDependency(Dependency dependency) throws DependencyDownloadException { + Path file = this.cacheDirectory.resolve(dependency.getFileName(null)); + + // if the file already exists, don't attempt to re-download it. + if (Files.exists(file)) { + return file; + } + + DependencyDownloadException lastError = null; + + String forceRepo = dependency.getRepo(); + if (forceRepo == null) { + // attempt to download the dependency from each repo in order. + for (DependencyRepository repo : DependencyRepository.values()) { + if (repo.getId().equals("maven") && TimeZone.getDefault().getID().startsWith("Asia")) { + continue; + } + try { + repo.download(dependency, file); + return file; + } catch (DependencyDownloadException e) { + lastError = e; + } + } + } else { + DependencyRepository repository = DependencyRepository.getByID(forceRepo); + if (repository != null) { + try { + repository.download(dependency, file); + return file; + } catch (DependencyDownloadException e) { + lastError = e; + } + } + } + throw Objects.requireNonNull(lastError); + } + + private Path remapDependency(Dependency dependency, Path normalFile) throws Exception { + List rules = new ArrayList<>(dependency.getRelocations()); + if (rules.isEmpty()) { + return normalFile; + } + + Path remappedFile = this.cacheDirectory.resolve(dependency.getFileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped")); + + // if the remapped source exists already, just use that. + if (Files.exists(remappedFile)) { + return remappedFile; + } + + getRelocationHandler().remap(normalFile, remappedFile, rules); + return remappedFile; + } + + private static Path setupCacheDirectory(CustomFishingPlugin plugin) { + File folder = new File(plugin.getDataFolder(), "libs"); + folder.mkdirs(); + return folder.toPath(); + } + + @Override + public void close() { + IOException firstEx = null; + + for (IsolatedClassLoader loader : this.loaders.values()) { + try { + loader.close(); + } catch (IOException ex) { + if (firstEx == null) { + firstEx = ex; + } else { + firstEx.addSuppressed(ex); + } + } + } + + if (firstEx != null) { + firstEx.printStackTrace(); + } + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRegistry.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRegistry.java new file mode 100644 index 00000000..4d183b2b --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRegistry.java @@ -0,0 +1,83 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies; + +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import com.google.gson.JsonElement; +import net.momirealms.customfishing.api.data.StorageType; + +/** + * Applies LuckPerms specific behaviour for {@link Dependency}s. + */ +public class DependencyRegistry { + + private static final SetMultimap STORAGE_DEPENDENCIES = ImmutableSetMultimap.builder() + .putAll(StorageType.MongoDB, Dependency.MONGODB_DRIVER_CORE, Dependency.MONGODB_DRIVER_SYNC, Dependency.MONGODB_DRIVER_BSON) + .putAll(StorageType.MariaDB, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI, Dependency.MARIADB_DRIVER) + .putAll(StorageType.MySQL, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI, Dependency.MYSQL_DRIVER) + .putAll(StorageType.SQLite, Dependency.SQLITE_DRIVER) + .putAll(StorageType.H2, Dependency.H2_DRIVER) + .build(); + + public DependencyRegistry() { + + } + + public boolean shouldAutoLoad(Dependency dependency) { + switch (dependency) { + // all used within 'isolated' classloaders, and are therefore not + // relocated. + case ASM: + case ASM_COMMONS: + case JAR_RELOCATOR: + case H2_DRIVER: + case SQLITE_DRIVER: + return false; + default: + return true; + } + } + + @SuppressWarnings("ConstantConditions") + public static boolean isGsonRelocated() { + return JsonElement.class.getName().startsWith("net.momirealms"); + } + + private static boolean classExists(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private static boolean slf4jPresent() { + return classExists("org.slf4j.Logger") && classExists("org.slf4j.LoggerFactory"); + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRepository.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRepository.java new file mode 100644 index 00000000..384a0fd7 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRepository.java @@ -0,0 +1,141 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies; + +import com.google.common.io.ByteStreams; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +/** + * Represents a repository which contains {@link Dependency}s. + */ +public enum DependencyRepository { + + MAVEN_CENTRAL("maven", "https://repo1.maven.org/maven2/") { + @Override + protected URLConnection openConnection(Dependency dependency) throws IOException { + URLConnection connection = super.openConnection(dependency); + connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(5)); + connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(5)); + return connection; + } + }, + + /** + * Maven Central + */ + MAVEN_CENTRAL_MIRROR("aliyun", "https://maven.aliyun.com/repository/public/"), + /** + * Code MC + */ + CODE_MC("codemc", "https://repo.codemc.io/repository/maven-public/"); + + private final String url; + private final String id; + + DependencyRepository(String id, String url) { + this.url = url; + this.id = id; + } + + public static DependencyRepository getByID(String id) { + for (DependencyRepository repository : values()) { + if (id.equals(repository.id)) { + return repository; + } + } + return null; + } + + /** + * Opens a connection to the given {@code dependency}. + * + * @param dependency the dependency to download + * @return the connection + * @throws IOException if unable to open a connection + */ + protected URLConnection openConnection(Dependency dependency) throws IOException { + URL dependencyUrl = new URL(this.url + dependency.getMavenRepoPath()); + return dependencyUrl.openConnection(); + } + + /** + * Downloads the raw bytes of the {@code dependency}. + * + * @param dependency the dependency to download + * @return the downloaded bytes + * @throws DependencyDownloadException if unable to download + */ + public byte[] downloadRaw(Dependency dependency) throws DependencyDownloadException { + try { + URLConnection connection = openConnection(dependency); + try (InputStream in = connection.getInputStream()) { + byte[] bytes = ByteStreams.toByteArray(in); + if (bytes.length == 0) { + throw new DependencyDownloadException("Empty stream"); + } + return bytes; + } + } catch (Exception e) { + throw new DependencyDownloadException(e); + } + } + + /** + * @param dependency the dependency to download + * @return the downloaded bytes + * @throws DependencyDownloadException if unable to download + */ + public byte[] download(Dependency dependency) throws DependencyDownloadException { + return downloadRaw(dependency); + } + + /** + * Downloads the the {@code dependency} to the {@code file}, ensuring the + * downloaded bytes match the checksum. + * + * @param dependency the dependency to download + * @param file the file to write to + * @throws DependencyDownloadException if unable to download + */ + public void download(Dependency dependency, Path file) throws DependencyDownloadException { + try { + Files.write(file, download(dependency)); + } catch (IOException e) { + throw new DependencyDownloadException(e); + } + } + + public String getId() { + return id; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/classloader/IsolatedClassLoader.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/classloader/IsolatedClassLoader.java new file mode 100644 index 00000000..01b450e0 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/classloader/IsolatedClassLoader.java @@ -0,0 +1,54 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies.classloader; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * A classloader "isolated" from the rest of the Minecraft server. + * + *

Used to load specific LuckPerms dependencies without causing conflicts + * with other plugins, or libraries provided by the server implementation.

+ */ +public class IsolatedClassLoader extends URLClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + public IsolatedClassLoader(URL[] urls) { + /* + * ClassLoader#getSystemClassLoader returns the AppClassLoader + * + * Calling #getParent on this returns the ExtClassLoader (Java 8) or + * the PlatformClassLoader (Java 9). Since we want this classloader to + * be isolated from the Minecraft server (the app), we set the parent + * to be the platform class loader. + */ + super(urls, ClassLoader.getSystemClassLoader().getParent()); + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/Relocation.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/Relocation.java new file mode 100644 index 00000000..e4513f49 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/Relocation.java @@ -0,0 +1,66 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies.relocation; + +import java.util.Objects; + +public final class Relocation { + private static final String RELOCATION_PREFIX = "net.momirealms.customfishing.libraries."; + + public static Relocation of(String id, String pattern) { + return new Relocation(pattern.replace("{}", "."), RELOCATION_PREFIX + id); + } + + private final String pattern; + private final String relocatedPattern; + + private Relocation(String pattern, String relocatedPattern) { + this.pattern = pattern; + this.relocatedPattern = relocatedPattern; + } + + public String getPattern() { + return this.pattern; + } + + public String getRelocatedPattern() { + return this.relocatedPattern; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Relocation that = (Relocation) o; + return Objects.equals(this.pattern, that.pattern) && + Objects.equals(this.relocatedPattern, that.relocatedPattern); + } + + @Override + public int hashCode() { + return Objects.hash(this.pattern, this.relocatedPattern); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHandler.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHandler.java new file mode 100644 index 00000000..8479442c --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHandler.java @@ -0,0 +1,92 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies.relocation; + + +import net.momirealms.customfishing.libraries.dependencies.Dependency; +import net.momirealms.customfishing.libraries.dependencies.DependencyManager; +import net.momirealms.customfishing.libraries.dependencies.classloader.IsolatedClassLoader; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.*; + +/** + * Handles class runtime relocation of packages in downloaded dependencies + */ +public class RelocationHandler { + public static final Set DEPENDENCIES = EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR); + private static final String JAR_RELOCATOR_CLASS = "me.lucko.jarrelocator.JarRelocator"; + private static final String JAR_RELOCATOR_RUN_METHOD = "run"; + + private final Constructor jarRelocatorConstructor; + private final Method jarRelocatorRunMethod; + + public RelocationHandler(DependencyManager dependencyManager) { + ClassLoader classLoader = null; + try { + // download the required dependencies for remapping + dependencyManager.loadDependencies(DEPENDENCIES); + // get a classloader containing the required dependencies as sources + classLoader = dependencyManager.obtainClassLoaderWith(DEPENDENCIES); + + // load the relocator class + Class jarRelocatorClass = classLoader.loadClass(JAR_RELOCATOR_CLASS); + + // prepare the the reflected constructor & method instances + this.jarRelocatorConstructor = jarRelocatorClass.getDeclaredConstructor(File.class, File.class, Map.class); + this.jarRelocatorConstructor.setAccessible(true); + + this.jarRelocatorRunMethod = jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD); + this.jarRelocatorRunMethod.setAccessible(true); + } catch (Exception e) { + try { + if (classLoader instanceof IsolatedClassLoader) { + ((IsolatedClassLoader) classLoader).close(); + } + } catch (IOException ex) { + e.addSuppressed(ex); + } + + throw new RuntimeException(e); + } + } + + public void remap(Path input, Path output, List relocations) throws Exception { + Map mappings = new HashMap<>(); + for (Relocation relocation : relocations) { + mappings.put(relocation.getPattern(), relocation.getRelocatedPattern()); + } + + // create and invoke a new relocator + Object relocator = this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings); + this.jarRelocatorRunMethod.invoke(relocator); + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHelper.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHelper.java new file mode 100644 index 00000000..76958563 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHelper.java @@ -0,0 +1,37 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.dependencies.relocation; + +public final class RelocationHelper { + + // screw maven shade + public static final String OKIO_STRING = String.valueOf(new char[]{'o', 'k', 'i', 'o'}); + public static final String OKHTTP3_STRING = String.valueOf(new char[]{'o', 'k', 'h', 't', 't', 'p', '3'}); + + private RelocationHelper() { + + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/LibraryLoader.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/LibraryLoader.java deleted file mode 100644 index 85df0dde..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/LibraryLoader.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * This file is part of helper, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * 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 net.momirealms.customfishing.libraries.libraryloader; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.util.LogUtils; - -import java.io.File; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.util.Objects; -import java.util.StringJoiner; - -/** - * Resolves {@link MavenLibrary} annotations for a class, and loads the dependency - * into the classloader. - */ -public final class LibraryLoader { - - @SuppressWarnings("Guava") - private static final Supplier URL_INJECTOR = Suppliers.memoize(() -> URLClassLoaderAccess.create((URLClassLoader) CustomFishingPluginImpl.getInstance().getClass().getClassLoader())); - - /** - * Resolves all {@link MavenLibrary} annotations on the given object. - * - * @param object the object to load libraries for. - */ - public static void loadAll(Object object) { - loadAll(object.getClass()); - } - - /** - * Resolves all {@link MavenLibrary} annotations on the given class. - * - * @param clazz the class to load libraries for. - */ - public static void loadAll(Class clazz) { - MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class); - for (MavenLibrary lib : libs) { - load(lib.groupId(), lib.artifactId(), lib.version(), lib.repo().url()); - } - } - - public static void load(String groupId, String artifactId, String version, String repoUrl) { - load(new Dependency(groupId, artifactId, version, repoUrl)); - } - - public static void loadDependencies(String... libs) { - if (libs == null || libs.length % 2 != 0) - return; - for (int i = 0; i < libs.length; i+=2) { - String[] split = libs[i].split(":"); - load(new Dependency( - split[0], - split[1], - split[2], - libs[i+1] - )); - } - } - - public static void load(Dependency d) { -// LogUtils.info(String.format("Loading dependency %s:%s:%s", d.groupId, d.artifactId, d.version)); - String name = d.artifactId() + "-" + d.version(); - File saveLocation = new File(getLibFolder(d), name + ".jar"); - if (!saveLocation.exists()) { - try { - LogUtils.info("Dependency '" + name + "' is not already in the libraries folder. Attempting to download..."); - URL url = d.getUrl(); - try (InputStream is = url.openStream()) { - Files.copy(is, saveLocation.toPath()); - LogUtils.info("Dependency '" + name + "' successfully downloaded."); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - if (!saveLocation.exists()) - throw new RuntimeException("Unable to download dependency: " + d); - try { - URL_INJECTOR.get().addURL(saveLocation.toURI().toURL()); - } catch (Exception e) { - throw new RuntimeException("Unable to load dependency: " + saveLocation, e); - } - } - - @SuppressWarnings("all") - private static File getLibFolder(Dependency dependency) { - File pluginDataFolder = CustomFishingPlugin.getInstance().getDataFolder(); - File serverDir = pluginDataFolder.getParentFile().getParentFile(); - - File helperDir = new File(serverDir, "libraries"); - String[] split = dependency.groupId().split("\\."); - File jarDir; - StringJoiner stringJoiner = new StringJoiner(File.separator); - for (String str : split) { - stringJoiner.add(str); - } - jarDir = new File(helperDir, stringJoiner + File.separator + dependency.artifactId + File.separator + dependency.version); - jarDir.mkdirs(); - return jarDir; - } - - public record Dependency(String groupId, String artifactId, String version, String repoUrl) { - public Dependency(String groupId, String artifactId, String version, String repoUrl) { - this.groupId = Objects.requireNonNull(groupId, "groupId"); - this.artifactId = Objects.requireNonNull(artifactId, "artifactId"); - this.version = Objects.requireNonNull(version, "version"); - this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl"); - } - - public URL getUrl() throws MalformedURLException { - String repo = this.repoUrl; - if (!repo.endsWith("/")) { - repo += "/"; - } - repo += "%s/%s/%s/%s-%s.jar"; - - String url = String.format(repo, this.groupId.replace(".", "/"), this.artifactId, this.version, this.artifactId, this.version); - return new URL(url); - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof Dependency other)) return false; - return this.groupId().equals(other.groupId()) && - this.artifactId().equals(other.artifactId()) && - this.version().equals(other.version()) && - this.repoUrl().equals(other.repoUrl()); - } - - @Override - public int hashCode() { - final int PRIME = 59; - int result = 1; - result = result * PRIME + this.groupId().hashCode(); - result = result * PRIME + this.artifactId().hashCode(); - result = result * PRIME + this.version().hashCode(); - result = result * PRIME + this.repoUrl().hashCode(); - return result; - } - - @Override - public String toString() { - return "LibraryLoader.Dependency(" + - "groupId=" + this.groupId() + ", " + - "artifactId=" + this.artifactId() + ", " + - "version=" + this.version() + ", " + - "repoUrl=" + this.repoUrl() + ")"; - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java new file mode 100644 index 00000000..5649a0bf --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java @@ -0,0 +1,155 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.loader; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +/** + * Classloader that can load a jar from within another jar file. + * + *

The "loader" jar contains the loading code & public API classes, + * and is class-loaded by the platform.

+ * + *

The inner "plugin" jar contains the plugin itself, and is class-loaded + * by the loading code & this classloader.

+ */ +public class JarInJarClassLoader extends URLClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + /** + * Creates a new jar-in-jar class loader. + * + * @param loaderClassLoader the loader plugin's classloader (setup and created by the platform) + * @param jarResourcePath the path to the jar-in-jar resource within the loader jar + * @throws LoadingException if something unexpectedly bad happens + */ + public JarInJarClassLoader(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { + super(new URL[]{extractJar(loaderClassLoader, jarResourcePath)}, loaderClassLoader); + } + + public void addJarToClasspath(URL url) { + addURL(url); + } + + public void deleteJarResource() { + URL[] urls = getURLs(); + if (urls.length == 0) { + return; + } + + try { + Path path = Paths.get(urls[0].toURI()); + Files.deleteIfExists(path); + } catch (Exception e) { + // ignore + } + } + + /** + * Creates a new plugin instance. + * + * @param bootstrapClass the name of the bootstrap plugin class + * @param loaderPluginType the type of the loader plugin, the only parameter of the bootstrap + * plugin constructor + * @param loaderPlugin the loader plugin instance + * @param the type of the loader plugin + * @return the instantiated bootstrap plugin + */ + public LoaderBootstrap instantiatePlugin(String bootstrapClass, Class loaderPluginType, T loaderPlugin) throws LoadingException { + Class plugin; + try { + plugin = loadClass(bootstrapClass).asSubclass(LoaderBootstrap.class); + } catch (ReflectiveOperationException e) { + throw new LoadingException("Unable to load bootstrap class", e); + } + + Constructor constructor; + try { + constructor = plugin.getConstructor(loaderPluginType); + } catch (ReflectiveOperationException e) { + throw new LoadingException("Unable to get bootstrap constructor", e); + } + + try { + return constructor.newInstance(loaderPlugin); + } catch (ReflectiveOperationException e) { + throw new LoadingException("Unable to create bootstrap plugin instance", e); + } + } + + /** + * Extracts the "jar-in-jar" from the loader plugin into a temporary file, + * then returns a URL that can be used by the {@link JarInJarClassLoader}. + * + * @param loaderClassLoader the classloader for the "host" loader plugin + * @param jarResourcePath the inner jar resource path + * @return a URL to the extracted file + */ + private static URL extractJar(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { + // get the jar-in-jar resource + URL jarInJar = loaderClassLoader.getResource(jarResourcePath); + if (jarInJar == null) { + throw new LoadingException("Could not locate jar-in-jar"); + } + + // create a temporary file + // on posix systems by default this is only read/writable by the process owner + Path path; + try { + path = Files.createTempFile("luckperms-jarinjar", ".jar.tmp"); + } catch (IOException e) { + throw new LoadingException("Unable to create a temporary file", e); + } + + // mark that the file should be deleted on exit + path.toFile().deleteOnExit(); + + // copy the jar-in-jar to the temporary file path + try (InputStream in = jarInJar.openStream()) { + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new LoadingException("Unable to copy jar-in-jar to temporary path", e); + } + + try { + return path.toUri().toURL(); + } catch (MalformedURLException e) { + throw new LoadingException("Unable to get URL from path", e); + } + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/Repository.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java similarity index 73% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/Repository.java rename to plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java index c0798824..98ddd06d 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/libraryloader/Repository.java +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java @@ -1,5 +1,5 @@ /* - * This file is part of helper, licensed under the MIT License. + * This file is part of LuckPerms, licensed under the MIT License. * * Copyright (c) lucko (Luck) * Copyright (c) contributors @@ -23,23 +23,17 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.libraryloader; - -import java.lang.annotation.*; +package net.momirealms.customfishing.libraries.loader; /** - * Represents a maven repository. + * Minimal bootstrap plugin, called by the loader plugin. */ -@Documented -@Target(ElementType.LOCAL_VARIABLE) -@Retention(RetentionPolicy.RUNTIME) -public @interface Repository { +public interface LoaderBootstrap { - /** - * Gets the base url of the repository. - * - * @return the base url of the repository - */ - String url(); + void onLoad(); + + default void onEnable() {} + + default void onDisable() {} } diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java new file mode 100644 index 00000000..26528e94 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java @@ -0,0 +1,41 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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 net.momirealms.customfishing.libraries.loader; + +/** + * Runtime exception used if there is a problem during loading + */ +public class LoadingException extends RuntimeException { + + public LoadingException(String message) { + super(message); + } + + public LoadingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java index bf132b0e..feec5f40 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java @@ -45,8 +45,6 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.ExperienceOrb; import org.bukkit.entity.Player; import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java index 9af10e3a..17ac9644 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java +++ b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java @@ -113,7 +113,7 @@ public class Competition implements FishingCompetition { private void arrangeTimerTask() { this.competitionTimerTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> { if (decreaseTime()) { - end(); + end(true); return; } updatePublicPlaceholders(); @@ -149,15 +149,17 @@ public class Competition implements FishingCompetition { * and sets the remaining time to zero. */ @Override - public void stop() { + public void stop(boolean triggerEvent) { if (!competitionTimerTask.isCancelled()) this.competitionTimerTask.cancel(); if (this.bossBarManager != null) this.bossBarManager.unload(); if (this.actionBarManager != null) this.actionBarManager.unload(); this.ranking.clear(); this.remainingTime = 0; - CompetitionEvent competitionEvent = new CompetitionEvent(CompetitionEvent.State.STOP, this); - Bukkit.getPluginManager().callEvent(competitionEvent); + if (triggerEvent) { + CompetitionEvent competitionEvent = new CompetitionEvent(CompetitionEvent.State.STOP, this); + Bukkit.getPluginManager().callEvent(competitionEvent); + } } /** @@ -166,7 +168,7 @@ public class Competition implements FishingCompetition { * gives prizes to top participants and participation rewards, performs end actions, and clears the ranking. */ @Override - public void end() { + public void end(boolean triggerEvent) { // mark it as ended this.remainingTime = 0; @@ -213,8 +215,10 @@ public class Competition implements FishingCompetition { } // call event - CompetitionEvent competitionEndEvent = new CompetitionEvent(CompetitionEvent.State.END, this); - Bukkit.getPluginManager().callEvent(competitionEndEvent); + if (triggerEvent) { + CompetitionEvent competitionEndEvent = new CompetitionEvent(CompetitionEvent.State.END, this); + Bukkit.getPluginManager().callEvent(competitionEndEvent); + } // 1 seconds delay for other servers to read the redis data CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(this.ranking::clear, 1, TimeUnit.SECONDS); diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java index 63b48ff7..3c649cda 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java @@ -58,13 +58,7 @@ public class CompetitionManagerImpl implements CompetitionManager { public void load() { loadConfig(); this.timerCheckTask = plugin.getScheduler().runTaskAsyncTimer( - () -> { - try { - timerCheck(); - } catch (Exception e) { - e.printStackTrace(); - } - }, + this::timerCheck, 1, 1, TimeUnit.SECONDS @@ -76,8 +70,9 @@ public class CompetitionManagerImpl implements CompetitionManager { this.timerCheckTask.cancel(); this.commandConfigMap.clear(); this.timeConfigMap.clear(); - if (currentCompetition != null && currentCompetition.isOnGoing()) - currentCompetition.end(); + if (currentCompetition != null && currentCompetition.isOnGoing()) { + plugin.getScheduler().runTaskAsync(() -> currentCompetition.stop(true)); + } } public void disable() { @@ -86,7 +81,7 @@ public class CompetitionManagerImpl implements CompetitionManager { this.commandConfigMap.clear(); this.timeConfigMap.clear(); if (currentCompetition != null && currentCompetition.isOnGoing()) - currentCompetition.stop(); + currentCompetition.stop(false); } /** @@ -293,7 +288,7 @@ public class CompetitionManagerImpl implements CompetitionManager { private void start(CompetitionConfig config) { if (getOnGoingCompetition() != null) { // END - currentCompetition.end(); + currentCompetition.end(true); plugin.getScheduler().runTaskAsyncLater(() -> { // start one second later this.currentCompetition = new Competition(config); diff --git a/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java index 43745e3c..267ec3bc 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java @@ -87,7 +87,11 @@ public class SchedulerImpl implements Scheduler { */ @Override public void runTaskAsync(Runnable runnable) { - this.schedule.execute(runnable); + try { + this.schedule.execute(runnable); + } catch (Exception e) { + e.printStackTrace(); + } } /** @@ -114,7 +118,13 @@ public class SchedulerImpl implements Scheduler { */ @Override public CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit) { - return new ScheduledTask(schedule.schedule(runnable, delay, timeUnit)); + return new ScheduledTask(schedule.schedule(() -> { + try { + runnable.run(); + } catch (Exception e) { + e.printStackTrace(); + } + }, delay, timeUnit)); } /** @@ -157,7 +167,13 @@ public class SchedulerImpl implements Scheduler { */ @Override public CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit) { - return new ScheduledTask(schedule.scheduleAtFixedRate(runnable, delay, period, timeUnit)); + return new ScheduledTask(schedule.scheduleAtFixedRate(() -> { + try { + runnable.run(); + } catch (Exception e) { + e.printStackTrace(); + } + }, delay, period, timeUnit)); } /** diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java index b1ba449a..dbebba8f 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java +++ b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java @@ -207,11 +207,11 @@ public class RedisManager extends AbstractStorage { } case "end" -> { if (CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition() != null) - CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition().end(); + CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition().end(true); } case "stop" -> { if (CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition() != null) - CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition().stop(); + CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition().stop(true); } } }, new Location(Bukkit.getWorlds().get(0),0,0,0)); diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java index 50ec2d75..29f242b2 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java +++ b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java @@ -19,7 +19,6 @@ package net.momirealms.customfishing.storage.method.database.sql; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import me.clip.placeholderapi.PlaceholderAPI; import net.momirealms.customfishing.api.CustomFishingPlugin; import net.momirealms.customfishing.api.data.*; import net.momirealms.customfishing.api.util.LogUtils; diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java index 7095c0f5..f28e029c 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java @@ -17,21 +17,25 @@ package net.momirealms.customfishing.storage.method.database.sql; +import net.momirealms.customfishing.CustomFishingPluginImpl; import net.momirealms.customfishing.api.CustomFishingPlugin; import net.momirealms.customfishing.api.data.StorageType; +import net.momirealms.customfishing.libraries.dependencies.Dependency; import org.bukkit.configuration.file.YamlConfiguration; -import org.h2.jdbcx.JdbcConnectionPool; import java.io.File; +import java.lang.reflect.Method; import java.sql.Connection; -import java.sql.SQLException; +import java.util.EnumSet; /** * An implementation of AbstractSQLDatabase that uses the H2 embedded database for player data storage. */ public class H2Impl extends AbstractSQLDatabase { - private JdbcConnectionPool connectionPool; + private Object connectionPool; + private Method disposeMethod; + private Method getConnectionMethod; public H2Impl(CustomFishingPlugin plugin) { super(plugin); @@ -47,7 +51,17 @@ public class H2Impl extends AbstractSQLDatabase { super.tablePrefix = config.getString("H2.table-prefix", "customfishing"); final String url = String.format("jdbc:h2:%s", databaseFile.getAbsolutePath()); - this.connectionPool = JdbcConnectionPool.create(url, "sa", ""); + ClassLoader classLoader = ((CustomFishingPluginImpl) plugin).getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER)); + try { + Class connectionClass = classLoader.loadClass("org.h2.jdbcx.JdbcConnectionPool"); + Method createPoolMethod = connectionClass.getMethod("create", String.class, String.class, String.class); + this.connectionPool = createPoolMethod.invoke(null, url, "sa", ""); + this.disposeMethod = connectionClass.getMethod("dispose"); + this.getConnectionMethod = connectionClass.getMethod("getConnection"); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + super.createTableIfNotExist(); } @@ -57,7 +71,11 @@ public class H2Impl extends AbstractSQLDatabase { @Override public void disable() { if (connectionPool != null) { - connectionPool.dispose(); + try { + disposeMethod.invoke(connectionPool); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } } } @@ -67,7 +85,11 @@ public class H2Impl extends AbstractSQLDatabase { } @Override - public Connection getConnection() throws SQLException { - return connectionPool.getConnection(); + public Connection getConnection() { + try { + return (Connection) getConnectionMethod.invoke(connectionPool); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } } } diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml index e71d65fc..aeb3801d 100644 --- a/plugin/src/main/resources/config.yml +++ b/plugin/src/main/resources/config.yml @@ -234,7 +234,8 @@ mechanics: # Other settings other-settings: # It's recommended to use MiniMessage format. If you insist on using legacy color code "&", enable the support below. - legacy-color-code-support: false + # Disable this would improve performance + legacy-color-code-support: true # Thread pool settings thread-pool-settings: