/*
 * IDK Game Server by Steve Winfield
 * https://github.com/WinfieldSteve
 */
package org.stevewinfield.suja.idk.game.plugins;

import org.apache.log4j.Logger;
import org.stevewinfield.suja.idk.Bootloader;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
import java.io.FileReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class PluginManager {
    private static final Logger logger = Logger.getLogger(PluginManager.class);

    public GamePlugin getPlugin(final String name) {
        return this.plugins.containsKey(name) ? this.plugins.get(name) : null;
    }

    public PluginManager() {
    }

    public boolean addPlugin(final String filename, final Object content, final boolean showLog) {
        String extension = "";

        int i = filename.lastIndexOf('.');
        if (i > 0) {
            extension = filename.substring(i + 1);
        }
        String name = filename.substring(0, filename.length() - extension.length() - 1);
        if (extension.isEmpty()) {
            extension = "js";
            name = filename;
        }
        if (showLog) {
            logger.info("Loading plugin \"" + name + "\"");
        }

        final ScriptEngine engine = factory.getEngineByExtension(extension);
        if (engine == null) {
            logger.error("No script engine found for plugin " + name);
            return false;
        }
        try {
            if (engine.getFactory().getLanguageName().equals("ECMAScript")) {
                // needed to make the imports work
                if (engine.getFactory().getEngineName().equals("Oracle Nashorn")) {
                    engine.eval("load(\"nashorn:mozilla_compat.js\");");
                }
                engine.eval(
                        "importClass(org.stevewinfield.suja.idk.Bootloader);" +
                                "importPackage(org.stevewinfield.suja.idk.game.miscellaneous);" +
                                "importPackage(org.stevewinfield.suja.idk.game.players);" +
                                "importPackage(org.stevewinfield.suja.idk.game.rooms);" +
                                "importClass(org.stevewinfield.suja.idk.game.plugins.PluginManager);"
                );
            }
            GamePlugin plugin = new GamePlugin(name, engine);
            engine.put("IDK", plugin);
            engine.put("gamePlugin", plugin);
            engine.put("logger", Logger.getLogger(name));
            if (content instanceof FileReader) {
                engine.eval((FileReader) content);
            } else {
                engine.eval((String) content);
            }
            PluginInterfaces.ScriptPlugin scriptPlugin = ((Invocable) engine).getInterface(PluginInterfaces.ScriptPlugin.class);
            if (scriptPlugin == null) {
                logger.warn("No method initializePlugin() found for plugin " + name);
            } else {
                try {
                    scriptPlugin.initializePlugin();
                } catch (final Throwable t) {
                    logger.error("Plugin Initialization Error", t);
                }
            }
        } catch (final ScriptException e) {
            logger.error("ScriptException for plugin " + name, e);
        }
        if (plugins.containsKey(name) && showLog) {
            logger.warn("A plugin by the name " + name + " already exists. Replacing it by " + filename);
        }
        this.plugins.put(name, (GamePlugin) engine.get("IDK"));
        if (showLog) {
            logger.info("Loaded the plugin \"" + name + "\".");
        }
        return true;
    }

    public void loadLocalPlugins(final File pluginDir) {
        try {
            final File[] jars = pluginDir.listFiles();

            if (jars != null) {
                for (final File f : jars) {
                    final String name = f.getName();
                    final FileReader reader = new FileReader(f);
                    this.addPlugin(name, reader, true);
                    reader.close();
                }
            }
        } catch (final Exception e) {
            logger.error("Loading plugins failed.", e);
        }

        logger.info(plugins.size() + " plugin(s) local loaded from /" + pluginDir.getPath());
    }

    public void load(final File pluginDir) {
        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(Bootloader.getCustomClassLoader());

            this.plugins = new ConcurrentHashMap<>();
            this.factory = new ScriptEngineManager();
            this.loadLocalPlugins(pluginDir);

            logger.info(plugins.size() + " plugin(s) local loaded from /" + pluginDir.getPath());
        } finally {
            Thread.currentThread().setContextClassLoader(oldLoader);
        }
    }

    public static void log(final String txt) {
        logger.info("PLUGIN: " + txt);
    }

    private static URL[] buildClassPath(String... directories) {
        try {
            final List<URL> classPath = new ArrayList<>();
            for (String directory : directories) {
                File directoryFile = new File(directory);
                if (!directoryFile.exists()) {
                    directoryFile.mkdirs();
                }
                if (!directoryFile.isDirectory()) {
                    continue;
                }
                for (File pathname : directoryFile.listFiles()) {
                    if (pathname.isFile() && pathname.toString().toLowerCase().endsWith(".jar")) {
                        URL url = pathname.toURI().toURL();
                        classPath.add(url);
                    }
                }
            }
            return classPath.toArray(new URL[classPath.size()]);
        } catch (MalformedURLException e) {
            throw new IllegalStateException(e);
        }
    }

    private ConcurrentHashMap<String, GamePlugin> plugins;
    private ScriptEngineManager factory;
}

@@ -457,40 +457,8 @@
 RL;%0A
-import java.net.URLClassLoader;%0A
 impo
@@ -3855,37 +3855,210 @@
 nDir) %7B%0A        
-try %7B
+ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();%0A        try %7B%0A            Thread.currentThread().setContextClassLoader(Bootloader.getCustomClassLoader());
 %0A            fin
@@ -4511,32 +4511,121 @@
 %22, e);%0A        %7D
+ finally %7B%0A            Thread.currentThread().setContextClassLoader(oldLoader);%0A        %7D
 %0A%0A        logger
@@ -4760,200 +4760,8 @@
 ) %7B%0A
-        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();%0A        try %7B%0A            Thread.currentThread().setContextClassLoader(Bootloader.getCustomClassLoader());%0A%0A    
     
@@ -4802,28 +4802,24 @@
 ashMap%3C%3E();%0A
-    
         this
@@ -4852,28 +4852,24 @@
 eManager();%0A
-    
         this
@@ -4899,36 +4899,32 @@
 nDir);%0A%0A        
-    
 logger.info(plug
@@ -4997,107 +4997,8 @@
 ));%0A
-        %7D finally %7B%0A            Thread.currentThread().setContextClassLoader(oldLoader);%0A        %7D%0A
     

Set class loader when refreshing plugins