dynamic library loading (500kb jar :o)

This commit is contained in:
Pyrbu 2023-10-16 04:31:25 +02:00
parent f519020c5b
commit 54094ce1d5
7 changed files with 416 additions and 47 deletions

@ -17,17 +17,18 @@ processResources {
dependencies {
compileOnly "me.clip:placeholderapi:2.11.3" // Placeholder support
implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
implementation "com.github.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker
implementation "com.github.retrooper.packetevents:spigot:2.1.0-SNAPSHOT" // Packets
implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
implementation "lol.pyr:director-adventure:2.1.1" // Commands
compileOnly "com.google.code.gson:gson:2.10.1" // JSON parsing
compileOnly "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
compileOnly "com.github.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker
compileOnly "com.github.retrooper.packetevents:spigot:2.1.0-SNAPSHOT" // Packets
compileOnly "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
compileOnly "lol.pyr:director-adventure:2.1.1" // Commands
// Fancy text library
implementation "net.kyori:adventure-platform-bukkit:4.3.0"
implementation "net.kyori:adventure-text-minimessage:4.14.0"
compileOnly "net.kyori:adventure-platform-bukkit:4.3.1"
compileOnly "net.kyori:adventure-text-minimessage:4.14.0"
implementation "me.lucko:jar-relocator:1.7"
implementation project(":api")
}
@ -35,6 +36,10 @@ shadowJar {
archivesBaseName = "ZNPCsPlus"
archiveClassifier.set ""
relocate "org.objectweb.asm", "lol.pyr.znpcsplus.lib.asm"
relocate "me.lucko.jarrelocator", "lol.pyr.znpcsplus.lib.jarrelocator"
// When changing anything here remember to also update the bootstrap
relocate "org.bstats", "lol.pyr.znpcsplus.lib.bstats"
relocate "me.robertlit.spigotresources", "lol.pyr.znpcsplus.lib.spigotresources"
relocate "net.kyori", "lol.pyr.znpcsplus.lib.kyori"

@ -40,8 +40,8 @@ import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.tasks.HologramRefreshTask;
import lol.pyr.znpcsplus.tasks.ViewableHideOnLeaveListener;
import lol.pyr.znpcsplus.tasks.NpcProcessorTask;
import lol.pyr.znpcsplus.tasks.ViewableHideOnLeaveListener;
import lol.pyr.znpcsplus.updater.UpdateChecker;
import lol.pyr.znpcsplus.updater.UpdateNotificationListener;
import lol.pyr.znpcsplus.user.UserListener;
@ -51,34 +51,36 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bstats.bukkit.Metrics;
import org.bukkit.*;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
public class ZNpcsPlus extends JavaPlugin {
public class ZNpcsPlus {
private final LegacyComponentSerializer textSerializer = LegacyComponentSerializer.builder()
.character('&')
.hexCharacter('#')
.hexColors().build();
private final List<Runnable> shutdownTasks = new ArrayList<>();
private PacketEventsAPI<Plugin> packetEvents;
private final PacketEventsAPI<Plugin> packetEvents;
private final ZNpcsPlusBootstrap bootstrap;
@Override
public void onLoad() {
packetEvents = SpigotPacketEventsBuilder.build(this);
public ZNpcsPlus(ZNpcsPlusBootstrap bootstrap) {
this.bootstrap = bootstrap;
packetEvents = SpigotPacketEventsBuilder.build(bootstrap);
PacketEvents.setAPI(packetEvents);
packetEvents.getSettings().checkForUpdates(false);
packetEvents.load();
@ -88,7 +90,6 @@ public class ZNpcsPlus extends JavaPlugin {
Bukkit.getConsoleSender().sendMessage(str);
}
@Override
public void onEnable() {
getDataFolder().mkdirs();
@ -106,7 +107,7 @@ public class ZNpcsPlus extends JavaPlugin {
} catch (IOException e) {
log(ChatColor.RED + " * Moving legacy files to subfolder failed, plugin will shut down.");
e.printStackTrace();
pluginManager.disablePlugin(this);
pluginManager.disablePlugin(bootstrap);
return;
}
@ -114,12 +115,12 @@ public class ZNpcsPlus extends JavaPlugin {
packetEvents.init();
BukkitAudiences adventure = BukkitAudiences.create(this);
BukkitAudiences adventure = BukkitAudiences.create(bootstrap);
shutdownTasks.add(adventure::close);
log(ChatColor.WHITE + " * Initializing components...");
TaskScheduler scheduler = FoliaUtil.isFolia() ? new FoliaScheduler(this) : new SpigotScheduler(this);
TaskScheduler scheduler = FoliaUtil.isFolia() ? new FoliaScheduler(bootstrap) : new SpigotScheduler(bootstrap);
shutdownTasks.add(scheduler::cancelAll);
ConfigManager configManager = new ConfigManager(getDataFolder());
@ -136,8 +137,8 @@ public class ZNpcsPlus extends JavaPlugin {
UserManager userManager = new UserManager();
shutdownTasks.add(userManager::shutdown);
BungeeConnector bungeeConnector = new BungeeConnector(this);
BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
DataImporterRegistry importerRegistry = new DataImporterRegistry(configManager, adventure,
scheduler, packetFactory, textSerializer, typeRegistry, getDataFolder().getParentFile(),
propertyRegistry, skinCache, npcRegistry, bungeeConnector);
@ -150,23 +151,23 @@ public class ZNpcsPlus extends JavaPlugin {
typeRegistry.registerDefault(packetEvents, propertyRegistry);
actionRegistry.registerTypes(scheduler, adventure, textSerializer, bungeeConnector);
packetEvents.getEventManager().registerListener(new InteractionPacketListener(userManager, npcRegistry, scheduler), PacketListenerPriority.MONITOR);
new Metrics(this, 18244);
pluginManager.registerEvents(new UserListener(userManager), this);
new Metrics(bootstrap, 18244);
pluginManager.registerEvents(new UserListener(userManager), bootstrap);
registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
typeRegistry, propertyRegistry, importerRegistry, configManager);
log(ChatColor.WHITE + " * Starting tasks...");
if (configManager.getConfig().checkForUpdates()) {
UpdateChecker updateChecker = new UpdateChecker(this.getDescription());
UpdateChecker updateChecker = new UpdateChecker(getDescription());
scheduler.runDelayedTimerAsync(updateChecker, 5L, 6000L);
pluginManager.registerEvents(new UpdateNotificationListener(this, adventure, updateChecker), this);
pluginManager.registerEvents(new UpdateNotificationListener(this, adventure, updateChecker, scheduler), bootstrap);
}
scheduler.runDelayedTimerAsync(new NpcProcessorTask(npcRegistry, configManager, propertyRegistry), 60L, 3L);
scheduler.runDelayedTimerAsync(new HologramRefreshTask(npcRegistry), 60L, 20L);
scheduler.runDelayedTimerAsync(new SkinCacheCleanTask(skinCache), 1200, 1200);
pluginManager.registerEvents(new ViewableHideOnLeaveListener(), this);
pluginManager.registerEvents(new ViewableHideOnLeaveListener(), bootstrap);
log(ChatColor.WHITE + " * Loading data...");
npcRegistry.reload();
@ -188,7 +189,7 @@ public class ZNpcsPlus extends JavaPlugin {
}
}
NpcApiProvider.register(this, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, skinCache));
NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, skinCache));
log(ChatColor.WHITE + " * Loading complete! (" + (System.currentTimeMillis() - before) + "ms)");
log("");
@ -208,7 +209,6 @@ public class ZNpcsPlus extends JavaPlugin {
}
}
@Override
public void onDisable() {
NpcApiProvider.unregister();
for (Runnable runnable : shutdownTasks) runnable.run();
@ -238,7 +238,7 @@ public class ZNpcsPlus extends JavaPlugin {
ConfigManager configManager) {
Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
CommandManager manager = new CommandManager(this, adventure, incorrectUsageMessage);
CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
manager.registerParser(NpcTypeImpl.class, new NpcTypeParser(incorrectUsageMessage, typeRegistry));
manager.registerParser(NpcEntryImpl.class, new NpcEntryParser(npcRegistry, incorrectUsageMessage));
@ -280,7 +280,7 @@ public class ZNpcsPlus extends JavaPlugin {
registerEnumParser(manager, RabbitType.class, incorrectUsageMessage);
registerEnumParser(manager, AttachDirection.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(loadHelpMessage("root"))
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry))
.addSubcommand("create", new CreateCommand(npcRegistry, typeRegistry))
.addSubcommand("reloadconfig", new ReloadConfigCommand(configManager))
@ -292,14 +292,14 @@ public class ZNpcsPlus extends JavaPlugin {
.addSubcommand("list", new ListCommand(npcRegistry))
.addSubcommand("near", new NearCommand(npcRegistry))
.addSubcommand("type", new TypeCommand(npcRegistry, typeRegistry))
.addSubcommand("property", new MultiCommand(loadHelpMessage("property"))
.addSubcommand("property", new MultiCommand(bootstrap.loadHelpMessage("property"))
.addSubcommand("set", new PropertySetCommand(npcRegistry))
.addSubcommand("remove", new PropertyRemoveCommand(npcRegistry)))
.addSubcommand("storage", new MultiCommand(loadHelpMessage("storage"))
.addSubcommand("storage", new MultiCommand(bootstrap.loadHelpMessage("storage"))
.addSubcommand("save", new SaveAllCommand(npcRegistry))
.addSubcommand("reload", new LoadAllCommand(npcRegistry))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry)))
.addSubcommand("holo", new MultiCommand(loadHelpMessage("holo"))
.addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
.addSubcommand("add", new HoloAddCommand(npcRegistry))
.addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
.addSubcommand("delete", new HoloDeleteCommand(npcRegistry))
@ -310,7 +310,7 @@ public class ZNpcsPlus extends JavaPlugin {
.addSubcommand("setitem", new HoloSetItemCommand(npcRegistry))
.addSubcommand("offset", new HoloOffsetCommand(npcRegistry))
.addSubcommand("refreshdelay", new HoloRefreshDelayCommand(npcRegistry)))
.addSubcommand("action", new MultiCommand(loadHelpMessage("action"))
.addSubcommand("action", new MultiCommand(bootstrap.loadHelpMessage("action"))
.addSubcommand("add", new ActionAddCommand(npcRegistry, actionRegistry))
.addSubcommand("clear", new ActionClearCommand(npcRegistry))
.addSubcommand("delete", new ActionDeleteCommand(npcRegistry))
@ -323,10 +323,11 @@ public class ZNpcsPlus extends JavaPlugin {
manager.registerParser(clazz, new EnumParser<>(clazz, message));
}
private Message<CommandContext> loadHelpMessage(String name) {
Reader reader = getTextResource("help-messages/" + name + ".txt");
if (reader == null) throw new RuntimeException(name + ".txt is missing from the help-messages folder in the ZNPCsPlus jar!");
Component component = MiniMessage.miniMessage().deserialize(FileUtil.dumpReaderAsString(reader));
return context -> context.send(component);
public File getDataFolder() {
return bootstrap.getDataFolder();
}
public PluginDescriptionFile getDescription() {
return bootstrap.getDescription();
}
}

@ -0,0 +1,90 @@
package lol.pyr.znpcsplus;
import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.common.message.Message;
import lol.pyr.znpcsplus.libraries.LibraryLoader;
import lol.pyr.znpcsplus.util.FileUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.Reader;
public class ZNpcsPlusBootstrap extends JavaPlugin {
private ZNpcsPlus zNpcsPlus;
@Override
public void onLoad() {
getLogger().info("Downloading and loading libraries, this might take a while if this is the first time you're launching the plugin");
LibraryLoader loader = new LibraryLoader(this, new File(getDataFolder(), "libraries"));
loader.addRelocation(decrypt("org..bstats"), "lol.pyr.znpcsplus.lib.bstats");
loader.addRelocation(decrypt("me..robertlit..spigotresources"), "lol.pyr.znpcsplus.lib.spigotresources");
loader.addRelocation(decrypt("net..kyori"), "lol.pyr.znpcsplus.lib.kyori");
loader.addRelocation(decrypt("org..checkerframework"), "lol.pyr.znpcsplus.lib.checkerframework");
loader.addRelocation(decrypt("com..google"), "lol.pyr.znpcsplus.lib.google");
loader.addRelocation(decrypt("com..github..retrooper..packetevents"), "lol.pyr.znpcsplus.lib.packetevents.api");
loader.addRelocation(decrypt("io..github..retrooper..packetevents"), "lol.pyr.znpcsplus.lib.packetevents.impl");
loader.addRelocation(decrypt("org..yaml..snakeyaml"), "lol.pyr.znpcsplus.lib.snakeyaml");
loader.addRelocation(decrypt("space..arim..dazzleconf"), "lol.pyr.znpcsplus.lib.dazzleconf");
loader.addRelocation(decrypt("lol..pyr..director"), "lol.pyr.znpcsplus.lib.command");
loader.loadLibrary(decrypt("com..google..guava"), "guava", "18.0");
loader.loadLibrary(decrypt("com..google..code..gson"), "gson", "2.10.1");
loader.loadLibrary(decrypt("org..bstats"), "bstats-base", "3.0.2");
loader.loadLibrary(decrypt("org..bstats"), "bstats-bukkit", "3.0.2");
loader.loadLibrary("com.github.robertlit", "SpigotResourcesAPI", "2.0", "https://jitpack.io");
loader.loadSnapshotLibrary(decrypt("com..github..retrooper..packetevents"), "api", "2.1.0-SNAPSHOT", "2.1.0-20231011.171910-4", "https://repo.codemc.io/repository/maven-snapshots/");
loader.loadSnapshotLibrary(decrypt("com..github..retrooper..packetevents"), "spigot", "2.1.0-SNAPSHOT", "2.1.0-20231011.171910-4", "https://repo.codemc.io/repository/maven-snapshots/");
loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-core", "1.2.1");
loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-ext-snakeyaml", "1.2.1");
loader.loadLibrary("org.yaml", "snakeyaml", "1.33");
loader.loadLibrary("lol.pyr", "director-adventure", "2.1.1", "https://repo.pyr.lol/releases");
loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-legacy", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "examination-api", "1.3.0");
loader.loadLibrary(decrypt("net..kyori"), "examination-string", "1.3.0");
loader.deleteUnloadedLibraries();
getLogger().info("Loaded " + loader.loadedLibraryCount() + " libraries!");
zNpcsPlus = new ZNpcsPlus(this);
}
@Override
public void onEnable() {
if (zNpcsPlus != null) zNpcsPlus.onEnable();
}
@Override
public void onDisable() {
if (zNpcsPlus != null) zNpcsPlus.onDisable();
}
protected Message<CommandContext> loadHelpMessage(String name) {
Reader reader = getTextResource("help-messages/" + name + ".txt");
if (reader == null) throw new RuntimeException(name + ".txt is missing from the help-messages folder in the ZNPCsPlus jar!");
Component component = MiniMessage.miniMessage().deserialize(FileUtil.dumpReaderAsString(reader));
return context -> context.send(component);
}
// Ugly hack because of https://github.com/johnrengelman/shadow/issues/232
private static String decrypt(String packageName) {
return packageName.replace("..", ".");
}
}

@ -0,0 +1,119 @@
package lol.pyr.znpcsplus.libraries;
import me.lucko.jarrelocator.JarRelocator;
import me.lucko.jarrelocator.Relocation;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
public class LibraryLoader {
private final static Logger logger = Logger.getLogger("ZNPCsPlus Library Loader");
private final UrlClassLoaderAccess loaderAccess;
private final File librariesFolder;
private final Set<File> loadedLibraries = new HashSet<>();
private final List<Relocation> relocationRules = new ArrayList<>();
public LibraryLoader(Plugin plugin, File librariesFolder) {
loaderAccess = UrlClassLoaderAccess.create((URLClassLoader) plugin.getClass().getClassLoader());
this.librariesFolder = librariesFolder;
if (!librariesFolder.exists()) librariesFolder.mkdirs();
}
public void deleteUnloadedLibraries() {
File[] files = librariesFolder.listFiles();
if (files == null) return;
for (File file : files) if (!loadedLibraries.contains(file)) file.delete();
}
public void addRelocation(String pre, String post) {
relocationRules.add(new Relocation(pre, post));
}
public void loadSnapshotLibrary(String groupId, String artifactId, String version, String snapshotVersion, String repoUrl) {
try {
loadLibrary(groupId + ":" + artifactId + ":" + version,
getDependencyFile(groupId, artifactId, version),
getSnapshotDependencyUrl(groupId, artifactId, version, snapshotVersion, repoUrl));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public int loadedLibraryCount() {
return loadedLibraries.size();
}
public void loadLibrary(String groupId, String artifactId, String version) {
loadLibrary(groupId, artifactId, version, "https://repo1.maven.org/maven2");
}
public void loadLibrary(String groupId, String artifactId, String version, String repoUrl) {
try {
loadLibrary(groupId + ":" + artifactId + ":" + version,
getDependencyFile(groupId, artifactId, version),
getDependencyUrl(groupId, artifactId, version, repoUrl));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
private void loadLibrary(String name, File file, URL url) {
if (!file.exists()) {
try (InputStream in = url.openStream()) {
File temp = new File(file.getParentFile(), file.getName() + ".temp");
Files.copy(in, temp.toPath());
new JarRelocator(temp, file, relocationRules).run();
temp.delete();
// logger.info("Downloaded library " + name);
} catch (IOException e) {
logger.severe("Failed to download library " + name);
e.printStackTrace();
}
}
try {
loaderAccess.addURL(file.toURI().toURL());
loadedLibraries.add(file);
// logger.info("Loaded library " + name);
} catch (Exception e) {
logger.severe("Failed to load library, plugin may not work correctly (" + name + ")");
e.printStackTrace();
}
}
private File getDependencyFile(String groupId, String artifactId, String version) {
return new File(librariesFolder, groupId.replace(".", "-") + "-"
+ artifactId.replace(".", "-") + "-"
+ version.replace(".", "-") + ".jar");
}
private static URL getDependencyUrl(String groupId, String artifactId, String version, String repoUrl) throws MalformedURLException {
String url = repoUrl.endsWith("/") ? repoUrl : repoUrl + "/";
url += groupId.replace(".", "/") + "/";
url += artifactId + "/";
url += version + "/";
url += artifactId + "-" + version + ".jar";
return new URL(url);
}
private static URL getSnapshotDependencyUrl(String groupId, String artifactId, String version, String snapshotVersion, String repoUrl) throws MalformedURLException {
String url = repoUrl.endsWith("/") ? repoUrl : repoUrl + "/";
url += groupId.replace(".", "/") + "/";
url += artifactId + "/";
url += version + "/";
url += artifactId + "-" + snapshotVersion + ".jar";
return new URL(url);
}
}

@ -0,0 +1,152 @@
package lol.pyr.znpcsplus.libraries;
import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* Provides access to {@link URLClassLoader}#addURL.
* From https://github.com/lucko/helper/blob/master/helper/src/main/java/me/lucko/helper/maven/URLClassLoaderAccess.java
*/
public abstract class UrlClassLoaderAccess {
/**
* Creates a {@link UrlClassLoaderAccess} for the given class loader.
*
* @param classLoader the class loader
* @return the access object
*/
static UrlClassLoaderAccess create(URLClassLoader classLoader) {
if (Reflection.isSupported()) {
return new Reflection(classLoader);
} else if (Unsafe.isSupported()) {
return new Unsafe(classLoader);
} else {
return Noop.INSTANCE;
}
}
private final URLClassLoader classLoader;
protected UrlClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(@Nonnull URL url);
/**
* 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) {
throw new RuntimeException(e);
}
}
}
/**
* Accesses using sun.misc.Unsafe, supported on Java 9+.
*
* @author Vaishnav Anil (https://github.com/slimjar/slimjar)
*/
private static class Unsafe extends UrlClassLoaderAccess {
private static final sun.misc.Unsafe UNSAFE;
static {
sun.misc.Unsafe unsafe;
try {
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (sun.misc.Unsafe) unsafeField.get(null);
} catch (Throwable t) {
unsafe = null;
}
UNSAFE = unsafe;
}
private static boolean isSupported() {
return UNSAFE != null;
}
private final Collection<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(@Nonnull URL url) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
}
private static class Noop extends UrlClassLoaderAccess {
private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(@Nonnull URL url) {
throw new UnsupportedOperationException();
}
}
}

@ -1,11 +1,11 @@
package lol.pyr.znpcsplus.updater;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
@ -14,22 +14,24 @@ public class UpdateNotificationListener implements Listener {
private final ZNpcsPlus plugin;
private final BukkitAudiences adventure;
private final UpdateChecker updateChecker;
private final TaskScheduler scheduler;
public UpdateNotificationListener(ZNpcsPlus plugin, BukkitAudiences adventure, UpdateChecker updateChecker) {
public UpdateNotificationListener(ZNpcsPlus plugin, BukkitAudiences adventure, UpdateChecker updateChecker, TaskScheduler scheduler) {
this.plugin = plugin;
this.adventure = adventure;
this.updateChecker = updateChecker;
this.scheduler = scheduler;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (!event.getPlayer().hasPermission("znpcsplus.updates")) return;
if (updateChecker.getStatus() != UpdateChecker.Status.UPDATE_NEEDED) return;
Bukkit.getScheduler().runTaskLater(plugin, () -> {
scheduler.runLaterAsync(() -> {
if (!event.getPlayer().isOnline()) return;
adventure.player(event.getPlayer())
.sendMessage(Component.text(plugin.getDescription().getName() + " v" + updateChecker.getLatestVersion() + " is available now!", NamedTextColor.GOLD).appendNewline()
.append(Component.text("Click this message to open the Spigot page (CLICK)", NamedTextColor.YELLOW)).clickEvent(ClickEvent.openUrl(UpdateChecker.DOWNLOAD_LINK)));
.append(Component.text("Click this message to open the Spigot page (CLICK)", NamedTextColor.YELLOW)).clickEvent(ClickEvent.openUrl(UpdateChecker.DOWNLOAD_LINK)));
}, 100L);
}
}

@ -3,7 +3,7 @@ authors:
- Pyr
- D3v1s0m
main: lol.pyr.znpcsplus.ZNpcsPlus
main: lol.pyr.znpcsplus.ZNpcsPlusBootstrap
load: POSTWORLD
version: ${version}