673 lines
26 KiB
Java
673 lines
26 KiB
Java
package world.bentobox.bentobox.managers;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
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;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.jar.JarEntry;
|
|
import java.util.jar.JarFile;
|
|
import java.util.logging.Level;
|
|
import java.util.stream.Collectors;
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.configuration.ConfigurationSection;
|
|
import org.bukkit.configuration.InvalidConfigurationException;
|
|
import org.bukkit.configuration.file.YamlConfiguration;
|
|
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;
|
|
|
|
import world.bentobox.bentobox.BentoBox;
|
|
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;
|
|
import world.bentobox.bentobox.api.events.addon.AddonEvent;
|
|
import world.bentobox.bentobox.commands.BentoBoxCommand;
|
|
import world.bentobox.bentobox.database.objects.DataObject;
|
|
import world.bentobox.bentobox.util.Util;
|
|
|
|
/**
|
|
* @author tastybento, ComminQ
|
|
*/
|
|
public class AddonsManager {
|
|
|
|
private static final String DEFAULT = ".default";
|
|
|
|
private static final String GAMEMODE = "[gamemode].";
|
|
|
|
@NonNull
|
|
private final List<Addon> addons;
|
|
@NonNull
|
|
private final Map<@NonNull Addon, @Nullable AddonClassLoader> loaders;
|
|
@NonNull
|
|
private final Map<@NonNull Addon, @Nullable Plugin> pladdons;
|
|
@NonNull
|
|
private final Map<String, Class<?>> classes;
|
|
private final BentoBox plugin;
|
|
private @NonNull
|
|
final Map<@NonNull String, @Nullable GameModeAddon> worldNames;
|
|
private @NonNull
|
|
final Map<@NonNull Addon, @NonNull List<Listener>> listeners;
|
|
|
|
private final PluginLoader pluginLoader;
|
|
|
|
public AddonsManager(@NonNull BentoBox plugin) {
|
|
this.plugin = plugin;
|
|
addons = new ArrayList<>();
|
|
loaders = new HashMap<>();
|
|
pladdons = new HashMap<>();
|
|
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
|
|
InputStream resource = parent.getResource("addon.yml");
|
|
if (resource == null) {
|
|
plugin.logError("Failed to register addon: no addon.yml found");
|
|
return;
|
|
}
|
|
// Open a reader to the jar
|
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource))) {
|
|
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));
|
|
}
|
|
|
|
/**
|
|
* Loads all the addons from the addons folder
|
|
*/
|
|
public void loadAddons() {
|
|
plugin.log("Loading addons...");
|
|
File f = new File(plugin.getDataFolder(), "addons");
|
|
if (!f.exists() && !f.mkdirs()) {
|
|
plugin.logError("Cannot create addons folder!");
|
|
return;
|
|
}
|
|
Arrays.stream(Objects.requireNonNull(f.listFiles())).filter(x -> !x.isDirectory() && x.getName().endsWith(".jar")).forEach(this::loadAddon);
|
|
plugin.log("Loaded " + getLoadedAddons().size() + " addons.");
|
|
|
|
if (!getLoadedAddons().isEmpty()) {
|
|
sortAddons();
|
|
}
|
|
}
|
|
|
|
private void loadAddon(@NonNull File f) {
|
|
Addon addon;
|
|
AddonClassLoader addonClassLoader;
|
|
try (JarFile jar = new JarFile(f)) {
|
|
// try loading the addon
|
|
// Get description in the addon.yml file
|
|
YamlConfiguration data = addonDescription(jar);
|
|
// Check if the addon is already loaded (duplicate version?)
|
|
String main = data.getString("main");
|
|
if (main != null && this.getAddonByMainClassName(main).isPresent()) {
|
|
getAddonByMainClassName(main).ifPresent(a -> {
|
|
plugin.logError("Duplicate addon! Addon " + a.getDescription().getName() + " " + a.getDescription().getVersion() + " has already been loaded!");
|
|
plugin.logError("Remove the duplicate and restart!");
|
|
});
|
|
return;
|
|
}
|
|
// Load the addon
|
|
try {
|
|
|
|
Plugin pladdon = pluginLoader.loadPlugin(f);
|
|
if (pladdon instanceof Pladdon) {
|
|
addon = ((Pladdon) pladdon).getAddon();
|
|
addon.setDescription(AddonClassLoader.asDescription(data));
|
|
// Mark pladdon as enabled.
|
|
((Pladdon) pladdon).setEnabled();
|
|
pladdons.put(addon, pladdon);
|
|
} 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 '" + f.getName() + "'. Error is: " + e.getMessage());
|
|
plugin.logStacktrace(e);
|
|
return;
|
|
}
|
|
|
|
// 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());
|
|
|
|
// Fire the load event
|
|
new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.LOAD).build();
|
|
|
|
// Add it to the list of addons
|
|
addons.remove(addon);
|
|
addons.add(addon);
|
|
// Checks if this addon is compatible with the current BentoBox version.
|
|
if (!isAddonCompatibleWithBentoBox(addon)) {
|
|
// It is not, abort.
|
|
plugin.logError("Cannot load " + addon.getDescription().getName() + " because it requires BentoBox version " + addon.getDescription().getApiVersion() + " or greater.");
|
|
plugin.logError("NOTE: Please update BentoBox.");
|
|
addon.setState(State.INCOMPATIBLE);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
addon.setState(Addon.State.LOADED);
|
|
// Run the onLoad.
|
|
addon.onLoad();
|
|
// if game mode, get the world name and generate
|
|
if (addon instanceof GameModeAddon gameMode && !addon.getState().equals(State.DISABLED)) {
|
|
if (!gameMode.getWorldSettings().getWorldName().isEmpty()) {
|
|
worldNames.put(gameMode.getWorldSettings().getWorldName().toLowerCase(Locale.ENGLISH), gameMode);
|
|
}
|
|
}
|
|
} catch (NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
|
|
// Looks like the addon is incompatible, because it tries to refer to missing classes...
|
|
handleAddonIncompatibility(addon, e);
|
|
} catch (Exception e) {
|
|
// Unhandled exception. We'll give a bit of debug here.
|
|
handleAddonError(addon, e);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Enables all the addons
|
|
*/
|
|
public void enableAddons() {
|
|
if (getLoadedAddons().isEmpty()) return;
|
|
plugin.log("Enabling game mode addons...");
|
|
// Enable GameModes first, then other addons
|
|
getLoadedAddons().stream().filter(a -> !a.getState().equals(State.DISABLED)).filter(GameModeAddon.class::isInstance).forEach(this::enableAddon);
|
|
plugin.log("Enabling other addons...");
|
|
getLoadedAddons().stream().filter(a -> !a.getState().equals(State.DISABLED)).filter(g -> !(g instanceof GameModeAddon)).forEach(this::enableAddon);
|
|
// Set perms for enabled addons
|
|
this.getEnabledAddons().forEach(this::setPerms);
|
|
plugin.log("Addons successfully enabled.");
|
|
}
|
|
|
|
boolean setPerms(Addon addon) {
|
|
ConfigurationSection perms = addon.getDescription().getPermissions();
|
|
if (perms == null) return false;
|
|
for (String perm : perms.getKeys(true)) {
|
|
// Only try to register perms for end nodes
|
|
if (perms.contains(perm + DEFAULT) && perms.contains(perm + ".description")) {
|
|
try {
|
|
registerPermission(perms, perm);
|
|
} catch (InvalidAddonDescriptionException e) {
|
|
plugin.logError("Addon " + addon.getDescription().getName() + ": " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void registerPermission(ConfigurationSection perms, String perm) throws InvalidAddonDescriptionException {
|
|
String name = perms.getString(perm + DEFAULT);
|
|
if (name == null) {
|
|
throw new InvalidAddonDescriptionException("Permission default is invalid in addon.yml: " + perm + DEFAULT);
|
|
}
|
|
PermissionDefault pd = PermissionDefault.getByName(name);
|
|
if (pd == null) {
|
|
throw new InvalidAddonDescriptionException("Permission default is invalid in addon.yml: " + perm + DEFAULT);
|
|
}
|
|
String desc = perms.getString(perm + ".description");
|
|
// Replace placeholders for Game Mode Addon names
|
|
if (perm.contains(GAMEMODE)) {
|
|
getGameModeAddons().stream().map(Addon::getPermissionPrefix)
|
|
.forEach(p -> DefaultPermissions.registerPermission(perm.replace(GAMEMODE, p), desc, pd));
|
|
} else {
|
|
// Single perm
|
|
DefaultPermissions.registerPermission(perm, desc, pd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enables an addon
|
|
* @param addon addon
|
|
*/
|
|
private void enableAddon(Addon addon) {
|
|
plugin.log("Enabling " + addon.getDescription().getName() + " (" + addon.getDescription().getVersion() + ")...");
|
|
try {
|
|
// If this is a GameModeAddon create the worlds, register it and load the blueprints
|
|
if (addon instanceof GameModeAddon gameMode) {
|
|
// Create the gameWorlds
|
|
gameMode.createWorlds();
|
|
plugin.getIWM().addGameMode(gameMode);
|
|
// Save and load blueprints
|
|
plugin.getBlueprintsManager().extractDefaultBlueprints(gameMode);
|
|
plugin.getBlueprintsManager().loadBlueprintBundles(gameMode);
|
|
}
|
|
addon.onEnable();
|
|
if (addon.getState().equals(State.DISABLED)) {
|
|
plugin.log(addon.getDescription().getName() + " is disabled.");
|
|
return;
|
|
}
|
|
if (addon instanceof GameModeAddon gameMode) {
|
|
// Set the worlds for the commands
|
|
gameMode.getPlayerCommand().ifPresent(c -> c.setWorld(gameMode.getOverWorld()));
|
|
gameMode.getAdminCommand().ifPresent(c -> c.setWorld(gameMode.getOverWorld()));
|
|
}
|
|
new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.ENABLE).build();
|
|
addon.setState(Addon.State.ENABLED);
|
|
} catch (NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
|
|
// Looks like the addon is incompatible, because it tries to refer to missing classes...
|
|
handleAddonIncompatibility(addon, e);
|
|
} catch (Exception e) {
|
|
// Unhandled exception. We'll give a bit of debug here.
|
|
handleAddonError(addon, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles an addon which failed to load due to an incompatibility (missing class, missing method).
|
|
* @param addon instance of the Addon.
|
|
* @param e - linkage exception
|
|
* @since 1.1
|
|
*/
|
|
private void handleAddonIncompatibility(@NonNull Addon addon, LinkageError e) {
|
|
// Set the AddonState as "INCOMPATIBLE".
|
|
addon.setState(Addon.State.INCOMPATIBLE);
|
|
plugin.logWarning("Skipping " + addon.getDescription().getName() + " as it is incompatible with the current version of BentoBox or of server software...");
|
|
plugin.logWarning("NOTE: The addon is referring to no longer existing classes.");
|
|
plugin.logWarning("NOTE: DO NOT report this as a bug from BentoBox.");
|
|
StringBuilder a = new StringBuilder();
|
|
addon.getDescription().getAuthors().forEach(author -> a.append(author).append(" "));
|
|
plugin.getLogger().log(Level.SEVERE, "Please report this stack trace to the addon's author(s): " + a, e);
|
|
|
|
}
|
|
|
|
private boolean isAddonCompatibleWithBentoBox(@NonNull Addon addon) {
|
|
return isAddonCompatibleWithBentoBox(addon, plugin.getDescription().getVersion());
|
|
}
|
|
|
|
/**
|
|
* Checks if the addon does not explicitly rely on API from a more recent BentoBox version.
|
|
* @param addon instance of the Addon.
|
|
* @param pluginVersion plugin version string.
|
|
* @return {@code true} if the addon relies on available BentoBox API, {@code false} otherwise.
|
|
* @since 1.11.0
|
|
*/
|
|
boolean isAddonCompatibleWithBentoBox(@NonNull Addon addon, String pluginVersion) {
|
|
String[] apiVersion = addon.getDescription().getApiVersion().split("\\D");
|
|
String[] bentoboxVersion = pluginVersion.split("\\D");
|
|
|
|
for (int i = 0; i < apiVersion.length; i++) {
|
|
int bentoboxNumber = 0;
|
|
if (i < bentoboxVersion.length && Util.isInteger(bentoboxVersion[i], false)) {
|
|
bentoboxNumber = Integer.parseInt(bentoboxVersion[i]);
|
|
}
|
|
int apiNumber = Util.isInteger(apiVersion[i], false) ? Integer.parseInt(apiVersion[i]) : -1;
|
|
|
|
if (bentoboxNumber > apiNumber) {
|
|
return true; // BentoBox version is greater than the required version -> compatible
|
|
}
|
|
if (bentoboxNumber < apiNumber) {
|
|
return false; // BentoBox is definitely outdated
|
|
}
|
|
// If it is equal, go to the next number
|
|
}
|
|
|
|
return true; // Everything is equal, so return true
|
|
}
|
|
|
|
/**
|
|
* Handles an addon which failed to load due to an error.
|
|
* @param addon instance of the Addon.
|
|
* @param throwable Throwable that was thrown and which led to the error.
|
|
* @since 1.1
|
|
*/
|
|
private void handleAddonError(@NonNull Addon addon, @NonNull Throwable throwable) {
|
|
// Set the AddonState as "ERROR".
|
|
addon.setState(Addon.State.ERROR);
|
|
plugin.logError("Skipping " + addon.getDescription().getName() + " due to an unhandled exception...");
|
|
// Send stacktrace, required for addon development
|
|
plugin.logStacktrace(throwable);
|
|
}
|
|
|
|
/**
|
|
* Reloads all the enabled addons
|
|
*/
|
|
public void reloadAddons() {
|
|
disableAddons();
|
|
// Reload BentoBox commands
|
|
new BentoBoxCommand();
|
|
loadAddons();
|
|
enableAddons();
|
|
}
|
|
|
|
/**
|
|
* Disable all the enabled addons
|
|
*/
|
|
public void disableAddons() {
|
|
if (!getEnabledAddons().isEmpty()) {
|
|
plugin.log("Disabling addons...");
|
|
// Disable addons
|
|
getEnabledAddons().forEach(this::disable);
|
|
plugin.log("Addons successfully disabled.");
|
|
}
|
|
// Unregister all commands
|
|
plugin.getCommandsManager().unregisterCommands();
|
|
// Clear all maps
|
|
listeners.clear();
|
|
pladdons.clear();
|
|
addons.clear();
|
|
loaders.clear();
|
|
classes.clear();
|
|
}
|
|
|
|
/**
|
|
* Gets the addon by name
|
|
* @param name addon name, not null
|
|
* @return Optional addon object
|
|
*/
|
|
@NonNull
|
|
@SuppressWarnings("unchecked")
|
|
public <T extends Addon> Optional<T> getAddonByName(@NonNull String name){
|
|
return addons.stream().filter(a -> a.getDescription().getName().equalsIgnoreCase(name)).map(a -> (T) a).findFirst();
|
|
}
|
|
|
|
/**
|
|
* Gets the addon by main class name
|
|
* @param name - main class name
|
|
* @return Optional addon object
|
|
*/
|
|
@NonNull
|
|
@SuppressWarnings("unchecked")
|
|
public <T extends Addon> Optional<T> getAddonByMainClassName(@NonNull String name){
|
|
return addons.stream().filter(a -> a.getDescription().getMain().equalsIgnoreCase(name)).map(a -> (T) a).findFirst();
|
|
}
|
|
|
|
@NonNull
|
|
private YamlConfiguration addonDescription(@NonNull JarFile jar) throws InvalidAddonFormatException, IOException, InvalidConfigurationException {
|
|
// Obtain the addon.yml file
|
|
JarEntry entry = jar.getJarEntry("addon.yml");
|
|
if (entry == null) {
|
|
throw new InvalidAddonFormatException("Addon '" + jar.getName() + "' doesn't contains addon.yml file");
|
|
}
|
|
// Open a reader to the jar
|
|
BufferedReader reader = new BufferedReader(new InputStreamReader(jar.getInputStream(entry)));
|
|
// Grab the description in the addon.yml file
|
|
YamlConfiguration data = new YamlConfiguration();
|
|
data.load(reader);
|
|
reader.close();
|
|
return data;
|
|
}
|
|
|
|
@NonNull
|
|
public List<Addon> getAddons() {
|
|
return addons;
|
|
}
|
|
|
|
/**
|
|
* @return List of enabled game mode addons
|
|
* @since 1.1
|
|
*/
|
|
@NonNull
|
|
public List<GameModeAddon> getGameModeAddons() {
|
|
return getEnabledAddons().stream()
|
|
.filter(GameModeAddon.class::isInstance)
|
|
.map(GameModeAddon.class::cast)
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Gets the list of Addons that are loaded.
|
|
* @return list of loaded Addons.
|
|
* @since 1.1
|
|
*/
|
|
@NonNull
|
|
public List<Addon> getLoadedAddons() {
|
|
return addons.stream().filter(addon -> addon.getState().equals(Addon.State.LOADED)).collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Gets the list of Addons that are enabled.
|
|
* @return list of enabled Addons.
|
|
* @since 1.1
|
|
*/
|
|
@NonNull
|
|
public List<Addon> getEnabledAddons() {
|
|
return addons.stream().filter(addon -> addon.getState().equals(Addon.State.ENABLED)).collect(Collectors.toList());
|
|
}
|
|
|
|
@Nullable
|
|
public AddonClassLoader getLoader(@NonNull final Addon addon) {
|
|
return loaders.get(addon);
|
|
}
|
|
|
|
/**
|
|
* Finds a class by name that has been loaded by this loader
|
|
* @param name name of the class, not null
|
|
* @return Class the class or null if not found
|
|
*/
|
|
@Nullable
|
|
public Class<?> getClassByName(@NonNull final String name) {
|
|
try {
|
|
return classes.getOrDefault(name, loaders.values().stream().filter(Objects::nonNull).map(l -> l.findClass(name, false)).filter(Objects::nonNull).findFirst().orElse(null));
|
|
} catch (Exception ignored) {
|
|
// Ignored.
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Sets a class that this loader should know about
|
|
*
|
|
* @param name name of the class, not null
|
|
* @param clazz the class, not null
|
|
*/
|
|
public void setClass(@NonNull final String name, @NonNull final Class<?> clazz) {
|
|
classes.putIfAbsent(name, clazz);
|
|
}
|
|
|
|
/**
|
|
* Sorts the addons into loading order taking into account dependencies
|
|
*/
|
|
private void sortAddons() {
|
|
// Lists all available addons as names.
|
|
List<String> names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList());
|
|
|
|
// Check that any dependencies exist
|
|
Iterator<Addon> addonsIterator = addons.iterator();
|
|
while (addonsIterator.hasNext()) {
|
|
Addon a = addonsIterator.next();
|
|
for (String dependency : a.getDescription().getDependencies()) {
|
|
if (!names.contains(dependency)) {
|
|
plugin.logError(a.getDescription().getName() + " has dependency on " + dependency + " that does not exist. Addon will not load!");
|
|
addonsIterator.remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load dependencies or soft dependencies
|
|
Map<String,Addon> sortedAddons = new LinkedHashMap<>();
|
|
// Start with nodes with no dependencies
|
|
addons.stream().filter(a -> a.getDescription().getDependencies().isEmpty() && a.getDescription().getSoftDependencies().isEmpty())
|
|
.forEach(a -> sortedAddons.put(a.getDescription().getName(), a));
|
|
// Fill remaining
|
|
List<Addon> remaining = addons.stream().filter(a -> !sortedAddons.containsKey(a.getDescription().getName())).collect(Collectors.toList());
|
|
|
|
// Run through remaining addons
|
|
remaining.forEach(addon -> {
|
|
// Get the addon's dependencies.
|
|
List<String> dependencies = new ArrayList<>(addon.getDescription().getDependencies());
|
|
dependencies.addAll(addon.getDescription().getSoftDependencies());
|
|
|
|
// Remove already sorted addons (dependencies) from the list
|
|
dependencies.removeIf(sortedAddons::containsKey);
|
|
|
|
if (dependencies.stream().noneMatch(dependency -> addon.getDescription().getDependencies().contains(dependency))) {
|
|
sortedAddons.put(addon.getDescription().getName(), addon);
|
|
}
|
|
});
|
|
|
|
addons.clear();
|
|
addons.addAll(sortedAddons.values());
|
|
}
|
|
|
|
/**
|
|
* Get the world generator if it exists
|
|
* @param worldName - name of world - case insensitive
|
|
* @param id - specific generator id
|
|
* @return ChunkGenerator or null if none found
|
|
* @since 1.2.0
|
|
*/
|
|
@Nullable
|
|
public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
|
|
// Clean up world name
|
|
String w = worldName.replace("_nether", "").replace("_the_end", "").toLowerCase(Locale.ENGLISH);
|
|
if (worldNames.containsKey(w)) {
|
|
return worldNames.get(w).getDefaultWorldGenerator(worldName, id);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Register a listener
|
|
* @param addon - the addon registering
|
|
* @param listener - listener
|
|
*/
|
|
public void registerListener(@NonNull Addon addon, @NonNull Listener listener) {
|
|
Bukkit.getPluginManager().registerEvents(listener, BentoBox.getInstance());
|
|
listeners.computeIfAbsent(addon, k -> new ArrayList<>()).add(listener);
|
|
}
|
|
|
|
/**
|
|
* Disables an addon
|
|
* @param addon - addon
|
|
*/
|
|
private void disable(@NonNull Addon addon) {
|
|
// Clear listeners
|
|
if (listeners.containsKey(addon)) {
|
|
listeners.get(addon).forEach(HandlerList::unregisterAll);
|
|
listeners.remove(addon);
|
|
}
|
|
// Unregister flags
|
|
plugin.getFlagsManager().unregister(addon);
|
|
// Disable
|
|
if (addon.isEnabled()) {
|
|
plugin.log("Disabling " + addon.getDescription().getName() + "...");
|
|
try {
|
|
addon.onDisable();
|
|
} catch (Exception e) {
|
|
plugin.logError("Error occurred when disabling addon " + addon.getDescription().getName());
|
|
plugin.logError("Report this to the addon's author(s)");
|
|
addon.getDescription().getAuthors().forEach(plugin::logError);
|
|
plugin.logStacktrace(e);
|
|
}
|
|
new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.DISABLE).build();
|
|
}
|
|
// Clear loaders
|
|
if (loaders.containsKey(addon)) {
|
|
Set<String> unmodifiableSet = Collections.unmodifiableSet(loaders.get(addon).getClasses());
|
|
for (String className : unmodifiableSet) {
|
|
classes.remove(className);
|
|
}
|
|
addon.setState(State.DISABLED);
|
|
loaders.remove(addon);
|
|
}
|
|
// Disable pladdons
|
|
if (pladdons.containsKey(addon)) {
|
|
this.pluginLoader.disablePlugin(Objects.requireNonNull(this.pladdons.get(addon)));
|
|
pladdons.remove(addon);
|
|
}
|
|
// Remove it from the addons list
|
|
addons.remove(addon);
|
|
}
|
|
|
|
/*
|
|
* Get a list of addon classes that are of type {@link DataObject}
|
|
* but not {@link ConfigObject}. Configs are not transitioned to database.
|
|
* Used in database transition.
|
|
* @return list of DataObjects
|
|
* @since 1.5.0
|
|
*/
|
|
public List<Class<?>> getDataObjects() {
|
|
return classes.values().stream()
|
|
.filter(DataObject.class::isAssignableFrom)
|
|
// Do not include config files
|
|
.filter(c -> !ConfigObject.class.isAssignableFrom(c))
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Notifies all addons that BentoBox has loaded all addons
|
|
* @since 1.8.0
|
|
*/
|
|
public void allLoaded() {
|
|
addons.forEach(Addon::allLoaded);
|
|
}
|
|
|
|
}
|