Package org.notbukkit

Source Code of org.notbukkit.RubyPluginLoader

package org.notbukkit;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Pattern;

import org.apache.commons.lang.Validate;
import org.bukkit.Server;
import org.bukkit.event.Event;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.*;
import org.jruby.CompatVersion;
import org.jruby.RubyInstanceConfig.CompileMode;
import org.jruby.RubySymbol;
import org.jruby.embed.EmbedEvalUnit;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

/**
* Loader for Ruby plugins; will be registered in PluginManager.
*
* Info about JRuby:
*  - Embedding: http://kenai.com/projects/jruby/pages/DirectJRubyEmbedding
*  - YAML & co: http://kenai.com/projects/jruby/pages/AccessingJRubyObjectInJava
*
* @author Zeerix
*/
public final class RubyPluginLoader implements PluginLoader {

    // *** referenced classes ***

    private final Server server;

    // *** data ***

    private final Pattern[] fileFilters = new Pattern[] {
        Pattern.compile("\\.rb$"),
    };

    // script files
    private final String initScript = "/rubybukkit/init-plugin.rb";
    private final String createScript = "/rubybukkit/new-plugin.rb";

    // *** interface ***

    public RubyPluginLoader(Server server) {
        this.server = server;
    }

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

    public Plugin loadPlugin(File file, boolean ignoreSoftDependencies) throws InvalidPluginException, UnknownDependencyException {
        Validate.notNull(file, "File cannot be null");
        if (!file.exists()) {
            throw new InvalidPluginException(new FileNotFoundException(String.format("%s does not exist", file.getPath())));
        }

        // create a scripting container for every plugin to encapsulate it
        ScriptingContainer runtime = setupScriptingContainer(file);
        try {
            // run init script
            runResourceScript(runtime, initScript);

            final EmbedEvalUnit eval = runtime.parse(PathType.RELATIVE, file.getPath());
            /*IRubyObject res =*/ eval.run();

            // create plugin description
            final PluginDescriptionFile description = getDescriptionFile(runtime);
            if (description == null)
                return null;    // silent fail if the script doesn't contain a plugin description

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

            // create instance of main class
            RubyPlugin plugin = (RubyPlugin)runResourceScript(runtime, createScript);

            plugin.initialize(this, server, description, dataFolder, file, runtime);
            return plugin;
        } catch (Exception e) {
            throw new InvalidPluginException(e);
        }
    }

    /**
     * create and setup ScriptingContainer
     */
    private ScriptingContainer setupScriptingContainer(File file) {
        ScriptingContainer runtime = new ScriptingContainer(LocalContextScope.SINGLETHREAD);

        runtime.setCompileMode(CompileMode.JIT);

        runtime.setClassLoader(RubyPluginLoader.class.getClassLoader());

        // Setup load paths, the "file:" thing is for internal stuff like rubygems
        String[] loadPaths = new String[] {
            file.getAbsoluteFile().getParent(),
            RubyBukkit.thisJar.getAbsolutePath(),
            "file:" + RubyBukkit.jrubyJar.getAbsoluteFile() + "!/META_INF/jruby.home/lib/ruby/site_ruby/" + RubyBukkit.rubyVersion,
            "file:" + RubyBukkit.jrubyJar.getAbsoluteFile() + "!/META_INF/jruby.home/lib/ruby/site_ruby/shared",
            "file:" + RubyBukkit.jrubyJar.getAbsoluteFile() + "!/META_INF/jruby.home/lib/ruby/" + RubyBukkit.rubyVersion,
        };
        runtime.setLoadPaths(Arrays.asList(loadPaths));

        if (RubyBukkit.rubyVersion.equals("1.9"))
            runtime.setCompatVersion(CompatVersion.RUBY1_9);

        return runtime;
    }

    /**
     * execute Ruby script from resource (embedded in .jar)
     */
    private Object runResourceScript(ScriptingContainer runtime, String filename) throws IOException {
        InputStream script = getClass().getResourceAsStream(filename);
        if (script == null)
            throw new FileNotFoundException(filename);
        try {
            return runtime.runScriptlet(script, filename);
        } finally {
            script.close();
        }
    }

