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

Last change on this file since 4877 was 4877, checked in by akks, 12 years ago

see #7200 - mark Curves plugin as obsolete (Arc moved to Utilsplugin2)

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