bentobox/src/main/java/world/bentobox/bentobox/api/addons/AddonClassLoader.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();
}
}