org.spout.api.plugin.PluginLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.spout.api.plugin.PluginLoader.java

Source

/*
 * This file is part of Spout.
 *
 * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
 * Spout is licensed under the Spout License Version 1.
 *
 * Spout is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * In addition, 180 days after any changes are published, you can use the
 * software, incorporating those changes, under the terms of the MIT license,
 * as described in the Spout License Version 1.
 *
 * Spout is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License,
 * the MIT license and the Spout License Version 1 along with this program.
 * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
 * License and see <http://spout.in/licensev1> for the full license, including
 * the MIT license.
 */
package org.spout.api.plugin;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;

import org.apache.commons.collections.map.CaseInsensitiveMap;

import org.spout.api.Engine;
import org.spout.api.Spout;
import org.spout.api.event.server.plugin.PluginDisableEvent;
import org.spout.api.event.server.plugin.PluginEnableEvent;
import org.spout.api.exception.InvalidDescriptionFileException;
import org.spout.api.exception.InvalidPluginException;
import org.spout.api.exception.UnknownDependencyException;
import org.spout.api.exception.UnknownSoftDependencyException;
import org.spout.api.plugin.security.PluginSecurityManager;
import org.spout.api.protocol.Protocol;

public class PluginLoader {
    public static final String YAML_SPOUT = "properties.yml";
    public static final String YAML_OTHER = "plugin.yml";
    protected final Engine engine;
    private final PluginSecurityManager manager;
    private final double key;
    @SuppressWarnings("unchecked")
    private final Map<String, PluginClassLoader> loaders = new CaseInsensitiveMap();

    public PluginLoader(final Engine engine, final PluginSecurityManager manager, final double key) {
        this.engine = engine;
        this.manager = manager;
        this.key = key;
    }

    /**
     * Enables the plugin
     *
     * @param plugin to enable
     */
    public synchronized void enablePlugin(Plugin plugin) {
        if (!plugin.isEnabled()) {

            String name = plugin.getDescription().getName();
            if (!loaders.containsKey(name)) {
                loaders.put(name, (PluginClassLoader) plugin.getClassLoader());
            }

            try {
                String version = plugin.getDescription().getVersion();
                Spout.info("Enabling " + plugin.getName() + " v" + version + "...");
                plugin.enabled = true;
                plugin.onEnable();
                Spout.info(plugin.getName() + " v" + version + " enabled.");
            } catch (Throwable e) {
                engine.getLogger().log(Level.SEVERE, "An error occured when enabling '"
                        + plugin.getDescription().getFullName() + "': " + e.getMessage(), e);
            }

            engine.getEventManager().callEvent(new PluginEnableEvent(plugin));
        }
    }

    /**
     * Disables the plugin
     *
     * @param plugin to disable
     */
    public synchronized void disablePlugin(Plugin plugin) {
        if (plugin.isEnabled()) {

            String name = plugin.getDescription().getName();
            if (!loaders.containsKey(name)) {
                loaders.put(name, (PluginClassLoader) plugin.getClassLoader());
            }

            try {
                plugin.enabled = true;
                plugin.onDisable();
            } catch (Throwable t) {
                engine.getLogger().log(Level.SEVERE, "An error occurred when disabling plugin '"
                        + plugin.getDescription().getFullName() + "' : " + t.getMessage(), t);
            }

            engine.getEventManager().callEvent(new PluginDisableEvent(plugin));
        }
    }

    /**
     * Loads the file as a plugin
     *
     * @param file to load
     * @return instance of the plugin
     */
    public synchronized Plugin loadPlugin(File file)
            throws InvalidPluginException, UnknownDependencyException, InvalidDescriptionFileException {
        return loadPlugin(file, false);
    }

