Ticket #6396: reload-plugins-0.2.patch

File reload-plugins-0.2.patch, 21.5 KB (added by olejorgenb, 13 years ago)
  • src/org/openstreetmap/josm/actions/JosmAction.java

    diff --git a/src/org/openstreetmap/josm/actions/JosmAction.java b/src/org/openstreetmap/josm/actions/JosmAction.java
    index e111b87..d227894 100644
    a b abstract public class JosmAction extends AbstractAction implements Destroyable {  
    148148        DataSet.addSelectionListener(selectionChangeAdapter);
    149149        initEnabledState();
    150150    }
     151    protected void uninstallAdapters() {
     152        if(layerChangeAdapter != null)
     153            MapView.removeLayerChangeListener(layerChangeAdapter);
     154        if(selectionChangeAdapter != null) {
     155            DataSet.removeSelectionListener(selectionChangeAdapter);
     156        }
     157    }
    151158
    152159    /**
    153160     * Override in subclasses to init the enabled state of an action when it is
  • 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..189e9b9 100644
    a b public class MapFrame extends JPanel implements Destroyable, LayerChangeListener  
    281281        return button;
    282282    }
    283283
     284    // Hopefully this is enough. TODO: Should we call .validate()?
     285    public void removeToggleDialog(final ToggleDialog dlg) {
     286        if (dialogsPanel.initialized) {
     287            dialogsPanel.remove(dlg);
     288        }
     289        allDialogs.remove(dlg);
     290        toolBarToggle.remove(dlg);
     291    }
     292
    284293    public void addMapMode(IconToggleButton b) {
    285294        toolBarActions.add(b);
    286295        toolGroup.add(b);
    public class MapFrame extends JPanel implements Destroyable, LayerChangeListener  
    290299            throw new IllegalArgumentException("MapMode action must be subclass of MapMode");
    291300    }
    292301
     302    public void removeMapMode(IconToggleButton mode) {
     303        toolBarActions.remove(mode);
     304        toolGroup.remove(mode);
     305        mapModes.remove(mode.getAction());
     306    }
     307
    293308    /**
    294309     * Fires an property changed event "visible".
    295310     */
  • src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java

    diff --git a/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java b/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java
    index 7c576fc..66f52df 100644
    a b public class PreferenceTabbedPane extends JTabbedPane implements MouseWheelListe  
    279279        settingsFactory.add(new RemoteControlPreference.Factory());
    280280        settingsFactory.add(new ImageryPreference.Factory());
    281281
     282        // FIXME: Can we really be sure that all plugins is loaded? This does only work because java are lazy with class loading and this class is referenced late...
     283        // Possible to make PluginHandler create a wrapper factory
    282284        PluginHandler.getPreferenceSetting(settingsFactory);
    283285
    284286        // always the last: advanced tab
  • 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..c45708f 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.JMultilineLabel;  
    5455import org.openstreetmap.josm.gui.MapFrame;
    5556import org.openstreetmap.josm.gui.download.DownloadSelection;
    5657import org.openstreetmap.josm.gui.help.HelpUtil;
     58import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    5759import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
     60import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
    5861import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    5962import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    60 import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
    6163import org.openstreetmap.josm.tools.CheckParameterUtil;
    6264import org.openstreetmap.josm.tools.GBC;
    6365import org.openstreetmap.josm.tools.ImageProvider;
    public class PluginHandler {  
    429431    }
    430432
    431433    /**
    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
     434     * Helper to extract the jar paths of the plugins
    437435     */
    438     public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
     436    private static URL[] createJarURLs(Collection<PluginInformation> plugins) {
    439437        // iterate all plugins and collect all libraries of all plugins:
    440438        List<URL> allPluginLibraries = new LinkedList<URL>();
    441439        File pluginDir = Main.pref.getPluginsDirectory();
    public class PluginHandler {  
    451449
    452450        // create a classloader for all plugins:
    453451        URL[] jarUrls = new URL[allPluginLibraries.size()];
    454         jarUrls = allPluginLibraries.toArray(jarUrls);
     452        return allPluginLibraries.toArray(jarUrls);
     453    }
     454    /**
     455     * Creates a class loader for loading plugin code.
     456     *
     457     * @param plugins the collection of plugins which are going to be loaded with this
     458     * class loader
     459     * @return the class loader
     460     */
     461    public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
     462        URL[] jarUrls = createJarURLs(plugins);
    455463        URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader());
    456464        return pluginClassLoader;
    457465    }
    458466
     467    private static String getPackagePartOfClassName(String name) {
     468        for (int i = name.length() - 1; i >= 0; i--) {
     469            if (name.charAt(i) == '.')
     470                return name.substring(0, i);
     471        }
     472        return "";
     473    }
     474
     475    private static boolean isSubPackageOf(String sub, String parent) {
     476        return sub.startsWith(parent);
     477    }
     478
     479    /**
     480     * Creates a class loader that will reload the supplied plugins
     481     *
     482     * The plugins should not define classes in packages above the package of the 'Plugin-Class' (defined in MANIFEST)
     483     * ie. all classes must belong to a package matching: <plugin-class-package>.*
     484     */
     485    public static ClassLoader createClassReloader(Collection<PluginInformation> plugins) {
     486        final Set<String> packageList = new HashSet<String>();
     487        for (PluginInformation pi : plugins) {
     488            packageList.add(getPackagePartOfClassName(pi.className));
     489        }
     490
     491        ClassLoader deceiving = new ClassLoader(Main.class.getClassLoader()) {
     492            // Overriding loadClass/1 does not seem to work ...
     493            // Are the 'fullClassName' guaranteed to be full?
     494            @Override
     495            protected Class<?> loadClass(String fullClassName, boolean resolve) throws ClassNotFoundException {
     496                String packageOfClass = getPackagePartOfClassName(fullClassName);
     497                boolean hide = false;
     498                for(String pkg : packageList) {
     499                    if(isSubPackageOf(packageOfClass, pkg)) {
     500                        hide = true;
     501                        break;
     502                    }
     503                }
     504                if(hide) {
     505                    System.out.println(fullClassName);
     506                    return null;
     507                } else {
     508                    // is it possible that super.loadClass/2 calls this.loadClass/2 again, causing infinite recursion?
     509                    return super.loadClass(fullClassName, resolve);
     510                }
     511            }
     512        };
     513        URL[] jarUrls = createJarURLs(plugins);
     514        return new URLClassLoader(jarUrls, deceiving) {
     515            // make it easier to identify the current class loader in heapdumps
     516            public long time = System.currentTimeMillis();
     517            @Override public String toString() {
     518                return "Reloading CL";
     519            }
     520        };
     521    }
     522
     523    // TODO: MANIFEST property instead? This is simpler for everyone though?
     524    private static boolean supportReload(PluginProxy pluginProxy) {
     525        try {
     526            Method m = pluginProxy.plugin.getClass().getMethod("preReloadCleanup");
     527            return !m.getDeclaringClass().equals(Plugin.class);
     528        } catch (NoSuchMethodException e) {
     529            return false;
     530        }
     531    }
     532
     533    // FIXME: fucked if p1 depends on p2 and only p2 is reloadable
     534    // Should be OK to let 'sources' keep the old class loader since the new class loader is prepended?
     535    //   Somewhat complicated if we should filter out the reloaded jars from the old loader...
     536    // 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.
     537    // TODO: add progressmonitor support
     538    // TODO: do we need to respect the loadearly/late flags?
     539    // TODO: seems to be quick enough, but is it problematic to put in a worker thread?
     540    // TODO: use 'Plugin-Date' manifest value if defined to determine if plugin need reload
     541    /**
     542     * Reloads all loaded plugins implementing 'preReloadCleanup'
     543     */
     544    public static void reloadPlugins() {
     545        //        ProgressMonitor monitor = null;
     546        //        if (monitor == null) {
     547        //            monitor = NullProgressMonitor.INSTANCE;
     548        //        }
     549        //        loadLocallyAvailablePluginInformation(null); // can't be run from the gui thread
     550        List<PluginInformation> reloadablePlugins = new ArrayList<PluginInformation>();
     551        for (Iterator<PluginProxy> iter = pluginList.iterator(); iter.hasNext();) {
     552            PluginProxy pp = iter.next();
     553            if (supportReload(pp)) {
     554                pp.preReloadCleanup();
     555                reloadablePlugins.add(pp.getPluginInformation());
     556                iter.remove();
     557            }
     558        }
     559        if (reloadablePlugins.isEmpty())
     560            return;
     561        // safest not to have a to-be-reloaded map mode active
     562        if (Main.map != null) {
     563            Main.map.selectSelectTool(false);
     564        }
     565        //        try {
     566        //            List<PluginInformation> plugins = PluginHandler.buildListOfPluginsToLoad(null,monitor.createSubTaskMonitor(1, false));
     567        //            monitor.beginTask(tr("Loading plugins ..."));
     568        //            monitor.subTask(tr("Checking plugin preconditions..."));
     569        //            Collection<PluginInformation> toLoad = preproccessPluginList(null, plugins);
     570
     571        ClassLoader cl = createClassReloader(reloadablePlugins);
     572
     573        List<PluginProxy> nonReloadablePlugins = new ArrayList<PluginProxy>(pluginList);
     574        loadPluginsNoCheck(null, reloadablePlugins, null, cl); // adds the plugins to pluginList
     575        for (PluginProxy plugin : pluginList) {
     576            if (!nonReloadablePlugins.contains(plugin)) { // only notify reloaded plugins
     577                plugin.mapFrameInitialized(Main.map, Main.map);
     578            }
     579        }
     580        //        PluginHandler.notifyMapFrameChanged(Main.map, Main.map);
     581        //        } finally {
     582        //            monitor.finishTask();
     583        //        }
     584    }
     585
    459586    /**
    460587     * Loads and instantiates the plugin described by <code>plugin</code> using
    461588     * the class loader <code>pluginClassLoader</code>.
    public class PluginHandler {  
    493620     * @param plugins the list of plugins
    494621     * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
    495622     */
    496     public static void loadPlugins(Window parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
     623    public static void loadPlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
    497624        if (monitor == null) {
    498625            monitor = NullProgressMonitor.INSTANCE;
    499626        }
    500627        try {
    501628            monitor.beginTask(tr("Loading plugins ..."));
    502629            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             );
     630            Collection<PluginInformation> toLoad = preproccessPluginList(parent, plugins);
    522631            if (toLoad.isEmpty())
    523632                return;
     633            loadPluginsNoCheck(parent, toLoad, monitor, PluginHandler.createClassLoader(toLoad));
     634        } finally {
     635            monitor.finishTask();
     636        }
     637    }
    524638
    525             ClassLoader pluginClassLoader = createClassLoader(toLoad);
    526             sources.add(0, pluginClassLoader);
    527             monitor.setTicksCount(toLoad.size());
    528             for (PluginInformation info : toLoad) {
     639    public static void loadPluginsNoCheck(Window parent, Collection<PluginInformation> plugins,
     640            ProgressMonitor monitor, ClassLoader classLoader) {
     641        sources.add(0, classLoader);
     642        if (monitor != null)
     643            monitor.setTicksCount(plugins.size());
     644        for (PluginInformation info : plugins) {
     645            if (monitor != null)
    529646                monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
    530                 loadPlugin(parent, info, pluginClassLoader);
     647            else
     648                System.out.println(tr("Loading plugin ''{0}''...", info.name));
     649            loadPlugin(parent, info, classLoader);
     650            if (monitor != null)
    531651                monitor.worked(1);
     652        }
     653    }
     654
     655    private static Collection<PluginInformation> preproccessPluginList(Window parent,
     656            Collection<PluginInformation> plugins) {
     657        List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
     658        for (PluginInformation pi : plugins) {
     659            if (checkLoadPreconditions(parent, plugins, pi)) {
     660                toLoad.add(pi);
    532661            }
    533         } finally {
    534             monitor.finishTask();
    535662        }
     663        // sort the plugins according to their "staging" equivalence class. The
     664        // lower the value of "stage" the earlier the plugin should be loaded.
     665        //
     666        Collections.sort(toLoad, new Comparator<PluginInformation>() {
     667            public int compare(PluginInformation o1, PluginInformation o2) {
     668                if (o1.stage < o2.stage)
     669                    return -1;
     670                if (o1.stage == o2.stage)
     671                    return 0;
     672                return 1;
     673            }
     674        });
     675        return toLoad;
    536676    }
    537677
    538678    /**
    public class PluginHandler {  
    838978        return null;
    839979    }
    840980
     981    // FIXME: add mechanism to remove DowloadSections
    841982    public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
    842983        for (PluginProxy p : pluginList) {
    843984            p.addDownloadSelection(downloadSelections);
    844985        }
    845986    }
    846987
    847     public static void getPreferenceSetting(Collection<PreferenceSettingFactory> settings) {
    848         for (PluginProxy plugin : pluginList) {
    849             settings.add(new PluginPreferenceFactory(plugin));
     988    // FIXME: Quick hack to control the preference dialog (The preferences system could use a refactoring IMO)
     989    // Relies on internal behavior in at least PreferenceTabbedPane. Adding functionality there is probably better
     990    // long-term.
     991    //
     992    // The hack fixes two issues (in reality they are the same issue):
     993    // Before, PreferenceTabbedPane would keep a reference to all plugins in its PreferenceSettingFactory list,
     994    // causing stray references to reloaded plugins.
     995    // Secondary, the preferences dialog will now actually work for reloaded plugins.
     996    private static class JointPluginPreferenceSettingsFactory implements PreferenceSettingFactory {
     997        @Override
     998        public PreferenceSetting createPreferenceSetting() {
     999            return new PreferenceSetting() {
     1000                private List<PreferenceSetting> settings = new ArrayList<PreferenceSetting>();
     1001
     1002                { // "Constructor"
     1003                    for (PluginProxy plugin : pluginList) {
     1004                        // Delay setting creation so we don't depend on static initializer magic(?)
     1005                        PreferenceSetting setting = plugin.getPreferenceSetting();
     1006                        if (setting != null)
     1007                            settings.add(setting);
     1008                    }
     1009                }
     1010
     1011                @Override
     1012                public boolean ok() {
     1013                    for (PreferenceSetting s : settings) {
     1014                        s.ok();
     1015                    }
     1016                    return false;
     1017                }
     1018
     1019                @Override
     1020                public void addGui(PreferenceTabbedPane gui) {
     1021                    for (PreferenceSetting s : settings) {
     1022                        s.addGui(gui);
     1023                    }
     1024                }
     1025            };
    8501026        }
    8511027    }
    8521028
     1029    // This is only called once from the static initializer in PreferenceTabbedPane
     1030    public static void getPreferenceSetting(Collection<PreferenceSettingFactory> settings) {
     1031        settings.add(new JointPluginPreferenceSettingsFactory());
     1032    }
     1033
    8531034    /**
    8541035     * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding
    8551036     * ".jar" files.
  • 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);