/
area/
classes/net/sourceforge/pain/logic/
classes/net/sourceforge/pain/logic/event/
classes/net/sourceforge/pain/logic/fn/util/
classes/net/sourceforge/pain/network/console/
classes/net/sourceforge/pain/plugin/
classes/net/sourceforge/pain/plugin/reset/
classes/net/sourceforge/pain/plugin/shutdown/
classes/net/sourceforge/pain/plugin/social/
classest/net/sourceforge/pain/db/data/
doc/
doc/paindb/resources/
src/net/sourceforge/pain/logic/
src/net/sourceforge/pain/logic/event/
src/net/sourceforge/pain/logic/fn/util/
src/net/sourceforge/pain/network/console/
src/net/sourceforge/pain/network/console/telnet/
src/net/sourceforge/pain/plugin/
src/net/sourceforge/pain/plugin/command/
src/net/sourceforge/pain/plugin/reset/
src/net/sourceforge/pain/plugin/shutdown/
src/net/sourceforge/pain/plugin/social/
src/net/sourceforge/pain/util/
tests/
tests/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.*;

public final class PluginManager {

	public final static String PLUGINS_HOME = "net.sourceforge.pain.plugin.";
	public final static int PLUGINS_HOME_LENGTH = PLUGINS_HOME.length();

	private Map loaders = new Hashtable();
	private Map loadersByDir = new Hashtable();

	protected String dirPath = null;
	protected int counter = 0;

	public PluginManager(String dirPath) {
		this.dirPath = dirPath;
	}

	public void init() throws Exception {
		String fileName = Core.getApplicationPath() + "/etc/plugin.cfg"; //plugins to load during startup
		BufferedReader reader = new BufferedReader(new FileReader(fileName));
		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("initializing plugin:" + className);
			loadPlugin(className);
		}
		Log.debug("Plugin Manager initialization COMPLETE");
	}

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

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

//	public synchronized Plugin getPluginWithLoad(String pluginName) throws Exception {
//		Plugin p = getPlugin(pluginName);
//		if (p == null) {
//			p = loadPlugin(pluginName);
//		}
//		return p;
//	}

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

	public synchronized Plugin loadPlugin(String pluginName) throws Exception {
		Log.debug("PlugM: loadPlugin:" + pluginName);
		PluginClassLoader loader = (PluginClassLoader) loaders.get(pluginName);
		if (loader == null) {
			boolean loaded = false;
			try {
				loader = new PluginClassLoader(this, pluginName);
				loaders.put(pluginName, loader);
				loadersByDir.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 = loaders.values().iterator(); it.hasNext();) {
						((PluginClassLoader) it.next()).getPlugin().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 askerPluginName, String className) throws ClassNotFoundException {
		final String pluginDir = className.substring(PLUGINS_HOME_LENGTH, className.indexOf('.', PLUGINS_HOME_LENGTH) + 1);
		PluginClassLoader cl = (PluginClassLoader) loadersByDir.get(pluginDir);
		if (cl == null) {
			throw new RuntimeException("Plugin is not loaded:" + pluginDir + "asker:" + askerPluginName);
		}
		final Class clazz = cl.loadClass(className);
		final Plugin p = cl.getPlugin();
		if (askerPluginName != null) {
			p.addChild(askerPluginName);
		}
		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);
		Core.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) loaders.remove(p.pluginName);
		loadersByDir.remove(l.pluginDir);
	}


}