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

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

deprecate ParallelWay plugin

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