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

Last change on this file since 4175 was 4167, checked in by stoecker, 13 years ago

fix #5975 - wrong plugin report in bug report

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