/FileExtraction/Java_unseen/Wind-Development_WindSpigot/WindSpigot-API/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
package org.bukkit.plugin.java;

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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.regex.Pattern;

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.AuthorNagException;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.InvalidDescriptionException;
import org.bukkit.plugin.InvalidPluginException;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.plugin.RegisteredListener;
import org.bukkit.plugin.UnknownDependencyException;
import org.yaml.snakeyaml.error.YAMLException;

/**
 * Represents a Java plugin loader, allowing plugins in the form of .jar
 */
public final class JavaPluginLoader implements PluginLoader {
	final Server server;
	private final Pattern[] fileFilters = new Pattern[] { Pattern.compile("\\.jar$"), };
	private final Map<String, Class<?>> classes = new java.util.concurrent.ConcurrentHashMap<String, Class<?>>(); // Spigot
	private final Map<String, PluginClassLoader> loaders = new LinkedHashMap<String, PluginClassLoader>();

	/**
	 * 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 ex) {
			throw new InvalidDescriptionException(ex);
		} catch (YAMLException ex) {
			throw new InvalidDescriptionException(ex);
		} finally {
			if (jar != null) {
				try {
					jar.close();
				} catch (IOException e) {
				}
			}
			if (stream != null) {
				try {
					stream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	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 cnfe) {
				}
				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<Class<? extends Event>, Set<RegisteredListener>> 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<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>();
		Set<Method> methods;
		try {
			Method[] publicMethods = listener.getClass().getMethods();
			Method[] privateMethods = listener.getClass().getDeclaredMethods();
			methods = new HashSet<Method>(publicMethods.length + privateMethods.length, 1.0f);
			for (Method method : publicMethods) {
				methods.add(method);
			}
			for (Method method : privateMethods) {
				methods.add(method);
			}
		} 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<RegisteredListener> eventSet = ret.get(eventClass);
			if (eventSet == null) {
				eventSet = new HashSet<RegisteredListener>();
				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;
				}
			}

			EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Spigot
				public void execute(Listener listener, Event event) throws EventException {
					try {
						if (!eventClass.isAssignableFrom(event.getClass())) {
							return;
						}
						method.invoke(listener, event);
					} catch (InvocationTargetException ex) {
						throw new EventException(ex.getCause());
					} catch (Throwable t) {
						throw new EventException(t);
					}
				}
			}, plugin, method, eventClass); // Spigot
			// WindSpigot - remove dead code
			// 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);
				// PaperSpigot start - Disable plugins that fail to load
				disablePlugin(jPlugin);
				return;
				// PaperSpigot end
			}

			// 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<String> names = loader.getClasses();

				for (String name : names) {
					removeClass(name);
				}
			}
		}
	}
}
