Package net.lahwran.bukkit.jython

Source Code of net.lahwran.bukkit.jython.PythonPluginLoader

/**
*
*/
package net.lahwran.bukkit.jython;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.Validate;
import org.bukkit.Server;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
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.TimedRegisteredListener;
import org.bukkit.plugin.UnknownDependencyException;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.python.core.Py;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.util.PythonInterpreter;
import org.yaml.snakeyaml.error.YAMLException;

import com.master.bukkit.python.ReflectionHelper;

/**
* A jython plugin loader. depends on JavaPluginLoader and SimplePluginManager.
*
* @author masteroftime
* @author lahwran
*/
public class PythonPluginLoader implements PluginLoader {

    private final Server server;

    /**
     * Filter - matches all of the following, for the regex illiterate:
     * <pre>
     * plugin_py_dir
     * plugin.py.dir
     * plugin.py.zip
     * plugin.pyp
     * </pre>
     */
    public static final Pattern[] fileFilters = new Pattern[] {
            Pattern.compile("^(.*)\\.py\\.dir$"),
            Pattern.compile("^(.*)_py_dir$"),
            Pattern.compile("^(.*)\\.py\\.zip$"),
            Pattern.compile("^(.*)\\.pyp$"),
            Pattern.compile("^(.*)\\.py$"),
        };

    private HashSet<String> loadedplugins = new HashSet<String>();

    /**
     * @param server server to initialize with
     */
    public PythonPluginLoader(Server server) {
        this.server = server;
    }

    public Plugin loadPlugin(File file) throws InvalidPluginException/*, UnknownDependencyException*/ {
        return loadPlugin(file, false);
    }

