Ticket #6396: reload-plugins-0.2.patch
File reload-plugins-0.2.patch, 21.5 KB (added by , 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 { 148 148 DataSet.addSelectionListener(selectionChangeAdapter); 149 149 initEnabledState(); 150 150 } 151 protected void uninstallAdapters() { 152 if(layerChangeAdapter != null) 153 MapView.removeLayerChangeListener(layerChangeAdapter); 154 if(selectionChangeAdapter != null) { 155 DataSet.removeSelectionListener(selectionChangeAdapter); 156 } 157 } 151 158 152 159 /** 153 160 * 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. 2 package org.openstreetmap.josm.actions; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.event.ActionEvent; 7 import java.awt.event.KeyEvent; 8 9 import org.openstreetmap.josm.plugins.PluginHandler; 10 import org.openstreetmap.josm.tools.Shortcut; 11 12 public 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; 52 52 import org.openstreetmap.josm.actions.OpenFileAction; 53 53 import org.openstreetmap.josm.actions.OpenLocationAction; 54 54 import org.openstreetmap.josm.actions.OrthogonalizeAction; 55 import org.openstreetmap.josm.actions.ReloadPluginsAction; 55 56 import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo; 56 57 import org.openstreetmap.josm.actions.PasteAction; 57 58 import org.openstreetmap.josm.actions.PasteTagsAction; … … public class MainMenu extends JMenuBar { 359 360 current.setAccelerator(Shortcut.registerShortcut("system:help", tr("Help"), KeyEvent.VK_F1, 360 361 Shortcut.GROUP_DIRECT).getKeyStroke()); 361 362 add(helpMenu, about); 363 add(helpMenu, new ReloadPluginsAction()); 362 364 363 365 new PresetsMenuEnabler(presetsMenu).refreshEnabled(); 364 366 } -
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 281 281 return button; 282 282 } 283 283 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 284 293 public void addMapMode(IconToggleButton b) { 285 294 toolBarActions.add(b); 286 295 toolGroup.add(b); … … public class MapFrame extends JPanel implements Destroyable, LayerChangeListener 290 299 throw new IllegalArgumentException("MapMode action must be subclass of MapMode"); 291 300 } 292 301 302 public void removeMapMode(IconToggleButton mode) { 303 toolBarActions.remove(mode); 304 toolGroup.remove(mode); 305 mapModes.remove(mode.getAction()); 306 } 307 293 308 /** 294 309 * Fires an property changed event "visible". 295 310 */ -
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 279 279 settingsFactory.add(new RemoteControlPreference.Factory()); 280 280 settingsFactory.add(new ImageryPreference.Factory()); 281 281 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 282 284 PluginHandler.getPreferenceSetting(settingsFactory); 283 285 284 286 // 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 { 72 72 this.info = info; 73 73 } 74 74 75 public void preReloadCleanup() { 76 System.out.println("no cleanup for this class"); 77 } 78 75 79 /** 76 80 * @return The directory for the plugin to store all kind of stuff. 77 81 */ -
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; 13 13 import java.awt.event.ActionEvent; 14 14 import java.io.File; 15 15 import java.io.FilenameFilter; 16 import java.lang.reflect.Method; 16 17 import java.net.URL; 17 18 import java.net.URLClassLoader; 18 19 import java.util.ArrayList; … … import org.openstreetmap.josm.gui.JMultilineLabel; 54 55 import org.openstreetmap.josm.gui.MapFrame; 55 56 import org.openstreetmap.josm.gui.download.DownloadSelection; 56 57 import org.openstreetmap.josm.gui.help.HelpUtil; 58 import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 57 59 import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 60 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 58 61 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 59 62 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 60 import org.openstreetmap.josm.io.remotecontrol.RemoteControl;61 63 import org.openstreetmap.josm.tools.CheckParameterUtil; 62 64 import org.openstreetmap.josm.tools.GBC; 63 65 import org.openstreetmap.josm.tools.ImageProvider; … … public class PluginHandler { 429 431 } 430 432 431 433 /** 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 437 435 */ 438 p ublic static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {436 private static URL[] createJarURLs(Collection<PluginInformation> plugins) { 439 437 // iterate all plugins and collect all libraries of all plugins: 440 438 List<URL> allPluginLibraries = new LinkedList<URL>(); 441 439 File pluginDir = Main.pref.getPluginsDirectory(); … … public class PluginHandler { 451 449 452 450 // create a classloader for all plugins: 453 451 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); 455 463 URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader()); 456 464 return pluginClassLoader; 457 465 } 458 466 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 459 586 /** 460 587 * Loads and instantiates the plugin described by <code>plugin</code> using 461 588 * the class loader <code>pluginClassLoader</code>. … … public class PluginHandler { 493 620 * @param plugins the list of plugins 494 621 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null. 495 622 */ 496 public static void loadPlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {623 public static void loadPlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) { 497 624 if (monitor == null) { 498 625 monitor = NullProgressMonitor.INSTANCE; 499 626 } 500 627 try { 501 628 monitor.beginTask(tr("Loading plugins ...")); 502 629 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); 522 631 if (toLoad.isEmpty()) 523 632 return; 633 loadPluginsNoCheck(parent, toLoad, monitor, PluginHandler.createClassLoader(toLoad)); 634 } finally { 635 monitor.finishTask(); 636 } 637 } 524 638 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) 529 646 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) 531 651 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); 532 661 } 533 } finally {534 monitor.finishTask();535 662 } 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; 536 676 } 537 677 538 678 /** … … public class PluginHandler { 838 978 return null; 839 979 } 840 980 981 // FIXME: add mechanism to remove DowloadSections 841 982 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) { 842 983 for (PluginProxy p : pluginList) { 843 984 p.addDownloadSelection(downloadSelections); 844 985 } 845 986 } 846 987 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 }; 850 1026 } 851 1027 } 852 1028 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 853 1034 /** 854 1035 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding 855 1036 * ".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 1 1 // License: GPL. Copyright 2007 by Immanuel Scholz and others 2 2 package org.openstreetmap.josm.plugins; 3 3 4 import java.lang.reflect.Method; 4 5 import java.util.List; 5 6 6 7 import org.openstreetmap.josm.gui.MapFrame; … … public class PluginProxy extends Plugin { 33 34 } 34 35 } 35 36 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 36 47 @Override public PreferenceSetting getPreferenceSetting() { 37 48 try { 38 49 return (PreferenceSetting)plugin.getClass().getMethod("getPreferenceSetting").invoke(plugin);