    /**
     * Loads the file as a plugin
     *
     * @param file to load
     * @param ignoreSoftDepends ignores soft dependencies when it attempts to load the plugin
     * @return instance of the plugin
     */
    public synchronized Plugin loadPlugin(File file, boolean ignoreSoftDepends)
            throws InvalidPluginException, UnknownDependencyException, InvalidDescriptionFileException {
        Plugin result = null;
        PluginDescriptionFile desc;
        PluginClassLoader loader;

        desc = getDescription(file);
        if (desc.isValidPlatform(engine.getPlatform())) {

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

            processDependencies(desc);

            if (!ignoreSoftDepends) {
                processSoftDependencies(desc);
            }

            try {
                loader = new PluginClassLoader(this, this.getClass().getClassLoader(), desc);
                loader.addURL(file.toURI().toURL());
                Class<?> main = Class.forName(desc.getMain(), true, loader);
                Class<? extends Plugin> plugin = main.asSubclass(Plugin.class);

                boolean locked = manager.lock(key);

                Constructor<? extends Plugin> constructor = plugin.getConstructor();

                result = constructor.newInstance();

                result.initialize(this, engine, desc, dataFolder, file, loader);

                for (String protocolString : desc.getProtocols()) {
                    Class<? extends Protocol> protocol = Class.forName(protocolString, true, loader)
                            .asSubclass(Protocol.class);
                    Constructor<? extends Protocol> pConstructor = protocol.getConstructor();
                    Protocol.registerProtocol(pConstructor.newInstance());
                }

                if (!locked) {
                    manager.unlock(key);
                }
            } catch (Exception e) {
                throw new InvalidPluginException(e);
            } catch (UnsupportedClassVersionError e) {
                String version = e.getMessage().replaceFirst("Unsupported major.minor version ", "").split(" ")[0];
                engine.getLogger().severe("Plugin " + desc.getName()
                        + " is built for a newer Java version than your current installation, and cannot be loaded!");
                engine.getLogger()
                        .severe("To run " + desc.getName() + ", you need Java version " + version + " or higher!");
                throw new InvalidPluginException(e);
            }

            loader.setPlugin(result);
            loaders.put(desc.getName(), loader);
        }

        return result;
    }

    /**
     * @param description Plugin description element
     */
    protected synchronized void processSoftDependencies(PluginDescriptionFile description)
            throws UnknownSoftDependencyException {
        List<String> softdepend = description.getSoftDepends();
        if (softdepend == null) {
            softdepend = new ArrayList<>();
        }

        for (String depend : softdepend) {
            if (!loaders.containsKey(depend)) {
                throw new UnknownSoftDependencyException(depend);
            }
        }
    }

    /**
     * @param desc Plugin description element
     */
    protected synchronized void processDependencies(PluginDescriptionFile desc) throws UnknownDependencyException {
        List<String> depends = desc.getDepends();
        if (depends == null) {
            depends = new ArrayList<>();
        }

        for (String depend : depends) {
            if (!loaders.containsKey(depend.toLowerCase())) {
                throw new UnknownDependencyException(depend);
            }
        }
    }

    /**
     * @param file Plugin file object
     * @return The current plugin's description element.
     */
    protected static synchronized PluginDescriptionFile getDescription(File file)
            throws InvalidPluginException, InvalidDescriptionFileException {
        if (!file.exists()) {
            throw new InvalidPluginException(file.getName() + " does not exist!");
        }

        PluginDescriptionFile description = null;
        JarFile jar = null;
        InputStream in = null;
        try {
            // Spout plugin properties file
            jar = new JarFile(file);
            JarEntry entry = jar.getJarEntry(YAML_SPOUT);

            // Fallback plugin properties file
            if (entry == null) {
                entry = jar.getJarEntry(YAML_OTHER);
            }

            if (entry == null) {
                throw new InvalidPluginException("Jar has no properties.yml or plugin.yml!");
            }

            in = jar.getInputStream(entry);
            description = new PluginDescriptionFile(in);
        } catch (IOException e) {
            throw new InvalidPluginException(e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    Spout.getLogger().log(Level.WARNING, "Problem closing input stream", e);
                }
            }
            if (jar != null) {
                try {
                    jar.close();
                } catch (IOException e) {
                    Spout.getLogger().log(Level.WARNING, "Problem closing jar input stream", e);
                }
            }
        }
        return description;
    }

    protected Class<?> getClassByName(final String name, final PluginClassLoader commonLoader) {
        Set<String> ignore = new HashSet<>();

        for (String dependency : commonLoader.getDepends()) {
            try {
                Class<?> clazz = loaders.get(dependency).findClass(name, false);
                if (clazz != null) {
                    return clazz;
                }
            } catch (ClassNotFoundException ignored) {
            }
            ignore.add(dependency.toLowerCase());
        }

        for (String softDependency : commonLoader.getSoftDepends()) {
            try {
                Class<?> clazz = loaders.get(softDependency).findClass(name, false);
                if (clazz != null) {
                    return clazz;
                }
            } catch (ClassNotFoundException ignored) {
            }
            ignore.add(softDependency.toLowerCase());
        }

        for (String current : loaders.keySet()) {
            if (ignore.contains(current)) {
                continue;
            }
            PluginClassLoader loader = loaders.get(current);
            if (loader == commonLoader) {
                continue;
            }
            try {
                Class<?> clazz = loader.findClass(name, false);
                if (clazz != null) {
                    return clazz;
                }
            } catch (ClassNotFoundException ignored) {
            }
        }
        return null;
    }
}