    public Plugin loadPlugin(File file, boolean ignoreSoftDependencies)
            throws InvalidPluginException/*, InvalidDescriptionException, UnknownDependencyException*/ {

        if (!file.exists()) {
            throw new InvalidPluginException(new FileNotFoundException(String.format("%s does not exist",
                    file.getPath())));
        }

        PluginDataFile data = null;

        if (file.getName().endsWith(".py")) {
            if (file.isDirectory())
                throw new InvalidPluginException(new Exception("python files cannot be directories! try .py.dir instead."));
            data = new PluginPythonFile(file);
        } else if (file.getName().endsWith("dir")) {
            if (!file.isDirectory())
                throw new InvalidPluginException(new Exception("python directories cannot be normal files! try .py or .py.zip instead."));
            data = new PluginPythonDirectory(file);
        } else if (file.getName().endsWith("zip") || file.getName().endsWith("pyp")) {
            if (file.isDirectory())
                throw new InvalidPluginException(new Exception("python zips cannot be directories! try .py.dir instead."));
            data = new PluginPythonZip(file);
        } else {
            throw new InvalidPluginException(new Exception("filename '"+file.getName()+"' does not end in py, dir, zip, or pyp! did you add a regex without altering loadPlugin()?"));
        }

        try {
            return loadPlugin(file, ignoreSoftDependencies, data);
        } finally {
            try {
                data.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Plugin loadPlugin(File file, boolean ignoreSoftDependencies, PluginDataFile data) throws InvalidPluginException/*, InvalidDescriptionException, UnknownDependencyException*/ {
        System.out.println("[PythonLoader] Loading Plugin " + file.getName());
        PythonPlugin result = null;
        PluginDescriptionFile description = null;
        boolean hasyml = true;
        boolean hassolidmeta = false; // whether we have coder-set metadata. true for have set metadata, false for inferred metadata.
        try {
            InputStream stream = data.getStream("plugin.yml");
            if (stream == null)
                hasyml = false;

            if (hasyml) {
                description = new PluginDescriptionFile(stream);
                hassolidmeta = true;
            } else {
                String stripped = stripExtension(file.getName());
                if (stripped == null)
                    //throw new BukkitScrewedUpException("This would only occur if bukkit called the loader on a plugin which does not match the loader's regex.");
                    throw new InvalidPluginException(new Exception("This shouldn't be happening; go tell whoever altered the plugin loading api in bukkit that they're whores."));
                description = new PluginDescriptionFile(stripped, "dev", "plugin.py");
            }
            if (stream != null)
                stream.close();
        } catch (IOException ex) {
            throw new InvalidPluginException(ex);
        } catch (YAMLException ex) {
            throw new InvalidPluginException(ex);
        } catch (InvalidDescriptionException ex) {
            throw new InvalidPluginException(ex);
        }

        File dataFolder = new File(file.getParentFile(), description.getName());

        if (dataFolder.getAbsolutePath().equals(file.getAbsolutePath())) {
            throw new InvalidPluginException(new Exception(String.format("Projected datafolder: '%s' for %s is the same file as the plugin itself (%s)",
                    dataFolder,
                    description.getName(),
                    file)));
        }

        if (dataFolder.exists() && !dataFolder.isDirectory()) {
            throw new InvalidPluginException(new Exception(String.format("Projected datafolder: '%s' for %s (%s) exists and is not a directory",
                    dataFolder,
                    description.getName(),
                    file)));
        }

        ArrayList<String> depend;

        try {
            depend = (ArrayList<String>) description.getDepend();
            if (depend == null) {
                depend = new ArrayList<String>();
            }
        } catch (ClassCastException ex) {
            throw new InvalidPluginException(ex);
        }

        for (String pluginName : depend) {
            if (!isPluginLoaded(pluginName)) {
                throw new UnknownDependencyException(pluginName);
            }
        }

        PyList pythonpath = Py.getSystemState().path;
        PyString filepath = new PyString(file.getAbsolutePath());
        if (data.shouldAddPathEntry()) {
            if (pythonpath.__contains__(filepath)) {
                throw new InvalidPluginException(new Exception("path " + filepath
                        + " already on pythonpath!")); //can't imagine how this would happen, but better safe than sorry
            }
            pythonpath.append(filepath);
        }


        String mainfile = description.getMain();
        InputStream instream = null;
        try {
            instream = data.getStream(mainfile);

            if (instream == null) {
                mainfile = "plugin.py";
                instream = data.getStream(mainfile);
            }
            if (instream == null) {
                mainfile = "main.py";
                instream = data.getStream(mainfile);
            }
        } catch (IOException e) {
            throw new InvalidPluginException(e);
        }

        if (instream == null) {
            throw new InvalidPluginException(new FileNotFoundException("Data file does not contain "+mainfile));
        }
        try {
            PythonHooks hook = new PythonHooks(description);

            PythonInterpreter interp = new PythonInterpreter();

            interp.set("hook", hook);
            interp.set("info", description);
           
            // Decorator Enhancements
            interp.exec("import __builtin__");
            interp.exec("__builtin__.hook = hook");
            interp.exec("__builtin__.info = info");
           
            // Hardcoded for now, may be worth thinking about generalizing it as sort of "addons" for the PythonPluginLoader
            // Could be used to extend the capabilities of python plugins the same way the metaclass decorators do, without requiring any changes to the PythonPluginLoader itself
            String[] pre_plugin_scripts = {"imports.py", "meta_decorators.py"};
            String[] post_plugin_scripts = {"meta_loader.py"};
           
            // Run scripts designed to be run before plugin creation
            for (String script : pre_plugin_scripts) {
              InputStream metastream = this.getClass().getClassLoader().getResourceAsStream("scripts/"+script);
              interp.execfile(metastream);
              metastream.close();
            }

            interp.execfile(instream);

            instream.close();

            try {
                if (!hasyml) {
                    Object name = interp.get("__plugin_name__");
                    Object version = interp.get("__plugin_version__");
                    Object website = interp.get("__plugin_website__");
                    Object main = interp.get("__plugin_mainclass__");
                    hassolidmeta = name != null && version != null;
                    if (name != null)
                        ReflectionHelper.setPrivateValue(description, "name", name.toString());
                    if (version != null)
                        ReflectionHelper.setPrivateValue(description, "version", version.toString());
                    if (website != null)
                        ReflectionHelper.setPrivateValue(description, "website", website.toString());
                    if (main != null)
                        ReflectionHelper.setPrivateValue(description, "main", main.toString());
                }
            } catch (Throwable t) {
                Logger.getLogger("Minecraft").log(Level.SEVERE, "Error while setting python-set description values", t);
            }

            String mainclass = description.getMain();
            PyObject pyClass = interp.get(mainclass);
            if (pyClass == null)
                pyClass = interp.get("Plugin");
            if (pyClass == null)
                result = new PythonPlugin();
            else
                result = (PythonPlugin) pyClass.__call__().__tojava__(PythonPlugin.class);
           
            interp.set("pyplugin", result);
           
            result.hooks = hook;
            result.interp = interp;
           
            // Run scripts designed to be run after plugin creation
            for (String script : post_plugin_scripts) {
              InputStream metastream = this.getClass().getClassLoader().getResourceAsStream("scripts/"+script);
              interp.execfile(metastream);
              metastream.close();
            }
           
            result.initialize(this, server, description, dataFolder, file);
            result.setDataFile(data);
           
        } catch (Throwable t) {
            if (data.shouldAddPathEntry() && pythonpath.__contains__(filepath)) {
                pythonpath.remove(filepath);
            }
            throw new InvalidPluginException(t);
        }

        if (data.getNeedsSolidMeta() && !hassolidmeta) {
            throw new InvalidPluginException(new Exception("Released plugins require either a plugin.yml or both __plugin_name__ and __plugin_version__ set in the main python file!"));
        }

        if (!loadedplugins.contains(description.getName()))
            loadedplugins.add(description.getName());
        return result;
    }

    /**
     * Remove the extension for use in an automatic plugin name
     * @param toStrip filename to strip
     * @return stripped file name or null if not changed
     */
    private String stripExtension(String toStrip) {
        for (Pattern p : fileFilters) {
            Matcher m = p.matcher(toStrip);
            if (m.matches()) {
                return m.group(1);
            }
        }
        return null;
    }

    private boolean isPluginLoaded(String name) {
        if (loadedplugins.contains(name))
            return true;
        if (ReflectionHelper.isJavaPluginLoaded(server.getPluginManager(), name))
            return true;
        return false;
    }

    public Pattern[] getPluginFileFilters() {
        return fileFilters;
    }

    /*public EventExecutor createExecutor(Type type, Listener listener) {
        if (listener.getClass().equals(PythonListener.class)) { // 8 times faster than instanceof \o/
            return new EventExecutor() {
                public void execute(Listener listener, Event event) {
                    ((PythonListener) listener).onEvent(event);
                }
            };
        } else {
            JavaPluginLoader jplugload = ReflectionHelper.getJavaPluginLoader(server.getPluginManager());
            return jplugload.createExecutor(type, listener);
        }
    }*/

    public void disablePlugin(Plugin plugin) {
        if (!(plugin instanceof PythonPlugin)) {
            throw new IllegalArgumentException("Plugin is not associated with this PluginLoader");
        }

        if (plugin.isEnabled()) {
            PythonPlugin pyPlugin = (PythonPlugin) plugin;
            //ClassLoader cloader = jPlugin.getClassLoader();

            try {
                pyPlugin.setEnabled(false);
            } catch (Throwable ex) {
                server.getLogger().log(Level.SEVERE,
                        "Error occurred while disabling " + plugin.getDescription().getFullName()
                                + " (Is it up to date?): " + ex.getMessage(),
                        ex);
            }

            server.getPluginManager().callEvent(new PluginDisableEvent(plugin));

            String pluginName = pyPlugin.getDescription().getName();
            if (loadedplugins.contains(pluginName))
                loadedplugins.remove(pluginName);
        }
    }

    public void enablePlugin(Plugin plugin) {
        if (!(plugin instanceof PythonPlugin)) {
            throw new IllegalArgumentException("Plugin is not associated with this PluginLoader");
        }

        if (!plugin.isEnabled()) {
            PythonPlugin pyPlugin = (PythonPlugin) plugin;

            String pluginName = pyPlugin.getDescription().getName();

            if (!loadedplugins.contains(pluginName))
                loadedplugins.add(pluginName);

            try {
                pyPlugin.setEnabled(true);
            } catch (Throwable ex) {
                server.getLogger().log(Level.SEVERE,
                        "Error occurred while enabling " + plugin.getDescription().getFullName()
                                + " (Is it up to date?): " + ex.getMessage(),
                        ex);
            }

            //finally register the listener for the hook events
            server.getPluginManager().registerEvents(pyPlugin.listener, pyPlugin);

            // 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));
        }
    }

    @Override
    public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(
            Listener listener, Plugin plugin) {
        boolean useTimings = server.getPluginManager().useTimings();
        Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>();

        if(!listener.getClass().equals(PythonListener.class)) {
            throw new IllegalArgumentException("Listener to register is not a PythonListener");
        }

        PythonListener pyListener = (PythonListener)listener;

        for(Map.Entry<Class<? extends Event>, Set<PythonEventHandler>> entry : pyListener.handlers.entrySet()) {
            Set<RegisteredListener> eventSet = new HashSet<RegisteredListener>();

            for(final PythonEventHandler handler : entry.getValue()) {
                EventExecutor executor = new EventExecutor() {

                    @Override
                    public void execute(Listener listener, Event event) throws EventException {
                        if(!listener.getClass().equals(PythonListener.class)) {
                            throw new IllegalArgumentException("No PythonListener passed to EventExecutor! If this happens someone really fucked up something");
                        }
                        ((PythonListener)listener).fireEvent(event, handler);
                    }
                };
                if(useTimings) {
                    eventSet.add(new TimedRegisteredListener(pyListener, executor, handler.priority, plugin, false));
                }
                else {
                    eventSet.add(new RegisteredListener(pyListener, executor, handler.priority, plugin, false));
                }
            }
            ret.put(entry.getKey(), eventSet);
        }
        return ret;
    }

    @Override
    public PluginDescriptionFile getPluginDescription(File file)
            throws InvalidDescriptionException {
        Validate.notNull(file, "File cannot be null");

        InputStream stream = null;
        PluginDataFile data = null;

        if (file.getName().endsWith(".py")) {
            if (file.isDirectory())
                //cause we can't throw InvalidPluginExceptions from here we throw an InvalidDescriptionExecption with the InvalidPlugin as cause
                throw new InvalidDescriptionException(new InvalidPluginException(new Exception("python files cannot be directories! try .py.dir instead.")));
            data = new PluginPythonFile(file);
        } else if (file.getName().endsWith("dir")) {
            if (!file.isDirectory())
                throw new InvalidDescriptionException(new InvalidPluginException(new Exception("python directories cannot be normal files! try .py or .py.zip instead.")));
            data = new PluginPythonDirectory(file);
        } else if (file.getName().endsWith("zip") || file.getName().endsWith("pyp")) {
            if (file.isDirectory())
                throw new InvalidDescriptionException(new InvalidPluginException(new Exception("python zips cannot be directories! try .py.dir instead.")));
            try {
                data = new PluginPythonZip(file);
            } catch (InvalidPluginException ex) {
                throw new InvalidDescriptionException(ex);
            }
        } else {
            throw new InvalidDescriptionException(new InvalidPluginException(new Exception("filename '"+file.getName()+"' does not end in py, dir, zip, or pyp! did you add a regex without altering loadPlugin()?")));
        }

        try {
            stream = data.getStream("plugin.yml");

            if(stream == null) {
                //TODO Does this cause serious problems with plugins which have no plugin.yml file?
                throw new InvalidDescriptionException(new InvalidPluginException(new FileNotFoundException("Jar does not contain plugin.yml")));
            }

            return new PluginDescriptionFile(stream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {}
            }
        }
        return null;
    }
}
TOP

Related Classes of net.lahwran.bukkit.jython.PythonPluginLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.