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

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

fix #6153 - spelling fixes

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