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

Last change on this file since 5299 was 5290, checked in by simon04, 12 years ago

fix #7608 - deprecate Epsg31287 plugin

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