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

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

added missing plugin deprecation reasons

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