/
codebase/src/net/sourceforge/pain/admin/console/command/
codebase/src/net/sourceforge/pain/data/role/
codebase/src/net/sourceforge/pain/network/console/telnet/
codebase/src/net/sourceforge/pain/network/guitool/
codebase/src/net/sourceforge/pain/plugin/
codebase/src/net/sourceforge/pain/util/
db/src/net/sourceforge/pain/util/
gui/
gui/lib/
gui/src/net/sourceforge/pain/tools/guitool/dbbrowse/
gui/src/net/sourceforge/pain/tools/guitool/dialog/
gui/src/net/sourceforge/pain/tools/guitool/menu/
gui/src/net/sourceforge/pain/tools/guitool/resources/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/explorer/
mudlibs/tinylib/
mudlibs/tinylib/area/
mudlibs/tinylib/etc/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/affect/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/prototype/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/trigger/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/affect/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/deploy/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/guitool/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/guitool/event/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/fn/util/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/trigger/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/trigger/impl/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/command/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/reset/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/shutdown/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/social/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/util/
tests/
tests/src/
tests/src/net/sourceforge/pain/db/data/
package net.sourceforge.pain.plugin;


import net.sourceforge.pain.*;
import net.sourceforge.pain.util.*;

import java.io.*;
import java.util.*;

/**
 * Manager for plugin loading/reloading/unloading...
 */
public final class PluginManager {

    String PLUGINS_HOME_PREFIX;
    int PLUGINS_HOME_PREFIX_LENGTH;

    private Map loadersByPlugName = new Hashtable();
    private Map loadersByPlugDir = new Hashtable();
    private LinkedList pluginsList = new LinkedList();

    int counter = 0;
    Object classesDir;

    public PluginManager() {
    }

    /**
     * All classes under pluginsHomePackagePrefix assumed as
     *
     * @param pluginsHomePackagePrefix - package name ending with '.' (example: net.sourceforge.pain.tinylib.plugin)
     */
    public void init(String pluginsHomePackagePrefix, String pluginClassesDir) {
        assert PLUGINS_HOME_PREFIX == null;
        Log.debug("PlugM: init started");

        if (!pluginsHomePackagePrefix.endsWith(".")) {
            throw new IllegalArgumentException("plugins home package prefix should ends with '.' ");
        }
        PLUGINS_HOME_PREFIX = pluginsHomePackagePrefix;
        PLUGINS_HOME_PREFIX_LENGTH = PLUGINS_HOME_PREFIX.length();
        classesDir = pluginClassesDir;
    }

    public void loadPluginsFromFile(File configFile) throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader(configFile));
        List plugNames = new ArrayList();
        try {
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                if (line.length() == 0 || line.charAt(0) == '#' || line.charAt(0) == '!') {
                    continue;
                }
                plugNames.add(line.trim());
            }
        } finally {
            reader.close();
        }
        for (Iterator it = plugNames.iterator(); it.hasNext();) {
            String className = (String) it.next();
            Log.debug("PlugM:initializing plugin:" + className);
            loadPlugin(className);
        }
        Log.debug("PlugM: initialization COMPLETE");
    }

    /**
     * @param pluginName - is className without PLUGINS_HOME_PREFIX
     */
    public synchronized Plugin getPlugin(String pluginName) {
        Log.debug("PlugM: getPlugin:" + pluginName);
        PluginClassLoader loader = (PluginClassLoader) loadersByPlugName.get(pluginName);

        if (loader != null) {
            return loader.getPlugin();
        }
        return null;
    }


    /**
     * @param pluginName is className without PLUGINS_HOME prefix
     * @throws Exception
     */

    public synchronized Plugin loadPlugin(String pluginName) throws Exception {
        Log.debug("PlugM: loadPlugin:" + pluginName);
        PluginClassLoader loader = (PluginClassLoader) loadersByPlugName.get(pluginName);
        if (loader == null) {
            boolean loaded = false;
            try {
                loader = new PluginClassLoader(this, pluginName);
                pluginsList.add(loader.getPlugin());
                loadersByPlugName.put(pluginName, loader);
                loadersByPlugDir.put(loader.pluginDir, loader);
                loaded = true;
            } finally {
                if (!loaded) { //during plugin loading pluginCL
                    //can ask some classes from other plugins
                    //and loading plugin becomes they child (it's name cached),
                    // we should remove this data if plugin was not loaded!
                    for (Iterator it = pluginsList.iterator(); it.hasNext();) {
                        ((Plugin) it.next()).removeChild(pluginName);
                    }
                }
            }
        }
        return loader.getPlugin();
    }

    /**
     * if plugin A uses classes from plugin B this dialog will be called during plugin A class instantiation
     * this method is also used in LogicLoader to provide plugin classes to logic ones
     * todo: logic classes should be reloaded only if they use this plugin
     */
    public Class loadClassByPluginClassloader(String requesterPluginName, String requestedClassName) throws ClassNotFoundException {
        final String pluginDir = requestedClassName.substring(PLUGINS_HOME_PREFIX_LENGTH, requestedClassName.indexOf('.', PLUGINS_HOME_PREFIX_LENGTH) + 1);
        PluginClassLoader cl = (PluginClassLoader) loadersByPlugDir.get(pluginDir);
        if (cl == null) {
            throw new RuntimeException("Plugin is not loaded:" + pluginDir + "asker:" + requesterPluginName);
        }
        final Class clazz = cl.loadClass(requestedClassName);
        final Plugin p = cl.getPlugin();
        if (requesterPluginName != null) {
            p.addChild(requesterPluginName);
        }
        return clazz;
    }

    /**
     * WARN: all net.sourceforge.pain.logic.* classes have direct access to plugins
     * we will unload all this code after plugin is unloaded in this method
     * WARN: all child plugins will be unloaded!
     */
    public void unloadPlugin(Plugin p) {
        if (p == null) {
            return;
        }
        Log.debug("PlugM: unloadPlugin:" + p.pluginName);
        _unloadPlugin(p);
        Codebase.getLogicLoader().reload(); //todo: do it only if logic had direct access to this plug (trace it with classloader)
    }

    private void _unloadPlugin(Plugin p) {
        final Set childs = p.childs;
        Log.debug("PlugM: has childs:" + !childs.isEmpty());
        if (!childs.isEmpty()) {
            for (Iterator it = childs.iterator(); it.hasNext();) {
                Plugin child = getPlugin((String) it.next());
                if (child == null) {
                    return;
                }
                Log.debug("PlugM: unloading child plugin:" + child.pluginName);
                unloadPlugin(child);
            }
        }
        try {
            p.deinit();
        } catch (Exception e) {
            Log.error(e.getMessage(), e);
        }
        //++ remove mapping
        PluginClassLoader l = (PluginClassLoader) loadersByPlugName.remove(p.pluginName);
        loadersByPlugDir.remove(l.pluginDir);
        pluginsList.remove(p);
    }

    public List getActivePluginsList() {
        return Collections.unmodifiableList(pluginsList);
    }

    public boolean isInPluginPackages(String className) {
        assert PLUGINS_HOME_PREFIX != null;
        return className.startsWith(PLUGINS_HOME_PREFIX);
    }
}