    @Override
    public PluginDescriptionFile getPluginDescription(File file) throws InvalidDescriptionException {
        Validate.notNull(file, "File cannot be null");
        if (!file.exists()) {
            throw new InvalidDescriptionException(new FileNotFoundException(String.format("%s does not exist", file.getPath())));
        }

        ScriptingContainer runtime = setupScriptingContainer(file);
        try {
            // run init script
            runResourceScript(runtime, initScript);

            final EmbedEvalUnit eval = runtime.parse(PathType.RELATIVE, file.getPath());
            /*IRubyObject res =*/ eval.run();

            return getDescriptionFile(runtime);

        } catch (IOException e) {
            throw new InvalidDescriptionException(e);
        }
    }

    /**
     * extract description from Ruby script
     * "Plugin.getDescription" will return our map
     */
    private PluginDescriptionFile getDescriptionFile(ScriptingContainer runtime) throws InvalidDescriptionException {
        Object desc = convertFromRuby(runtime.runScriptlet("Plugin.getDescription"));
        if (!(desc instanceof Map)) {
            throw new InvalidDescriptionException("Plugin.getDescription must return a Map");
        }

        final Yaml yaml = new Yaml(new SafeConstructor());
        final StringReader reader = new StringReader( yaml.dump(desc) );
        return new PluginDescriptionFile(reader);
    }

    private static Object convertFromRuby(Object object) throws InvalidDescriptionException {
        if (object == null || object instanceof String)
            return object;
        if (object instanceof Boolean)
            return (boolean)(Boolean)object;
        if (object instanceof Long)
            return (long)(Long)object;
        if (object instanceof Double)
            return (double)(Double)object;
        if (object instanceof RubySymbol)
            return convertFromRuby( ((RubySymbol)object).asJavaString() );
        if (object instanceof List)
            return convertFromRuby( (List<Object>)object );
        if (object instanceof Map)
            return convertFromRuby( (Map<Object, Object>)object );

        throw new InvalidDescriptionException("Unknown Ruby object: " + object.getClass().getName());
    }

    private static Object convertFromRuby(Map<Object, Object> map) throws InvalidDescriptionException {
        Map<Object, Object> result = new HashMap<Object, Object>();
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            Object key = convertFromRuby(entry.getKey());
            Object value = convertFromRuby(entry.getValue());
            result.put(key, value);
        }
        return result;
    }

    private static Object convertFromRuby(List<Object> list) throws InvalidDescriptionException {
        List<Object> result = new ArrayList<Object>();
        for (Object entry : list) {
            result.add(convertFromRuby(entry));
        }
        return result;
    }

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

    @Override
    public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(Listener listener, Plugin plugin) {
        return Collections.emptyMap()// no annotation based registering for Ruby plugins yet
    }

    @Override
    public void enablePlugin(Plugin plugin) {
        Validate.isTrue(plugin instanceof RubyPlugin, "Plugin is not associated with this PluginLoader");

        if (!plugin.isEnabled()) {
            String message = String.format("[%s] Loading %s.", plugin.getDescription().getName(), plugin.getDescription().getFullName());
            server.getLogger().info(message);

            try {
                RubyPlugin rPlugin = (RubyPlugin)plugin;
                rPlugin.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);
            }

            // 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 void disablePlugin(Plugin plugin) {
        Validate.isTrue(plugin instanceof RubyPlugin, "Plugin is not associated with this PluginLoader");

        if (plugin.isEnabled()) {
            String message = String.format("[%s] Unloading %s.", plugin.getDescription().getName(), plugin.getDescription().getFullName());
            server.getLogger().info(message);

            try {
                RubyPlugin rPlugin = (RubyPlugin)plugin;
                rPlugin.setEnabled(false);
            } catch (Throwable ex) {
                server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + ": " + ex.getMessage(), 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 PluginDisableEvent(plugin));
        }
    }
}
TOP

Related Classes of org.notbukkit.RubyPluginLoader

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.