All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bukkit.plugin.java.JavaPluginLoader Maven / Gradle / Ivy

package org.bukkit.plugin.java;

import org.apache.commons.lang.Validate;
import org.bukkit.Server;
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 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 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");
		
		Map, Set> ret = new HashMap<>();
		Set methods;
		try {
			
			Method[] classMethods = listener.getClass().getDeclaredMethods();
			methods = new HashSet<>(classMethods.length, 1f);
			Collections.addAll(methods, classMethods);
      
      /*
      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 eventClass = checkClass.asSubclass(Event.class);
			method.setAccessible(true);
			Set eventSet = ret.computeIfAbsent(eventClass, k -> new HashSet<>());
			
			// walkmc start -> disable deprecated checker
      /*
      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;
        }
      }
       */
			
			eventSet.add(new RegisteredListener(listener, (listener1, event) -> {
				if (!eventClass.isAssignableFrom(event.getClass())) return;
				
				try {
					
					method.invoke(listener1, event);
					
				} catch (InvocationTargetException ex) {
					throw new EventException(ex.getCause());
				} catch (Throwable t) {
					throw new EventException(t);
				}
			}, 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