207 lines
7.7 KiB
Java
207 lines
7.7 KiB
Java
package world.bentobox.bentobox.api.addons;
|
|
|
|
import java.io.File;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
import org.bukkit.Material;
|
|
import org.bukkit.configuration.file.YamlConfiguration;
|
|
import org.bukkit.plugin.InvalidDescriptionException;
|
|
import org.eclipse.jdt.annotation.NonNull;
|
|
import org.eclipse.jdt.annotation.Nullable;
|
|
|
|
import world.bentobox.bentobox.BentoBox;
|
|
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonDescriptionException;
|
|
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonFormatException;
|
|
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonInheritException;
|
|
import world.bentobox.bentobox.managers.AddonsManager;
|
|
|
|
/**
|
|
* Loads addons and sets up permissions
|
|
* @author Tastybento, ComminQ
|
|
*/
|
|
public class AddonClassLoader extends URLClassLoader {
|
|
|
|
private final Map<String, Class<?>> classes = new HashMap<>();
|
|
private final Addon addon;
|
|
private final AddonsManager loader;
|
|
|
|
/**
|
|
* For testing only
|
|
* @param addon addon
|
|
* @param loader Addons Manager
|
|
* @param jarFile Jar File
|
|
* @throws MalformedURLException exception
|
|
*/
|
|
protected AddonClassLoader(Addon addon, AddonsManager loader, File jarFile) throws MalformedURLException {
|
|
super(new URL[]{jarFile.toURI().toURL()});
|
|
this.addon = addon;
|
|
this.loader = loader;
|
|
}
|
|
|
|
public AddonClassLoader(AddonsManager addonsManager, YamlConfiguration data, File jarFile, ClassLoader parent)
|
|
throws InvalidAddonInheritException,
|
|
MalformedURLException,
|
|
InvalidDescriptionException,
|
|
InvalidAddonDescriptionException,
|
|
InstantiationException,
|
|
IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
|
super(new URL[]{jarFile.toURI().toURL()}, parent);
|
|
|
|
loader = addonsManager;
|
|
|
|
Class<?> javaClass;
|
|
try {
|
|
String mainClass = data.getString("main");
|
|
if (mainClass == null) {
|
|
throw new InvalidAddonFormatException("addon.yml does not define a main class!");
|
|
}
|
|
javaClass = Class.forName(mainClass, true, this);
|
|
if(mainClass.startsWith("world.bentobox.bentobox")){
|
|
throw new InvalidAddonFormatException("Package declaration cannot start with 'world.bentobox.bentobox'");
|
|
}
|
|
} catch (Exception e) {
|
|
throw new InvalidDescriptionException("Could not load '" + jarFile.getName() + "' in folder '" + jarFile.getParent() + "' - " + e.getMessage());
|
|
}
|
|
|
|
Class<? extends Addon> addonClass;
|
|
try {
|
|
addonClass = javaClass.asSubclass(Addon.class);
|
|
} catch (ClassCastException e) {
|
|
throw new InvalidAddonInheritException("Main class does not extend 'Addon'");
|
|
}
|
|
|
|
addon = addonClass.getDeclaredConstructor().newInstance();
|
|
addon.setDescription(asDescription(data));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Converts the addon.yml to an AddonDescription
|
|
* @param data - yaml config (addon.yml)
|
|
* @return Addon Description
|
|
* @throws InvalidAddonDescriptionException - if there's a bug in the addon.yml
|
|
*/
|
|
@NonNull
|
|
public static AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException {
|
|
// Validate addon.yml
|
|
if (!data.contains("main")) {
|
|
throw new InvalidAddonDescriptionException("Missing 'main' tag. A main class must be listed in addon.yml");
|
|
}
|
|
if (!data.contains("name")) {
|
|
throw new InvalidAddonDescriptionException("Missing 'name' tag. An addon name must be listed in addon.yml");
|
|
}
|
|
if (!data.contains("version")) {
|
|
throw new InvalidAddonDescriptionException("Missing 'version' tag. A version must be listed in addon.yml");
|
|
}
|
|
if (!data.contains("authors")) {
|
|
throw new InvalidAddonDescriptionException("Missing 'authors' tag. At least one author must be listed in addon.yml");
|
|
}
|
|
|
|
AddonDescription.Builder builder = new AddonDescription.Builder(
|
|
// Mandatory elements
|
|
Objects.requireNonNull(data.getString("main")),
|
|
Objects.requireNonNull(data.getString("name")),
|
|
Objects.requireNonNull(data.getString("version")))
|
|
.authors(Objects.requireNonNull(data.getString("authors")))
|
|
// Optional elements
|
|
.metrics(data.getBoolean("metrics", true))
|
|
.repository(data.getString("repository", ""));
|
|
|
|
String depend = data.getString("depend");
|
|
if (depend != null) {
|
|
builder.dependencies(Arrays.asList(depend.split("\\s*,\\s*")));
|
|
}
|
|
String softDepend = data.getString("softdepend");
|
|
if (softDepend != null) {
|
|
builder.softDependencies(Arrays.asList(softDepend.split("\\s*,\\s*")));
|
|
}
|
|
Material icon = Material.getMaterial(data.getString("icon", "PAPER").toUpperCase(Locale.ENGLISH));
|
|
if (icon == null) {
|
|
throw new InvalidAddonDescriptionException("'icon' tag refers to an unknown Material: " + data.getString("icon"));
|
|
}
|
|
builder.icon(Objects.requireNonNull(icon));
|
|
|
|
String apiVersion = data.getString("api-version");
|
|
if (apiVersion != null) {
|
|
if (!apiVersion.replace("-SNAPSHOT", "").matches("^(\\d+\\.)?(\\d+\\.)?(\\*|\\d+)$")) {
|
|
throw new InvalidAddonDescriptionException("Provided API version '" + apiVersion + "' is not valid. It must only contain digits and dots and not end with a dot.");
|
|
}
|
|
if (apiVersion.contains("-SNAPSHOT")) {
|
|
BentoBox.getInstance().logWarning(data.getString("name") + " addon depends on development version of BentoBox plugin. Some functions may be not implemented.");
|
|
}
|
|
builder.apiVersion(apiVersion);
|
|
}
|
|
// Set permissions
|
|
if (data.isConfigurationSection("permissions")) {
|
|
builder.permissions(Objects.requireNonNull(data.getConfigurationSection("permissions")));
|
|
}
|
|
|
|
return builder.build();
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see java.net.URLClassLoader#findClass(java.lang.String)
|
|
*/
|
|
@Override
|
|
@Nullable
|
|
protected Class<?> findClass(String name) {
|
|
return findClass(name, true);
|
|
}
|
|
|
|
/**
|
|
* This is a custom findClass that enables classes in other addons to be found
|
|
* @param name - class name
|
|
* @param checkGlobal - check globally or not when searching
|
|
* @return Class - class if found
|
|
*/
|
|
public Class<?> findClass(String name, boolean checkGlobal) {
|
|
if (name.startsWith("world.bentobox.bentobox")) {
|
|
return null;
|
|
}
|
|
Class<?> result = classes.get(name);
|
|
if (result == null) {
|
|
if (checkGlobal) {
|
|
result = loader.getClassByName(name);
|
|
}
|
|
|
|
if (result == null) {
|
|
try {
|
|
result = super.findClass(name);
|
|
} catch (ClassNotFoundException | NoClassDefFoundError e) {
|
|
// Do nothing.
|
|
}
|
|
if (result != null) {
|
|
loader.setClass(name, result);
|
|
|
|
}
|
|
}
|
|
classes.put(name, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @return the addon
|
|
*/
|
|
public Addon getAddon() {
|
|
return addon;
|
|
}
|
|
|
|
/**
|
|
* @return class list
|
|
*/
|
|
public Set<String> getClasses() {
|
|
return classes.keySet();
|
|
}
|
|
}
|