diff --git a/build.gradle.kts b/build.gradle.kts index 17fb805..3c873a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { - version = "2.3.2.2" + version = "2.3.3.0" apply() apply(plugin = "java") diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index 8c7f682..01e6c6c 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -12,7 +12,10 @@ dependencies { compileOnly("me.clip:placeholderapi:2.11.5") // config - compileOnly("dev.dejvokep:boosted-yaml:1.3.1") + compileOnly("dev.dejvokep:boosted-yaml:1.3.2") + + // bStats + compileOnly("org.bstats:bstats-bukkit:3.0.2") // team compileOnly("me.neznamy:tab-api:4.0.2") @@ -48,11 +51,6 @@ dependencies { // adventure implementation("net.kyori:adventure-api:4.15.0") implementation("net.kyori:adventure-platform-bukkit:4.3.2") - implementation("net.kyori:adventure-text-minimessage:4.15.0") - implementation("net.kyori:adventure-text-serializer-legacy:4.15.0") - - // bStats - implementation("org.bstats:bstats-bukkit:3.0.2") // local lib implementation(files("libs/BiomeAPI.jar")) @@ -63,5 +61,14 @@ tasks { relocate ("net.kyori", "net.momirealms.customnameplates.libraries") relocate ("org.bstats", "net.momirealms.customnameplates.libraries.bstats") relocate ("net.momirealms.biomeapi", "net.momirealms.customnameplates.libraries.biomeapi") + relocate ("org.apache.commons.pool2", "net.momirealms.customnameplates.libraries.commonspool2") + relocate ("com.mysql", "net.momirealms.customnameplates.libraries.mysql") + relocate ("org.mariadb", "net.momirealms.customnameplates.libraries.mariadb") + relocate ("com.zaxxer.hikari", "net.momirealms.customnameplates.libraries.hikari") + relocate ("redis.clients.jedis", "net.momirealms.customnameplates.libraries.jedis") + relocate ("com.mongodb", "net.momirealms.customnameplates.libraries.mongodb") + relocate ("org.bson", "net.momirealms.customnameplates.libraries.bson") + relocate ("dev.jorel.commandapi", "net.momirealms.customnameplates.libraries.commandapi") + relocate ("dev.dejvokep.boostedyaml", "net.momirealms.customnameplates.libraries.boostedyaml") } } diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java index aaa8884..564dacb 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java @@ -22,7 +22,10 @@ import net.momirealms.customnameplates.api.event.CustomNameplatesReloadEvent; import net.momirealms.customnameplates.api.util.LogUtils; import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl; import net.momirealms.customnameplates.paper.command.CommandManager; -import net.momirealms.customnameplates.paper.helper.LibraryLoader; +import net.momirealms.customnameplates.paper.libraries.classpath.ReflectionClassPathAppender; +import net.momirealms.customnameplates.paper.libraries.dependencies.Dependency; +import net.momirealms.customnameplates.paper.libraries.dependencies.DependencyManager; +import net.momirealms.customnameplates.paper.libraries.dependencies.DependencyManagerImpl; import net.momirealms.customnameplates.paper.mechanic.actionbar.ActionBarManagerImpl; import net.momirealms.customnameplates.paper.mechanic.background.BackGroundManagerImpl; import net.momirealms.customnameplates.paper.mechanic.bossbar.BossBarManagerImpl; @@ -50,16 +53,41 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import java.io.File; -import java.util.TimeZone; +import java.util.ArrayList; +import java.util.List; public class CustomNameplatesPluginImpl extends CustomNameplatesPlugin implements Listener { private CoolDownManager coolDownManager; private PacketManager packetManager; + private DependencyManager dependencyManager; @Override public void onLoad() { - this.loadLibraries(); + 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.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 + ) + )); ReflectionUtils.load(); } @@ -155,27 +183,6 @@ public class CustomNameplatesPluginImpl extends CustomNameplatesPlugin implement } } - private void loadLibraries() { - 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, - "org.mariadb.jdbc:mariadb-java-client:3.3.0", mavenRepo, - "com.mysql:mysql-connector-j:8.2.0", mavenRepo, - "commons-io:commons-io:2.15.1", 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.43.2.2", mavenRepo, - "dev.jorel:commandapi-bukkit-shade:9.3.0", mavenRepo - ); - } - public CoolDownManager getCoolDownManager() { return coolDownManager; } @@ -183,4 +190,8 @@ public class CustomNameplatesPluginImpl extends CustomNameplatesPlugin implement public PacketManager getPacketManager() { return packetManager; } + + public DependencyManager getDependencyManager() { + return dependencyManager; + } } diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/helper/LibraryLoader.java b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/LibraryLoader.java deleted file mode 100644 index 07ac490..0000000 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/helper/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.customnameplates.paper.helper; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import net.momirealms.customnameplates.api.CustomNameplatesPlugin; -import net.momirealms.customnameplates.api.util.LogUtils; -import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl; - -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) CustomNameplatesPluginImpl.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 = CustomNameplatesPlugin.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/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibraries.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/ClassPathAppender.java similarity index 74% rename from paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibraries.java rename to paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/ClassPathAppender.java index f4ce9c3..4b787a9 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibraries.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.helper; +package net.momirealms.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/JarInJarClassPathAppender.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/JarInJarClassPathAppender.java new file mode 100644 index 0000000..d535c40 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.libraries.classpath; + +import net.momirealms.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/ReflectionClassPathAppender.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/ReflectionClassPathAppender.java new file mode 100644 index 0000000..89897f0 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/helper/URLClassLoaderAccess.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/URLClassLoaderAccess.java similarity index 62% rename from paper/src/main/java/net/momirealms/customnameplates/paper/helper/URLClassLoaderAccess.java rename to paper/src/main/java/net/momirealms/customnameplates/paper/libraries/classpath/URLClassLoaderAccess.java index 476d11a..fbba7af 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/helper/URLClassLoaderAccess.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.helper; +package net.momirealms.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/Dependency.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/Dependency.java new file mode 100644 index 0000000..25021a0 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/Dependency.java @@ -0,0 +1,360 @@ +/* + * 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.customnameplates.paper.libraries.dependencies; + +import com.google.common.collect.ImmutableList; +import net.momirealms.customnameplates.paper.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" + ), + ASM_COMMONS( + "org.ow2.asm", + "asm-commons", + "9.1", + null, + "asm-commons" + ), + JAR_RELOCATOR( + "me.lucko", + "jar-relocator", + "1.7", + null, + "jar-relocator" + ), + KYORI_OPTION( + "net{}kyori", + "option", + "1.0.0", + null, + "kyori-option", + Relocation.of("option", "net{}kyori{}option") + ), + ADVENTURE_API( + "net{}kyori", + "adventure-api", + "4.15.0", + null, + "adventure-api", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_KEY( + "net{}kyori", + "adventure-key", + "4.15.0", + null, + "adventure-key", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_NBT( + "net{}kyori", + "adventure-nbt", + "4.15.0", + null, + "adventure-nbt", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_LEGACY_SERIALIZER( + "net{}kyori", + "adventure-text-serializer-legacy", + "4.15.0", + null, + "adventure-text-serializer-legacy", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_TEXT_LOGGER( + "net{}kyori", + "adventure-text-logger-slf4j", + "4.15.0", + null, + "adventure-text-logger-slf4j", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_GSON( + "net{}kyori", + "adventure-text-serializer-gson", + "4.15.0", + null, + "adventure-text-serializer-gson", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_GSON_LEGACY( + "net{}kyori", + "adventure-text-serializer-gson-legacy-impl", + "4.15.0", + null, + "adventure-text-serializer-gson-legacy-impl", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_PLATFORM( + "net{}kyori", + "adventure-platform-api", + "4.3.2", + null, + "adventure-platform-api", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_PLATFORM_BUKKIT( + "net{}kyori", + "adventure-platform-bukkit", + "4.3.2", + null, + "adventure-platform-bukkit", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_PLATFORM_FACET( + "net{}kyori", + "adventure-platform-facet", + "4.3.2", + null, + "adventure-platform-facet", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + ADVENTURE_TEXT_MINIMESSAGE( + "net{}kyori", + "adventure-text-minimessage", + "4.15.0", + null, + "adventure-text-minimessage", + Relocation.of("adventure", "net{}kyori{}adventure") + ), + COMMAND_API( + "dev{}jorel", + "commandapi-bukkit-shade", + "9.3.0", + null, + "commandapi-bukkit", + Relocation.of("commandapi", "dev{}jorel{}commandapi") + ), + MARIADB_DRIVER( + "org{}mariadb{}jdbc", + "mariadb-java-client", + "3.3.2", + null, + "mariadb-java-client", + Relocation.of("mariadb", "org{}mariadb") + ), + BOOSTED_YAML( + "dev{}dejvokep", + "boosted-yaml", + "1.3.2", + null, + "boosted-yaml", + Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml") + ), + MYSQL_DRIVER( + "com{}mysql", + "mysql-connector-j", + "8.3.0", + null, + "mysql-connector-j", + Relocation.of("mysql", "com{}mysql") + ), + H2_DRIVER( + "com.h2database", + "h2", + "2.2.224", + null, + "h2database" + ), + SQLITE_DRIVER( + "org.xerial", + "sqlite-jdbc", + "3.45.1.0", + null, + "sqlite-jdbc" + ), + HIKARI( + "com{}zaxxer", + "HikariCP", + "5.0.1", + null, + "HikariCP", + Relocation.of("hikari", "com{}zaxxer{}hikari") + ), + SLF4J_SIMPLE( + "org.slf4j", + "slf4j-simple", + "2.0.12", + null, + "slf4j-simple" + ), + SLF4J_API( + "org.slf4j", + "slf4j-api", + "2.0.12", + null, + "slf4j-api" + ), + MONGODB_DRIVER_CORE( + "org{}mongodb", + "mongodb-driver-core", + "4.11.1", + null, + "mongodb-driver-core", + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + MONGODB_DRIVER_SYNC( + "org{}mongodb", + "mongodb-driver-sync", + "4.11.1", + null, + "mongodb-driver-sync", + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + MONGODB_DRIVER_BSON( + "org{}mongodb", + "bson", + "4.11.1", + null, + "mongodb-bson", + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + JEDIS( + "redis{}clients", + "jedis", + "5.1.0", + null, + "jedis", + Relocation.of("jedis", "redis{}clients{}jedis"), + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ), + BSTATS_BASE( + "org{}bstats", + "bstats-base", + "3.0.2", + null, + "bstats-base", + Relocation.of("bstats", "org{}bstats") + ), + BSTATS_BUKKIT( + "org{}bstats", + "bstats-bukkit", + "3.0.2", + null, + "bstats-bukkit", + Relocation.of("bstats", "org{}bstats") + ), + COMMONS_POOL_2( + "org{}apache{}commons", + "commons-pool2", + "2.11.0", + null, + "commons-pool2", + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ), + GSON( + "com.google.code.gson", + "gson", + "2.10.1", + null, + "gson" + ),; + + private final String mavenRepoPath; + private final String version; + private final List relocations; + private final String repo; + private final String artifact; + + private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; + + Dependency(String groupId, String artifactId, String version, String repo, String artifact) { + this(groupId, artifactId, version, repo, artifact, new Relocation[0]); + } + + Dependency(String groupId, String artifactId, String version, String repo, String artifact, 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; + this.artifact = artifact; + } + + private static String rewriteEscaping(String s) { + return s.replace("{}", "."); + } + + public String getFileName(String classifier) { + String name = artifact.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyDownloadException.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyDownloadException.java new file mode 100644 index 0000000..1bed837 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibrary.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyManager.java similarity index 55% rename from paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibrary.java rename to paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyManager.java index 010f319..ccdadf2 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibrary.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.helper; +package net.momirealms.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyManagerImpl.java new file mode 100644 index 0000000..d593884 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyManagerImpl.java @@ -0,0 +1,231 @@ +/* + * 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.customnameplates.paper.libraries.dependencies; + +import com.google.common.collect.ImmutableSet; +import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl; +import net.momirealms.customnameplates.paper.libraries.classpath.ClassPathAppender; +import net.momirealms.customnameplates.paper.libraries.dependencies.classloader.IsolatedClassLoader; +import net.momirealms.customnameplates.paper.libraries.dependencies.relocation.Relocation; +import net.momirealms.customnameplates.paper.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(CustomNameplatesPluginImpl 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(CustomNameplatesPluginImpl 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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyRegistry.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyRegistry.java new file mode 100644 index 0000000..b9a94c5 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyRegistry.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.customnameplates.paper.libraries.dependencies; + +import com.google.gson.JsonElement; + +/** + * Applies LuckPerms specific behaviour for {@link Dependency}s. + */ +public class DependencyRegistry { + + public boolean shouldAutoLoad(Dependency dependency) { + return switch (dependency) { + // all used within 'isolated' classloaders, and are therefore not + // relocated. + case ASM, ASM_COMMONS, JAR_RELOCATOR, H2_DRIVER, SQLITE_DRIVER -> false; + default -> 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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyRepository.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/DependencyRepository.java new file mode 100644 index 0000000..b6ac7a3 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/classloader/IsolatedClassLoader.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/classloader/IsolatedClassLoader.java new file mode 100644 index 0000000..0272b25 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/Relocation.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/Relocation.java new file mode 100644 index 0000000..ced09e4 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.libraries.dependencies.relocation; + +import java.util.Objects; + +public final class Relocation { + private static final String RELOCATION_PREFIX = "net.momirealms.customnameplates.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/RelocationHandler.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/RelocationHandler.java new file mode 100644 index 0000000..c93d411 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/RelocationHandler.java @@ -0,0 +1,91 @@ +/* + * 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.customnameplates.paper.libraries.dependencies.relocation; + +import net.momirealms.customnameplates.paper.libraries.dependencies.Dependency; +import net.momirealms.customnameplates.paper.libraries.dependencies.DependencyManager; +import net.momirealms.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/RelocationHelper.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/dependencies/relocation/RelocationHelper.java new file mode 100644 index 0000000..209e8b5 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/loader/JarInJarClassLoader.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/loader/JarInJarClassLoader.java new file mode 100644 index 0000000..3c3a836 --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/helper/Repository.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/loader/LoaderBootstrap.java similarity index 73% rename from paper/src/main/java/net/momirealms/customnameplates/paper/helper/Repository.java rename to paper/src/main/java/net/momirealms/customnameplates/paper/libraries/loader/LoaderBootstrap.java index fa047ec..91845aa 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/helper/Repository.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.helper; - -import java.lang.annotation.*; +package net.momirealms.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/loader/LoadingException.java b/paper/src/main/java/net/momirealms/customnameplates/paper/libraries/loader/LoadingException.java new file mode 100644 index 0000000..adc317f --- /dev/null +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/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.customnameplates.paper.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/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java index aca7130..21625ad 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java @@ -238,7 +238,7 @@ public class VersionManagerImpl implements VersionManager, Listener { final Player player = event.getPlayer(); if (player.isOp()) { if (!PlaceholderAPI.isRegistered("player")) { - AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, "You haven't installed Player Expansion yet. Click HERE to download."); + AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, "You haven't installed Player Expansion yet. Click HERE to download. Otherwise you might encounter console spam on first installation."); } } } diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java index 1dd7dbc..a33a00f 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java @@ -43,6 +43,7 @@ public class MagicCosmeticsListener implements Listener { if (cosmetic instanceof Hat hat) { if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) { unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY()); + unlimitedPlayer.teleport(); } } } @@ -59,6 +60,7 @@ public class MagicCosmeticsListener implements Listener { final Cosmetic cosmetic = playerData.getHat(); if (cosmetic instanceof Hat hat) { unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY()); + unlimitedPlayer.teleport(); } } } @@ -72,6 +74,7 @@ public class MagicCosmeticsListener implements Listener { if (cosmetic instanceof Hat hat) { if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) { unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY()); + unlimitedPlayer.teleport(); } } } @@ -82,6 +85,7 @@ public class MagicCosmeticsListener implements Listener { if (event.getCosmeticType() == CosmeticType.HAT) { if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) { unlimitedPlayer.setHatOffset(0); + unlimitedPlayer.teleport(); } } } @@ -92,6 +96,7 @@ public class MagicCosmeticsListener implements Listener { if (cosmetic instanceof Hat hat) { if (unlimitedTagManager.getUnlimitedObject(event.getPlayerData().getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) { unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY()); + unlimitedPlayer.teleport(); } } } diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java index 77a849a..4403b36 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java @@ -48,7 +48,7 @@ public class SchedulerImpl implements Scheduler { } /** - * Reloads the scheduler configuration based on CustomFishingPlugin settings. + * Reloads the scheduler configuration based on settings. */ public void reload() { try { diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java index 993cdc4..fd7845b 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java @@ -75,7 +75,7 @@ public abstract class AbstractHikariDatabase extends AbstractSQLDatabase impleme return; } - super.tablePrefix = section.getString("table-prefix", "customfishing"); + super.tablePrefix = section.getString("table-prefix", "nameplates"); HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setUsername(section.getString("user", "root")); hikariConfig.setPassword(section.getString("password", "pa55w0rd")); diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java index 7b60399..b0fefbd 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java @@ -19,19 +19,23 @@ package net.momirealms.customnameplates.paper.storage.method.database.sql; import net.momirealms.customnameplates.api.CustomNameplatesPlugin; import net.momirealms.customnameplates.api.data.StorageType; +import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl; +import net.momirealms.customnameplates.paper.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(CustomNameplatesPlugin plugin) { super(plugin); @@ -44,10 +48,20 @@ public class H2Impl extends AbstractSQLDatabase { public void initialize() { YamlConfiguration config = plugin.getConfig("database.yml"); File databaseFile = new File(plugin.getDataFolder(), config.getString("H2.file", "data.db")); - super.tablePrefix = config.getString("H2.table-prefix", "customnameplates"); + super.tablePrefix = config.getString("H2.table-prefix", "nameplates"); final String url = String.format("jdbc:h2:%s", databaseFile.getAbsolutePath()); - this.connectionPool = JdbcConnectionPool.create(url, "sa", ""); + ClassLoader classLoader = ((CustomNameplatesPluginImpl) 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/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java index 04d7fc6..683af52 100644 --- a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java +++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java @@ -51,7 +51,7 @@ public class SQLiteImpl extends AbstractSQLDatabase { public void initialize() { YamlConfiguration config = plugin.getConfig("database.yml"); this.databaseFile = new File(plugin.getDataFolder(), config.getString("SQLite.file", "data") + ".db"); - super.tablePrefix = config.getString("SQLite.table-prefix", "customfishing"); + super.tablePrefix = config.getString("SQLite.table-prefix", "nameplates"); super.createTableIfNotExist(); } diff --git a/universe/build.gradle.kts b/universe/build.gradle.kts index 6828d39..9d4a00f 100644 --- a/universe/build.gradle.kts +++ b/universe/build.gradle.kts @@ -11,5 +11,14 @@ tasks { relocate ("net.kyori", "net.momirealms.customnameplates.libraries") relocate ("org.bstats", "net.momirealms.customnameplates.libraries.bstats") relocate ("net.momirealms.biomeapi", "net.momirealms.customnameplates.libraries.biomeapi") + relocate ("org.apache.commons.pool2", "net.momirealms.customnameplates.libraries.commonspool2") + relocate ("com.mysql", "net.momirealms.customnameplates.libraries.mysql") + relocate ("org.mariadb", "net.momirealms.customnameplates.libraries.mariadb") + relocate ("com.zaxxer.hikari", "net.momirealms.customnameplates.libraries.hikari") + relocate ("redis.clients.jedis", "net.momirealms.customnameplates.libraries.jedis") + relocate ("com.mongodb", "net.momirealms.customnameplates.libraries.mongodb") + relocate ("org.bson", "net.momirealms.customnameplates.libraries.bson") + relocate ("dev.jorel.commandapi", "net.momirealms.customnameplates.libraries.commandapi") + relocate ("dev.dejvokep.boostedyaml", "net.momirealms.customnameplates.libraries.boostedyaml") } } \ No newline at end of file