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); } }