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

org.bukkit.plugin.SimplePluginManager Maven / Gradle / Ivy

package org.bukkit.plugin;

import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang.Validate;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.PluginCommandYamlParser;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.util.FileUtil;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Handles all plugin management from the Server
 */
public final class SimplePluginManager implements PluginManager {
  private static File updateDirectory = null;
  private final Server server;
  private final Map fileAssociations = new HashMap();
  private final List plugins = new ArrayList();
  private final Map lookupNames = new HashMap();
  private final SimpleCommandMap commandMap;
  private final Map permissions = new HashMap();
  private final Map> defaultPerms = new LinkedHashMap>();
  private final Map> permSubs = new HashMap>();
  private final Map> defSubs = new HashMap>();
  private boolean useTimings = false;

  public SimplePluginManager(Server instance, SimpleCommandMap commandMap) {
    server = instance;
    this.commandMap = commandMap;

    defaultPerms.put(true, new HashSet());
    defaultPerms.put(false, new HashSet());
  }

  /**
   * Registers the specified plugin loader
   *
   * @param loader Class name of the PluginLoader to register
   * @throws IllegalArgumentException Thrown when the given Class is not a
   *                                  valid PluginLoader
   */
  public void registerInterface(Class loader) throws IllegalArgumentException {
    PluginLoader instance;

    if (PluginLoader.class.isAssignableFrom(loader)) {
      Constructor constructor;

      try {
        constructor = loader.getConstructor(Server.class);
        instance = constructor.newInstance(server);
      } catch (NoSuchMethodException ex) {
        String className = loader.getName();

        throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex);
      } catch (Exception ex) {
        throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex);
      }
    } else {
      throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName()));
    }

    Pattern[] patterns = instance.getPluginFileFilters();

    synchronized (this) {
      for (Pattern pattern : patterns) {
        fileAssociations.put(pattern, instance);
      }
    }
  }

  /**
   * Loads the plugins contained within the specified directory
   *
   * @param directory Directory to check for plugins
   * @return A list of all plugins loaded
   */
  public Plugin[] loadPlugins(File directory) {
    Validate.notNull(directory, "Directory cannot be null");
    Validate.isTrue(directory.isDirectory(), "Directory must be a directory");

    List result = new ArrayList<>();
    Set filters = fileAssociations.keySet();

    if (!(server.getUpdateFolder().equals(""))) {
      updateDirectory = new File(directory, server.getUpdateFolder());
    }

    Map plugins = new HashMap();
    Set loadedPlugins = new HashSet();
    Map> dependencies = new HashMap>();
    Map> softDependencies = new HashMap>();

    // This is where it figures out all possible plugins
    for (File file : directory.listFiles()) {
      PluginLoader loader = null;
      for (Pattern filter : filters) {
        Matcher match = filter.matcher(file.getName());
        if (match.find()) {
          loader = fileAssociations.get(filter);
        }
      }

      if (loader == null) continue;

      PluginDescriptionFile description = null;
      try {
        description = loader.getPluginDescription(file);
        String name = description.getName();
        if (name.equalsIgnoreCase("bukkit") || name.equalsIgnoreCase("minecraft") || name.equalsIgnoreCase("mojang")) {
          server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': Restricted Name");
          continue;
        } else if (description.rawName.indexOf(' ') != -1) {
          server.getLogger().warning(String.format(
            "Plugin `%s' uses the space-character (0x20) in its name `%s' - this is discouraged",
            description.getFullName(),
            description.rawName
          ));
        }
      } catch (InvalidDescriptionException ex) {
        server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
        continue;
      }

      File replacedFile = plugins.put(description.getName(), file);
      if (replacedFile != null) {
        server.getLogger().severe(String.format(
          "Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'",
          description.getName(),
          file.getPath(),
          replacedFile.getPath(),
          directory.getPath()
        ));
      }

      Collection softDependencySet = description.getSoftDepend();
      if (softDependencySet != null && !softDependencySet.isEmpty()) {
        if (softDependencies.containsKey(description.getName())) {
          // Duplicates do not matter, they will be removed together if applicable
          softDependencies.get(description.getName()).addAll(softDependencySet);
        } else {
          softDependencies.put(description.getName(), new LinkedList(softDependencySet));
        }
      }

      Collection dependencySet = description.getDepend();
      if (dependencySet != null && !dependencySet.isEmpty()) {
        dependencies.put(description.getName(), new LinkedList(dependencySet));
      }

      Collection loadBeforeSet = description.getLoadBefore();
      if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) {
        for (String loadBeforeTarget : loadBeforeSet) {
          if (softDependencies.containsKey(loadBeforeTarget)) {
            softDependencies.get(loadBeforeTarget).add(description.getName());
          } else {
            // softDependencies is never iterated, so 'ghost' plugins aren't an issue
            Collection shortSoftDependency = new LinkedList();
            shortSoftDependency.add(description.getName());
            softDependencies.put(loadBeforeTarget, shortSoftDependency);
          }
        }
      }
    }

    while (!plugins.isEmpty()) {
      boolean missingDependency = true;
      Iterator pluginIterator = plugins.keySet().iterator();

      while (pluginIterator.hasNext()) {
        String plugin = pluginIterator.next();

        if (dependencies.containsKey(plugin)) {
          Iterator dependencyIterator = dependencies.get(plugin).iterator();

          while (dependencyIterator.hasNext()) {
            String dependency = dependencyIterator.next();

            // Dependency loaded
            if (loadedPlugins.contains(dependency)) {
              dependencyIterator.remove();

              // We have a dependency not found
            } else if (!plugins.containsKey(dependency)) {
              missingDependency = false;
              File file = plugins.get(plugin);
              pluginIterator.remove();
              softDependencies.remove(plugin);
              dependencies.remove(plugin);

              server.getLogger().log(
                Level.SEVERE,
                "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'",
                new UnknownDependencyException(dependency));
              break;
            }
          }

          if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) {
            dependencies.remove(plugin);
          }
        }
        if (softDependencies.containsKey(plugin)) {

          // Soft depend is no longer around
          softDependencies.get(plugin).removeIf(softDependency -> !plugins.containsKey(softDependency));

          if (softDependencies.get(plugin).isEmpty()) {
            softDependencies.remove(plugin);
          }
        }
        if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) {
          // We're clear to load, no more soft or hard dependencies left
          File file = plugins.get(plugin);
          pluginIterator.remove();
          missingDependency = false;

          try {
            result.add(loadPlugin(file));
            loadedPlugins.add(plugin);
          } catch (InvalidPluginException ex) {
            server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
          }
        }
      }

      if (missingDependency) {
        // We now iterate over plugins until something loads
        // This loop will ignore soft dependencies
        pluginIterator = plugins.keySet().iterator();

        while (pluginIterator.hasNext()) {
          String plugin = pluginIterator.next();

          if (!dependencies.containsKey(plugin)) {
            softDependencies.remove(plugin);
            missingDependency = false;
            File file = plugins.get(plugin);
            pluginIterator.remove();

            try {
              result.add(loadPlugin(file));
              loadedPlugins.add(plugin);
              break;
            } catch (InvalidPluginException ex) {
              server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
            }
          }
        }
        // We have no plugins left without a depend
        if (missingDependency) {
          softDependencies.clear();
          dependencies.clear();
          Iterator failedPluginIterator = plugins.values().iterator();

          while (failedPluginIterator.hasNext()) {
            File file = failedPluginIterator.next();
            failedPluginIterator.remove();
            server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected");
          }
        }
      }
    }

    org.bukkit.command.defaults.TimingsCommand.timingStart = System.nanoTime(); // Spigot
    return result.toArray(new Plugin[result.size()]);
  }

  /**
   * Loads the plugin in the specified file
   * 

* File must be valid according to the current enabled Plugin interfaces * * @param file File containing the plugin to load * @return The Plugin loaded, or null if it was invalid * @throws InvalidPluginException Thrown when the specified file is not a * valid plugin * @throws UnknownDependencyException If a required dependency could not * be found */ public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException { Validate.notNull(file, "File cannot be null"); checkUpdate(file); Set filters = fileAssociations.keySet(); Plugin result = null; for (Pattern filter : filters) { String name = file.getName(); Matcher match = filter.matcher(name); if (match.find()) { PluginLoader loader = fileAssociations.get(filter); result = loader.loadPlugin(file); } } if (result != null) { plugins.add(result); lookupNames.put(result.getDescription().getName(), result); } return result; } private void checkUpdate(File file) { if (updateDirectory == null || !updateDirectory.isDirectory()) { return; } File updateFile = new File(updateDirectory, file.getName()); if (updateFile.isFile() && FileUtil.copy(updateFile, file)) { updateFile.delete(); } } /** * Checks if the given plugin is loaded and returns it when applicable *

* Please note that the name of the plugin is case-sensitive * * @param name Name of the plugin to check * @return Plugin if it exists, otherwise null */ public synchronized Plugin getPlugin(String name) { return lookupNames.get(name.replace(' ', '_')); } public synchronized Plugin[] getPlugins() { return plugins.toArray(new Plugin[0]); } /** * Checks if the given plugin is enabled or not *

* Please note that the name of the plugin is case-sensitive. * * @param name Name of the plugin to check * @return true if the plugin is enabled, otherwise false */ public boolean isPluginEnabled(String name) { Plugin plugin = getPlugin(name); return isPluginEnabled(plugin); } /** * Checks if the given plugin is enabled or not * * @param plugin Plugin to check * @return true if the plugin is enabled, otherwise false */ public boolean isPluginEnabled(Plugin plugin) { if ((plugin != null) && (plugins.contains(plugin))) { return plugin.isEnabled(); } else { return false; } } public void enablePlugin(final Plugin plugin) { if (!plugin.isEnabled()) { List pluginCommands = PluginCommandYamlParser.parse(plugin); if (!pluginCommands.isEmpty()) { commandMap.registerAll(plugin.getDescription().getName(), pluginCommands); } try { plugin.getPluginLoader().enablePlugin(plugin); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); } HandlerList.bakeAll(); } } public void disablePlugins() { Plugin[] plugins = getPlugins(); for (int i = plugins.length - 1; i >= 0; i--) { disablePlugin(plugins[i]); } } public void disablePlugin(final Plugin plugin) { if (plugin.isEnabled()) { try { plugin.getPluginLoader().disablePlugin(plugin); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); } try { server.getScheduler().cancelTasks(plugin); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while cancelling tasks for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); } try { server.getServicesManager().unregisterAll(plugin); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering services for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); } try { HandlerList.unregisterAll(plugin); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering events for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); } try { server.getMessenger().unregisterIncomingPluginChannel(plugin); server.getMessenger().unregisterOutgoingPluginChannel(plugin); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering plugin channels for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); } } } public void clearPlugins() { synchronized (this) { disablePlugins(); plugins.clear(); lookupNames.clear(); HandlerList.unregisterAll(); fileAssociations.clear(); permissions.clear(); defaultPerms.get(true).clear(); defaultPerms.get(false).clear(); } } /** * Calls an event with the given details. *

* This method only synchronizes when the event is not asynchronous. * * @param event Event details */ public void callEvent(Event event) { if (event.isAsynchronous()) { if (Thread.holdsLock(this)) { throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); } if (server.isPrimaryThread()) { throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread."); } fireEvent(event); } else { synchronized (this) { fireEvent(event); } } } private void fireEvent(Event event) { HandlerList handlers = event.getHandlers(); RegisteredListener[] listeners = handlers.getRegisteredListeners(); for (RegisteredListener registration : listeners) { if (!registration.getPlugin().isEnabled()) { continue; } try { registration.callEvent(event); } catch (AuthorNagException ex) { Plugin plugin = registration.getPlugin(); if (plugin.isNaggable()) { plugin.setNaggable(false); server.getLogger().log(Level.SEVERE, String.format( "Nag author(s): '%s' of '%s' about the following: %s", plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(), ex.getMessage() )); } } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(), ex); } } } public void registerEvents(Listener listener, Plugin plugin) { if (!plugin.isEnabled()) { throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); } for (Map.Entry, Set> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) { getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue()); } } public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) { registerEvent(event, listener, priority, executor, plugin, false); } /** * Registers the given event to the specified listener using a directly * passed EventExecutor * * @param event Event class to register * @param listener PlayerListener to register * @param priority Priority of this event * @param executor EventExecutor to register * @param plugin Plugin to register * @param ignoreCancelled Do not call executor if event was already * cancelled */ public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled) { Validate.notNull(listener, "Listener cannot be null"); Validate.notNull(priority, "Priority cannot be null"); Validate.notNull(executor, "Executor cannot be null"); Validate.notNull(plugin, "Plugin cannot be null"); if (!plugin.isEnabled()) { throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); } if (useTimings) { getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); } else { getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); } } private HandlerList getEventListeners(Class type) { try { Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList"); method.setAccessible(true); return (HandlerList) method.invoke(null); } catch (Exception e) { throw new IllegalPluginAccessException(e.toString()); } } private Class getRegistrationClass(Class clazz) { try { clazz.getDeclaredMethod("getHandlerList"); return clazz; } catch (NoSuchMethodException e) { if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Event.class) && Event.class.isAssignableFrom(clazz.getSuperclass())) { return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); } else { throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!"); } } } public Permission getPermission(String name) { return permissions.get(name.toLowerCase()); } public void addPermission(Permission perm) { String name = perm.getName().toLowerCase(); if (permissions.containsKey(name)) { throw new IllegalArgumentException("The permission " + name + " is already defined!"); } permissions.put(name, perm); calculatePermissionDefault(perm); } public Set getDefaultPermissions(boolean op) { return ImmutableSet.copyOf(defaultPerms.get(op)); } public void removePermission(Permission perm) { removePermission(perm.getName()); } public void removePermission(String name) { permissions.remove(name.toLowerCase()); } public void recalculatePermissionDefaults(Permission perm) { if (perm != null && permissions.containsKey(perm.getName().toLowerCase())) { defaultPerms.get(true).remove(perm); defaultPerms.get(false).remove(perm); calculatePermissionDefault(perm); } } private void calculatePermissionDefault(Permission perm) { if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { defaultPerms.get(true).add(perm); dirtyPermissibles(true); } if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { defaultPerms.get(false).add(perm); dirtyPermissibles(false); } } private void dirtyPermissibles(boolean op) { Set permissibles = getDefaultPermSubscriptions(op); for (Permissible p : permissibles) { p.recalculatePermissions(); } } public void subscribeToPermission(String permission, Permissible permissible) { String name = permission.toLowerCase(); Map map = permSubs.get(name); if (map == null) { map = new WeakHashMap(); permSubs.put(name, map); } map.put(permissible, true); } public void unsubscribeFromPermission(String permission, Permissible permissible) { String name = permission.toLowerCase(); Map map = permSubs.get(name); if (map != null) { map.remove(permissible); if (map.isEmpty()) { permSubs.remove(name); } } } public Set getPermissionSubscriptions(String permission) { String name = permission.toLowerCase(); Map map = permSubs.get(name); if (map == null) { return ImmutableSet.of(); } else { return ImmutableSet.copyOf(map.keySet()); } } public void subscribeToDefaultPerms(boolean op, Permissible permissible) { Map map = defSubs.get(op); if (map == null) { map = new WeakHashMap(); defSubs.put(op, map); } map.put(permissible, true); } public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) { Map map = defSubs.get(op); if (map != null) { map.remove(permissible); if (map.isEmpty()) { defSubs.remove(op); } } } public Set getDefaultPermSubscriptions(boolean op) { Map map = defSubs.get(op); if (map == null) { return ImmutableSet.of(); } else { return ImmutableSet.copyOf(map.keySet()); } } public Set getPermissions() { return new HashSet(permissions.values()); } public boolean useTimings() { return useTimings; } /** * Sets whether or not per event timing code should be used * * @param use True if per event timing code should be used */ public void useTimings(boolean use) { useTimings = use; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy