org.bukkit.plugin.java.JavaPluginLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of walk-server Show documentation
Show all versions of walk-server Show documentation
A spigot fork to kotlin structure and news.
package org.bukkit.plugin.java;
import org.apache.commons.lang.Validate;
import org.bukkit.Server;
import org.bukkit.Warning;
import org.bukkit.Warning.WarningState;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.plugin.*;
import org.spigotmc.CustomTimingsHandler;
import org.yaml.snakeyaml.error.YAMLException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.regex.Pattern;
/**
* Represents a Java plugin loader, allowing plugins in the form of .jar
*/
public final class JavaPluginLoader implements PluginLoader {
public static final CustomTimingsHandler pluginParentTimer = new CustomTimingsHandler("** Plugins"); // Spigot
final Server server;
private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$"),};
private final Map> classes = new java.util.concurrent.ConcurrentHashMap>(); // Spigot
private final Map loaders = new LinkedHashMap();
/**
* This class was not meant to be constructed explicitly
*
* @param instance the server instance
*/
@Deprecated
public JavaPluginLoader(Server instance) {
Validate.notNull(instance, "Server cannot be null");
server = instance;
}
public Plugin loadPlugin(final File file) throws InvalidPluginException {
Validate.notNull(file, "File cannot be null");
if (!file.exists()) {
throw new InvalidPluginException(new FileNotFoundException(file.getPath() + " does not exist"));
}
final PluginDescriptionFile description;
try {
description = getPluginDescription(file);
} catch (InvalidDescriptionException ex) {
throw new InvalidPluginException(ex);
}
final File parentFile = file.getParentFile();
final File dataFolder = new File(parentFile, description.getName());
@SuppressWarnings("deprecation") final File oldDataFolder = new File(parentFile, description.getRawName());
// Found old data folder
if (dataFolder.equals(oldDataFolder)) {
// They are equal -- nothing needs to be done!
} else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) {
server.getLogger().warning(String.format(
"While loading %s (%s) found old-data folder: `%s' next to the new one `%s'",
description.getFullName(),
file,
oldDataFolder,
dataFolder
));
} else if (oldDataFolder.isDirectory() && !dataFolder.exists()) {
if (!oldDataFolder.renameTo(dataFolder)) {
throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'");
}
server.getLogger().log(Level.INFO, String.format(
"While loading %s (%s) renamed data folder: `%s' to `%s'",
description.getFullName(),
file,
oldDataFolder,
dataFolder
));
}
if (dataFolder.exists() && !dataFolder.isDirectory()) {
throw new InvalidPluginException(String.format(
"Projected datafolder: `%s' for %s (%s) exists and is not a directory",
dataFolder,
description.getFullName(),
file
));
}
for (final String pluginName : description.getDepend()) {
if (loaders == null) {
throw new UnknownDependencyException(pluginName);
}
PluginClassLoader current = loaders.get(pluginName);
if (current == null) {
throw new UnknownDependencyException(pluginName);
}
}
final PluginClassLoader loader;
try {
loader = new PluginClassLoader(this, getClass().getClassLoader(), description, dataFolder, file);
} catch (InvalidPluginException ex) {
throw ex;
} catch (Throwable ex) {
throw new InvalidPluginException(ex);
}
loaders.put(description.getName(), loader);
return loader.plugin;
}
public PluginDescriptionFile getPluginDescription(File file) throws InvalidDescriptionException {
Validate.notNull(file, "File cannot be null");
JarFile jar = null;
InputStream stream = null;
try {
jar = new JarFile(file);
JarEntry entry = jar.getJarEntry("plugin.yml");
if (entry == null) {
throw new InvalidDescriptionException(new FileNotFoundException("Jar does not contain plugin.yml"));
}
stream = jar.getInputStream(entry);
return new PluginDescriptionFile(stream);
} catch (IOException | YAMLException ex) {
throw new InvalidDescriptionException(ex);
} finally {
if (jar != null) {
try {
jar.close();
} catch (IOException ignored) {
}
}
if (stream != null) {
try {
stream.close();
} catch (IOException ignored) {
}
}
}
}
public Pattern[] getPluginFileFilters() {
return fileFilters.clone();
}
Class> getClassByName(final String name) {
Class> cachedClass = classes.get(name);
if (cachedClass != null) {
return cachedClass;
} else {
for (String current : loaders.keySet()) {
PluginClassLoader loader = loaders.get(current);
try {
cachedClass = loader.findClass(name, false);
} catch (ClassNotFoundException ignored) {
}
if (cachedClass != null) {
return cachedClass;
}
}
}
return null;
}
void setClass(final String name, final Class> clazz) {
if (!classes.containsKey(name)) {
classes.put(name, clazz);
if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
Class extends ConfigurationSerializable> serializable = clazz.asSubclass(ConfigurationSerializable.class);
ConfigurationSerialization.registerClass(serializable);
}
}
}
private void removeClass(String name) {
Class> clazz = classes.remove(name);
try {
if ((clazz != null) && (ConfigurationSerializable.class.isAssignableFrom(clazz))) {
Class extends ConfigurationSerializable> serializable = clazz.asSubclass(ConfigurationSerializable.class);
ConfigurationSerialization.unregisterClass(serializable);
}
} catch (NullPointerException ex) {
// Boggle!
// (Native methods throwing NPEs is not fun when you can't stop it before-hand)
}
}
public Map, Set> createRegisteredListeners(Listener listener, final Plugin plugin) {
Validate.notNull(plugin, "Plugin can not be null");
Validate.notNull(listener, "Listener can not be null");
boolean useTimings = server.getPluginManager().useTimings();
Map, Set> ret = new HashMap, Set>();
Set methods;
try {
Method[] publicMethods = listener.getClass().getMethods();
Method[] privateMethods = listener.getClass().getDeclaredMethods();
methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0f);
Collections.addAll(methods, publicMethods);
Collections.addAll(methods, privateMethods);
} catch (NoClassDefFoundError e) {
plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
return ret;
}
for (final Method method : methods) {
final EventHandler eh = method.getAnnotation(EventHandler.class);
if (eh == null) continue;
// Do not register bridge or synthetic methods to avoid event duplication
// Fixes SPIGOT-893
if (method.isBridge() || method.isSynthetic()) {
continue;
}
final Class> checkClass;
if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
continue;
}
final Class extends Event> eventClass = checkClass.asSubclass(Event.class);
method.setAccessible(true);
Set eventSet = ret.get(eventClass);
if (eventSet == null) {
eventSet = new HashSet<>();
ret.put(eventClass, eventSet);
}
for (Class> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
// This loop checks for extending deprecated events
if (clazz.getAnnotation(Deprecated.class) != null) {
Warning warning = clazz.getAnnotation(Warning.class);
WarningState warningState = server.getWarningState();
if (!warningState.printFor(warning)) {
break;
}
plugin.getLogger().log(
Level.WARNING,
String.format(
"\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated." +
" \"%s\"; please notify the authors %s.",
plugin.getDescription().getFullName(),
clazz.getName(),
method.toGenericString(),
(warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected",
Arrays.toString(plugin.getDescription().getAuthors().toArray())),
warningState == WarningState.ON ? new AuthorNagException(null) : null);
break;
}
}
final CustomTimingsHandler timings = new CustomTimingsHandler("Plugin: " + plugin.getDescription().getFullName() + " Event: " + listener.getClass().getName() + "::" + method.getName() + "(" + eventClass.getSimpleName() + ")", pluginParentTimer); // Spigot
EventExecutor executor = new EventExecutor() {
public void execute(Listener listener, Event event) throws EventException {
try {
if (!eventClass.isAssignableFrom(event.getClass())) {
return;
}
// Spigot start
boolean isAsync = event.isAsynchronous();
if (!isAsync) timings.startTiming();
method.invoke(listener, event);
if (!isAsync) timings.stopTiming();
// Spigot end
} catch (InvocationTargetException ex) {
throw new EventException(ex.getCause());
} catch (Throwable t) {
throw new EventException(t);
}
}
};
if (false) { // Spigot - RL handles useTimings check now
eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
} else {
eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
}
}
return ret;
}
public void enablePlugin(final Plugin plugin) {
Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
if (!plugin.isEnabled()) {
plugin.getLogger().info("Enabling " + plugin.getDescription().getFullName());
JavaPlugin jPlugin = (JavaPlugin) plugin;
String pluginName = jPlugin.getDescription().getName();
if (!loaders.containsKey(pluginName)) {
loaders.put(pluginName, (PluginClassLoader) jPlugin.getClassLoader());
}
try {
jPlugin.setEnabled(true);
} catch (Throwable ex) {
server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
}
// Perhaps abort here, rather than continue going, but as it stands,
// an abort is not possible the way it's currently written
server.getPluginManager().callEvent(new PluginEnableEvent(plugin));
}
}
public void disablePlugin(Plugin plugin) {
Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
if (plugin.isEnabled()) {
String message = String.format("Disabling %s", plugin.getDescription().getFullName());
plugin.getLogger().info(message);
server.getPluginManager().callEvent(new PluginDisableEvent(plugin));
JavaPlugin jPlugin = (JavaPlugin) plugin;
ClassLoader cloader = jPlugin.getClassLoader();
try {
jPlugin.setEnabled(false);
} catch (Throwable ex) {
server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
}
loaders.remove(jPlugin.getDescription().getName());
if (cloader instanceof PluginClassLoader) {
PluginClassLoader loader = (PluginClassLoader) cloader;
Set names = loader.getClasses();
for (String name : names) {
removeClass(name);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy