From 6e9513f2eaf7ca68ed4c813de9d37e9b10f33e95 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 31 May 2021 12:42:54 -0700 Subject: [PATCH] Enable plugins to register Addons (#1768) * Enable plugins to register Addons * Pladdon approach. Loads addons as plugins. * Added auto-move for pladdons. --- .../bentobox/api/addons/AddonClassLoader.java | 8 +- .../bentobox/bentobox/api/addons/Pladdon.java | 51 +++++++++++++ .../bentobox/managers/AddonsManager.java | 75 +++++++++++++++++-- src/main/resources/plugin.yml | 15 +++- 4 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java diff --git a/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java b/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java index 1886fce45..3c62a4153 100644 --- a/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java +++ b/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.java @@ -33,14 +33,14 @@ public class AddonClassLoader extends URLClassLoader { private Addon addon; private AddonsManager loader; - public AddonClassLoader(AddonsManager addonsManager, YamlConfiguration data, File path, ClassLoader parent) + public AddonClassLoader(AddonsManager addonsManager, YamlConfiguration data, File jarFile, ClassLoader parent) throws InvalidAddonInheritException, MalformedURLException, InvalidDescriptionException, InvalidAddonDescriptionException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { - super(new URL[]{path.toURI().toURL()}, parent); + super(new URL[]{jarFile.toURI().toURL()}, parent); loader = addonsManager; @@ -55,7 +55,7 @@ public class AddonClassLoader extends URLClassLoader { throw new InvalidAddonFormatException("Package declaration cannot start with 'world.bentobox.bentobox'"); } } catch (Exception e) { - throw new InvalidDescriptionException("Could not load '" + path.getName() + "' in folder '" + path.getParent() + "' - " + e.getMessage()); + throw new InvalidDescriptionException("Could not load '" + jarFile.getName() + "' in folder '" + jarFile.getParent() + "' - " + e.getMessage()); } Class addonClass; @@ -78,7 +78,7 @@ public class AddonClassLoader extends URLClassLoader { * @throws InvalidAddonDescriptionException - if there's a bug in the addon.yml */ @NonNull - private AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException { + public static AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException { AddonDescription.Builder builder = new AddonDescription.Builder(data.getString("main"), data.getString("name"), data.getString("version")) .authors(data.getString("authors")) .metrics(data.getBoolean("metrics", true)) diff --git a/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java b/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java new file mode 100644 index 000000000..1b712757d --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java @@ -0,0 +1,51 @@ +package world.bentobox.bentobox.api.addons; + +import java.io.File; +import java.io.IOException; + +import org.bukkit.plugin.java.JavaPlugin; + +import com.google.common.io.Files; + +/** + * @author tastybento + * + */ +public abstract class Pladdon extends JavaPlugin { + + private static final String ADDONS_FOLDER = "BentoBox/addons"; + + public abstract Addon getAddon(); + + @Override + public void onLoad() { + String parentFolder = getFile().getParent(); + if (parentFolder == null || !parentFolder.endsWith(ADDONS_FOLDER)) { + // Jar is in the wrong place. Let's move it + moveJar(); + } + } + + public void moveJar() { + getLogger().severe(getFile().getName() + " must be in the BentoBox/addons folder! Trying to move it there..."); + File addons = new File(getFile().getParent(), ADDONS_FOLDER); + if (addons.exists() || addons.mkdirs()) { + File to = new File(addons, getFile().getName()); + if (!to.exists()) { + try { + Files.move(getFile(), to); + getLogger().severe(getFile().getName() + " moved successfully."); + + } catch (IOException ex) { + getLogger().severe("Failed to move it. " + ex.getMessage()); + getLogger().severe("Move " + getFile().getName() + " manually into the BentoBox/addons folder. Then restart server."); + } + } else { + getLogger().warning(getFile().getName() + " already is in the addons folder. Delete the one in the plugins folder."); + } + } else { + getLogger().severe("BentoBox addons folder could not be made! " + addons.getAbsolutePath()); + } + + } +} diff --git a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java index c32ec6063..b7c5b26a2 100644 --- a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java @@ -4,6 +4,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,6 +31,9 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.generator.ChunkGenerator; import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.permissions.DefaultPermissions; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -38,6 +43,7 @@ import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon.State; import world.bentobox.bentobox.api.addons.AddonClassLoader; import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.addons.Pladdon; import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonDescriptionException; import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonFormatException; import world.bentobox.bentobox.api.configuration.ConfigObject; @@ -65,6 +71,8 @@ public class AddonsManager { private @NonNull Map<@NonNull String, @Nullable GameModeAddon> worldNames; private @NonNull Map<@NonNull Addon, @NonNull List> listeners; + private final PluginLoader pluginLoader; + public AddonsManager(@NonNull BentoBox plugin) { this.plugin = plugin; addons = new ArrayList<>(); @@ -72,6 +80,42 @@ public class AddonsManager { classes = new HashMap<>(); listeners = new HashMap<>(); worldNames = new HashMap<>(); + pluginLoader = plugin.getPluginLoader(); + } + + /** + * Register a plugin as an addon + * @param parent - parent plugin + * @param addon - addon class + */ + public void registerAddon(Plugin parent, Addon addon) { + plugin.log("Registering " + parent.getDescription().getName()); + + // Get description in the addon.yml file + // Open a reader to the jar + try (BufferedReader reader = new BufferedReader(new InputStreamReader(parent.getResource("addon.yml")))) { + setAddonFile(parent, addon); + // Grab the description in the addon.yml file + YamlConfiguration data = new YamlConfiguration(); + data.load(reader); + // Description + addon.setDescription(AddonClassLoader.asDescription(data)); + // Set various files + addon.setDataFolder(parent.getDataFolder()); + // Initialize + initializeAddon(addon); + sortAddons(); + + } catch (Exception e) { + plugin.logError("Failed to register addon: " + e); + } + + } + + private void setAddonFile(Plugin parent, Addon addon) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method getFileMethod = JavaPlugin.class.getDeclaredMethod("getFile"); + getFileMethod.setAccessible(true); + addon.setFile((File) getFileMethod.invoke(parent)); } /** @@ -109,10 +153,24 @@ public class AddonsManager { return; } // Load the addon - addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader()); + try { - // Get the addon itself - addon = addonClassLoader.getAddon(); + Plugin pladdon = pluginLoader.loadPlugin(f); + if (pladdon instanceof Pladdon) { + addon = ((Pladdon) pladdon).getAddon(); + addon.setDescription(AddonClassLoader.asDescription(data)); + } else { + plugin.logError("Could not load pladdon!"); + return; + } + } catch (Exception ex) { + // Addon not pladdon + addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader()); + // Get the addon itself + addon = addonClassLoader.getAddon(); + // Add to the list of loaders + loaders.put(addon, addonClassLoader); + } } catch (Exception e) { // We couldn't load the addon, aborting. plugin.logError("Could not load addon! " + e.getMessage()); @@ -123,7 +181,12 @@ public class AddonsManager { // Initialize some settings addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName())); addon.setFile(f); + // Initialize addon + initializeAddon(addon); + } + + private void initializeAddon(Addon addon) { // Locales plugin.getLocalesManager().copyLocalesFromAddonJar(addon); plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName()); @@ -134,10 +197,6 @@ public class AddonsManager { // Add it to the list of addons addons.remove(addon); addons.add(addon); - - // Add to the list of loaders - loaders.put(addon, addonClassLoader); - // Checks if this addon is compatible with the current BentoBox version. if (!isAddonCompatibleWithBentoBox(addon)) { // It is not, abort. @@ -165,6 +224,7 @@ public class AddonsManager { // Unhandled exception. We'll give a bit of debug here. handleAddonError(addon, e); } + } /** @@ -383,6 +443,7 @@ public class AddonsManager { // Grab the description in the addon.yml file YamlConfiguration data = new YamlConfiguration(); data.load(reader); + reader.close(); return data; } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b9eca6271..631731424 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -12,11 +12,22 @@ load: STARTUP loadbefore: [Multiverse-Core, Residence] -softdepend: [Vault, PlaceholderAPI, dynmap, WorldEdit, WorldBorderAPI, BsbMongo, WorldGeneratorApi, AdvancedChests, LangUtils, WildStacker, LuckPerms] +softdepend: + - Vault + - PlaceholderAPI + - dynmap + - WorldEdit + - WorldBorderAPI + - BsbMongo + - WorldGeneratorApi + - AdvancedChests + - LangUtils + - WildStacker + - LuckPerms permissions: bentobox.admin: - description: Allows most of bentobox commands usage + description: Allows admin command usage default: op children: bentobox.admin.catalog: