source: josm/trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java@ 3872

Last change on this file since 3872 was 3730, checked in by bastiK, 13 years ago

improve migration when remotecontrol plugin is removed (set remotecontol.enabled=yes) (see also #5748)

  • Property svn:eol-style set to native
File size: 44.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Font;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.Insets;
12import java.awt.Window;
13import java.awt.event.ActionEvent;
14import java.io.File;
15import java.io.FilenameFilter;
16import java.net.URL;
17import java.net.URLClassLoader;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.HashMap;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30import java.util.Map.Entry;
31import java.util.TreeSet;
32import java.util.concurrent.ExecutionException;
33import java.util.concurrent.ExecutorService;
34import java.util.concurrent.Executors;
35import java.util.concurrent.Future;
36
37import javax.swing.AbstractAction;
38import javax.swing.BorderFactory;
39import javax.swing.Box;
40import javax.swing.JButton;
41import javax.swing.JCheckBox;
42import javax.swing.JLabel;
43import javax.swing.JOptionPane;
44import javax.swing.JPanel;
45import javax.swing.JScrollPane;
46import javax.swing.JTextArea;
47import javax.swing.UIManager;
48
49import org.openstreetmap.josm.Main;
50import org.openstreetmap.josm.data.Version;
51import org.openstreetmap.josm.gui.HelpAwareOptionPane;
52import org.openstreetmap.josm.gui.JMultilineLabel;
53import org.openstreetmap.josm.gui.MapFrame;
54import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
55import org.openstreetmap.josm.gui.download.DownloadSelection;
56import org.openstreetmap.josm.gui.help.HelpUtil;
57import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
58import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
59import org.openstreetmap.josm.gui.progress.ProgressMonitor;
60import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
61import org.openstreetmap.josm.tools.CheckParameterUtil;
62import org.openstreetmap.josm.tools.GBC;
63import org.openstreetmap.josm.tools.ImageProvider;
64
65/**
66 * PluginHandler is basically a collection of static utility functions used to bootstrap
67 * and manage the loaded plugins.
68 *
69 */
70public class PluginHandler {
71
72 /* deprecated plugins that are removed on start */
73 public final static Collection<DeprecatedPlugin> DEPRECATED_PLUGINS;
74 static {
75 String IN_CORE = tr("integrated into main program");
76
77 class RemotecontrolMigration implements Runnable {
78 public void run() {
79 System.out.println("Migrating remotecontrol plugin...");
80 if (!RemoteControl.PROP_REMOTECONTROL_ENABLED.isSet()) {
81 System.out.println(" Setting "+RemoteControl.PROP_REMOTECONTROL_ENABLED.getKey()+"=true");
82 RemoteControl.PROP_REMOTECONTROL_ENABLED.put(true);
83 }
84 System.out.println("...Done.");
85 }
86 }
87
88 DEPRECATED_PLUGINS = Arrays.asList(new DeprecatedPlugin[] {
89 new DeprecatedPlugin("mappaint", IN_CORE),
90 new DeprecatedPlugin("unglueplugin", IN_CORE),
91 new DeprecatedPlugin("lang-de", IN_CORE),
92 new DeprecatedPlugin("lang-en_GB", IN_CORE),
93 new DeprecatedPlugin("lang-fr", IN_CORE),
94 new DeprecatedPlugin("lang-it", IN_CORE),
95 new DeprecatedPlugin("lang-pl", IN_CORE),
96 new DeprecatedPlugin("lang-ro", IN_CORE),
97 new DeprecatedPlugin("lang-ru", IN_CORE),
98 new DeprecatedPlugin("ewmsplugin", IN_CORE),
99 new DeprecatedPlugin("ywms", IN_CORE),
100 new DeprecatedPlugin("tways-0.2", IN_CORE),
101 new DeprecatedPlugin("geotagged", IN_CORE),
102 new DeprecatedPlugin("landsat", tr("replaced by new {0} plugin","lakewalker")),
103 new DeprecatedPlugin("namefinder", IN_CORE),
104 new DeprecatedPlugin("waypoints", IN_CORE),
105 new DeprecatedPlugin("slippy_map_chooser", IN_CORE),
106 new DeprecatedPlugin("tcx-support", tr("replaced by new {0} plugin","dataimport")),
107 new DeprecatedPlugin("usertools", IN_CORE),
108 new DeprecatedPlugin("AgPifoJ", IN_CORE),
109 new DeprecatedPlugin("utilsplugin", IN_CORE),
110 new DeprecatedPlugin("ghost", IN_CORE),
111 new DeprecatedPlugin("validator", IN_CORE),
112 new DeprecatedPlugin("multipoly", IN_CORE),
113 new DeprecatedPlugin("remotecontrol", IN_CORE, new RemotecontrolMigration()),
114 new DeprecatedPlugin("imagery", IN_CORE),
115 new DeprecatedPlugin("slippymap", IN_CORE),
116 new DeprecatedPlugin("wmsplugin", IN_CORE),
117 });
118 }
119
120 public static class DeprecatedPlugin implements Comparable<DeprecatedPlugin> {
121 public String name;
122 // short explanation, can be null
123 public String reason;
124 // migration, can be null
125 private Runnable migration;
126
127 public DeprecatedPlugin(String name) {
128 this.name = name;
129 }
130
131 public DeprecatedPlugin(String name, String reason) {
132 this.name = name;
133 this.reason = reason;
134 }
135
136 public DeprecatedPlugin(String name, String reason, Runnable migration) {
137 this.name = name;
138 this.reason = reason;
139 this.migration = migration;
140 }
141
142 public void migrate() {
143 if (migration != null) {
144 migration.run();
145 }
146 }
147
148 public int compareTo(DeprecatedPlugin o) {
149 return name.compareTo(o.name);
150 }
151 }
152
153 final public static String [] UNMAINTAINED_PLUGINS = new String[] {"gpsbabelgui", "Intersect_way"};
154
155 /**
156 * All installed and loaded plugins (resp. their main classes)
157 */
158 public final static Collection<PluginProxy> pluginList = new LinkedList<PluginProxy>();
159
160 /**
161 * Removes deprecated plugins from a collection of plugins. Modifies the
162 * collection <code>plugins</code>.
163 *
164 * Also notifies the user about removed deprecated plugins
165 *
166 * @param plugins the collection of plugins
167 */
168 private static void filterDeprecatedPlugins(Window parent, Collection<String> plugins) {
169 Set<DeprecatedPlugin> removedPlugins = new TreeSet<DeprecatedPlugin>();
170 for (DeprecatedPlugin depr : DEPRECATED_PLUGINS) {
171 if (plugins.contains(depr.name)) {
172 plugins.remove(depr.name);
173 Main.pref.removeFromCollection("plugins", depr.name);
174 removedPlugins.add(depr);
175 depr.migrate();
176 }
177 }
178 if (removedPlugins.isEmpty())
179 return;
180
181 // notify user about removed deprecated plugins
182 //
183 StringBuilder sb = new StringBuilder();
184 sb.append("<html>");
185 sb.append(trn(
186 "The following plugin is no longer necessary and has been deactivated:",
187 "The following plugins are no longer necessary and have been deactivated:",
188 removedPlugins.size()
189 ));
190 sb.append("<ul>");
191 for (DeprecatedPlugin depr: removedPlugins) {
192 sb.append("<li>").append(depr.name);
193 if (depr.reason != null) {
194 sb.append(" (").append(depr.reason).append(")");
195 }
196 sb.append("</li>");
197 }
198 sb.append("</ul>");
199 sb.append("</html>");
200 JOptionPane.showMessageDialog(
201 parent,
202 sb.toString(),
203 tr("Warning"),
204 JOptionPane.WARNING_MESSAGE
205 );
206 }
207
208 /**
209 * Removes unmaintained plugins from a collection of plugins. Modifies the
210 * collection <code>plugins</code>. Also removes the plugin from the list
211 * of plugins in the preferences, if necessary.
212 *
213 * Asks the user for every unmaintained plugin whether it should be removed.
214 *
215 * @param plugins the collection of plugins
216 */
217 private static void filterUnmaintainedPlugins(Window parent, Collection<String> plugins) {
218 for (String unmaintained : UNMAINTAINED_PLUGINS) {
219 if (!plugins.contains(unmaintained)) {
220 continue;
221 }
222 String msg = tr("<html>Loading of the plugin \"{0}\" was requested."
223 + "<br>This plugin is no longer developed and very likely will produce errors."
224 +"<br>It should be disabled.<br>Delete from preferences?</html>", unmaintained);
225 if (confirmDisablePlugin(parent, msg,unmaintained)) {
226 Main.pref.removeFromCollection("plugins", unmaintained);
227 plugins.remove(unmaintained);
228 }
229 }
230 }
231
232 /**
233 * Checks whether the locally available plugins should be updated and
234 * asks the user if running an update is OK. An update is advised if
235 * JOSM was updated to a new version since the last plugin updates or
236 * if the plugins were last updated a long time ago.
237 *
238 * @param parent the parent window relative to which the confirmation dialog
239 * is to be displayed
240 * @return true if a plugin update should be run; false, otherwise
241 */
242 public static boolean checkAndConfirmPluginUpdate(Window parent) {
243 String message = null;
244 String togglePreferenceKey = null;
245 int v = Version.getInstance().getVersion();
246 if (Main.pref.getInteger("pluginmanager.version", 0) < v) {
247 message =
248 "<html>"
249 + tr("You updated your JOSM software.<br>"
250 + "To prevent problems the plugins should be updated as well.<br><br>"
251 + "Update plugins now?"
252 )
253 + "</html>";
254 togglePreferenceKey = "pluginmanager.version-based-update.policy";
255 } else {
256 long tim = System.currentTimeMillis();
257 long last = Main.pref.getLong("pluginmanager.lastupdate", 0);
258 Integer maxTime = Main.pref.getInteger("pluginmanager.time-based-update.interval", 60);
259 long d = (tim - last) / (24 * 60 * 60 * 1000l);
260 if ((last <= 0) || (maxTime <= 0)) {
261 Main.pref.put("pluginmanager.lastupdate", Long.toString(tim));
262 } else if (d > maxTime) {
263 message =
264 "<html>"
265 + tr("Last plugin update more than {0} days ago.", d)
266 + "</html>";
267 togglePreferenceKey = "pluginmanager.time-based-update.policy";
268 }
269 }
270 if (message == null) return false;
271
272 ButtonSpec [] options = new ButtonSpec[] {
273 new ButtonSpec(
274 tr("Update plugins"),
275 ImageProvider.get("dialogs", "refresh"),
276 tr("Click to update the activated plugins"),
277 null /* no specific help context */
278 ),
279 new ButtonSpec(
280 tr("Skip update"),
281 ImageProvider.get("cancel"),
282 tr("Click to skip updating the activated plugins"),
283 null /* no specific help context */
284 )
285 };
286
287 UpdatePluginsMessagePanel pnlMessage = new UpdatePluginsMessagePanel();
288 pnlMessage.setMessage(message);
289 pnlMessage.initDontShowAgain(togglePreferenceKey);
290
291 // check whether automatic update at startup was disabled
292 //
293 String policy = Main.pref.get(togglePreferenceKey, "ask");
294 policy = policy.trim().toLowerCase();
295 if (policy.equals("never")) {
296 if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) {
297 System.out.println(tr("Skipping plugin update after JOSM upgrade. Automatic update at startup is disabled."));
298 } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
299 System.out.println(tr("Skipping plugin update after elapsed update interval. Automatic update at startup is disabled."));
300 }
301 return false;
302 }
303
304 if (policy.equals("always")) {
305 if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
306 System.out.println(tr("Running plugin update after JOSM upgrade. Automatic update at startup is enabled."));
307 } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
308 System.out.println(tr("Running plugin update after elapsed update interval. Automatic update at startup is disabled."));
309 }
310 return true;
311 }
312
313 if (!policy.equals("ask")) {
314 System.err.println(tr("Unexpected value ''{0}'' for preference ''{1}''. Assuming value ''ask''.", policy, togglePreferenceKey));
315 }
316 int ret = HelpAwareOptionPane.showOptionDialog(
317 parent,
318 pnlMessage,
319 tr("Update plugins"),
320 JOptionPane.WARNING_MESSAGE,
321 null,
322 options,
323 options[0],
324 ht("/Preferences/Plugins#AutomaticUpdate")
325 );
326
327 if (pnlMessage.isRememberDecision()) {
328 switch(ret) {
329 case 0:
330 Main.pref.put(togglePreferenceKey, "always");
331 break;
332 case JOptionPane.CLOSED_OPTION:
333 case 1:
334 Main.pref.put(togglePreferenceKey, "never");
335 break;
336 }
337 } else {
338 Main.pref.put(togglePreferenceKey, "ask");
339 }
340 return ret == 0;
341 }
342
343 /**
344 * Alerts the user if a plugin required by another plugin is missing
345 *
346 * @param plugin the the plugin
347 * @param missingRequiredPlugin the missing required plugin
348 */
349 private static void alertMissingRequiredPlugin(Window parent, String plugin, Set<String> missingRequiredPlugin) {
350 StringBuilder sb = new StringBuilder();
351 sb.append("<html>");
352 sb.append(trn("Plugin {0} requires a plugin which was not found. The missing plugin is:",
353 "Plugin {0} requires {1} plugins which were not found. The missing plugins are:",
354 missingRequiredPlugin.size(),
355 plugin,
356 missingRequiredPlugin.size()
357 ));
358 sb.append("<ul>");
359 for (String p: missingRequiredPlugin) {
360 sb.append("<li>").append(p).append("</li>");
361 }
362 sb.append("</ul>").append("</html>");
363 JOptionPane.showMessageDialog(
364 parent,
365 sb.toString(),
366 tr("Error"),
367 JOptionPane.ERROR_MESSAGE
368 );
369 }
370
371 private static void alertJOSMUpdateRequired(Window parent, String plugin, int requiredVersion) {
372 HelpAwareOptionPane.showOptionDialog(
373 parent,
374 tr("<html>Plugin {0} requires JOSM version {1}. The current JOSM version is {2}.<br>"
375 +"You have to update JOSM in order to use this plugin.</html>",
376 plugin, requiredVersion, Version.getInstance().getVersion()
377 ),
378 tr("Warning"),
379 JOptionPane.WARNING_MESSAGE,
380 HelpUtil.ht("/Plugin/Loading#JOSMUpdateRequired")
381 );
382 }
383
384 /**
385 * Checks whether all preconditions for loading the plugin <code>plugin</code> are met. The
386 * current JOSM version must be compatible with the plugin and no other plugins this plugin
387 * depends on should be missing.
388 *
389 * @param plugins the collection of all loaded plugins
390 * @param plugin the plugin for which preconditions are checked
391 * @return true, if the preconditions are met; false otherwise
392 */
393 public static boolean checkLoadPreconditions(Window parent, Collection<PluginInformation> plugins, PluginInformation plugin) {
394
395 // make sure the plugin is compatible with the current JOSM version
396 //
397 int josmVersion = Version.getInstance().getVersion();
398 if (plugin.mainversion > josmVersion && josmVersion != Version.JOSM_UNKNOWN_VERSION) {
399 alertJOSMUpdateRequired(parent, plugin.name, plugin.mainversion);
400 return false;
401 }
402
403 // make sure the dependencies to other plugins are not broken
404 //
405 if(plugin.requires != null){
406 Set<String> pluginNames = new HashSet<String>();
407 for (PluginInformation pi: plugins) {
408 pluginNames.add(pi.name);
409 }
410 Set<String> missingPlugins = new HashSet<String>();
411 for (String requiredPlugin : plugin.requires.split(";")) {
412 if (!pluginNames.contains(requiredPlugin)) {
413 missingPlugins.add(requiredPlugin);
414 }
415 }
416 if (!missingPlugins.isEmpty()) {
417 alertMissingRequiredPlugin(parent, plugin.name, missingPlugins);
418 return false;
419 }
420 }
421 return true;
422 }
423
424 /**
425 * Creates a class loader for loading plugin code.
426 *
427 * @param plugins the collection of plugins which are going to be loaded with this
428 * class loader
429 * @return the class loader
430 */
431 public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
432 // iterate all plugins and collect all libraries of all plugins:
433 List<URL> allPluginLibraries = new LinkedList<URL>();
434 File pluginDir = Main.pref.getPluginsDirectory();
435 for (PluginInformation info : plugins) {
436 if (info.libraries == null) {
437 continue;
438 }
439 allPluginLibraries.addAll(info.libraries);
440 File pluginJar = new File(pluginDir, info.name + ".jar");
441 URL pluginJarUrl = PluginInformation.fileToURL(pluginJar);
442 allPluginLibraries.add(pluginJarUrl);
443 }
444
445 // create a classloader for all plugins:
446 URL[] jarUrls = new URL[allPluginLibraries.size()];
447 jarUrls = allPluginLibraries.toArray(jarUrls);
448 URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader());
449 return pluginClassLoader;
450 }
451
452 /**
453 * Loads and instantiates the plugin described by <code>plugin</code> using
454 * the class loader <code>pluginClassLoader</code>.
455 *
456 * @param plugin the plugin
457 * @param pluginClassLoader the plugin class loader
458 */
459 public static void loadPlugin(Window parent, PluginInformation plugin, ClassLoader pluginClassLoader) {
460 String msg = tr("Could not load plugin {0}. Delete from preferences?", plugin.name);
461 try {
462 Class<?> klass = plugin.loadClass(pluginClassLoader);
463 if (klass != null) {
464 System.out.println(tr("loading plugin ''{0}'' (version {1})", plugin.name, plugin.localversion));
465 pluginList.add(plugin.load(klass));
466 }
467 msg = null;
468 } catch(PluginException e) {
469 e.printStackTrace();
470 if (e.getCause() instanceof ClassNotFoundException) {
471 msg = tr("<html>Could not load plugin {0} because the plugin<br>main class ''{1}'' was not found.<br>"
472 + "Delete from preferences?", plugin.name, plugin.className);
473 }
474 } catch (Throwable e) {
475 e.printStackTrace();
476 }
477 if(msg != null && confirmDisablePlugin(parent, msg, plugin.name))
478 Main.pref.removeFromCollection("plugins", plugin.name);
479 }
480
481 /**
482 * Loads the plugin in <code>plugins</code> from locally available jar files into
483 * memory.
484 *
485 * @param plugins the list of plugins
486 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
487 */
488 public static void loadPlugins(Window parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
489 if (monitor == null) {
490 monitor = NullProgressMonitor.INSTANCE;
491 }
492 try {
493 monitor.beginTask(tr("Loading plugins ..."));
494 monitor.subTask(tr("Checking plugin preconditions..."));
495 List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
496 for (PluginInformation pi: plugins) {
497 if (checkLoadPreconditions(parent, plugins, pi)) {
498 toLoad.add(pi);
499 }
500 }
501 // sort the plugins according to their "staging" equivalence class. The
502 // lower the value of "stage" the earlier the plugin should be loaded.
503 //
504 Collections.sort(
505 toLoad,
506 new Comparator<PluginInformation>() {
507 public int compare(PluginInformation o1, PluginInformation o2) {
508 if (o1.stage < o2.stage) return -1;
509 if (o1.stage == o2.stage) return 0;
510 return 1;
511 }
512 }
513 );
514 if (toLoad.isEmpty())
515 return;
516
517 ClassLoader pluginClassLoader = createClassLoader(toLoad);
518 ImageProvider.sources.add(0, pluginClassLoader);
519 monitor.setTicksCount(toLoad.size());
520 for (PluginInformation info : toLoad) {
521 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
522 loadPlugin(parent, info, pluginClassLoader);
523 monitor.worked(1);
524 }
525 } finally {
526 monitor.finishTask();
527 }
528 }
529
530 /**
531 * Loads plugins from <code>plugins</code> which have the flag {@see PluginInformation#early}
532 * set to true.
533 *
534 * @param plugins the collection of plugins
535 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
536 */
537 public static void loadEarlyPlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
538 List<PluginInformation> earlyPlugins = new ArrayList<PluginInformation>(plugins.size());
539 for (PluginInformation pi: plugins) {
540 if (pi.early) {
541 earlyPlugins.add(pi);
542 }
543 }
544 loadPlugins(parent, earlyPlugins, monitor);
545 }
546
547 /**
548 * Loads plugins from <code>plugins</code> which have the flag {@see PluginInformation#early}
549 * set to false.
550 *
551 * @param plugins the collection of plugins
552 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
553 */
554 public static void loadLatePlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
555 List<PluginInformation> latePlugins = new ArrayList<PluginInformation>(plugins.size());
556 for (PluginInformation pi: plugins) {
557 if (!pi.early) {
558 latePlugins.add(pi);
559 }
560 }
561 loadPlugins(parent, latePlugins, monitor);
562 }
563
564 /**
565 * Loads locally available plugin information from local plugin jars and from cached
566 * plugin lists.
567 *
568 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
569 * @return the list of locally available plugin information
570 *
571 */
572 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
573 if (monitor == null) {
574 monitor = NullProgressMonitor.INSTANCE;
575 }
576 try {
577 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
578 ExecutorService service = Executors.newSingleThreadExecutor();
579 Future<?> future = service.submit(task);
580 try {
581 future.get();
582 } catch(ExecutionException e) {
583 e.printStackTrace();
584 return null;
585 } catch(InterruptedException e) {
586 e.printStackTrace();
587 return null;
588 }
589 HashMap<String, PluginInformation> ret = new HashMap<String, PluginInformation>();
590 for (PluginInformation pi: task.getAvailablePlugins()) {
591 ret.put(pi.name, pi);
592 }
593 return ret;
594 } finally {
595 monitor.finishTask();
596 }
597 }
598
599 private static void alertMissingPluginInformation(Window parent, Collection<String> plugins) {
600 StringBuilder sb = new StringBuilder();
601 sb.append("<html>");
602 sb.append(trn("JOSM could not find information about the following plugin:",
603 "JOSM could not find information about the following plugins:",
604 plugins.size()));
605 sb.append("<ul>");
606 for (String plugin: plugins) {
607 sb.append("<li>").append(plugin).append("</li>");
608 }
609 sb.append("</ul>");
610 sb.append(trn("The plugin is not going to be loaded.",
611 "The plugins are not going to be loaded.",
612 plugins.size()));
613 sb.append("</html>");
614 HelpAwareOptionPane.showOptionDialog(
615 parent,
616 sb.toString(),
617 tr("Warning"),
618 JOptionPane.WARNING_MESSAGE,
619 HelpUtil.ht("/Plugin/Loading#MissingPluginInfos")
620 );
621 }
622
623 /**
624 * Builds the set of plugins to load. Deprecated and unmaintained plugins are filtered
625 * out. This involves user interaction. This method displays alert and confirmation
626 * messages.
627 *
628 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
629 * @return the set of plugins to load (as set of plugin names)
630 */
631 public static List<PluginInformation> buildListOfPluginsToLoad(Window parent, ProgressMonitor monitor) {
632 if (monitor == null) {
633 monitor = NullProgressMonitor.INSTANCE;
634 }
635 try {
636 monitor.beginTask(tr("Determine plugins to load..."));
637 Set<String> plugins = new HashSet<String>();
638 plugins.addAll(Main.pref.getCollection("plugins", new LinkedList<String>()));
639 if (System.getProperty("josm.plugins") != null) {
640 plugins.addAll(Arrays.asList(System.getProperty("josm.plugins").split(",")));
641 }
642 monitor.subTask(tr("Removing deprecated plugins..."));
643 filterDeprecatedPlugins(parent, plugins);
644 monitor.subTask(tr("Removing unmaintained plugins..."));
645 filterUnmaintainedPlugins(parent, plugins);
646 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1,false));
647 List<PluginInformation> ret = new LinkedList<PluginInformation>();
648 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
649 String plugin = it.next();
650 if (infos.containsKey(plugin)) {
651 ret.add(infos.get(plugin));
652 it.remove();
653 }
654 }
655 if (!plugins.isEmpty()) {
656 alertMissingPluginInformation(parent, plugins);
657 }
658 return ret;
659 } finally {
660 monitor.finishTask();
661 }
662 }
663
664 private static void alertFailedPluginUpdate(Window parent, Collection<PluginInformation> plugins) {
665 StringBuffer sb = new StringBuffer();
666 sb.append("<html>");
667 sb.append(trn(
668 "Updating the following plugin has failed:",
669 "Updating the following plugins has failed:",
670 plugins.size()
671 )
672 );
673 sb.append("<ul>");
674 for (PluginInformation pi: plugins) {
675 sb.append("<li>").append(pi.name).append("</li>");
676 }
677 sb.append("</ul>");
678 sb.append(trn(
679 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
680 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
681 plugins.size()
682 ));
683 sb.append("</html>");
684 HelpAwareOptionPane.showOptionDialog(
685 parent,
686 sb.toString(),
687 tr("Plugin update failed"),
688 JOptionPane.ERROR_MESSAGE,
689 HelpUtil.ht("/Plugin/Loading#FailedPluginUpdated")
690 );
691 }
692
693 /**
694 * Updates the plugins in <code>plugins</code>.
695 *
696 * @param parent the parent window for message boxes
697 * @param plugins the collection of plugins to update. Must not be null.
698 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
699 * @throws IllegalArgumentException thrown if plugins is null
700 */
701 public static List<PluginInformation> updatePlugins(Window parent,
702 List<PluginInformation> plugins, ProgressMonitor monitor)
703 throws IllegalArgumentException{
704 CheckParameterUtil.ensureParameterNotNull(plugins, "plugins");
705 if (monitor == null) {
706 monitor = NullProgressMonitor.INSTANCE;
707 }
708 try {
709 monitor.beginTask("");
710 ExecutorService service = Executors.newSingleThreadExecutor();
711
712 // try to download the plugin lists
713 //
714 ReadRemotePluginInformationTask task1 = new ReadRemotePluginInformationTask(
715 monitor.createSubTaskMonitor(1,false),
716 Main.pref.getPluginSites()
717 );
718 Future<?> future = service.submit(task1);
719 try {
720 future.get();
721 plugins = buildListOfPluginsToLoad(parent,monitor.createSubTaskMonitor(1, false));
722 } catch(ExecutionException e) {
723 System.out.println(tr("Warning: failed to download plugin information list"));
724 e.printStackTrace();
725 // don't abort in case of error, continue with downloading plugins below
726 } catch(InterruptedException e) {
727 System.out.println(tr("Warning: failed to download plugin information list"));
728 e.printStackTrace();
729 // don't abort in case of error, continue with downloading plugins below
730 }
731
732 // filter plugins which actually have to be updated
733 //
734 Collection<PluginInformation> pluginsToUpdate = new ArrayList<PluginInformation>();
735 for(PluginInformation pi: plugins) {
736 if (pi.isUpdateRequired()) {
737 pluginsToUpdate.add(pi);
738 }
739 }
740
741 if (!pluginsToUpdate.isEmpty()) {
742 // try to update the locally installed plugins
743 //
744 PluginDownloadTask task2 = new PluginDownloadTask(
745 monitor.createSubTaskMonitor(1,false),
746 pluginsToUpdate,
747 tr("Update plugins")
748 );
749
750 future = service.submit(task2);
751 try {
752 future.get();
753 } catch(ExecutionException e) {
754 e.printStackTrace();
755 alertFailedPluginUpdate(parent, pluginsToUpdate);
756 return plugins;
757 } catch(InterruptedException e) {
758 e.printStackTrace();
759 alertFailedPluginUpdate(parent, pluginsToUpdate);
760 return plugins;
761 }
762 // notify user if downloading a locally installed plugin failed
763 //
764 if (! task2.getFailedPlugins().isEmpty()) {
765 alertFailedPluginUpdate(parent, task2.getFailedPlugins());
766 return plugins;
767 }
768 }
769 } finally {
770 monitor.finishTask();
771 }
772 // remember the update because it was successful
773 //
774 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion());
775 Main.pref.put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis()));
776 return plugins;
777 }
778
779 /**
780 * Ask the user for confirmation that a plugin shall be disabled.
781 *
782 * @param reason the reason for disabling the plugin
783 * @param name the plugin name
784 * @return true, if the plugin shall be disabled; false, otherwise
785 */
786 public static boolean confirmDisablePlugin(Window parent, String reason, String name) {
787 ButtonSpec [] options = new ButtonSpec[] {
788 new ButtonSpec(
789 tr("Disable plugin"),
790 ImageProvider.get("dialogs", "delete"),
791 tr("Click to delete the plugin ''{0}''", name),
792 null /* no specific help context */
793 ),
794 new ButtonSpec(
795 tr("Keep plugin"),
796 ImageProvider.get("cancel"),
797 tr("Click to keep the plugin ''{0}''", name),
798 null /* no specific help context */
799 )
800 };
801 int ret = HelpAwareOptionPane.showOptionDialog(
802 parent,
803 reason,
804 tr("Disable plugin"),
805 JOptionPane.WARNING_MESSAGE,
806 null,
807 options,
808 options[0],
809 null // FIXME: add help topic
810 );
811 return ret == 0;
812 }
813
814 /**
815 * Notified loaded plugins about a new map frame
816 *
817 * @param old the old map frame
818 * @param map the new map frame
819 */
820 public static void notifyMapFrameChanged(MapFrame old, MapFrame map) {
821 for (PluginProxy plugin : pluginList) {
822 plugin.mapFrameInitialized(old, map);
823 }
824 }
825
826 public static Object getPlugin(String name) {
827 for (PluginProxy plugin : pluginList)
828 if(plugin.getPluginInformation().name.equals(name))
829 return plugin.plugin;
830 return null;
831 }
832
833 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
834 for (PluginProxy p : pluginList) {
835 p.addDownloadSelection(downloadSelections);
836 }
837 }
838
839 public static void getPreferenceSetting(Collection<PreferenceSettingFactory> settings) {
840 for (PluginProxy plugin : pluginList) {
841 settings.add(new PluginPreferenceFactory(plugin));
842 }
843 }
844
845 /**
846 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding
847 * ".jar" files.
848 *
849 * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
850 * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
851 * installation of the respective plugin is sillently skipped.
852 *
853 * @param dowarn if true, warning messages are displayed; false otherwise
854 */
855 public static void installDownloadedPlugins(boolean dowarn) {
856 File pluginDir = Main.pref.getPluginsDirectory();
857 if (! pluginDir.exists() || ! pluginDir.isDirectory() || ! pluginDir.canWrite())
858 return;
859
860 final File[] files = pluginDir.listFiles(new FilenameFilter() {
861 public boolean accept(File dir, String name) {
862 return name.endsWith(".jar.new");
863 }});
864
865 for (File updatedPlugin : files) {
866 final String filePath = updatedPlugin.getPath();
867 File plugin = new File(filePath.substring(0, filePath.length() - 4));
868 String pluginName = updatedPlugin.getName().substring(0, updatedPlugin.getName().length() - 8);
869 if (plugin.exists()) {
870 if (!plugin.delete() && dowarn) {
871 System.err.println(tr("Warning: failed to delete outdated plugin ''{0}''.", plugin.toString()));
872 System.err.println(tr("Warning: failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM is still going to load the old plugin version.", pluginName));
873 continue;
874 }
875 }
876 if (!updatedPlugin.renameTo(plugin) && dowarn) {
877 System.err.println(tr("Warning: failed to install plugin ''{0}'' from temporary download file ''{1}''. Renaming failed.", plugin.toString(), updatedPlugin.toString()));
878 System.err.println(tr("Warning: failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM is still going to load the old plugin version.", pluginName));
879 }
880 }
881 return;
882 }
883
884 private static boolean confirmDeactivatingPluginAfterException(PluginProxy plugin) {
885 ButtonSpec [] options = new ButtonSpec[] {
886 new ButtonSpec(
887 tr("Disable plugin"),
888 ImageProvider.get("dialogs", "delete"),
889 tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name),
890 null /* no specific help context */
891 ),
892 new ButtonSpec(
893 tr("Keep plugin"),
894 ImageProvider.get("cancel"),
895 tr("Click to keep the plugin ''{0}''",plugin.getPluginInformation().name),
896 null /* no specific help context */
897 )
898 };
899
900 StringBuffer msg = new StringBuffer();
901 msg.append("<html>");
902 msg.append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", plugin.getPluginInformation().name));
903 msg.append("<br>");
904 if(plugin.getPluginInformation().author != null) {
905 msg.append(tr("According to the information within the plugin, the author is {0}.", plugin.getPluginInformation().author));
906 msg.append("<br>");
907 }
908 msg.append(tr("Try updating to the newest version of this plugin before reporting a bug."));
909 msg.append("<br>");
910 msg.append(tr("Should the plugin be disabled?"));
911 msg.append("</html>");
912
913 int ret = HelpAwareOptionPane.showOptionDialog(
914 Main.parent,
915 msg.toString(),
916 tr("Update plugins"),
917 JOptionPane.QUESTION_MESSAGE,
918 null,
919 options,
920 options[0],
921 ht("/ErrorMessages#ErrorInPlugin")
922 );
923 return ret == 0;
924 }
925
926 /**
927 * Replies the plugin which most likely threw the exception <code>ex</code>.
928 *
929 * @param ex the exception
930 * @return the plugin; null, if the exception proably wasn't thrown from a plugin
931 */
932 private static PluginProxy getPluginCausingException(Throwable ex) {
933 for (PluginProxy p : pluginList) {
934 String baseClass = p.getPluginInformation().className;
935 int i = baseClass.lastIndexOf(".");
936 baseClass = baseClass.substring(0, i);
937 for (StackTraceElement element : ex.getStackTrace()) {
938 String c = element.getClassName();
939 if (c.startsWith(baseClass))
940 return p;
941 }
942 }
943 return null;
944 }
945
946 /**
947 * Checks whether the exception <code>e</code> was thrown by a plugin. If so,
948 * conditionally deactivates the plugin, but asks the user first.
949 *
950 * @param e the exception
951 */
952 public static void disablePluginAfterException(Throwable e) {
953 PluginProxy plugin = null;
954 // Check for an explicit problem when calling a plugin function
955 if (e instanceof PluginException) {
956 plugin = ((PluginException) e).plugin;
957 }
958 if (plugin == null) {
959 plugin = getPluginCausingException(e);
960 }
961 if (plugin == null)
962 // don't know what plugin threw the exception
963 return;
964
965 Set<String> plugins = new HashSet<String>(
966 Main.pref.getCollection("plugins",Collections.<String> emptySet())
967 );
968 if (! plugins.contains(plugin.getPluginInformation().name))
969 // plugin not activated ? strange in this context but anyway, don't bother
970 // the user with dialogs, skip conditional deactivation
971 return;
972
973 if (!confirmDeactivatingPluginAfterException(plugin))
974 // user doesn't want to deactivate the plugin
975 return;
976
977 // deactivate the plugin
978 plugins.remove(plugin.getPluginInformation().name);
979 Main.pref.putCollection("plugins", plugins);
980 JOptionPane.showMessageDialog(
981 Main.parent,
982 tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."),
983 tr("Information"),
984 JOptionPane.INFORMATION_MESSAGE
985 );
986 return;
987 }
988
989 public static String getBugReportText() {
990 String text = "";
991 LinkedList <String> pl = new LinkedList<String>(Main.pref.getCollection("plugins", new LinkedList<String>()));
992 for (final PluginProxy pp : pluginList) {
993 PluginInformation pi = pp.getPluginInformation();
994 pl.remove(pi.name);
995 pl.add(pi.name + " (" + (pi.localversion != null && !pi.localversion.equals("")
996 ? pi.localversion : "unknown") + ")");
997 }
998 Collections.sort(pl);
999 for (String s : pl)
1000 text += "Plugin: " + s + "\n";
1001 return text;
1002 }
1003
1004 public static JPanel getInfoPanel() {
1005 JPanel pluginTab = new JPanel(new GridBagLayout());
1006 for (final PluginProxy p : pluginList) {
1007 final PluginInformation info = p.getPluginInformation();
1008 String name = info.name
1009 + (info.version != null && !info.version.equals("") ? " Version: " + info.version : "");
1010 pluginTab.add(new JLabel(name), GBC.std());
1011 pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
1012 pluginTab.add(new JButton(new AbstractAction(tr("Information")) {
1013 public void actionPerformed(ActionEvent event) {
1014 StringBuilder b = new StringBuilder();
1015 for (Entry<String, String> e : info.attr.entrySet()) {
1016 b.append(e.getKey());
1017 b.append(": ");
1018 b.append(e.getValue());
1019 b.append("\n");
1020 }
1021 JTextArea a = new JTextArea(10, 40);
1022 a.setEditable(false);
1023 a.setText(b.toString());
1024 JOptionPane.showMessageDialog(Main.parent, new JScrollPane(a), tr("Plugin information"),
1025 JOptionPane.INFORMATION_MESSAGE);
1026 }
1027 }), GBC.eol());
1028
1029 JTextArea description = new JTextArea((info.description == null ? tr("no description available")
1030 : info.description));
1031 description.setEditable(false);
1032 description.setFont(new JLabel().getFont().deriveFont(Font.ITALIC));
1033 description.setLineWrap(true);
1034 description.setWrapStyleWord(true);
1035 description.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
1036 description.setBackground(UIManager.getColor("Panel.background"));
1037
1038 pluginTab.add(description, GBC.eop().fill(GBC.HORIZONTAL));
1039 }
1040 return pluginTab;
1041 }
1042
1043 static private class UpdatePluginsMessagePanel extends JPanel {
1044 private JMultilineLabel lblMessage;
1045 private JCheckBox cbDontShowAgain;
1046
1047 protected void build() {
1048 setLayout(new GridBagLayout());
1049 GridBagConstraints gc = new GridBagConstraints();
1050 gc.anchor = GridBagConstraints.NORTHWEST;
1051 gc.fill = GridBagConstraints.BOTH;
1052 gc.weightx = 1.0;
1053 gc.weighty = 1.0;
1054 gc.insets = new Insets(5,5,5,5);
1055 add(lblMessage = new JMultilineLabel(""), gc);
1056 lblMessage.setFont(lblMessage.getFont().deriveFont(Font.PLAIN));
1057
1058 gc.gridy = 1;
1059 gc.fill = GridBagConstraints.HORIZONTAL;
1060 gc.weighty = 0.0;
1061 add(cbDontShowAgain = new JCheckBox(tr("Do not ask again and remember my decision (go to Preferences->Plugins to change it later)")), gc);
1062 cbDontShowAgain.setFont(cbDontShowAgain.getFont().deriveFont(Font.PLAIN));
1063 }
1064
1065 public UpdatePluginsMessagePanel() {
1066 build();
1067 }
1068
1069 public void setMessage(String message) {
1070 lblMessage.setText(message);
1071 }
1072
1073 public void initDontShowAgain(String preferencesKey) {
1074 String policy = Main.pref.get(preferencesKey, "ask");
1075 policy = policy.trim().toLowerCase();
1076 cbDontShowAgain.setSelected(! policy.equals("ask"));
1077 }
1078
1079 public boolean isRememberDecision() {
1080 return cbDontShowAgain.isSelected();
1081 }
1082 }
1083}
Note: See TracBrowser for help on using the repository browser.