Ticket #6396: reload-plugins-0.1.patch

File reload-plugins-0.1.patch, 15.5 KB (added by olejorgenb, 14 years ago)
  • new file src/org/openstreetmap/josm/actions/ReloadPluginsAction.java

    diff --git a/src/org/openstreetmap/josm/actions/ReloadPluginsAction.java b/src/org/openstreetmap/josm/actions/ReloadPluginsAction.java
    new file mode 100644
    index 0000000..b5cb91b
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.actions;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.event.ActionEvent;
     7import java.awt.event.KeyEvent;
     8
     9import org.openstreetmap.josm.plugins.PluginHandler;
     10import org.openstreetmap.josm.tools.Shortcut;
     11
     12public class ReloadPluginsAction extends JosmAction {
     13
     14    public ReloadPluginsAction() {
     15        super(tr("Reload plugins"), null, tr("Reload plugins"), Shortcut.registerShortcut("system:reload-plugins",
     16                tr("Reload plugins"), KeyEvent.VK_F5, Shortcut.GROUP_DIRECT, Shortcut.SHIFT_DEFAULT), true);
     17    }
     18
     19    @Override
     20    public void actionPerformed(ActionEvent e) {
     21        PluginHandler.reloadPlugins();
     22    }
     23}
  • src/org/openstreetmap/josm/gui/MainMenu.java

    diff --git a/src/org/openstreetmap/josm/gui/MainMenu.java b/src/org/openstreetmap/josm/gui/MainMenu.java
    index b17cb98..a86c584 100644
    a b import org.openstreetmap.josm.actions.NewAction;  
    5252import org.openstreetmap.josm.actions.OpenFileAction;
    5353import org.openstreetmap.josm.actions.OpenLocationAction;
    5454import org.openstreetmap.josm.actions.OrthogonalizeAction;
     55import org.openstreetmap.josm.actions.ReloadPluginsAction;
    5556import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo;
    5657import org.openstreetmap.josm.actions.PasteAction;
    5758import org.openstreetmap.josm.actions.PasteTagsAction;
    public class MainMenu extends JMenuBar {  
    359360        current.setAccelerator(Shortcut.registerShortcut("system:help", tr("Help"), KeyEvent.VK_F1,
    360361                Shortcut.GROUP_DIRECT).getKeyStroke());
    361362        add(helpMenu, about);
     363        add(helpMenu, new ReloadPluginsAction());
    362364
    363365        new PresetsMenuEnabler(presetsMenu).refreshEnabled();
    364366    }
  • src/org/openstreetmap/josm/gui/MapFrame.java

    diff --git a/src/org/openstreetmap/josm/gui/MapFrame.java b/src/org/openstreetmap/josm/gui/MapFrame.java
    index 96bddae..2969794 100644
    a b public class MapFrame extends JPanel implements Destroyable, LayerChangeListener  
    290290            throw new IllegalArgumentException("MapMode action must be subclass of MapMode");
    291291    }
    292292
     293    public void removeMapMode(IconToggleButton mode) {
     294        toolBarActions.remove(mode);
     295        toolGroup.remove(mode);
     296        mapModes.remove(mode.getAction());
     297    }
     298
    293299    /**
    294300     * Fires an property changed event "visible".
    295301     */
  • src/org/openstreetmap/josm/plugins/Plugin.java

    diff --git a/src/org/openstreetmap/josm/plugins/Plugin.java b/src/org/openstreetmap/josm/plugins/Plugin.java
    index 0ef60ac..058da9c 100644
    a b public abstract class Plugin {  
    7272        this.info = info;
    7373    }
    7474
     75    public void preReloadCleanup() {
     76        System.out.println("no cleanup for this class");
     77    }
     78
    7579    /**
    7680     * @return The directory for the plugin to store all kind of stuff.
    7781     */
  • src/org/openstreetmap/josm/plugins/PluginHandler.java

    diff --git a/src/org/openstreetmap/josm/plugins/PluginHandler.java b/src/org/openstreetmap/josm/plugins/PluginHandler.java
    index db94edf..a97f4d4 100644
    a b import java.awt.Window;  
    1313import java.awt.event.ActionEvent;
    1414import java.io.File;
    1515import java.io.FilenameFilter;
     16import java.lang.reflect.Method;
    1617import java.net.URL;
    1718import java.net.URLClassLoader;
    1819import java.util.ArrayList;
    import org.openstreetmap.josm.gui.help.HelpUtil;  
    5758import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
    5859import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    5960import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    60 import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
    6161import org.openstreetmap.josm.tools.CheckParameterUtil;
    6262import org.openstreetmap.josm.tools.GBC;
    6363import org.openstreetmap.josm.tools.ImageProvider;
    public class PluginHandler {  
    429429    }
    430430
    431431    /**
    432      * Creates a class loader for loading plugin code.
    433      *
    434      * @param plugins the collection of plugins which are going to be loaded with this
    435      * class loader
    436      * @return the class loader
     432     * Helper to extract the jar paths of the plugins
    437433     */
    438     public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
     434    private static URL[] createJarURLs(Collection<PluginInformation> plugins) {
    439435        // iterate all plugins and collect all libraries of all plugins:
    440436        List<URL> allPluginLibraries = new LinkedList<URL>();
    441437        File pluginDir = Main.pref.getPluginsDirectory();
    public class PluginHandler {  
    451447
    452448        // create a classloader for all plugins:
    453449        URL[] jarUrls = new URL[allPluginLibraries.size()];
    454         jarUrls = allPluginLibraries.toArray(jarUrls);
     450        return allPluginLibraries.toArray(jarUrls);
     451    }
     452    /**
     453     * Creates a class loader for loading plugin code.
     454     *
     455     * @param plugins the collection of plugins which are going to be loaded with this
     456     * class loader
     457     * @return the class loader
     458     */
     459    public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
     460        URL[] jarUrls = createJarURLs(plugins);
    455461        URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader());
    456462        return pluginClassLoader;
    457463    }
    458464
     465    private static String getPackagePartOfClassName(String name) {
     466        for (int i = name.length() - 1; i >= 0; i--) {
     467            if (name.charAt(i) == '.')
     468                return name.substring(0, i);
     469        }
     470        return "";
     471    }
     472
     473    private static boolean isSubPackageOf(String sub, String parent) {
     474        return sub.startsWith(parent);
     475    }
     476
     477    /**
     478     * Creates a class loader that will reload the supplied plugins
     479     *
     480     * The plugins should not define classes in packages above the package of the 'Plugin-Class' (defined in MANIFEST)
     481     * ie. all classes must belong to a package matching: <plugin-class-package>.*
     482     */
     483    public static ClassLoader createClassReloader(Collection<PluginInformation> plugins) {
     484        final Set<String> packageList = new HashSet<String>();
     485        for (PluginInformation pi : plugins) {
     486            packageList.add(getPackagePartOfClassName(pi.className));
     487        }
     488
     489        ClassLoader deceiving = new ClassLoader(Main.class.getClassLoader()) {
     490            // Overriding loadClass/1 does not seem to work ...
     491            // Are the 'fullClassName' guaranteed to be full?
     492            @Override
     493            protected Class<?> loadClass(String fullClassName, boolean resolve) throws ClassNotFoundException {
     494                String packageOfClass = getPackagePartOfClassName(fullClassName);
     495                boolean hide = false;
     496                for(String pkg : packageList) {
     497                    if(isSubPackageOf(packageOfClass, pkg)) {
     498                        hide = true;
     499                        break;
     500                    }
     501                }
     502                if(hide) {
     503                    System.out.println(fullClassName);
     504                    return null;
     505                } else {
     506                    // is it possible that super.loadClass/2 calls this.loadClass/2 again, causing infinite recursion?
     507                    return super.loadClass(fullClassName, resolve);
     508                }
     509            }
     510        };
     511        URL[] jarUrls = createJarURLs(plugins);
     512        return new URLClassLoader(jarUrls, deceiving);
     513    }
     514
     515    // TODO: MANIFEST property instead? This is simpler for everyone though?
     516    private static boolean supportReload(PluginProxy pluginProxy) {
     517        try {
     518            Method m = pluginProxy.plugin.getClass().getMethod("preReloadCleanup");
     519            return !m.getDeclaringClass().equals(Plugin.class);
     520        } catch (NoSuchMethodException e) {
     521            return false;
     522        }
     523    }
     524
     525    // FIXME: fucked if p1 depends on p2 and only p2 is reloadable
     526    // Should be OK to let 'sources' keep the old class loader since the new class loader is prepended?
     527    //   Somewhat complicated if we should filter out the reloaded jars from the old loader...
     528    // TODO: This does not check preconditions for the plugins, assuming they are fulfilled as the plugins are already loaded. This is of course not 100% correct.
     529    // TODO: add progressmonitor support
     530    // TODO: do we need to respect the loadearly/late flags?
     531    // TODO: seems to be quick enough, but is it problematic to to in a worker thread?
     532    /**
     533     * Reloads all loaded plugins implementing 'preReloadCleanup'
     534     */
     535    public static void reloadPlugins() {
     536        //        ProgressMonitor monitor = null;
     537        //        if (monitor == null) {
     538        //            monitor = NullProgressMonitor.INSTANCE;
     539        //        }
     540        List<PluginInformation> plugins = new ArrayList<PluginInformation>();
     541        for (Iterator<PluginProxy> iter = pluginList.iterator(); iter.hasNext();) {
     542            PluginProxy pp = iter.next();
     543            if (supportReload(pp)) {
     544                pp.preReloadCleanup();
     545                plugins.add(pp.getPluginInformation());
     546                iter.remove();
     547            }
     548        }
     549        if (plugins.isEmpty())
     550            return;
     551        // safest to not have a to-be-reloaded map mode active
     552        Main.map.selectSelectTool(false);
     553        //        try {
     554
     555        //            List<PluginInformation> plugins = PluginHandler.buildListOfPluginsToLoad(null,monitor.createSubTaskMonitor(1, false));
     556
     557        //            monitor.beginTask(tr("Loading plugins ..."));
     558        //            monitor.subTask(tr("Checking plugin preconditions..."));
     559        //            Collection<PluginInformation> toLoad = preproccessPluginList(null, plugins);
     560
     561        ClassLoader cl = createClassReloader(plugins);
     562
     563        loadPluginsNoCheck(null, plugins, null, cl);
     564        for (PluginProxy plugin : pluginList) {
     565            plugin.mapFrameInitialized(Main.map, Main.map);
     566        }
     567        //        PluginHandler.notifyMapFrameChanged(Main.map, Main.map);
     568        //        } finally {
     569        //            monitor.finishTask();
     570        //        }
     571    }
     572
    459573    /**
    460574     * Loads and instantiates the plugin described by <code>plugin</code> using
    461575     * the class loader <code>pluginClassLoader</code>.
    public class PluginHandler {  
    493607     * @param plugins the list of plugins
    494608     * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
    495609     */
    496     public static void loadPlugins(Window parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
     610    public static void loadPlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
    497611        if (monitor == null) {
    498612            monitor = NullProgressMonitor.INSTANCE;
    499613        }
    500614        try {
    501615            monitor.beginTask(tr("Loading plugins ..."));
    502616            monitor.subTask(tr("Checking plugin preconditions..."));
    503             List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
    504             for (PluginInformation pi: plugins) {
    505                 if (checkLoadPreconditions(parent, plugins, pi)) {
    506                     toLoad.add(pi);
    507                 }
    508             }
    509             // sort the plugins according to their "staging" equivalence class. The
    510             // lower the value of "stage" the earlier the plugin should be loaded.
    511             //
    512             Collections.sort(
    513                     toLoad,
    514                     new Comparator<PluginInformation>() {
    515                         public int compare(PluginInformation o1, PluginInformation o2) {
    516                             if (o1.stage < o2.stage) return -1;
    517                             if (o1.stage == o2.stage) return 0;
    518                             return 1;
    519                         }
    520                     }
    521             );
     617            Collection<PluginInformation> toLoad = preproccessPluginList(parent, plugins);
    522618            if (toLoad.isEmpty())
    523619                return;
     620            loadPluginsNoCheck(parent, toLoad, monitor, PluginHandler.createClassLoader(toLoad));
     621        } finally {
     622            monitor.finishTask();
     623        }
     624    }
    524625
    525             ClassLoader pluginClassLoader = createClassLoader(toLoad);
    526             sources.add(0, pluginClassLoader);
    527             monitor.setTicksCount(toLoad.size());
    528             for (PluginInformation info : toLoad) {
     626    public static void loadPluginsNoCheck(Window parent, Collection<PluginInformation> plugins,
     627            ProgressMonitor monitor, ClassLoader classLoader) {
     628        sources.add(0, classLoader);
     629        if (monitor != null)
     630            monitor.setTicksCount(plugins.size());
     631        for (PluginInformation info : plugins) {
     632            if (monitor != null)
    529633                monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
    530                 loadPlugin(parent, info, pluginClassLoader);
     634            else
     635                System.out.println(tr("Loading plugin ''{0}''...", info.name));
     636            loadPlugin(parent, info, classLoader);
     637            if (monitor != null)
    531638                monitor.worked(1);
     639        }
     640    }
     641
     642    private static Collection<PluginInformation> preproccessPluginList(Window parent,
     643            Collection<PluginInformation> plugins) {
     644        List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
     645        for (PluginInformation pi : plugins) {
     646            if (checkLoadPreconditions(parent, plugins, pi)) {
     647                toLoad.add(pi);
    532648            }
    533         } finally {
    534             monitor.finishTask();
    535649        }
     650        // sort the plugins according to their "staging" equivalence class. The
     651        // lower the value of "stage" the earlier the plugin should be loaded.
     652        //
     653        Collections.sort(toLoad, new Comparator<PluginInformation>() {
     654            public int compare(PluginInformation o1, PluginInformation o2) {
     655                if (o1.stage < o2.stage)
     656                    return -1;
     657                if (o1.stage == o2.stage)
     658                    return 0;
     659                return 1;
     660            }
     661        });
     662        return toLoad;
    536663    }
    537664
    538665    /**
  • src/org/openstreetmap/josm/plugins/PluginProxy.java

    diff --git a/src/org/openstreetmap/josm/plugins/PluginProxy.java b/src/org/openstreetmap/josm/plugins/PluginProxy.java
    index 9d2e094..8388c79 100644
    a b  
    11// License: GPL. Copyright 2007 by Immanuel Scholz and others
    22package org.openstreetmap.josm.plugins;
    33
     4import java.lang.reflect.Method;
    45import java.util.List;
    56
    67import org.openstreetmap.josm.gui.MapFrame;
    public class PluginProxy extends Plugin {  
    3334        }
    3435    }
    3536
     37    @Override public void preReloadCleanup() {
     38        try {
     39            Method m = plugin.getClass().getMethod("preReloadCleanup");
     40            m.invoke(plugin);
     41        } catch (NoSuchMethodException e) {
     42        } catch (Exception e) {
     43            BugReportExceptionHandler.handleException(new PluginException(this, getPluginInformation().name, e));
     44        }
     45    }
     46
    3647    @Override public PreferenceSetting getPreferenceSetting() {
    3748        try {
    3849            return (PreferenceSetting)plugin.getClass().getMethod("getPreferenceSetting").invoke(plugin);