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

Last change on this file since 6552 was 6544, checked in by simon04, 10 years ago

fix #9473 - Added wayselector plugin to josm core. This plugin was initially written by Marko Mäkelä (Skela).

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