mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 22:45:04 +01:00
Allow extensions to load other extension's classes, and store extensions by IDs instead of name (#3946)
- the extensionmanagers `extension` method now takes in a extension id instead of name - extension folders are now created using extension id's - Extensions can load classes from other extensions now - Added warning about external class loading - Wherever applicable: store extensions internally by id instead of name
This commit is contained in:
parent
1d75d084a7
commit
34ff8c1217
5 changed files with 56 additions and 29 deletions
|
@ -36,13 +36,13 @@ import java.util.Collection;
|
|||
public abstract class ExtensionManager {
|
||||
|
||||
/**
|
||||
* Gets an extension with the given name.
|
||||
* Gets an extension by the given ID.
|
||||
*
|
||||
* @param name the name of the extension
|
||||
* @return an extension with the given name
|
||||
* @param id the ID of the extension
|
||||
* @return an extension with the given ID
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Extension extension(@NonNull String name);
|
||||
public abstract Extension extension(@NonNull String id);
|
||||
|
||||
/**
|
||||
* Enables the given {@link Extension}.
|
||||
|
|
|
@ -194,7 +194,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
|
||||
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
|
||||
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.description().name(), ex);
|
||||
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.extension;
|
|||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.extension.ExtensionDescription;
|
||||
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
|
||||
|
@ -39,14 +40,17 @@ import java.nio.file.Path;
|
|||
|
||||
public class GeyserExtensionClassLoader extends URLClassLoader {
|
||||
private final GeyserExtensionLoader loader;
|
||||
private final ExtensionDescription description;
|
||||
private final Object2ObjectMap<String, Class<?>> classes = new Object2ObjectOpenHashMap<>();
|
||||
private boolean warnedForExternalClassAccess;
|
||||
|
||||
public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, Path path) throws MalformedURLException {
|
||||
public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, Path path, ExtensionDescription description) throws MalformedURLException {
|
||||
super(new URL[] { path.toUri().toURL() }, parent);
|
||||
this.loader = loader;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Extension load(ExtensionDescription description) throws InvalidExtensionException {
|
||||
public Extension load() throws InvalidExtensionException {
|
||||
try {
|
||||
Class<?> jarClass;
|
||||
try {
|
||||
|
@ -76,22 +80,32 @@ public class GeyserExtensionClassLoader extends URLClassLoader {
|
|||
}
|
||||
|
||||
protected Class<?> findClass(String name, boolean checkGlobal) throws ClassNotFoundException {
|
||||
if (name.startsWith("org.geysermc.geyser.") || name.startsWith("net.minecraft.")) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
|
||||
Class<?> result = this.classes.get(name);
|
||||
if (result == null) {
|
||||
result = super.findClass(name);
|
||||
if (result == null && checkGlobal) {
|
||||
result = this.loader.classByName(name);
|
||||
// Try to find class in current extension
|
||||
try {
|
||||
result = super.findClass(name);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// If class is not found in current extension, check in the global class loader
|
||||
// This is used for classes that are not in the extension, but are in other extensions
|
||||
if (checkGlobal) {
|
||||
if (!warnedForExternalClassAccess) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Extension " + this.description.name() + " loads class " + name + " from an external source. " +
|
||||
"This can change at any time and break the extension, additionally to potentially causing unexpected behaviour!");
|
||||
warnedForExternalClassAccess = true;
|
||||
}
|
||||
result = this.loader.classByName(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
// If class is found, cache it
|
||||
this.loader.setClass(name, result);
|
||||
this.classes.put(name, result);
|
||||
} else {
|
||||
// If class is not found, throw exception
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
|
||||
this.classes.put(name, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -66,26 +66,38 @@ public class GeyserExtensionLoader extends ExtensionLoader {
|
|||
}
|
||||
|
||||
Path parentFile = path.getParent();
|
||||
Path dataFolder = parentFile.resolve(description.name());
|
||||
|
||||
// Extension folders used to be created by name; this changes them to the ID
|
||||
Path oldDataFolder = parentFile.resolve(description.name());
|
||||
Path dataFolder = parentFile.resolve(description.id());
|
||||
|
||||
if (Files.exists(oldDataFolder) && Files.isDirectory(oldDataFolder) && !oldDataFolder.equals(dataFolder)) {
|
||||
try {
|
||||
Files.move(oldDataFolder, dataFolder, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidExtensionException("Failed to move data folder for extension " + description.name(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (Files.exists(dataFolder) && !Files.isDirectory(dataFolder)) {
|
||||
throw new InvalidExtensionException("The folder " + dataFolder + " is not a directory and is the data folder for the extension " + description.name() + "!");
|
||||
}
|
||||
|
||||
final GeyserExtensionClassLoader loader;
|
||||
try {
|
||||
loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), path);
|
||||
loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), path, description);
|
||||
} catch (Throwable e) {
|
||||
throw new InvalidExtensionException(e);
|
||||
}
|
||||
|
||||
this.classLoaders.put(description.name(), loader);
|
||||
this.classLoaders.put(description.id(), loader);
|
||||
|
||||
final Extension extension = loader.load(description);
|
||||
final Extension extension = loader.load();
|
||||
return this.setup(extension, description, dataFolder, new GeyserExtensionEventBus(GeyserImpl.getInstance().eventBus(), extension));
|
||||
}
|
||||
|
||||
private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder, ExtensionEventBus eventBus) {
|
||||
GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name());
|
||||
GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.id());
|
||||
return new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus);
|
||||
}
|
||||
|
||||
|
@ -152,7 +164,8 @@ public class GeyserExtensionLoader extends ExtensionLoader {
|
|||
GeyserExtensionDescription description = this.extensionDescription(path);
|
||||
|
||||
String name = description.name();
|
||||
if (extensions.containsKey(name) || extensionManager.extension(name) != null) {
|
||||
String id = description.id();
|
||||
if (extensions.containsKey(id) || extensionManager.extension(id) != null) {
|
||||
GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, path.toString()));
|
||||
return;
|
||||
}
|
||||
|
@ -169,8 +182,8 @@ public class GeyserExtensionLoader extends ExtensionLoader {
|
|||
return;
|
||||
}
|
||||
|
||||
extensions.put(name, path);
|
||||
loadedExtensions.put(name, this.loadExtension(path, description));
|
||||
extensions.put(id, path);
|
||||
loadedExtensions.put(id, this.loadExtension(path, description));
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", path.getFileName(), path.toAbsolutePath()), e);
|
||||
}
|
||||
|
|
|
@ -52,8 +52,8 @@ public class GeyserExtensionManager extends ExtensionManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Extension extension(@NonNull String name) {
|
||||
return this.extensions.get(name);
|
||||
public Extension extension(@NonNull String id) {
|
||||
return this.extensions.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,7 +83,7 @@ public class GeyserExtensionManager extends ExtensionManager {
|
|||
if (!extension.isEnabled()) {
|
||||
extension.setEnabled(true);
|
||||
GeyserImpl.getInstance().eventBus().register(extension, extension);
|
||||
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name()));
|
||||
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.name()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ public class GeyserExtensionManager extends ExtensionManager {
|
|||
GeyserImpl.getInstance().eventBus().unregisterAll(extension);
|
||||
|
||||
extension.setEnabled(false);
|
||||
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name()));
|
||||
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.name()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,6 +121,6 @@ public class GeyserExtensionManager extends ExtensionManager {
|
|||
|
||||
@Override
|
||||
public void register(@NonNull Extension extension) {
|
||||
this.extensions.put(extension.name(), extension);
|
||||
this.extensions.put(extension.description().id(), extension);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue