Added support for depend in addon.yml

This enables add-ons that must load after another addon to mark it in
the depend line of addon.yml
This commit is contained in:
tastybento 2018-08-17 21:09:19 -07:00
parent f152d218fc
commit 9adabc4fb2
3 changed files with 127 additions and 59 deletions

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -80,10 +81,14 @@ public class AddonClassLoader extends URLClassLoader {
DefaultPermissions.registerPermission(perm, desc, pd); DefaultPermissions.registerPermission(perm, desc, pd);
} }
private AddonDescription asDescription(YamlConfiguration data){ private AddonDescription asDescription(YamlConfiguration data) {
return new AddonDescriptionBuilder(data.getString("name")) AddonDescriptionBuilder adb = new AddonDescriptionBuilder(data.getString("name"))
.withVersion(data.getString("version")) .withVersion(data.getString("version"))
.withAuthor(data.getString("authors")).build(); .withAuthor(data.getString("authors"));
if (data.getString("depend") != null) {
adb.withDepend(Arrays.asList(data.getString("depend").split("\\s*,\\s*")));
}
return adb.build();
} }

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.api.addons; package world.bentobox.bentobox.api.addons;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -12,16 +13,18 @@ public final class AddonDescription {
private String name; private String name;
private String version; private String version;
private String description; private String description;
private List<String> authors; private List<String> authors = new ArrayList<>();
private List<String> depend = new ArrayList<>();
public AddonDescription() {} public AddonDescription() {}
public AddonDescription(String main, String name, String version, String description, List<String> authors) { public AddonDescription(String main, String name, String version, String description, List<String> authors, List<String> depend) {
this.main = main; this.main = main;
this.name = name; this.name = name;
this.version = version; this.version = version;
this.description = description; this.description = description;
this.authors = authors; this.authors = authors;
this.depend = depend;
} }
/** /**
@ -59,6 +62,20 @@ public final class AddonDescription {
this.authors = authors; this.authors = authors;
} }
/**
* @return the loadAfter
*/
public List<String> getDependencies() {
return depend;
}
/**
* @param loadAfter the loadAfter to set
*/
public void setLoadAfter(List<String> loadAfter) {
this.depend = loadAfter;
}
public String getName() { public String getName() {
return name; return name;
} }
@ -103,6 +120,11 @@ public final class AddonDescription {
return this; return this;
} }
public AddonDescriptionBuilder withDepend(List<String> addons) {
description.setLoadAfter(addons);
return this;
}
public AddonDescription build(){ public AddonDescription build(){
return description; return description;
} }

View File

