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

Last change on this file since 4701 was 4470, checked in by Don-vip, 13 years ago

fix #6894 - Plugin and JOSM version numbers badly displayed in warning popup

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