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

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

There is a newer version: 2.4.0
Show newest version
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 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");

    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 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