@ -8,14 +8,18 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
@ -29,7 +33,6 @@ import world.bentobox.bentobox.api.events.addon.AddonEvent;
*/ */
public class AddonsManager { public class AddonsManager {
private static final boolean DEBUG = false;
private static final String LOCALE_FOLDER = "locales"; private static final String LOCALE_FOLDER = "locales";
private List<Addon> addons; private List<Addon> addons;
private List<AddonClassLoader> loader; private List<AddonClassLoader> loader;
@ -40,21 +43,30 @@ public class AddonsManager {
this.plugin = plugin; this.plugin = plugin;
addons = new ArrayList<>(); addons = new ArrayList<>();
loader = new ArrayList<>(); loader = new ArrayList<>();
loadAddonsFromFile();
} }
/** public void loadAddonsFromFile() {
* Loads all the addons from the addons folder
*/
public void loadAddons() {
plugin.log("Loading addons..."); plugin.log("Loading addons...");
File f = new File(plugin.getDataFolder(), "addons"); File f = new File(plugin.getDataFolder(), "addons");
if (!f.exists() && !f.mkdirs()) { if (!f.exists() && !f.mkdirs()) {
plugin.logError("Cannot make addons folder!"); plugin.logError("Cannot make addons folder!");
return; return;
} }
Arrays.stream(Objects.requireNonNull(f.listFiles())).filter(x -> !x.isDirectory() && x.getName().endsWith(".jar")).forEach(this::loadAddon); Arrays.stream(Objects.requireNonNull(f.listFiles())).filter(x -> !x.isDirectory() && x.getName().endsWith(".jar")).forEach(this::loadAddon);
addons.forEach(Addon::onLoad); sortAddons();
}
/**
* Loads all the addons from the addons folder
*/
public void loadAddons() {
// Run each onLoad
addons.forEach(addon -> {
addon.onLoad();
Bukkit.getPluginManager().callEvent(AddonEvent.builder().addon(addon).reason(AddonEvent.Reason.LOAD).build());
plugin.log("Loading " + addon.getDescription().getName() + "...");
});
plugin.log("Loaded " + addons.size() + " addons."); plugin.log("Loaded " + addons.size() + " addons.");
} }
@ -81,56 +93,48 @@ public class AddonsManager {
return addons.stream().filter(a -> a.getDescription().getName().contains(name)).findFirst(); return addons.stream().filter(a -> a.getDescription().getName().contains(name)).findFirst();
} }
private YamlConfiguration addonDescription(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);
return data;
}
private void loadAddon(File f) { private void loadAddon(File f) {
try { Addon addon;
Addon addon; try (JarFile jar = new JarFile(f)) {
// Check that this is a jar // Get description in the addon.yml file
if (!f.getName().endsWith(".jar")) { YamlConfiguration data = addonDescription(jar);
throw new IOException("Filename must end in .jar. Name is '" + f.getName() + "'"); // Load the addon
AddonClassLoader addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader());
// Add to the list of loaders
loader.add(addonClassLoader);
// Get the addon itself
addon = addonClassLoader.getAddon();
// Initialize some settings
addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName()));
addon.setAddonFile(f);
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName());
// Obtain any locale files and save them
for (String localeFile : listJarYamlFiles(jar, LOCALE_FOLDER)) {
addon.saveResource(localeFile, localeDir, false, true);
} }
try (JarFile jar = new JarFile(f)) { plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName());
// Obtain the addon.yml file // Fire the load event
JarEntry entry = jar.getJarEntry("addon.yml"); Bukkit.getPluginManager().callEvent(AddonEvent.builder().addon(addon).reason(AddonEvent.Reason.LOAD).build());
if (entry == null) { // Add it to the list of addons
throw new InvalidAddonFormatException("Addon '" + f.getName() + "' doesn't contains addon.yml file"); addons.add(addon);
}
// 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);
// Load the addon
AddonClassLoader addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader());
// Add to the list of loaders
loader.add(addonClassLoader);
// Get the addon itself
addon = addonClassLoader.getAddon();
// Initialize some settings
addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName()));
addon.setAddonFile(f);
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName());
// Obtain any locale files and save them
for (String localeFile : listJarYamlFiles(jar, LOCALE_FOLDER)) {
addon.saveResource(localeFile, localeDir, false, true);
}
plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName());
// Fire the load event
Bukkit.getPluginManager().callEvent(AddonEvent.builder().addon(addon).reason(AddonEvent.Reason.LOAD).build());
// Add it to the list of addons
addons.add(addon);
// Inform the console
plugin.log("Loaded addon " + addon.getDescription().getName() + "...");
} catch (Exception e) {
plugin.log(e.getMessage());
}
} catch (Exception e) { } catch (Exception e) {
if (DEBUG) { plugin.log(e.getMessage());
plugin.log(f.getName() + " is not a jarfile, ignoring...");
}
} }
} }
@ -212,4 +216,41 @@ public class AddonsManager {
} }
return result; return result;
} }
private void sortAddons() {
Map<String,Addon> sortedAddons = new LinkedHashMap<>();
Map<Addon, List<String>> remaining = new HashMap<>();
// Start with nodes with no dependencies
addons.stream().filter(a -> a.getDescription().getDependencies().isEmpty()).forEach(a -> sortedAddons.put(a.getDescription().getName(), a));
// Fill remaining
addons.stream().filter(a -> !a.getDescription().getDependencies().isEmpty()).forEach(a -> remaining.put(a, a.getDescription().getDependencies()));
// Run through remaining addons
int index = 0;
while (index < 10 && !remaining.isEmpty()) {
index++;
Iterator<Entry<Addon, List<String>>> it = remaining.entrySet().iterator();
while (it.hasNext()) {
Entry<Addon, List<String>> a = it.next();
// If the dependent addon is loaded
List<String> deps = new ArrayList<>(a.getValue());
Iterator<String> depIt = deps.iterator();
while(depIt.hasNext()) {
String dep = depIt.next();
if (sortedAddons.containsKey(dep)) {
depIt.remove();
}
}
if (deps.isEmpty()) {
// Add addons loaded
sortedAddons.put(a.getKey().getDescription().getName(), a.getKey());
it.remove();
}
}
}
if (index == 10) {
plugin.logError("Circular dependency reference when loading addons");
}
addons.clear();
sortedAddons.values().forEach(addons::add);
}
} }