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

Last change on this file since 8390 was 8390, checked in by Don-vip, 9 years ago

Sonar - various performance improvements

  • Property svn:eol-style set to native
File size: 58.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Component;
9import java.awt.Font;
10import java.awt.GridBagConstraints;
11import java.awt.GridBagLayout;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.io.File;
15import java.io.FilenameFilter;
16import java.net.URL;
17import java.net.URLClassLoader;
18import java.security.AccessController;
19import java.security.PrivilegedAction;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Comparator;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.Map;
31import java.util.Map.Entry;
32import java.util.Set;
33import java.util.TreeSet;
34import java.util.concurrent.Callable;
35import java.util.concurrent.ExecutionException;
36import java.util.concurrent.ExecutorService;
37import java.util.concurrent.Executors;
38import java.util.concurrent.Future;
39import java.util.concurrent.FutureTask;
40import java.util.jar.JarFile;
41
42import javax.swing.AbstractAction;
43import javax.swing.BorderFactory;
44import javax.swing.Box;
45import javax.swing.JButton;
46import javax.swing.JCheckBox;
47import javax.swing.JLabel;
48import javax.swing.JOptionPane;
49import javax.swing.JPanel;
50import javax.swing.JScrollPane;
51import javax.swing.UIManager;
52
53import org.openstreetmap.josm.Main;
54import org.openstreetmap.josm.data.Version;
55import org.openstreetmap.josm.gui.HelpAwareOptionPane;
56import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
57import org.openstreetmap.josm.gui.download.DownloadSelection;
58import org.openstreetmap.josm.gui.help.HelpUtil;
59import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
60import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
61import org.openstreetmap.josm.gui.progress.ProgressMonitor;
62import org.openstreetmap.josm.gui.util.GuiHelper;
63import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
64import org.openstreetmap.josm.gui.widgets.JosmTextArea;
65import org.openstreetmap.josm.io.OfflineAccessException;
66import org.openstreetmap.josm.io.OnlineResource;
67import org.openstreetmap.josm.tools.GBC;
68import org.openstreetmap.josm.tools.I18n;
69import org.openstreetmap.josm.tools.ImageProvider;
70import org.openstreetmap.josm.tools.Utils;
71
72/**
73 * PluginHandler is basically a collection of static utility functions used to bootstrap
74 * and manage the loaded plugins.
75 * @since 1326
76 */
77public final class PluginHandler {
78
79 /**
80 * Deprecated plugins that are removed on start
81 */
82 public static final Collection<DeprecatedPlugin> DEPRECATED_PLUGINS;
83 static {
84 String IN_CORE = tr("integrated into main program");
85
86 DEPRECATED_PLUGINS = Arrays.asList(new DeprecatedPlugin[] {
87 new DeprecatedPlugin("mappaint", IN_CORE),
88 new DeprecatedPlugin("unglueplugin", IN_CORE),
89 new DeprecatedPlugin("lang-de", IN_CORE),
90 new DeprecatedPlugin("lang-en_GB", IN_CORE),
91 new DeprecatedPlugin("lang-fr", IN_CORE),
92 new DeprecatedPlugin("lang-it", IN_CORE),
93 new DeprecatedPlugin("lang-pl", IN_CORE),
94 new DeprecatedPlugin("lang-ro", IN_CORE),
95 new DeprecatedPlugin("lang-ru", IN_CORE),
96 new DeprecatedPlugin("ewmsplugin", IN_CORE),
97 new DeprecatedPlugin("ywms", IN_CORE),
98 new DeprecatedPlugin("tways-0.2", IN_CORE),
99 new DeprecatedPlugin("geotagged", IN_CORE),
100 new DeprecatedPlugin("landsat", tr("replaced by new {0} plugin","lakewalker")),
101 new DeprecatedPlugin("namefinder", IN_CORE),
102 new DeprecatedPlugin("waypoints", IN_CORE),
103 new DeprecatedPlugin("slippy_map_chooser", IN_CORE),
104 new DeprecatedPlugin("tcx-support", tr("replaced by new {0} plugin","dataimport")),
105 new DeprecatedPlugin("usertools", IN_CORE),
106 new DeprecatedPlugin("AgPifoJ", IN_CORE),
107 new DeprecatedPlugin("utilsplugin", IN_CORE),
108 new DeprecatedPlugin("ghost", IN_CORE),
109 new DeprecatedPlugin("validator", IN_CORE),
110 new DeprecatedPlugin("multipoly", IN_CORE),
111 new DeprecatedPlugin("multipoly-convert", IN_CORE),
112 new DeprecatedPlugin("remotecontrol", IN_CORE),
113 new DeprecatedPlugin("imagery", IN_CORE),
114 new DeprecatedPlugin("slippymap", IN_CORE),
115 new DeprecatedPlugin("wmsplugin", IN_CORE),
116 new DeprecatedPlugin("ParallelWay", IN_CORE),
117 new DeprecatedPlugin("dumbutils", tr("replaced by new {0} plugin","utilsplugin2")),
118 new DeprecatedPlugin("ImproveWayAccuracy", IN_CORE),
119 new DeprecatedPlugin("Curves", tr("replaced by new {0} plugin","utilsplugin2")),
120 new DeprecatedPlugin("epsg31287", tr("replaced by new {0} plugin", "proj4j")),
121 new DeprecatedPlugin("licensechange", tr("no longer required")),
122 new DeprecatedPlugin("restart", IN_CORE),
123 new DeprecatedPlugin("wayselector", IN_CORE),
124 new DeprecatedPlugin("openstreetbugs", tr("replaced by new {0} plugin", "notes")),
125 new DeprecatedPlugin("nearclick", tr("no longer required")),
126 new DeprecatedPlugin("notes", IN_CORE),
127 });
128 }
129
130 private PluginHandler() {
131 // Hide default constructor for utils classes
132 }
133
134 /**
135 * Description of a deprecated plugin
136 */
137 public static class DeprecatedPlugin implements Comparable<DeprecatedPlugin> {
138 /** Plugin name */
139 public final String name;
140 /** Short explanation about deprecation, can be {@code null} */
141 public final String reason;
142 /** Code to run to perform migration, can be {@code null} */
143 private final Runnable migration;
144
145 /**
146 * Constructs a new {@code DeprecatedPlugin}.
147 * @param name The plugin name
148 */
149 public DeprecatedPlugin(String name) {
150 this(name, null, null);
151 }
152
153 /**
154 * Constructs a new {@code DeprecatedPlugin} with a given reason.
155 * @param name The plugin name
156 * @param reason The reason about deprecation
157 */
158 public DeprecatedPlugin(String name, String reason) {
159 this(name, reason, null);
160 }
161
162 /**
163 * Constructs a new {@code DeprecatedPlugin}.
164 * @param name The plugin name
165 * @param reason The reason about deprecation
166 * @param migration The code to run to perform migration
167 */
168 public DeprecatedPlugin(String name, String reason, Runnable migration) {
169 this.name = name;
170 this.reason = reason;
171 this.migration = migration;
172 }
173
174 /**
175 * Performs migration.
176 */
177 public void migrate() {
178 if (migration != null) {
179 migration.run();
180 }
181 }
182
183 @Override
184 public int compareTo(DeprecatedPlugin o) {
185 return name.compareTo(o.name);
186 }
187 }
188
189 /**
190 * ClassLoader that makes the addURL method of URLClassLoader public.
191 *
192 * Like URLClassLoader, but allows to add more URLs after construction.
193 */
194 public static class DynamicURLClassLoader extends URLClassLoader {
195
196 public DynamicURLClassLoader(URL[] urls, ClassLoader parent) {
197 super(urls, parent);
198 }
199
200 @Override
201 public void addURL(URL url) {
202 super.addURL(url);
203 }
204 }
205
206 /**
207 * List of unmaintained plugins. Not really up-to-date as the vast majority of plugins are not maintained after a few months, sadly...
208 */
209 private static final String [] UNMAINTAINED_PLUGINS = new String[] {"gpsbabelgui", "Intersect_way"};
210
211 /**
212 * Default time-based update interval, in days (pluginmanager.time-based-update.interval)
213 */
214 public static final int DEFAULT_TIME_BASED_UPDATE_INTERVAL = 30;
215
216 /**
217 * All installed and loaded plugins (resp. their main classes)
218 */
219 public static final Collection<PluginProxy> pluginList = new LinkedList<>();
220
221 /**
222 * Global plugin ClassLoader.
223 */
224 private static DynamicURLClassLoader pluginClassLoader;
225
226 /**
227 * Add here all ClassLoader whose resource should be searched.
228 */
229 private static final List<ClassLoader> sources = new LinkedList<>();
230
231 static {
232 try {
233 sources.add(ClassLoader.getSystemClassLoader());
234 sources.add(org.openstreetmap.josm.gui.MainApplication.class.getClassLoader());
235 } catch (SecurityException ex) {
236 sources.add(ImageProvider.class.getClassLoader());
237 }
238 }
239
240 private static PluginDownloadTask pluginDownloadTask = null;
241
242 public static Collection<ClassLoader> getResourceClassLoaders() {
243 return Collections.unmodifiableCollection(sources);
244 }
245
246 /**
247 * Removes deprecated plugins from a collection of plugins. Modifies the
248 * collection <code>plugins</code>.
249 *
250 * Also notifies the user about removed deprecated plugins
251 *
252 * @param parent The parent Component used to display warning popup
253 * @param plugins the collection of plugins
254 */
255 private static void filterDeprecatedPlugins(Component parent, Collection<String> plugins) {
256 Set<DeprecatedPlugin> removedPlugins = new TreeSet<>();
257 for (DeprecatedPlugin depr : DEPRECATED_PLUGINS) {
258 if (plugins.contains(depr.name)) {
259 plugins.remove(depr.name);
260 Main.pref.removeFromCollection("plugins", depr.name);
261 removedPlugins.add(depr);
262 depr.migrate();
263 }
264 }
265 if (removedPlugins.isEmpty())
266 return;
267
268 // notify user about removed deprecated plugins
269 //
270 StringBuilder sb = new StringBuilder();
271 sb.append("<html>")
272 .append(trn(
273 "The following plugin is no longer necessary and has been deactivated:",
274 "The following plugins are no longer necessary and have been deactivated:",
275 removedPlugins.size()))
276 .append("<ul>");
277 for (DeprecatedPlugin depr: removedPlugins) {
278 sb.append("<li>").append(depr.name);
279 if (depr.reason != null) {
280 sb.append(" (").append(depr.reason).append(')');
281 }
282 sb.append("</li>");
283 }
284 sb.append("</ul></html>");
285 JOptionPane.showMessageDialog(
286 parent,
287 sb.toString(),
288 tr("Warning"),
289 JOptionPane.WARNING_MESSAGE
290 );
291 }
292
293 /**
294 * Removes unmaintained plugins from a collection of plugins. Modifies the
295 * collection <code>plugins</code>. Also removes the plugin from the list
296 * of plugins in the preferences, if necessary.
297 *
298 * Asks the user for every unmaintained plugin whether it should be removed.
299 *
300 * @param plugins the collection of plugins
301 */
302 private static void filterUnmaintainedPlugins(Component parent, Collection<String> plugins) {
303 for (String unmaintained : UNMAINTAINED_PLUGINS) {
304 if (!plugins.contains(unmaintained)) {
305 continue;
306 }
307 String msg = tr("<html>Loading of the plugin \"{0}\" was requested."
308 + "<br>This plugin is no longer developed and very likely will produce errors."
309 +"<br>It should be disabled.<br>Delete from preferences?</html>", unmaintained);
310 if (confirmDisablePlugin(parent, msg,unmaintained)) {
311 Main.pref.removeFromCollection("plugins", unmaintained);
312 plugins.remove(unmaintained);
313 }
314 }
315 }
316
317 /**
318 * Checks whether the locally available plugins should be updated and
319 * asks the user if running an update is OK. An update is advised if
320 * JOSM was updated to a new version since the last plugin updates or
321 * if the plugins were last updated a long time ago.
322 *
323 * @param parent the parent component relative to which the confirmation dialog
324 * is to be displayed
325 * @return true if a plugin update should be run; false, otherwise
326 */
327 public static boolean checkAndConfirmPluginUpdate(Component parent) {
328 if (!checkOfflineAccess()) {
329 Main.info(tr("{0} not available (offline mode)", tr("Plugin update")));
330 return false;
331 }
332 String message = null;
333 String togglePreferenceKey = null;
334 int v = Version.getInstance().getVersion();
335 if (Main.pref.getInteger("pluginmanager.version", 0) < v) {
336 message =
337 "<html>"
338 + tr("You updated your JOSM software.<br>"
339 + "To prevent problems the plugins should be updated as well.<br><br>"
340 + "Update plugins now?"
341 )
342 + "</html>";
343 togglePreferenceKey = "pluginmanager.version-based-update.policy";
344 } else {
345 long tim = System.currentTimeMillis();
346 long last = Main.pref.getLong("pluginmanager.lastupdate", 0);
347 Integer maxTime = Main.pref.getInteger("pluginmanager.time-based-update.interval", DEFAULT_TIME_BASED_UPDATE_INTERVAL);
348 long d = (tim - last) / (24 * 60 * 60 * 1000L);
349 if ((last <= 0) || (maxTime <= 0)) {
350 Main.pref.put("pluginmanager.lastupdate", Long.toString(tim));
351 } else if (d > maxTime) {
352 message =
353 "<html>"
354 + tr("Last plugin update more than {0} days ago.", d)
355 + "</html>";
356 togglePreferenceKey = "pluginmanager.time-based-update.policy";
357 }
358 }
359 if (message == null) return false;
360
361 ButtonSpec [] options = new ButtonSpec[] {
362 new ButtonSpec(
363 tr("Update plugins"),
364 ImageProvider.get("dialogs", "refresh"),
365 tr("Click to update the activated plugins"),
366 null /* no specific help context */
367 ),
368 new ButtonSpec(
369 tr("Skip update"),
370 ImageProvider.get("cancel"),
371 tr("Click to skip updating the activated plugins"),
372 null /* no specific help context */
373 )
374 };
375
376 UpdatePluginsMessagePanel pnlMessage = new UpdatePluginsMessagePanel();
377 pnlMessage.setMessage(message);
378 pnlMessage.initDontShowAgain(togglePreferenceKey);
379
380 // check whether automatic update at startup was disabled
381 //
382 String policy = Main.pref.get(togglePreferenceKey, "ask").trim().toLowerCase();
383 switch(policy) {
384 case "never":
385 if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) {
386 Main.info(tr("Skipping plugin update after JOSM upgrade. Automatic update at startup is disabled."));
387 } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
388 Main.info(tr("Skipping plugin update after elapsed update interval. Automatic update at startup is disabled."));
389 }
390 return false;
391
392 case "always":
393 if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) {
394 Main.info(tr("Running plugin update after JOSM upgrade. Automatic update at startup is enabled."));
395 } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
396 Main.info(tr("Running plugin update after elapsed update interval. Automatic update at startup is disabled."));
397 }
398 return true;
399
400 case "ask":
401 break;
402
403 default:
404 Main.warn(tr("Unexpected value ''{0}'' for preference ''{1}''. Assuming value ''ask''.", policy, togglePreferenceKey));
405 }
406
407 int ret = HelpAwareOptionPane.showOptionDialog(
408 parent,
409 pnlMessage,
410 tr("Update plugins"),
411 JOptionPane.WARNING_MESSAGE,
412 null,
413 options,
414 options[0],
415 ht("/Preferences/Plugins#AutomaticUpdate")
416 );
417
418 if (pnlMessage.isRememberDecision()) {
419 switch(ret) {
420 case 0:
421 Main.pref.put(togglePreferenceKey, "always");
422 break;
423 case JOptionPane.CLOSED_OPTION:
424 case 1:
425 Main.pref.put(togglePreferenceKey, "never");
426 break;
427 }
428 } else {
429 Main.pref.put(togglePreferenceKey, "ask");
430 }
431 return ret == 0;
432 }
433
434 private static boolean checkOfflineAccess() {
435 if (Main.isOffline(OnlineResource.ALL)) {
436 return false;
437 }
438 if (Main.isOffline(OnlineResource.JOSM_WEBSITE)) {
439 for (String updateSite : Main.pref.getPluginSites()) {
440 try {
441 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(updateSite, Main.getJOSMWebsite());
442 } catch (OfflineAccessException e) {
443 if (Main.isTraceEnabled()) {
444 Main.trace(e.getMessage());
445 }
446 return false;
447 }
448 }
449 }
450 return true;
451 }
452
453 /**
454 * Alerts the user if a plugin required by another plugin is missing
455 *
456 * @param parent The parent Component used to display error popup
457 * @param plugin the plugin
458 * @param missingRequiredPlugin the missing required plugin
459 */
460 private static void alertMissingRequiredPlugin(Component parent, String plugin, Set<String> missingRequiredPlugin) {
461 StringBuilder sb = new StringBuilder();
462 sb.append("<html>")
463 .append(trn("Plugin {0} requires a plugin which was not found. The missing plugin is:",
464 "Plugin {0} requires {1} plugins which were not found. The missing plugins are:",
465 missingRequiredPlugin.size(),
466 plugin,
467 missingRequiredPlugin.size()))
468 .append(Utils.joinAsHtmlUnorderedList(missingRequiredPlugin))
469 .append("</html>");
470 JOptionPane.showMessageDialog(
471 parent,
472 sb.toString(),
473 tr("Error"),
474 JOptionPane.ERROR_MESSAGE
475 );
476 }
477
478 private static void alertJOSMUpdateRequired(Component parent, String plugin, int requiredVersion) {
479 HelpAwareOptionPane.showOptionDialog(
480 parent,
481 tr("<html>Plugin {0} requires JOSM version {1}. The current JOSM version is {2}.<br>"
482 +"You have to update JOSM in order to use this plugin.</html>",
483 plugin, Integer.toString(requiredVersion), Version.getInstance().getVersionString()
484 ),
485 tr("Warning"),
486 JOptionPane.WARNING_MESSAGE,
487 HelpUtil.ht("/Plugin/Loading#JOSMUpdateRequired")
488 );
489 }
490
491 /**
492 * Checks whether all preconditions for loading the plugin <code>plugin</code> are met. The
493 * current JOSM version must be compatible with the plugin and no other plugins this plugin
494 * depends on should be missing.
495 *
496 * @param parent The parent Component used to display error popup
497 * @param plugins the collection of all loaded plugins
498 * @param plugin the plugin for which preconditions are checked
499 * @return true, if the preconditions are met; false otherwise
500 */
501 public static boolean checkLoadPreconditions(Component parent, Collection<PluginInformation> plugins, PluginInformation plugin) {
502
503 // make sure the plugin is compatible with the current JOSM version
504 //
505 int josmVersion = Version.getInstance().getVersion();
506 if (plugin.localmainversion > josmVersion && josmVersion != Version.JOSM_UNKNOWN_VERSION) {
507 alertJOSMUpdateRequired(parent, plugin.name, plugin.localmainversion);
508 return false;
509 }
510
511 // Add all plugins already loaded (to include early plugins when checking late ones)
512 Collection<PluginInformation> allPlugins = new HashSet<>(plugins);
513 for (PluginProxy proxy : pluginList) {
514 allPlugins.add(proxy.getPluginInformation());
515 }
516
517 return checkRequiredPluginsPreconditions(parent, allPlugins, plugin, true);
518 }
519
520 /**
521 * Checks if required plugins preconditions for loading the plugin <code>plugin</code> are met.
522 * No other plugins this plugin depends on should be missing.
523 *
524 * @param parent The parent Component used to display error popup. If parent is
525 * null, the error popup is suppressed
526 * @param plugins the collection of all loaded plugins
527 * @param plugin the plugin for which preconditions are checked
528 * @param local Determines if the local or up-to-date plugin dependencies are to be checked.
529 * @return true, if the preconditions are met; false otherwise
530 * @since 5601
531 */
532 public static boolean checkRequiredPluginsPreconditions(Component parent, Collection<PluginInformation> plugins, PluginInformation plugin, boolean local) {
533
534 String requires = local ? plugin.localrequires : plugin.requires;
535
536 // make sure the dependencies to other plugins are not broken
537 //
538 if (requires != null) {
539 Set<String> pluginNames = new HashSet<>();
540 for (PluginInformation pi: plugins) {
541 pluginNames.add(pi.name);
542 }
543 Set<String> missingPlugins = new HashSet<>();
544 List<String> requiredPlugins = local ? plugin.getLocalRequiredPlugins() : plugin.getRequiredPlugins();
545 for (String requiredPlugin : requiredPlugins) {
546 if (!pluginNames.contains(requiredPlugin)) {
547 missingPlugins.add(requiredPlugin);
548 }
549 }
550 if (!missingPlugins.isEmpty()) {
551 if (parent != null) {
552 alertMissingRequiredPlugin(parent, plugin.name, missingPlugins);
553 }
554 return false;
555 }
556 }
557 return true;
558 }
559
560 /**
561 * Get the class loader for loading plugin code.
562 *
563 * @return the class loader
564 */
565 public static synchronized DynamicURLClassLoader getPluginClassLoader() {
566 if (pluginClassLoader == null) {
567 pluginClassLoader = AccessController.doPrivileged(new PrivilegedAction<DynamicURLClassLoader>() {
568 public DynamicURLClassLoader run() {
569 return new DynamicURLClassLoader(new URL[0], Main.class.getClassLoader());
570 }
571 });
572 sources.add(0, pluginClassLoader);
573 }
574 return pluginClassLoader;
575 }
576
577 /**
578 * Add more plugins to the plugin class loader.
579 *
580 * @param plugins the plugins that should be handled by the plugin class loader
581 */
582 public static void extendPluginClassLoader(Collection<PluginInformation> plugins) {
583 // iterate all plugins and collect all libraries of all plugins:
584 File pluginDir = Main.pref.getPluginsDirectory();
585 DynamicURLClassLoader cl = getPluginClassLoader();
586
587 for (PluginInformation info : plugins) {
588 if (info.libraries == null) {
589 continue;
590 }
591 for (URL libUrl : info.libraries) {
592 cl.addURL(libUrl);
593 }
594 File pluginJar = new File(pluginDir, info.name + ".jar");
595 I18n.addTexts(pluginJar);
596 URL pluginJarUrl = Utils.fileToURL(pluginJar);
597 cl.addURL(pluginJarUrl);
598 }
599 }
600
601 /**
602 * Loads and instantiates the plugin described by <code>plugin</code> using
603 * the class loader <code>pluginClassLoader</code>.
604 *
605 * @param parent The parent component to be used for the displayed dialog
606 * @param plugin the plugin
607 * @param pluginClassLoader the plugin class loader
608 */
609 public static void loadPlugin(Component parent, PluginInformation plugin, ClassLoader pluginClassLoader) {
610 String msg = tr("Could not load plugin {0}. Delete from preferences?", plugin.name);
611 try {
612 Class<?> klass = plugin.loadClass(pluginClassLoader);
613 if (klass != null) {
614 Main.info(tr("loading plugin ''{0}'' (version {1})", plugin.name, plugin.localversion));
615 PluginProxy pluginProxy = plugin.load(klass);
616 pluginList.add(pluginProxy);
617 Main.addMapFrameListener(pluginProxy, true);
618 }
619 msg = null;
620 } catch (PluginException e) {
621 Main.error(e);
622 if (e.getCause() instanceof ClassNotFoundException) {
623 msg = tr("<html>Could not load plugin {0} because the plugin<br>main class ''{1}'' was not found.<br>"
624 + "Delete from preferences?</html>", plugin.name, plugin.className);
625 }
626 } catch (Exception e) {
627 Main.error(e);
628 }
629 if (msg != null && confirmDisablePlugin(parent, msg, plugin.name)) {
630 Main.pref.removeFromCollection("plugins", plugin.name);
631 }
632 }
633
634 /**
635 * Loads the plugin in <code>plugins</code> from locally available jar files into
636 * memory.
637 *
638 * @param parent The parent component to be used for the displayed dialog
639 * @param plugins the list of plugins
640 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
641 */
642 public static void loadPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
643 if (monitor == null) {
644 monitor = NullProgressMonitor.INSTANCE;
645 }
646 try {
647 monitor.beginTask(tr("Loading plugins ..."));
648 monitor.subTask(tr("Checking plugin preconditions..."));
649 List<PluginInformation> toLoad = new LinkedList<>();
650 for (PluginInformation pi: plugins) {
651 if (checkLoadPreconditions(parent, plugins, pi)) {
652 toLoad.add(pi);
653 }
654 }
655 // sort the plugins according to their "staging" equivalence class. The
656 // lower the value of "stage" the earlier the plugin should be loaded.
657 //
658 Collections.sort(
659 toLoad,
660 new Comparator<PluginInformation>() {
661 @Override
662 public int compare(PluginInformation o1, PluginInformation o2) {
663 if (o1.stage < o2.stage) return -1;
664 if (o1.stage == o2.stage) return 0;
665 return 1;
666 }
667 }
668 );
669 if (toLoad.isEmpty())
670 return;
671
672 extendPluginClassLoader(toLoad);
673 monitor.setTicksCount(toLoad.size());
674 for (PluginInformation info : toLoad) {
675 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
676 loadPlugin(parent, info, getPluginClassLoader());
677 monitor.worked(1);
678 }
679 } finally {
680 monitor.finishTask();
681 }
682 }
683
684 /**
685 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
686 * set to true.
687 *
688 * @param plugins the collection of plugins
689 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
690 */
691 public static void loadEarlyPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
692 List<PluginInformation> earlyPlugins = new ArrayList<>(plugins.size());
693 for (PluginInformation pi: plugins) {
694 if (pi.early) {
695 earlyPlugins.add(pi);
696 }
697 }
698 loadPlugins(parent, earlyPlugins, monitor);
699 }
700
701 /**
702 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
703 * set to false.
704 *
705 * @param parent The parent component to be used for the displayed dialog
706 * @param plugins the collection of plugins
707 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
708 */
709 public static void loadLatePlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
710 List<PluginInformation> latePlugins = new ArrayList<>(plugins.size());
711 for (PluginInformation pi: plugins) {
712 if (!pi.early) {
713 latePlugins.add(pi);
714 }
715 }
716 loadPlugins(parent, latePlugins, monitor);
717 }
718
719 /**
720 * Loads locally available plugin information from local plugin jars and from cached
721 * plugin lists.
722 *
723 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
724 * @return the list of locally available plugin information
725 *
726 */
727 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
728 if (monitor == null) {
729 monitor = NullProgressMonitor.INSTANCE;
730 }
731 try {
732 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
733 ExecutorService service = Executors.newSingleThreadExecutor();
734 Future<?> future = service.submit(task);
735 try {
736 future.get();
737 } catch(ExecutionException e) {
738 Main.error(e);
739 return null;
740 } catch(InterruptedException e) {
741 Main.warn("InterruptedException in "+PluginHandler.class.getSimpleName()+" while loading locally available plugin information");
742 return null;
743 }
744 Map<String, PluginInformation> ret = new HashMap<>();
745 for (PluginInformation pi: task.getAvailablePlugins()) {
746 ret.put(pi.name, pi);
747 }
748 return ret;
749 } finally {
750 monitor.finishTask();
751 }
752 }
753
754 private static void alertMissingPluginInformation(Component parent, Collection<String> plugins) {
755 StringBuilder sb = new StringBuilder();
756 sb.append("<html>")
757 .append(trn("JOSM could not find information about the following plugin:",
758 "JOSM could not find information about the following plugins:",
759 plugins.size()))
760 .append(Utils.joinAsHtmlUnorderedList(plugins))
761 .append(trn("The plugin is not going to be loaded.",
762 "The plugins are not going to be loaded.",
763 plugins.size()))
764 .append("</html>");
765 HelpAwareOptionPane.showOptionDialog(
766 parent,
767 sb.toString(),
768 tr("Warning"),
769 JOptionPane.WARNING_MESSAGE,
770 HelpUtil.ht("/Plugin/Loading#MissingPluginInfos")
771 );
772 }
773
774 /**
775 * Builds the set of plugins to load. Deprecated and unmaintained plugins are filtered
776 * out. This involves user interaction. This method displays alert and confirmation
777 * messages.
778 *
779 * @param parent The parent component to be used for the displayed dialog
780 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
781 * @return the set of plugins to load (as set of plugin names)
782 */
783 public static List<PluginInformation> buildListOfPluginsToLoad(Component parent, ProgressMonitor monitor) {
784 if (monitor == null) {
785 monitor = NullProgressMonitor.INSTANCE;
786 }
787 try {
788 monitor.beginTask(tr("Determine plugins to load..."));
789 Set<String> plugins = new HashSet<>();
790 plugins.addAll(Main.pref.getCollection("plugins", new LinkedList<String>()));
791 if (System.getProperty("josm.plugins") != null) {
792 plugins.addAll(Arrays.asList(System.getProperty("josm.plugins").split(",")));
793 }
794 monitor.subTask(tr("Removing deprecated plugins..."));
795 filterDeprecatedPlugins(parent, plugins);
796 monitor.subTask(tr("Removing unmaintained plugins..."));
797 filterUnmaintainedPlugins(parent, plugins);
798 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1,false));
799 List<PluginInformation> ret = new LinkedList<>();
800 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
801 String plugin = it.next();
802 if (infos.containsKey(plugin)) {
803 ret.add(infos.get(plugin));
804 it.remove();
805 }
806 }
807 if (!plugins.isEmpty()) {
808 alertMissingPluginInformation(parent, plugins);
809 }
810 return ret;
811 } finally {
812 monitor.finishTask();
813 }
814 }
815
816 private static void alertFailedPluginUpdate(Component parent, Collection<PluginInformation> plugins) {
817 StringBuilder sb = new StringBuilder();
818 sb.append("<html>")
819 .append(trn(
820 "Updating the following plugin has failed:",
821 "Updating the following plugins has failed:",
822 plugins.size()))
823 .append("<ul>");
824 for (PluginInformation pi: plugins) {
825 sb.append("<li>").append(pi.name).append("</li>");
826 }
827 sb.append("</ul>")
828 .append(trn(
829 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
830 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
831 plugins.size()))
832 .append("</html>");
833 HelpAwareOptionPane.showOptionDialog(
834 parent,
835 sb.toString(),
836 tr("Plugin update failed"),
837 JOptionPane.ERROR_MESSAGE,
838 HelpUtil.ht("/Plugin/Loading#FailedPluginUpdated")
839 );
840 }
841
842 private static Set<PluginInformation> findRequiredPluginsToDownload(
843 Collection<PluginInformation> pluginsToUpdate, List<PluginInformation> allPlugins, Set<PluginInformation> pluginsToDownload) {
844 Set<PluginInformation> result = new HashSet<>();
845 for (PluginInformation pi : pluginsToUpdate) {
846 for (String name : pi.getRequiredPlugins()) {
847 try {
848 PluginInformation installedPlugin = PluginInformation.findPlugin(name);
849 if (installedPlugin == null) {
850 // New required plugin is not installed, find its PluginInformation
851 PluginInformation reqPlugin = null;
852 for (PluginInformation pi2 : allPlugins) {
853 if (pi2.getName().equals(name)) {
854 reqPlugin = pi2;
855 break;
856 }
857 }
858 // Required plugin is known but not already on download list
859 if (reqPlugin != null && !pluginsToDownload.contains(reqPlugin)) {
860 result.add(reqPlugin);
861 }
862 }
863 } catch (PluginException e) {
864 Main.warn(tr("Failed to find plugin {0}", name));
865 Main.error(e);
866 }
867 }
868 }
869 return result;
870 }
871
872 /**
873 * Updates the plugins in <code>plugins</code>.
874 *
875 * @param parent the parent component for message boxes
876 * @param pluginsWanted the collection of plugins to update. Updates all plugins if {@code null}
877 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
878 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
879 * @throws IllegalArgumentException if plugins is null
880 */
881 public static Collection<PluginInformation> updatePlugins(Component parent,
882 Collection<PluginInformation> pluginsWanted, ProgressMonitor monitor, boolean displayErrMsg) {
883 Collection<PluginInformation> plugins = null;
884 pluginDownloadTask = null;
885 if (monitor == null) {
886 monitor = NullProgressMonitor.INSTANCE;
887 }
888 try {
889 monitor.beginTask("");
890 ExecutorService service = Executors.newSingleThreadExecutor();
891
892 // try to download the plugin lists
893 //
894 ReadRemotePluginInformationTask task1 = new ReadRemotePluginInformationTask(
895 monitor.createSubTaskMonitor(1, false),
896 Main.pref.getPluginSites(), displayErrMsg
897 );
898 Future<?> future = service.submit(task1);
899 List<PluginInformation> allPlugins = null;
900
901 try {
902 future.get();
903 allPlugins = task1.getAvailablePlugins();
904 plugins = buildListOfPluginsToLoad(parent, monitor.createSubTaskMonitor(1, false));
905 // If only some plugins have to be updated, filter the list
906 if (pluginsWanted != null && !pluginsWanted.isEmpty()) {
907 for (Iterator<PluginInformation> it = plugins.iterator(); it.hasNext();) {
908 PluginInformation pi = it.next();
909 boolean found = false;
910 for (PluginInformation piw : pluginsWanted) {
911 if (pi.name.equals(piw.name)) {
912 found = true;
913 break;
914 }
915 }
916 if (!found) {
917 it.remove();
918 }
919 }
920 }
921 } catch (ExecutionException e) {
922 Main.warn(tr("Failed to download plugin information list")+": ExecutionException");
923 Main.error(e);
924 // don't abort in case of error, continue with downloading plugins below
925 } catch (InterruptedException e) {
926 Main.warn(tr("Failed to download plugin information list")+": InterruptedException");
927 // don't abort in case of error, continue with downloading plugins below
928 }
929
930 // filter plugins which actually have to be updated
931 //
932 Collection<PluginInformation> pluginsToUpdate = new ArrayList<>();
933 for (PluginInformation pi: plugins) {
934 if (pi.isUpdateRequired()) {
935 pluginsToUpdate.add(pi);
936 }
937 }
938
939 if (!pluginsToUpdate.isEmpty()) {
940
941 Set<PluginInformation> pluginsToDownload = new HashSet<>(pluginsToUpdate);
942
943 if (allPlugins != null) {
944 // Updated plugins may need additional plugin dependencies currently not installed
945 //
946 Set<PluginInformation> additionalPlugins = findRequiredPluginsToDownload(pluginsToUpdate, allPlugins, pluginsToDownload);
947 pluginsToDownload.addAll(additionalPlugins);
948
949 // Iterate on required plugins, if they need themselves another plugins (i.e A needs B, but B needs C)
950 while (!additionalPlugins.isEmpty()) {
951 // Install the additional plugins to load them later
952 plugins.addAll(additionalPlugins);
953 additionalPlugins = findRequiredPluginsToDownload(additionalPlugins, allPlugins, pluginsToDownload);
954 pluginsToDownload.addAll(additionalPlugins);
955 }
956 }
957
958 // try to update the locally installed plugins
959 //
960 pluginDownloadTask = new PluginDownloadTask(
961 monitor.createSubTaskMonitor(1,false),
962 pluginsToDownload,
963 tr("Update plugins")
964 );
965
966 future = service.submit(pluginDownloadTask);
967 try {
968 future.get();
969 } catch(ExecutionException e) {
970 Main.error(e);
971 alertFailedPluginUpdate(parent, pluginsToUpdate);
972 return plugins;
973 } catch(InterruptedException e) {
974 Main.warn("InterruptedException in "+PluginHandler.class.getSimpleName()+" while updating plugins");
975 alertFailedPluginUpdate(parent, pluginsToUpdate);
976 return plugins;
977 }
978
979 // Update Plugin info for downloaded plugins
980 //
981 refreshLocalUpdatedPluginInfo(pluginDownloadTask.getDownloadedPlugins());
982
983 // notify user if downloading a locally installed plugin failed
984 //
985 if (! pluginDownloadTask.getFailedPlugins().isEmpty()) {
986 alertFailedPluginUpdate(parent, pluginDownloadTask.getFailedPlugins());
987 return plugins;
988 }
989 }
990 } finally {
991 monitor.finishTask();
992 }
993 if (pluginsWanted == null) {
994 // if all plugins updated, remember the update because it was successful
995 //
996 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion());
997 Main.pref.put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis()));
998 }
999 return plugins;
1000 }
1001
1002 /**
1003 * Ask the user for confirmation that a plugin shall be disabled.
1004 *
1005 * @param parent The parent component to be used for the displayed dialog
1006 * @param reason the reason for disabling the plugin
1007 * @param name the plugin name
1008 * @return true, if the plugin shall be disabled; false, otherwise
1009 */
1010 public static boolean confirmDisablePlugin(Component parent, String reason, String name) {
1011 ButtonSpec [] options = new ButtonSpec[] {
1012 new ButtonSpec(
1013 tr("Disable plugin"),
1014 ImageProvider.get("dialogs", "delete"),
1015 tr("Click to delete the plugin ''{0}''", name),
1016 null /* no specific help context */
1017 ),
1018 new ButtonSpec(
1019 tr("Keep plugin"),
1020 ImageProvider.get("cancel"),
1021 tr("Click to keep the plugin ''{0}''", name),
1022 null /* no specific help context */
1023 )
1024 };
1025 int ret = HelpAwareOptionPane.showOptionDialog(
1026 parent,
1027 reason,
1028 tr("Disable plugin"),
1029 JOptionPane.WARNING_MESSAGE,
1030 null,
1031 options,
1032 options[0],
1033 null // FIXME: add help topic
1034 );
1035 return ret == 0;
1036 }
1037
1038 /**
1039 * Returns the plugin of the specified name.
1040 * @param name The plugin name
1041 * @return The plugin of the specified name, if installed and loaded, or {@code null} otherwise.
1042 */
1043 public static Object getPlugin(String name) {
1044 for (PluginProxy plugin : pluginList)
1045 if (plugin.getPluginInformation().name.equals(name))
1046 return plugin.plugin;
1047 return null;
1048 }
1049
1050 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
1051 for (PluginProxy p : pluginList) {
1052 p.addDownloadSelection(downloadSelections);
1053 }
1054 }
1055
1056 public static Collection<PreferenceSettingFactory> getPreferenceSetting() {
1057 Collection<PreferenceSettingFactory> settings = new ArrayList<>();
1058 for (PluginProxy plugin : pluginList) {
1059 settings.add(new PluginPreferenceFactory(plugin));
1060 }
1061 return settings;
1062 }
1063
1064 /**
1065 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding
1066 * ".jar" files.
1067 *
1068 * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
1069 * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
1070 * installation of the respective plugin is silently skipped.
1071 *
1072 * @param dowarn if true, warning messages are displayed; false otherwise
1073 */
1074 public static void installDownloadedPlugins(boolean dowarn) {
1075 File pluginDir = Main.pref.getPluginsDirectory();
1076 if (! pluginDir.exists() || ! pluginDir.isDirectory() || ! pluginDir.canWrite())
1077 return;
1078
1079 final File[] files = pluginDir.listFiles(new FilenameFilter() {
1080 @Override
1081 public boolean accept(File dir, String name) {
1082 return name.endsWith(".jar.new");
1083 }});
1084 if (files == null)
1085 return;
1086
1087 for (File updatedPlugin : files) {
1088 final String filePath = updatedPlugin.getPath();
1089 File plugin = new File(filePath.substring(0, filePath.length() - 4));
1090 String pluginName = updatedPlugin.getName().substring(0, updatedPlugin.getName().length() - 8);
1091 if (plugin.exists() && !plugin.delete() && dowarn) {
1092 Main.warn(tr("Failed to delete outdated plugin ''{0}''.", plugin.toString()));
1093 Main.warn(tr("Failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM is still going to load the old plugin version.", pluginName));
1094 continue;
1095 }
1096 try {
1097 // Check the plugin is a valid and accessible JAR file before installing it (fix #7754)
1098 new JarFile(updatedPlugin).close();
1099 } catch (Exception e) {
1100 if (dowarn) {
1101 Main.warn(tr("Failed to install plugin ''{0}'' from temporary download file ''{1}''. {2}", plugin.toString(), updatedPlugin.toString(), e.getLocalizedMessage()));
1102 }
1103 continue;
1104 }
1105 // Install plugin
1106 if (!updatedPlugin.renameTo(plugin) && dowarn) {
1107 Main.warn(tr("Failed to install plugin ''{0}'' from temporary download file ''{1}''. Renaming failed.", plugin.toString(), updatedPlugin.toString()));
1108 Main.warn(tr("Failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM is still going to load the old plugin version.", pluginName));
1109 }
1110 }
1111 }
1112
1113 /**
1114 * Determines if the specified file is a valid and accessible JAR file.
1115 * @param jar The fil to check
1116 * @return true if file can be opened as a JAR file.
1117 * @since 5723
1118 */
1119 public static boolean isValidJar(File jar) {
1120 if (jar != null && jar.exists() && jar.canRead()) {
1121 try {
1122 new JarFile(jar).close();
1123 } catch (Exception e) {
1124 return false;
1125 }
1126 return true;
1127 }
1128 return false;
1129 }
1130
1131 /**
1132 * Replies the updated jar file for the given plugin name.
1133 * @param name The plugin name to find.
1134 * @return the updated jar file for the given plugin name. null if not found or not readable.
1135 * @since 5601
1136 */
1137 public static File findUpdatedJar(String name) {
1138 File pluginDir = Main.pref.getPluginsDirectory();
1139 // Find the downloaded file. We have tried to install the downloaded plugins
1140 // (PluginHandler.installDownloadedPlugins). This succeeds depending on the platform.
1141 File downloadedPluginFile = new File(pluginDir, name + ".jar.new");
1142 if (!isValidJar(downloadedPluginFile)) {
1143 downloadedPluginFile = new File(pluginDir, name + ".jar");
1144 if (!isValidJar(downloadedPluginFile)) {
1145 return null;
1146 }
1147 }
1148 return downloadedPluginFile;
1149 }
1150
1151 /**
1152 * Refreshes the given PluginInformation objects with new contents read from their corresponding jar file.
1153 * @param updatedPlugins The PluginInformation objects to update.
1154 * @since 5601
1155 */
1156 public static void refreshLocalUpdatedPluginInfo(Collection<PluginInformation> updatedPlugins) {
1157 if (updatedPlugins == null) return;
1158 for (PluginInformation pi : updatedPlugins) {
1159 File downloadedPluginFile = findUpdatedJar(pi.name);
1160 if (downloadedPluginFile == null) {
1161 continue;
1162 }
1163 try {
1164 pi.updateFromJar(new PluginInformation(downloadedPluginFile, pi.name));
1165 } catch(PluginException e) {
1166 Main.error(e);
1167 }
1168 }
1169 }
1170
1171 private static int askUpdateDisableKeepPluginAfterException(PluginProxy plugin) {
1172 final ButtonSpec[] options = new ButtonSpec[] {
1173 new ButtonSpec(
1174 tr("Update plugin"),
1175 ImageProvider.get("dialogs", "refresh"),
1176 tr("Click to update the plugin ''{0}''", plugin.getPluginInformation().name),
1177 null /* no specific help context */
1178 ),
1179 new ButtonSpec(
1180 tr("Disable plugin"),
1181 ImageProvider.get("dialogs", "delete"),
1182 tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name),
1183 null /* no specific help context */
1184 ),
1185 new ButtonSpec(
1186 tr("Keep plugin"),
1187 ImageProvider.get("cancel"),
1188 tr("Click to keep the plugin ''{0}''",plugin.getPluginInformation().name),
1189 null /* no specific help context */
1190 )
1191 };
1192
1193 final StringBuilder msg = new StringBuilder();
1194 msg.append("<html>")
1195 .append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", plugin.getPluginInformation().name))
1196 .append("<br>");
1197 if (plugin.getPluginInformation().author != null) {
1198 msg.append(tr("According to the information within the plugin, the author is {0}.", plugin.getPluginInformation().author))
1199 .append("<br>");
1200 }
1201 msg.append(tr("Try updating to the newest version of this plugin before reporting a bug."))
1202 .append("</html>");
1203
1204 try {
1205 FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
1206 @Override
1207 public Integer call() {
1208 return HelpAwareOptionPane.showOptionDialog(
1209 Main.parent,
1210 msg.toString(),
1211 tr("Update plugins"),
1212 JOptionPane.QUESTION_MESSAGE,
1213 null,
1214 options,
1215 options[0],
1216 ht("/ErrorMessages#ErrorInPlugin")
1217 );
1218 }
1219 });
1220 GuiHelper.runInEDT(task);
1221 return task.get();
1222 } catch (InterruptedException | ExecutionException e) {
1223 Main.warn(e);
1224 }
1225 return -1;
1226 }
1227
1228 /**
1229 * Replies the plugin which most likely threw the exception <code>ex</code>.
1230 *
1231 * @param ex the exception
1232 * @return the plugin; null, if the exception probably wasn't thrown from a plugin
1233 */
1234 private static PluginProxy getPluginCausingException(Throwable ex) {
1235 PluginProxy err = null;
1236 StackTraceElement[] stack = ex.getStackTrace();
1237 /* remember the error position, as multiple plugins may be involved,
1238 we search the topmost one */
1239 int pos = stack.length;
1240 for (PluginProxy p : pluginList) {
1241 String baseClass = p.getPluginInformation().className;
1242 baseClass = baseClass.substring(0, baseClass.lastIndexOf('.'));
1243 for (int elpos = 0; elpos < pos; ++elpos) {
1244 if (stack[elpos].getClassName().startsWith(baseClass)) {
1245 pos = elpos;
1246 err = p;
1247 }
1248 }
1249 }
1250 return err;
1251 }
1252
1253 /**
1254 * Checks whether the exception <code>e</code> was thrown by a plugin. If so,
1255 * conditionally updates or deactivates the plugin, but asks the user first.
1256 *
1257 * @param e the exception
1258 * @return plugin download task if the plugin has been updated to a newer version, {@code null} if it has been disabled or kept as it
1259 */
1260 public static PluginDownloadTask updateOrdisablePluginAfterException(Throwable e) {
1261 PluginProxy plugin = null;
1262 // Check for an explicit problem when calling a plugin function
1263 if (e instanceof PluginException) {
1264 plugin = ((PluginException) e).plugin;
1265 }
1266 if (plugin == null) {
1267 plugin = getPluginCausingException(e);
1268 }
1269 if (plugin == null)
1270 // don't know what plugin threw the exception
1271 return null;
1272
1273 Set<String> plugins = new HashSet<>(
1274 Main.pref.getCollection("plugins",Collections.<String> emptySet())
1275 );
1276 final PluginInformation pluginInfo = plugin.getPluginInformation();
1277 if (! plugins.contains(pluginInfo.name))
1278 // plugin not activated ? strange in this context but anyway, don't bother
1279 // the user with dialogs, skip conditional deactivation
1280 return null;
1281
1282 switch (askUpdateDisableKeepPluginAfterException(plugin)) {
1283 case 0:
1284 // update the plugin
1285 updatePlugins(Main.parent, Collections.singleton(pluginInfo), null, true);
1286 return pluginDownloadTask;
1287 case 1:
1288 // deactivate the plugin
1289 plugins.remove(plugin.getPluginInformation().name);
1290 Main.pref.putCollection("plugins", plugins);
1291 GuiHelper.runInEDTAndWait(new Runnable() {
1292 @Override
1293 public void run() {
1294 JOptionPane.showMessageDialog(
1295 Main.parent,
1296 tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."),
1297 tr("Information"),
1298 JOptionPane.INFORMATION_MESSAGE
1299 );
1300 }
1301 });
1302 return null;
1303 default:
1304 // user doesn't want to deactivate the plugin
1305 return null;
1306 }
1307 }
1308
1309 /**
1310 * Returns the list of loaded plugins as a {@code String} to be displayed in status report. Useful for bug reports.
1311 * @return The list of loaded plugins (one plugin per line)
1312 */
1313 public static String getBugReportText() {
1314 StringBuilder text = new StringBuilder();
1315 List <String> pl = new LinkedList<>(Main.pref.getCollection("plugins", new LinkedList<String>()));
1316 for (final PluginProxy pp : pluginList) {
1317 PluginInformation pi = pp.getPluginInformation();
1318 pl.remove(pi.name);
1319 pl.add(pi.name + " (" + (pi.localversion != null && !pi.localversion.isEmpty()
1320 ? pi.localversion : "unknown") + ")");
1321 }
1322 Collections.sort(pl);
1323 if (!pl.isEmpty()) {
1324 text.append("Plugins:\n");
1325 }
1326 for (String s : pl) {
1327 text.append("- ").append(s).append('\n');
1328 }
1329 return text.toString();
1330 }
1331
1332 /**
1333 * Returns the list of loaded plugins as a {@code JPanel} to be displayed in About dialog.
1334 * @return The list of loaded plugins (one "line" of Swing components per plugin)
1335 */
1336 public static JPanel getInfoPanel() {
1337 JPanel pluginTab = new JPanel(new GridBagLayout());
1338 for (final PluginProxy p : pluginList) {
1339 final PluginInformation info = p.getPluginInformation();
1340 String name = info.name
1341 + (info.version != null && !info.version.isEmpty() ? " Version: " + info.version : "");
1342 pluginTab.add(new JLabel(name), GBC.std());
1343 pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
1344 pluginTab.add(new JButton(new AbstractAction(tr("Information")) {
1345 @Override
1346 public void actionPerformed(ActionEvent event) {
1347 StringBuilder b = new StringBuilder();
1348 for (Entry<String, String> e : info.attr.entrySet()) {
1349 b.append(e.getKey());
1350 b.append(": ");
1351 b.append(e.getValue());
1352 b.append('\n');
1353 }
1354 JosmTextArea a = new JosmTextArea(10, 40);
1355 a.setEditable(false);
1356 a.setText(b.toString());
1357 a.setCaretPosition(0);
1358 JOptionPane.showMessageDialog(Main.parent, new JScrollPane(a), tr("Plugin information"),
1359 JOptionPane.INFORMATION_MESSAGE);
1360 }
1361 }), GBC.eol());
1362
1363 JosmTextArea description = new JosmTextArea(info.description == null ? tr("no description available")
1364 : info.description);
1365 description.setEditable(false);
1366 description.setFont(new JLabel().getFont().deriveFont(Font.ITALIC));
1367 description.setLineWrap(true);
1368 description.setWrapStyleWord(true);
1369 description.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
1370 description.setBackground(UIManager.getColor("Panel.background"));
1371 description.setCaretPosition(0);
1372
1373 pluginTab.add(description, GBC.eop().fill(GBC.HORIZONTAL));
1374 }
1375 return pluginTab;
1376 }
1377
1378 private static class UpdatePluginsMessagePanel extends JPanel {
1379 private JMultilineLabel lblMessage;
1380 private JCheckBox cbDontShowAgain;
1381
1382 protected final void build() {
1383 setLayout(new GridBagLayout());
1384 GridBagConstraints gc = new GridBagConstraints();
1385 gc.anchor = GridBagConstraints.NORTHWEST;
1386 gc.fill = GridBagConstraints.BOTH;
1387 gc.weightx = 1.0;
1388 gc.weighty = 1.0;
1389 gc.insets = new Insets(5,5,5,5);
1390 add(lblMessage = new JMultilineLabel(""), gc);
1391 lblMessage.setFont(lblMessage.getFont().deriveFont(Font.PLAIN));
1392
1393 gc.gridy = 1;
1394 gc.fill = GridBagConstraints.HORIZONTAL;
1395 gc.weighty = 0.0;
1396 add(cbDontShowAgain = new JCheckBox(tr("Do not ask again and remember my decision (go to Preferences->Plugins to change it later)")), gc);
1397 cbDontShowAgain.setFont(cbDontShowAgain.getFont().deriveFont(Font.PLAIN));
1398 }
1399
1400 public UpdatePluginsMessagePanel() {
1401 build();
1402 }
1403
1404 public void setMessage(String message) {
1405 lblMessage.setText(message);
1406 }
1407
1408 public void initDontShowAgain(String preferencesKey) {
1409 String policy = Main.pref.get(preferencesKey, "ask");
1410 policy = policy.trim().toLowerCase();
1411 cbDontShowAgain.setSelected(!"ask".equals(policy));
1412 }
1413
1414 public boolean isRememberDecision() {
1415 return cbDontShowAgain.isSelected();
1416 }
1417 }
1418}
Note: See TracBrowser for help on using the repository browser.