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

Last change on this file was 19535, checked in by stoecker, 3 weeks ago

remove a lot of PMD warnings, especially outdated ignores, see #24635

  • Property svn:eol-style set to native
File size: 78.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.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.Component;
10import java.awt.Font;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Insets;
14import java.awt.event.ActionEvent;
15import java.io.File;
16import java.io.IOException;
17import java.net.MalformedURLException;
18import java.net.URL;
19import java.security.AccessController;
20import java.security.PrivilegedAction;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.Comparator;
26import java.util.HashMap;
27import java.util.HashSet;
28import java.util.Iterator;
29import java.util.LinkedList;
30import java.util.List;
31import java.util.Locale;
32import java.util.Map;
33import java.util.Map.Entry;
34import java.util.Objects;
35import java.util.ServiceLoader;
36import java.util.Set;
37import java.util.TreeMap;
38import java.util.TreeSet;
39import java.util.concurrent.CopyOnWriteArrayList;
40import java.util.concurrent.ExecutionException;
41import java.util.concurrent.Future;
42import java.util.concurrent.FutureTask;
43import java.util.concurrent.TimeUnit;
44import java.util.jar.JarFile;
45import java.util.stream.Collectors;
46
47import javax.swing.AbstractAction;
48import javax.swing.BorderFactory;
49import javax.swing.Box;
50import javax.swing.JButton;
51import javax.swing.JCheckBox;
52import javax.swing.JLabel;
53import javax.swing.JOptionPane;
54import javax.swing.JPanel;
55import javax.swing.JScrollPane;
56import javax.swing.UIManager;
57
58import jakarta.annotation.Nullable;
59import org.openstreetmap.josm.actions.RestartAction;
60import org.openstreetmap.josm.data.Preferences;
61import org.openstreetmap.josm.data.PreferencesUtils;
62import org.openstreetmap.josm.data.Version;
63import org.openstreetmap.josm.gui.HelpAwareOptionPane;
64import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
65import org.openstreetmap.josm.gui.MainApplication;
66import org.openstreetmap.josm.gui.download.DownloadSelection;
67import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
68import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
69import org.openstreetmap.josm.gui.progress.ProgressMonitor;
70import org.openstreetmap.josm.gui.util.GuiHelper;
71import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
72import org.openstreetmap.josm.gui.widgets.JosmTextArea;
73import org.openstreetmap.josm.io.NetworkManager;
74import org.openstreetmap.josm.io.OfflineAccessException;
75import org.openstreetmap.josm.spi.preferences.Config;
76import org.openstreetmap.josm.tools.Destroyable;
77import org.openstreetmap.josm.tools.GBC;
78import org.openstreetmap.josm.tools.I18n;
79import org.openstreetmap.josm.tools.ImageProvider;
80import org.openstreetmap.josm.tools.Logging;
81import org.openstreetmap.josm.tools.OpenBrowser;
82import org.openstreetmap.josm.tools.PlatformManager;
83import org.openstreetmap.josm.tools.ResourceProvider;
84import org.openstreetmap.josm.tools.SubclassFilteredCollection;
85import org.openstreetmap.josm.tools.Utils;
86
87/**
88 * PluginHandler is basically a collection of static utility functions used to bootstrap
89 * and manage the loaded plugins.
90 * @since 1326
91 */
92public final class PluginHandler {
93 private static final String DIALOGS = "dialogs";
94 private static final String WARNING = marktr("Warning");
95 private static final String HTML_START = "<html>";
96 private static final String HTML_END = "</html>";
97 private static final String UPDATE_PLUGINS = marktr("Update plugins");
98 private static final String CANCEL = "cancel";
99 private static final String PLUGINS = "plugins";
100 private static final String DISABLE_PLUGIN = marktr("Disable plugin");
101 private static final String PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY = "pluginmanager.version-based-update.policy";
102 private static final String PLUGINMANAGER_LASTUPDATE = "pluginmanager.lastupdate";
103 private static final String PLUGINMANAGER_TIME_BASED_UPDATE_POLICY = "pluginmanager.time-based-update.policy";
104
105 /**
106 * Deprecated plugins that are removed on start
107 */
108 static final List<DeprecatedPlugin> DEPRECATED_PLUGINS;
109 static {
110 String inCore = tr("integrated into main program");
111 String replacedByPlugin = marktr("replaced by new {0} plugin");
112 String noLongerRequired = tr("no longer required");
113
114 DEPRECATED_PLUGINS = Arrays.asList(
115 new DeprecatedPlugin("mappaint", inCore),
116 new DeprecatedPlugin("unglueplugin", inCore),
117 new DeprecatedPlugin("lang-de", inCore),
118 new DeprecatedPlugin("lang-en_GB", inCore),
119 new DeprecatedPlugin("lang-fr", inCore),
120 new DeprecatedPlugin("lang-it", inCore),
121 new DeprecatedPlugin("lang-pl", inCore),
122 new DeprecatedPlugin("lang-ro", inCore),
123 new DeprecatedPlugin("lang-ru", inCore),
124 new DeprecatedPlugin("ewmsplugin", inCore),
125 new DeprecatedPlugin("ywms", inCore),
126 new DeprecatedPlugin("tways-0.2", inCore),
127 new DeprecatedPlugin("geotagged", inCore),
128 new DeprecatedPlugin("landsat", tr(replacedByPlugin, "scanaerial")),
129 new DeprecatedPlugin("namefinder", inCore),
130 new DeprecatedPlugin("waypoints", inCore),
131 new DeprecatedPlugin("slippy_map_chooser", inCore),
132 new DeprecatedPlugin("tcx-support", tr(replacedByPlugin, "dataimport")),
133 new DeprecatedPlugin("usertools", inCore),
134 new DeprecatedPlugin("AgPifoJ", inCore),
135 new DeprecatedPlugin("utilsplugin", inCore),
136 new DeprecatedPlugin("ghost", inCore),
137 new DeprecatedPlugin("validator", inCore),
138 new DeprecatedPlugin("multipoly", inCore),
139 new DeprecatedPlugin("multipoly-convert", inCore),
140 new DeprecatedPlugin("remotecontrol", inCore),
141 new DeprecatedPlugin("imagery", inCore),
142 new DeprecatedPlugin("slippymap", inCore),
143 new DeprecatedPlugin("wmsplugin", inCore),
144 new DeprecatedPlugin("ParallelWay", inCore),
145 new DeprecatedPlugin("dumbutils", tr(replacedByPlugin, "utilsplugin2")),
146 new DeprecatedPlugin("ImproveWayAccuracy", inCore),
147 new DeprecatedPlugin("Curves", tr(replacedByPlugin, "utilsplugin2")),
148 new DeprecatedPlugin("epsg31287", inCore),
149 new DeprecatedPlugin("licensechange", noLongerRequired),
150 new DeprecatedPlugin("restart", inCore),
151 new DeprecatedPlugin("wayselector", inCore),
152 new DeprecatedPlugin("openstreetbugs", inCore),
153 new DeprecatedPlugin("nearclick", noLongerRequired),
154 new DeprecatedPlugin("notes", inCore),
155 new DeprecatedPlugin("mirrored_download", inCore),
156 new DeprecatedPlugin("ImageryCache", inCore),
157 new DeprecatedPlugin("commons-imaging", tr(replacedByPlugin, "apache-commons")),
158 new DeprecatedPlugin("missingRoads", tr(replacedByPlugin, "ImproveOsm")),
159 new DeprecatedPlugin("trafficFlowDirection", tr(replacedByPlugin, "ImproveOsm")),
160 new DeprecatedPlugin("kendzi3d-jogl", tr(replacedByPlugin, "jogl")),
161 new DeprecatedPlugin("josm-geojson", inCore),
162 new DeprecatedPlugin("proj4j", inCore),
163 new DeprecatedPlugin("OpenStreetView", tr(replacedByPlugin, "OpenStreetCam")),
164 new DeprecatedPlugin("imageryadjust", inCore),
165 new DeprecatedPlugin("walkingpapers", tr(replacedByPlugin, "fieldpapers")),
166 new DeprecatedPlugin("czechaddress", noLongerRequired),
167 new DeprecatedPlugin("kendzi3d_Improved_by_Andrei", noLongerRequired),
168 new DeprecatedPlugin("videomapping", noLongerRequired),
169 new DeprecatedPlugin("public_transport_layer", tr(replacedByPlugin, "pt_assistant")),
170 new DeprecatedPlugin("lakewalker", tr(replacedByPlugin, "scanaerial")),
171 new DeprecatedPlugin("download_along", inCore),
172 new DeprecatedPlugin("plastic_laf", noLongerRequired),
173 new DeprecatedPlugin("osmarender", noLongerRequired),
174 new DeprecatedPlugin("geojson", inCore),
175 new DeprecatedPlugin("gpxfilter", inCore),
176 new DeprecatedPlugin("tag2link", inCore),
177 new DeprecatedPlugin("rapid", tr(replacedByPlugin, "MapWithAI")),
178 new DeprecatedPlugin("MovementAlert", inCore),
179 new DeprecatedPlugin("OpenStreetCam", tr(replacedByPlugin, "KartaView")),
180 new DeprecatedPlugin("scoutsigns", tr(replacedByPlugin, "KartaView")),
181 new DeprecatedPlugin("javafx-osx", inCore),
182 new DeprecatedPlugin("javafx-unixoid", inCore),
183 new DeprecatedPlugin("javafx-windows", inCore),
184 new DeprecatedPlugin("wikidata", tr(replacedByPlugin, "osmwiki-dataitem")),
185 new DeprecatedPlugin("mapdust", noLongerRequired),
186 new DeprecatedPlugin("tofix", noLongerRequired)
187 );
188 Collections.sort(DEPRECATED_PLUGINS);
189 }
190
191 private PluginHandler() {
192 // Hide default constructor for utils classes
193 }
194
195 static final class PluginInformationAction extends AbstractAction {
196 private final PluginInformation info;
197
198 PluginInformationAction(PluginInformation info) {
199 super(tr("Information"));
200 this.info = info;
201 }
202
203 /**
204 * Returns plugin information text.
205 * @return plugin information text
206 */
207 public String getText() {
208 StringBuilder b = new StringBuilder();
209 Map<Object, Object> sorted = new TreeMap<>(Comparator.comparing(String::valueOf));
210 sorted.putAll(info.attr);
211 for (Entry<Object, Object> e : sorted.entrySet()) {
212 b.append(e.getKey())
213 .append(": ")
214 .append(e.getValue())
215 .append('\n');
216 }
217 return b.toString();
218 }
219
220 @Override
221 public void actionPerformed(ActionEvent event) {
222 String text = getText();
223 JosmTextArea a = new JosmTextArea(10, 40);
224 a.setEditable(false);
225 a.setText(text);
226 a.setCaretPosition(0);
227 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), new JScrollPane(a), tr("Plugin information"),
228 JOptionPane.INFORMATION_MESSAGE);
229 }
230 }
231
232 /**
233 * Description of a deprecated plugin
234 */
235 public static class DeprecatedPlugin implements Comparable<DeprecatedPlugin> {
236 /** Plugin name */
237 public final String name;
238 /** Short explanation about deprecation, can be {@code null} */
239 public final String reason;
240
241 /**
242 * Constructs a new {@code DeprecatedPlugin} with a given reason.
243 * @param name The plugin name
244 * @param reason The reason about deprecation
245 */
246 public DeprecatedPlugin(String name, String reason) {
247 this.name = name;
248 this.reason = reason;
249 }
250
251 @Override
252 public int hashCode() {
253 return Objects.hash(name, reason);
254 }
255
256 @Override
257 public boolean equals(Object obj) {
258 if (this == obj)
259 return true;
260 if (obj == null)
261 return false;
262 if (getClass() != obj.getClass())
263 return false;
264 DeprecatedPlugin other = (DeprecatedPlugin) obj;
265 if (name == null) {
266 if (other.name != null)
267 return false;
268 } else if (!name.equals(other.name))
269 return false;
270 if (reason == null) {
271 if (other.reason != null)
272 return false;
273 } else if (!reason.equals(other.reason))
274 return false;
275 return true;
276 }
277
278 @Override
279 public int compareTo(DeprecatedPlugin o) {
280 int d = name.compareTo(o.name);
281 if (d == 0)
282 d = reason.compareTo(o.reason);
283 return d;
284 }
285 }
286
287 /**
288 * List of unmaintained plugins. Not really up-to-date as the vast majority of plugins are not maintained after a few months, sadly...
289 */
290 static final List<String> UNMAINTAINED_PLUGINS = List.of(
291 "irsrectify", // See https://josm.openstreetmap.de/changeset/29404/osm/
292 "surveyor2", // See https://josm.openstreetmap.de/changeset/29404/osm/
293 "gpsbabelgui",
294 "Intersect_way",
295 "ContourOverlappingMerge", // See #11202, #11518, https://github.com/bularcasergiu/ContourOverlappingMerge/issues/1
296 "LaneConnector", // See #11468, #11518, https://github.com/TrifanAdrian/LanecConnectorPlugin/issues/1
297 "Remove.redundant.points" // See #11468, #11518, https://github.com/bularcasergiu/RemoveRedundantPoints (not even created an issue...)
298 );
299
300 /**
301 * Default time-based update interval, in days (pluginmanager.time-based-update.interval)
302 */
303 public static final int DEFAULT_TIME_BASED_UPDATE_INTERVAL = 30;
304
305 /**
306 * All installed and loaded plugins (resp. their main classes)
307 */
308 static final Collection<PluginProxy> pluginList = new CopyOnWriteArrayList<>();
309
310 /**
311 * All installed but not loaded plugins
312 */
313 static final Collection<PluginInformation> pluginListNotLoaded = new LinkedList<>();
314
315 /**
316 * All exceptions that occurred during plugin loading
317 */
318 static final Map<String, Throwable> pluginLoadingExceptions = new HashMap<>();
319
320 /**
321 * Class loader to locate resources from plugins.
322 * @see #getJoinedPluginResourceCL()
323 */
324 private static DynamicURLClassLoader joinedPluginResourceCL;
325
326 /**
327 * Add here all ClassLoader whose resource should be searched.
328 */
329 private static final List<ClassLoader> sources = new LinkedList<>();
330 static {
331 try {
332 sources.add(ClassLoader.getSystemClassLoader());
333 sources.add(PluginHandler.class.getClassLoader());
334 } catch (SecurityException ex) {
335 Logging.debug(ex);
336 sources.add(ImageProvider.class.getClassLoader());
337 }
338 }
339
340 /**
341 * Plugin class loaders.
342 */
343 private static final Map<String, PluginClassLoader> classLoaders = new HashMap<>();
344
345 private static PluginDownloadTask pluginDownloadTask;
346
347 /**
348 * Returns the list of currently installed and loaded plugins, sorted by name.
349 * @return the list of currently installed and loaded plugins, sorted by name
350 * @since 10982
351 */
352 public static List<PluginInformation> getPlugins() {
353 return pluginList.stream().map(PluginProxy::getPluginInformation)
354 .sorted(Comparator.comparing(PluginInformation::getName)).collect(Collectors.toList());
355 }
356
357 /**
358 * Returns all ClassLoaders whose resource should be searched.
359 * @return all ClassLoaders whose resource should be searched
360 */
361 public static Collection<ClassLoader> getResourceClassLoaders() {
362 return Collections.unmodifiableCollection(sources);
363 }
364
365 /**
366 * Returns all plugin classloaders.
367 * @return all plugin classloaders
368 * @since 14978
369 */
370 public static Collection<PluginClassLoader> getPluginClassLoaders() {
371 return Collections.unmodifiableCollection(classLoaders.values());
372 }
373
374 /**
375 * Get a {@link ServiceLoader} for the specified service. This uses {@link #getJoinedPluginResourceCL()} as the
376 * class loader, so that we don't have to iterate through the {@link ClassLoader}s from {@link #getPluginClassLoaders()}.
377 * @param <S> The service type
378 * @param service The service class to look for
379 * @return The service loader
380 * @since 18833
381 */
382 public static <S> ServiceLoader<S> load(Class<S> service) {
383 return ServiceLoader.load(service, getJoinedPluginResourceCL());
384 }
385
386 /**
387 * Removes deprecated plugins from a collection of plugins. Modifies the
388 * collection <code>plugins</code>.
389 * <p>
390 * Also notifies the user about removed deprecated plugins
391 *
392 * @param parent The parent Component used to display warning popup
393 * @param plugins the collection of plugins
394 */
395 static void filterDeprecatedPlugins(Component parent, Collection<String> plugins) {
396 Set<DeprecatedPlugin> removedPlugins = new TreeSet<>();
397 for (DeprecatedPlugin depr : DEPRECATED_PLUGINS) {
398 if (plugins.contains(depr.name)) {
399 plugins.remove(depr.name);
400 PreferencesUtils.removeFromList(Config.getPref(), PLUGINS, depr.name);
401 removedPlugins.add(depr);
402 }
403 }
404 if (removedPlugins.isEmpty())
405 return;
406
407 // notify user about removed deprecated plugins
408 //
409 JOptionPane.showMessageDialog(
410 parent,
411 getRemovedPluginsMessage(removedPlugins),
412 tr(WARNING),
413 JOptionPane.WARNING_MESSAGE
414 );
415 }
416
417 static String getRemovedPluginsMessage(Collection<DeprecatedPlugin> removedPlugins) {
418 StringBuilder sb = new StringBuilder(32);
419 sb.append(HTML_START)
420 .append(trn(
421 "The following plugin is no longer necessary and has been deactivated:",
422 "The following plugins are no longer necessary and have been deactivated:",
423 removedPlugins.size()))
424 .append("<ul>");
425 for (DeprecatedPlugin depr: removedPlugins) {
426 sb.append("<li>").append(depr.name);
427 if (depr.reason != null) {
428 sb.append(" (").append(depr.reason).append(')');
429 }
430 sb.append("</li>");
431 }
432 sb.append("</ul>").append(HTML_END);
433 return sb.toString();
434 }
435
436 /**
437 * Removes unmaintained plugins from a collection of plugins. Modifies the
438 * collection <code>plugins</code>. Also removes the plugin from the list
439 * of plugins in the preferences, if necessary.
440 * <p>
441 * Asks the user for every unmaintained plugin whether it should be removed.
442 * @param parent The parent Component used to display warning popup
443 *
444 * @param plugins the collection of plugins
445 */
446 static void filterUnmaintainedPlugins(Component parent, Collection<String> plugins) {
447 for (String unmaintained : UNMAINTAINED_PLUGINS) {
448 if (!plugins.contains(unmaintained)) {
449 continue;
450 }
451 if (confirmDisablePlugin(parent, getUnmaintainedPluginMessage(unmaintained), unmaintained)) {
452 PreferencesUtils.removeFromList(Config.getPref(), PLUGINS, unmaintained);
453 plugins.remove(unmaintained);
454 }
455 }
456 }
457
458 static String getUnmaintainedPluginMessage(String unmaintained) {
459 return tr("<html>Loading of the plugin \"{0}\" was requested."
460 + "<br>This plugin is no longer developed and very likely will produce errors."
461 +"<br>It should be disabled.<br>Delete from preferences?</html>",
462 Utils.escapeReservedCharactersHTML(unmaintained));
463 }
464
465 /**
466 * Checks whether the locally available plugins should be updated and
467 * asks the user if running an update is OK. An update is advised if
468 * JOSM was updated to a new version since the last plugin updates or
469 * if the plugins were last updated a long time ago.
470 *
471 * @param parent the parent component relative to which the confirmation dialog
472 * is to be displayed
473 * @return true if a plugin update should be run; false, otherwise
474 */
475 public static boolean checkAndConfirmPluginUpdate(Component parent) {
476 if (Preferences.main().getPluginSites().stream().anyMatch(NetworkManager::isOffline)) {
477 Logging.info(OfflineAccessException.forResource(tr("Plugin update")).getMessage());
478 return false;
479 }
480 String message = null;
481 String togglePreferenceKey = null;
482 int v = Version.getInstance().getVersion();
483 if (Config.getPref().getInt("pluginmanager.version", 0) < v) {
484 message =
485 HTML_START
486 + tr("You updated your JOSM software.<br>"
487 + "To prevent problems the plugins should be updated as well.<br><br>"
488 + "Update plugins now?"
489 )
490 + HTML_END;
491 togglePreferenceKey = PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY;
492 } else {
493 long tim = System.currentTimeMillis();
494 long last = Config.getPref().getLong(PLUGINMANAGER_LASTUPDATE, 0);
495 int maxTime = Config.getPref().getInt("pluginmanager.time-based-update.interval", DEFAULT_TIME_BASED_UPDATE_INTERVAL);
496 long d = TimeUnit.MILLISECONDS.toDays(tim - last);
497 if ((last <= 0) || (maxTime <= 0)) {
498 Config.getPref().put(PLUGINMANAGER_LASTUPDATE, Long.toString(tim));
499 } else if (d > maxTime) {
500 message =
501 HTML_START
502 + tr("Last plugin update more than {0} days ago.", d)
503 + HTML_END;
504 togglePreferenceKey = PLUGINMANAGER_TIME_BASED_UPDATE_POLICY;
505 }
506 }
507 if (message == null) return false;
508
509 UpdatePluginsMessagePanel pnlMessage = new UpdatePluginsMessagePanel();
510 pnlMessage.setMessage(message);
511 pnlMessage.initDontShowAgain(togglePreferenceKey);
512
513 // check whether automatic update at startup was disabled
514 //
515 String policy = Config.getPref().get(togglePreferenceKey, "ask").trim().toLowerCase(Locale.ENGLISH);
516 switch (policy) {
517 case "never":
518 if (PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) {
519 Logging.info(tr("Skipping plugin update after JOSM upgrade. Automatic update at startup is disabled."));
520 } else if (PLUGINMANAGER_TIME_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) {
521 Logging.info(tr("Skipping plugin update after elapsed update interval. Automatic update at startup is disabled."));
522 }
523 return false;
524
525 case "always":
526 if (PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) {
527 Logging.info(tr("Running plugin update after JOSM upgrade. Automatic update at startup is enabled."));
528 } else if (PLUGINMANAGER_TIME_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) {
529 Logging.info(tr("Running plugin update after elapsed update interval. Automatic update at startup is disabled."));
530 }
531 return true;
532
533 case "ask":
534 break;
535
536 default:
537 Logging.warn(tr("Unexpected value ''{0}'' for preference ''{1}''. Assuming value ''ask''.", policy, togglePreferenceKey));
538 }
539
540 ButtonSpec[] options = {
541 new ButtonSpec(
542 tr(UPDATE_PLUGINS),
543 new ImageProvider(DIALOGS, "refresh"),
544 tr("Click to update the activated plugins"),
545 null /* no specific help context */
546 ),
547 new ButtonSpec(
548 tr("Skip update"),
549 new ImageProvider(CANCEL),
550 tr("Click to skip updating the activated plugins"),
551 null /* no specific help context */
552 )
553 };
554
555 int ret = HelpAwareOptionPane.showOptionDialog(
556 parent,
557 pnlMessage,
558 tr(UPDATE_PLUGINS),
559 JOptionPane.WARNING_MESSAGE,
560 null,
561 options,
562 options[0],
563 ht("/Preferences/Plugins#AutomaticUpdate")
564 );
565
566 if (pnlMessage.isRememberDecision()) {
567 switch (ret) {
568 case 0:
569 Config.getPref().put(togglePreferenceKey, "always");
570 break;
571 case JOptionPane.CLOSED_OPTION:
572 case 1:
573 Config.getPref().put(togglePreferenceKey, "never");
574 break;
575 default: // Do nothing
576 }
577 } else {
578 Config.getPref().put(togglePreferenceKey, "ask");
579 }
580 return ret == 0;
581 }
582
583 /**
584 * Alerts the user if a plugin required by another plugin is missing, and offer to download them &amp; restart JOSM
585 *
586 * @param parent The parent Component used to display error popup
587 * @param plugin the plugin
588 * @param missingRequiredPlugin the missing required plugin
589 */
590 private static void alertMissingRequiredPlugin(Component parent, String plugin, Set<String> missingRequiredPlugin) {
591 StringBuilder sb = new StringBuilder(48);
592 sb.append(HTML_START)
593 .append(trn("Plugin {0} requires a plugin which was not found. The missing plugin is:",
594 "Plugin {0} requires {1} plugins which were not found. The missing plugins are:",
595 missingRequiredPlugin.size(),
596 Utils.escapeReservedCharactersHTML(plugin),
597 missingRequiredPlugin.size()))
598 .append(Utils.joinAsHtmlUnorderedList(missingRequiredPlugin))
599 .append(HTML_END);
600 ButtonSpec[] specs = {
601 new ButtonSpec(
602 tr("Download and restart"),
603 new ImageProvider("restart"),
604 trn("Click to download missing plugin and restart JOSM",
605 "Click to download missing plugins and restart JOSM",
606 missingRequiredPlugin.size()),
607 null /* no specific help text */
608 ),
609 new ButtonSpec(
610 tr("Continue"),
611 new ImageProvider("ok"),
612 trn("Click to continue without this plugin",
613 "Click to continue without these plugins",
614 missingRequiredPlugin.size()),
615 null /* no specific help text */
616 )
617 };
618 if (0 == HelpAwareOptionPane.showOptionDialog(
619 parent,
620 sb.toString(),
621 tr("Error"),
622 JOptionPane.ERROR_MESSAGE,
623 null, /* no special icon */
624 specs,
625 specs[0],
626 ht("/Plugin/Loading#MissingRequiredPlugin"))) {
627 downloadRequiredPluginsAndRestart(parent, missingRequiredPlugin);
628 }
629 }
630
631 private static void downloadRequiredPluginsAndRestart(final Component parent, final Set<String> missingRequiredPlugin) {
632 // Update plugin list
633 final ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask(
634 Preferences.main().getOnlinePluginSites());
635 MainApplication.worker.submit(pluginInfoDownloadTask);
636
637 // Continuation
638 MainApplication.worker.submit(() -> {
639 // Build list of plugins to download
640 Set<PluginInformation> toDownload = new HashSet<>(pluginInfoDownloadTask.getAvailablePlugins());
641 toDownload.removeIf(info -> !missingRequiredPlugin.contains(info.getName()));
642 // Check if something has still to be downloaded
643 if (!toDownload.isEmpty()) {
644 // download plugins
645 final PluginDownloadTask task = new PluginDownloadTask(parent, toDownload, tr("Download plugins"));
646 MainApplication.worker.submit(task);
647 MainApplication.worker.submit(() -> {
648 // restart if some plugins have been downloaded
649 if (!task.getDownloadedPlugins().isEmpty()) {
650 // update plugin list in preferences
651 Set<String> plugins = new HashSet<>(Config.getPref().getList(PLUGINS));
652 for (PluginInformation plugin : task.getDownloadedPlugins()) {
653 plugins.add(plugin.name);
654 }
655 Config.getPref().putList(PLUGINS, new ArrayList<>(plugins));
656 // restart
657 RestartAction.restartJOSM();
658 } else {
659 Logging.warn("No plugin downloaded, restart canceled");
660 }
661 });
662 } else {
663 Logging.warn("No plugin to download, operation canceled");
664 }
665 });
666 }
667
668 private static void logWrongPlatform(String plugin, String pluginPlatform) {
669 Logging.warn(
670 tr("Plugin {0} must be run on a {1} platform.",
671 plugin, pluginPlatform
672 ));
673 }
674
675 private static void alertJavaUpdateRequired(Component parent, String plugin, int requiredVersion) {
676 final ButtonSpec[] options = {
677 new ButtonSpec(tr("OK"), ImageProvider.get("ok"), tr("Click to close the dialog"), null),
678 new ButtonSpec(tr("Update Java"), ImageProvider.get("java"), tr("Update Java"), null)
679 };
680 final int selected = HelpAwareOptionPane.showOptionDialog(
681 parent,
682 HTML_START + tr("Plugin {0} requires Java version {1}. The current Java version is {2}.<br>"
683 + "You have to update Java in order to use this plugin.",
684 plugin, Integer.toString(requiredVersion), Utils.getJavaVersion()
685 ) + HTML_END,
686 tr(WARNING),
687 JOptionPane.WARNING_MESSAGE,
688 null,
689 options,
690 options[0],
691 null
692 );
693 if (selected == 1 && !Utils.isRunningWebStart()) {
694 final String javaUrl = PlatformManager.getPlatform().getJavaUrl();
695 OpenBrowser.displayUrl(javaUrl);
696 }
697 }
698
699 private static void alertJOSMUpdateRequired(Component parent, String plugin, int requiredVersion) {
700 HelpAwareOptionPane.showOptionDialog(
701 parent,
702 tr("<html>Plugin {0} requires JOSM version {1}. The current JOSM version is {2}.<br>"
703 +"You have to update JOSM in order to use this plugin.</html>",
704 plugin, Integer.toString(requiredVersion), Version.getInstance().getVersionString()
705 ),
706 tr(WARNING),
707 JOptionPane.WARNING_MESSAGE,
708 null
709 );
710 }
711
712 /**
713 * Checks whether all preconditions for loading the plugin <code>plugin</code> are met. The
714 * current Java and JOSM versions must be compatible with the plugin and no other plugins this plugin
715 * depends on should be missing.
716 *
717 * @param parent The parent Component used to display error popup
718 * @param plugins the collection of all loaded plugins
719 * @param plugin the plugin for which preconditions are checked
720 * @return true, if the preconditions are met; false otherwise
721 */
722 public static boolean checkLoadPreconditions(Component parent, Collection<PluginInformation> plugins, PluginInformation plugin) {
723
724 // make sure the plugin is not meant for another platform
725 if (!plugin.isForCurrentPlatform()) {
726 // Just log a warning, this is unlikely to happen as we display only relevant plugins in HMI
727 logWrongPlatform(plugin.name, plugin.platform);
728 return false;
729 }
730
731 // make sure the plugin is compatible with the current Java version
732 if (plugin.localminjavaversion > Utils.getJavaVersion()) {
733 alertJavaUpdateRequired(parent, plugin.name, plugin.localminjavaversion);
734 return false;
735 }
736
737 // make sure the plugin is compatible with the current JOSM version
738 int josmVersion = Version.getInstance().getVersion();
739 if (plugin.localmainversion > josmVersion && josmVersion != Version.JOSM_UNKNOWN_VERSION) {
740 alertJOSMUpdateRequired(parent, plugin.name, plugin.localmainversion);
741 return false;
742 }
743
744 // Add all plugins already loaded (to include early plugins when checking late ones)
745 Collection<PluginInformation> allPlugins = new HashSet<>(plugins);
746 for (PluginProxy proxy : pluginList) {
747 allPlugins.add(proxy.getPluginInformation());
748 }
749
750 // Include plugins that have been processed but not been loaded (for javafx plugin)
751 allPlugins.addAll(pluginListNotLoaded);
752
753 return checkRequiredPluginsPreconditions(parent, allPlugins, plugin, true);
754 }
755
756 /**
757 * Checks if required plugins preconditions for loading the plugin <code>plugin</code> are met.
758 * No other plugins this plugin depends on should be missing.
759 *
760 * @param parent The parent Component used to display error popup. If parent is
761 * null, the error popup is suppressed
762 * @param plugins the collection of all processed plugins
763 * @param plugin the plugin for which preconditions are checked
764 * @param local Determines if the local or up-to-date plugin dependencies are to be checked.
765 * @return true, if the preconditions are met; false otherwise
766 * @since 5601
767 */
768 public static boolean checkRequiredPluginsPreconditions(Component parent, Collection<PluginInformation> plugins,
769 PluginInformation plugin, boolean local) {
770
771 String requires = local ? plugin.localrequires : plugin.requires;
772
773 // make sure the dependencies to other plugins are not broken
774 if (requires != null) {
775 Set<String> missingPlugins = findMissingPlugins(plugins, plugin, local);
776 if (!missingPlugins.isEmpty()) {
777 if (parent != null) {
778 alertMissingRequiredPlugin(parent, plugin.name, missingPlugins);
779 }
780 return false;
781 }
782 }
783 return true;
784 }
785
786 /**
787 * Find the missing plugin(s) for a specified plugin
788 * @param plugins The currently loaded plugins
789 * @param plugin The plugin to find the missing information for
790 * @param local Determines if the local or up-to-date plugin dependencies are to be checked.
791 * @return A set of missing plugins for the given plugin
792 */
793 private static Set<String> findMissingPlugins(Collection<PluginInformation> plugins, PluginInformation plugin, boolean local) {
794 Set<String> pluginNames = new HashSet<>();
795 for (PluginInformation pi: plugins) {
796 pluginNames.add(pi.name);
797 if (pi.provides != null) {
798 pluginNames.add(pi.provides);
799 }
800 }
801 Set<String> missingPlugins = new HashSet<>();
802 List<String> requiredPlugins = local ? plugin.getLocalRequiredPlugins() : plugin.getRequiredPlugins();
803 for (String requiredPlugin : requiredPlugins) {
804 if (!pluginNames.contains(requiredPlugin)) {
805 missingPlugins.add(requiredPlugin);
806 }
807 }
808 return missingPlugins;
809 }
810
811 /**
812 * Get class loader to locate resources from plugins.
813 * <p>
814 * It joins URLs of all plugins, to find images, etc.
815 * (Not for loading Java classes - each plugin has a separate {@link PluginClassLoader}
816 * for that purpose.)
817 * @return class loader to locate resources from plugins
818 */
819 private static synchronized DynamicURLClassLoader getJoinedPluginResourceCL() {
820 if (joinedPluginResourceCL == null) {
821 joinedPluginResourceCL = AccessController.doPrivileged((PrivilegedAction<DynamicURLClassLoader>)
822 () -> new DynamicURLClassLoader(new URL[0], PluginHandler.class.getClassLoader()));
823 sources.add(0, joinedPluginResourceCL);
824 }
825 return joinedPluginResourceCL;
826 }
827
828 /**
829 * Add more plugins to the joined plugin resource class loader.
830 *
831 * @param plugins the plugins to add
832 */
833 private static void extendJoinedPluginResourceCL(Collection<PluginInformation> plugins) {
834 // iterate all plugins and collect all libraries of all plugins:
835 File pluginDir = Preferences.main().getPluginsDirectory();
836 DynamicURLClassLoader cl = getJoinedPluginResourceCL();
837
838 for (PluginInformation info : plugins) {
839 if (info.libraries == null) {
840 continue;
841 }
842 for (URL libUrl : info.libraries) {
843 cl.addURL(libUrl);
844 }
845 File pluginJar = new File(pluginDir, info.name + ".jar");
846 I18n.addTexts(pluginJar);
847 URL pluginJarUrl = Utils.fileToURL(pluginJar);
848 cl.addURL(pluginJarUrl);
849 }
850 }
851
852 /**
853 * Loads and instantiates the plugin described by <code>plugin</code> using
854 * the class loader <code>pluginClassLoader</code>.
855 *
856 * @param parent The parent component to be used for the displayed dialog
857 * @param plugin the plugin
858 * @param pluginClassLoader the plugin class loader
859 */
860 private static void loadPlugin(Component parent, PluginInformation plugin, PluginClassLoader pluginClassLoader) {
861 String msg = tr("Could not load plugin {0}. Delete from preferences?", "'"+plugin.name+"'");
862 try {
863 Class<?> klass = plugin.loadClass(pluginClassLoader);
864 if (klass != null) {
865 Logging.info(tr("loading plugin ''{0}'' (version {1})", plugin.name, plugin.localversion));
866 PluginProxy pluginProxy = plugin.load(klass, pluginClassLoader);
867 pluginList.add(pluginProxy);
868 MainApplication.addAndFireMapFrameListener(pluginProxy);
869 }
870 msg = null;
871 } catch (PluginException e) {
872 pluginLoadingExceptions.put(plugin.name, e);
873 Logging.error(e);
874 if (e.getCause() instanceof ClassNotFoundException) {
875 msg = tr("<html>Could not load plugin {0} because the plugin<br>main class ''{1}'' was not found.<br>"
876 + "Delete from preferences?</html>", "'"+Utils.escapeReservedCharactersHTML(plugin.name)+"'", plugin.className);
877 }
878 } catch (RuntimeException e) {
879 pluginLoadingExceptions.put(plugin.name, e);
880 Logging.error(e);
881 }
882 if (msg != null && confirmDisablePlugin(parent, msg, plugin.name)) {
883 PreferencesUtils.removeFromList(Config.getPref(), PLUGINS, plugin.name);
884 }
885 }
886
887 /**
888 * Loads the plugin in <code>plugins</code> from locally available jar files into memory.
889 *
890 * @param parent The parent component to be used for the displayed dialog
891 * @param plugins the list of plugins
892 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
893 */
894 public static void loadPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
895 if (monitor == null) {
896 monitor = NullProgressMonitor.INSTANCE;
897 }
898 try {
899 monitor.beginTask(tr("Loading plugins ..."));
900 monitor.subTask(tr("Checking plugin preconditions..."));
901 List<PluginInformation> toLoad = new LinkedList<>();
902 for (PluginInformation pi: plugins) {
903 if (checkLoadPreconditions(parent, plugins, pi)) {
904 toLoad.add(pi);
905 } else {
906 pluginListNotLoaded.add(pi);
907 }
908 }
909 // sort the plugins according to their "staging" equivalence class. The
910 // lower the value of "stage" the earlier the plugin should be loaded.
911 //
912 toLoad.sort(Comparator.comparingInt(o -> o.stage));
913 if (toLoad.isEmpty())
914 return;
915
916 generateClassloaders(toLoad);
917
918 // resolve dependencies
919 resolveDependencies(toLoad);
920
921 extendJoinedPluginResourceCL(toLoad);
922 ResourceProvider.addAdditionalClassLoaders(getResourceClassLoaders());
923 monitor.setTicksCount(toLoad.size());
924 for (PluginInformation info : toLoad) {
925 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
926 loadPlugin(parent, info, classLoaders.get(info.name));
927 monitor.worked(1);
928 }
929 } finally {
930 monitor.finishTask();
931 }
932 }
933
934 /**
935 * Generate classloaders for a list of plugins
936 * @param toLoad The plugins to generate the classloaders for
937 */
938 @SuppressWarnings("squid:S2095") // NOSONAR the classloaders and put in a map which we want to keep.
939 private static void generateClassloaders(List<PluginInformation> toLoad) {
940 for (PluginInformation info : toLoad) {
941 PluginClassLoader cl = AccessController.doPrivileged((PrivilegedAction<PluginClassLoader>)
942 () -> new PluginClassLoader(
943 info.libraries.toArray(new URL[0]),
944 PluginHandler.class.getClassLoader(),
945 null));
946 classLoaders.put(info.name, cl);
947 }
948 }
949
950 /**
951 * Resolve dependencies for a list of plugins
952 * @param toLoad The plugins to resolve dependencies for
953 */
954 @SuppressWarnings("squid:S2095") // NOSONAR the classloaders are from a persistent map
955 private static void resolveDependencies(List<PluginInformation> toLoad) {
956 for (PluginInformation info : toLoad) {
957 PluginClassLoader cl = classLoaders.get(info.name);
958 for (String depName : info.getLocalRequiredPlugins()) {
959 boolean finished = false;
960 for (PluginInformation depInfo : toLoad) {
961 if (isDependency(depInfo, depName)) {
962 cl.addDependency(classLoaders.get(depInfo.name));
963 finished = true;
964 break;
965 }
966 }
967 if (finished) {
968 continue;
969 }
970 boolean found = false;
971 for (PluginProxy proxy : pluginList) {
972 if (isDependency(proxy.getPluginInformation(), depName)) {
973 found = cl.addDependency(proxy.getClassLoader());
974 break;
975 }
976 }
977 if (!found) {
978 Logging.error("unable to find dependency " + depName + " for plugin " + info.getName());
979 }
980 }
981 }
982 }
983
984 private static boolean isDependency(PluginInformation pi, String depName) {
985 return depName.equals(pi.getName()) || depName.equals(pi.provides);
986 }
987
988 /**
989 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early} set to true
990 * <i>and</i> a negative {@link PluginInformation#stage} value.
991 * <p>
992 * This is meant for plugins that provide additional {@link javax.swing.LookAndFeel}.
993 */
994 public static void loadVeryEarlyPlugins() {
995 List<PluginInformation> veryEarlyPlugins = PluginHandler.buildListOfPluginsToLoad(null, null)
996 .stream()
997 .filter(pi -> pi.early && pi.stage < 0)
998 .collect(Collectors.toList());
999 loadPlugins(null, veryEarlyPlugins, null);
1000 }
1001
1002 /**
1003 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early} set to true
1004 * <i>and</i> a non-negative {@link PluginInformation#stage} value.
1005 *
1006 * @param parent The parent component to be used for the displayed dialog
1007 * @param plugins the collection of plugins
1008 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
1009 */
1010 public static void loadEarlyPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
1011 List<PluginInformation> earlyPlugins = plugins.stream()
1012 .filter(pi -> pi.early && pi.stage >= 0)
1013 .collect(Collectors.toList());
1014 loadPlugins(parent, earlyPlugins, monitor);
1015 }
1016
1017 /**
1018 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early} set to false.
1019 *
1020 * @param parent The parent component to be used for the displayed dialog
1021 * @param plugins the collection of plugins
1022 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
1023 */
1024 public static void loadLatePlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
1025 List<PluginInformation> latePlugins = plugins.stream()
1026 .filter(pi -> !pi.early)
1027 .collect(Collectors.toList());
1028 loadPlugins(parent, latePlugins, monitor);
1029 }
1030
1031 /**
1032 * Loads locally available plugin information from local plugin jars and from cached
1033 * plugin lists.
1034 *
1035 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
1036 * @return the map of locally available plugin information, null in case of errors
1037 *
1038 */
1039 @Nullable
1040 @SuppressWarnings("squid:S1168") // The null return is part of the API for this method.
1041 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
1042 if (monitor == null) {
1043 monitor = NullProgressMonitor.INSTANCE;
1044 }
1045 try {
1046 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
1047 Future<?> future = MainApplication.worker.submit(task);
1048 try {
1049 future.get();
1050 } catch (ExecutionException e) {
1051 Logging.error(e);
1052 return null;
1053 } catch (InterruptedException e) {
1054 Thread.currentThread().interrupt();
1055 Logging.warn("InterruptedException in " + PluginHandler.class.getSimpleName()
1056 + " while loading locally available plugin information");
1057 return null;
1058 }
1059 Map<String, PluginInformation> ret = new HashMap<>();
1060 for (PluginInformation pi: task.getAvailablePlugins()) {
1061 ret.put(pi.name, pi);
1062 }
1063 return ret;
1064 } finally {
1065 monitor.finishTask();
1066 }
1067 }
1068
1069 private static void alertMissingPluginInformation(Component parent, Collection<String> plugins) {
1070 String sb = HTML_START +
1071 trn("JOSM could not find information about the following plugin:",
1072 "JOSM could not find information about the following plugins:",
1073 plugins.size()) +
1074 Utils.joinAsHtmlUnorderedList(plugins) +
1075 trn("The plugin is not going to be loaded.",
1076 "The plugins are not going to be loaded.",
1077 plugins.size()) +
1078 HTML_END;
1079 HelpAwareOptionPane.showOptionDialog(
1080 parent,
1081 sb,
1082 tr(WARNING),
1083 JOptionPane.WARNING_MESSAGE,
1084 ht("/Plugin/Loading#MissingPluginInfos")
1085 );
1086 }
1087
1088 /**
1089 * Builds the list of plugins to load. Deprecated and unmaintained plugins are filtered
1090 * out. This involves user interaction. This method displays alert and confirmation
1091 * messages.
1092 *
1093 * @param parent The parent component to be used for the displayed dialog
1094 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
1095 * @return the list of plugins to load (as set of plugin names)
1096 */
1097 public static List<PluginInformation> buildListOfPluginsToLoad(Component parent, ProgressMonitor monitor) {
1098 if (monitor == null) {
1099 monitor = NullProgressMonitor.INSTANCE;
1100 }
1101 try {
1102 monitor.beginTask(tr("Determining plugins to load..."));
1103 Set<String> plugins = new HashSet<>(Config.getPref().getList(PLUGINS, new LinkedList<>()));
1104 Logging.debug("Plugins list initialized to {0}", plugins);
1105 String systemProp = Utils.getSystemProperty("josm.plugins");
1106 if (systemProp != null) {
1107 plugins.addAll(Arrays.asList(systemProp.split(",", -1)));
1108 Logging.debug("josm.plugins system property set to ''{0}''. Plugins list is now {1}", systemProp, plugins);
1109 }
1110 monitor.subTask(tr("Removing deprecated plugins..."));
1111 filterDeprecatedPlugins(parent, plugins);
1112 monitor.subTask(tr("Removing unmaintained plugins..."));
1113 filterUnmaintainedPlugins(parent, plugins);
1114 Logging.debug("Plugins list is finally set to {0}", plugins);
1115 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1, false));
1116 List<PluginInformation> ret = new LinkedList<>();
1117 if (infos != null) {
1118 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
1119 String plugin = it.next();
1120 if (infos.containsKey(plugin)) {
1121 ret.add(infos.get(plugin));
1122 it.remove();
1123 }
1124 }
1125 }
1126 if (!plugins.isEmpty() && parent != null) {
1127 alertMissingPluginInformation(parent, plugins);
1128 }
1129 return ret;
1130 } finally {
1131 monitor.finishTask();
1132 }
1133 }
1134
1135 private static void alertFailedPluginUpdate(Component parent, Collection<PluginInformation> plugins) {
1136 StringBuilder sb = new StringBuilder(128);
1137 sb.append(HTML_START)
1138 .append(trn(
1139 "Updating the following plugin has failed:",
1140 "Updating the following plugins has failed:",
1141 plugins.size()))
1142 .append("<ul>");
1143 for (PluginInformation pi: plugins) {
1144 sb.append("<li>").append(Utils.escapeReservedCharactersHTML(pi.name)).append("</li>");
1145 }
1146 sb.append("</ul>")
1147 .append(trn(
1148 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
1149 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
1150 plugins.size()))
1151 .append(HTML_END);
1152 HelpAwareOptionPane.showOptionDialog(
1153 parent,
1154 sb.toString(),
1155 tr("Plugin update failed"),
1156 JOptionPane.ERROR_MESSAGE,
1157 ht("/Plugin/Loading#FailedPluginUpdated")
1158 );
1159 }
1160
1161 private static Set<PluginInformation> findRequiredPluginsToDownload(
1162 Collection<PluginInformation> pluginsToUpdate, List<PluginInformation> allPlugins, Set<PluginInformation> pluginsToDownload) {
1163 Set<PluginInformation> result = new HashSet<>();
1164 for (PluginInformation pi : pluginsToUpdate) {
1165 for (String name : pi.getRequiredPlugins()) {
1166 try {
1167 PluginInformation installedPlugin = PluginInformation.findPlugin(name);
1168 if (installedPlugin == null) {
1169 // New required plugin is not installed, find its PluginInformation
1170 PluginInformation reqPlugin = null;
1171 for (PluginInformation pi2 : allPlugins) {
1172 if (pi2.getName().equals(name)) {
1173 reqPlugin = pi2;
1174 break;
1175 }
1176 }
1177 // Required plugin is known but not already on download list
1178 if (reqPlugin != null && !pluginsToDownload.contains(reqPlugin)) {
1179 result.add(reqPlugin);
1180 }
1181 }
1182 } catch (PluginException e) {
1183 Logging.warn(tr("Failed to find plugin {0}", name));
1184 Logging.error(e);
1185 }
1186 }
1187 }
1188 return result;
1189 }
1190
1191 /**
1192 * Updates the plugins in <code>plugins</code>.
1193 *
1194 * @param parent the parent component for message boxes
1195 * @param pluginsWanted the collection of plugins to update. Updates all plugins if {@code null}
1196 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
1197 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
1198 * @return the list of plugins to load
1199 * @throws IllegalArgumentException if plugins is null
1200 */
1201 public static Collection<PluginInformation> updatePlugins(Component parent,
1202 Collection<PluginInformation> pluginsWanted, ProgressMonitor monitor, boolean displayErrMsg) {
1203 Collection<PluginInformation> plugins = null;
1204 pluginDownloadTask = null;
1205 if (monitor == null) {
1206 monitor = NullProgressMonitor.INSTANCE;
1207 }
1208 try {
1209 monitor.beginTask("");
1210
1211 // try to download the plugin lists
1212 ReadRemotePluginInformationTask task1 = new ReadRemotePluginInformationTask(
1213 monitor.createSubTaskMonitor(1, false),
1214 Preferences.main().getOnlinePluginSites(), displayErrMsg
1215 );
1216 List<PluginInformation> allPlugins = null;
1217 Future<?> future = MainApplication.worker.submit(task1);
1218
1219 try {
1220 future.get();
1221 allPlugins = task1.getAvailablePlugins();
1222 plugins = buildListOfPluginsToLoad(parent, monitor.createSubTaskMonitor(1, false));
1223 // If only some plugins have to be updated, filter the list
1224 if (!Utils.isEmpty(pluginsWanted)) {
1225 final Collection<String> pluginsWantedName = Utils.transform(pluginsWanted, piw -> piw.name);
1226 plugins = SubclassFilteredCollection.filter(plugins, pi -> pluginsWantedName.contains(pi.name));
1227 }
1228 } catch (ExecutionException e) {
1229 Logging.warn(tr("Failed to download plugin information list") + ": ExecutionException");
1230 Logging.error(e);
1231 // don't abort in case of error, continue with downloading plugins below
1232 } catch (InterruptedException e) {
1233 Thread.currentThread().interrupt();
1234 Logging.warn(tr("Failed to download plugin information list") + ": InterruptedException");
1235 // don't abort in case of error, continue with downloading plugins below
1236 }
1237
1238 // filter plugins which actually have to be updated
1239 Collection<PluginInformation> pluginsToUpdate = new ArrayList<>();
1240 if (plugins != null) {
1241 for (PluginInformation pi: plugins) {
1242 if (pi.isUpdateRequired()) {
1243 pluginsToUpdate.add(pi);
1244 }
1245 }
1246 }
1247
1248 if (!pluginsToUpdate.isEmpty()) {
1249
1250 Set<PluginInformation> pluginsToDownload = new HashSet<>(pluginsToUpdate);
1251
1252 if (allPlugins != null) {
1253 // Updated plugins may need additional plugin dependencies currently not installed
1254 //
1255 Set<PluginInformation> additionalPlugins = findRequiredPluginsToDownload(pluginsToUpdate, allPlugins, pluginsToDownload);
1256 pluginsToDownload.addAll(additionalPlugins);
1257
1258 // Iterate on required plugins, if they need themselves another plugins (i.e A needs B, but B needs C)
1259 while (!additionalPlugins.isEmpty()) {
1260 // Install the additional plugins to load them later
1261 if (plugins != null)
1262 plugins.addAll(additionalPlugins);
1263 additionalPlugins = findRequiredPluginsToDownload(additionalPlugins, allPlugins, pluginsToDownload);
1264 pluginsToDownload.addAll(additionalPlugins);
1265 }
1266 }
1267
1268 // try to update the locally installed plugins
1269 pluginDownloadTask = new PluginDownloadTask(
1270 monitor.createSubTaskMonitor(1, false),
1271 pluginsToDownload,
1272 tr(UPDATE_PLUGINS)
1273 );
1274 future = MainApplication.worker.submit(pluginDownloadTask);
1275
1276 try {
1277 future.get();
1278 } catch (ExecutionException e) {
1279 Logging.error(e);
1280 alertFailedPluginUpdate(parent, pluginsToUpdate);
1281 return plugins;
1282 } catch (InterruptedException e) {
1283 Thread.currentThread().interrupt();
1284 Logging.warn("InterruptedException in " + PluginHandler.class.getSimpleName()
1285 + " while updating plugins");
1286 alertFailedPluginUpdate(parent, pluginsToUpdate);
1287 return plugins;
1288 }
1289
1290 // Update Plugin info for downloaded plugins
1291 refreshLocalUpdatedPluginInfo(pluginDownloadTask.getDownloadedPlugins());
1292
1293 // notify user if downloading a locally installed plugin failed
1294 if (!pluginDownloadTask.getFailedPlugins().isEmpty()) {
1295 alertFailedPluginUpdate(parent, pluginDownloadTask.getFailedPlugins());
1296 return plugins;
1297 }
1298 }
1299 } finally {
1300 monitor.finishTask();
1301 }
1302 if (pluginsWanted == null) {
1303 // if all plugins updated, remember the update because it was successful
1304 Config.getPref().putInt("pluginmanager.version", Version.getInstance().getVersion());
1305 Config.getPref().put(PLUGINMANAGER_LASTUPDATE, Long.toString(System.currentTimeMillis()));
1306 }
1307 return plugins;
1308 }
1309
1310 /**
1311 * Ask the user for confirmation that a plugin shall be disabled.
1312 *
1313 * @param parent The parent component to be used for the displayed dialog
1314 * @param reason the reason for disabling the plugin
1315 * @param name the plugin name
1316 * @return true, if the plugin shall be disabled; false, otherwise
1317 */
1318 public static boolean confirmDisablePlugin(Component parent, String reason, String name) {
1319 ButtonSpec[] options = {
1320 new ButtonSpec(
1321 tr(DISABLE_PLUGIN),
1322 new ImageProvider(DIALOGS, "delete"),
1323 tr("Click to delete the plugin ''{0}''", name),
1324 null /* no specific help context */
1325 ),
1326 new ButtonSpec(
1327 tr("Keep plugin"),
1328 new ImageProvider(CANCEL),
1329 tr("Click to keep the plugin ''{0}''", name),
1330 null /* no specific help context */
1331 )
1332 };
1333 return 0 == HelpAwareOptionPane.showOptionDialog(
1334 parent,
1335 reason,
1336 tr(DISABLE_PLUGIN),
1337 JOptionPane.WARNING_MESSAGE,
1338 null,
1339 options,
1340 options[0],
1341 null // FIXME: add help topic
1342 );
1343 }
1344
1345 /**
1346 * Returns the plugin of the specified name.
1347 * @param name The plugin name
1348 * @return The plugin of the specified name, if installed and loaded, or {@code null} otherwise.
1349 */
1350 public static Object getPlugin(String name) {
1351 for (PluginProxy plugin : pluginList) {
1352 if (plugin.getPluginInformation().name.equals(name))
1353 return plugin.getPlugin();
1354 }
1355 return null;
1356 }
1357
1358 /**
1359 * Returns the plugin class loader for the plugin of the specified name.
1360 * @param name The plugin name
1361 * @return The plugin class loader for the plugin of the specified name, if
1362 * installed and loaded, or {@code null} otherwise.
1363 * @since 12323
1364 */
1365 public static PluginClassLoader getPluginClassLoader(String name) {
1366 for (PluginProxy plugin : pluginList) {
1367 if (plugin.getPluginInformation().name.equals(name))
1368 return plugin.getClassLoader();
1369 }
1370 return null;
1371 }
1372
1373 /**
1374 * Called in the download dialog to give the plugins a chance to modify the list
1375 * of bounding box selectors.
1376 * @param downloadSelections list of bounding box selectors
1377 */
1378 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
1379 for (PluginProxy p : pluginList) {
1380 p.addDownloadSelection(downloadSelections);
1381 }
1382 }
1383
1384 /**
1385 * Returns the list of plugin preference settings.
1386 * @return the list of plugin preference settings
1387 */
1388 public static Collection<PreferenceSettingFactory> getPreferenceSetting() {
1389 Collection<PreferenceSettingFactory> settings = new ArrayList<>();
1390 for (PluginProxy plugin : pluginList) {
1391 settings.add(new PluginPreferenceFactory(plugin));
1392 }
1393 return settings;
1394 }
1395
1396 /**
1397 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding ".jar" files.
1398 * <p>
1399 * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
1400 * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
1401 * installation of the respective plugin is silently skipped.
1402 *
1403 * @param pluginsToLoad list of plugin informations to update
1404 * @param dowarn if true, warning messages are displayed; false otherwise
1405 * @since 13294
1406 */
1407 public static void installDownloadedPlugins(Collection<PluginInformation> pluginsToLoad, boolean dowarn) {
1408 File pluginDir = Preferences.main().getPluginsDirectory();
1409 if (!pluginDir.exists() || !pluginDir.isDirectory() || !pluginDir.canWrite())
1410 return;
1411
1412 final File[] files = pluginDir.listFiles((dir, name) -> name.endsWith(".jar.new"));
1413 if (files == null)
1414 return;
1415
1416 for (File updatedPlugin : files) {
1417 final String filePath = updatedPlugin.getPath();
1418 File plugin = new File(filePath.substring(0, filePath.length() - 4));
1419 String pluginName = updatedPlugin.getName().substring(0, updatedPlugin.getName().length() - 8);
1420 try {
1421 // Check the plugin is a valid and accessible JAR file before installing it (fix #7754)
1422 new JarFile(updatedPlugin).close();
1423 } catch (IOException e) {
1424 if (dowarn) {
1425 Logging.log(Logging.LEVEL_WARN, tr("Failed to install plugin ''{0}'' from temporary download file ''{1}''. {2}",
1426 plugin.toString(), updatedPlugin.toString(), e.getLocalizedMessage()), e);
1427 }
1428 continue;
1429 }
1430 if (plugin.exists() && !plugin.delete() && dowarn) {
1431 Logging.warn(tr("Failed to delete outdated plugin ''{0}''.", plugin.toString()));
1432 Logging.warn(tr("Failed to install already downloaded plugin ''{0}''. " +
1433 "Skipping installation. JOSM is still going to load the old plugin version.",
1434 pluginName));
1435 continue;
1436 }
1437 // Install plugin
1438 if (updatedPlugin.renameTo(plugin)) {
1439 try {
1440 // Update plugin URL
1441 URL newPluginURL = plugin.toURI().toURL();
1442 URL oldPluginURL = updatedPlugin.toURI().toURL();
1443 pluginsToLoad.stream().filter(x -> x.libraries.contains(oldPluginURL)).forEach(
1444 x -> Collections.replaceAll(x.libraries, oldPluginURL, newPluginURL));
1445
1446 // Attempt to update loaded plugin (must implement Destroyable)
1447 PluginInformation tInfo = pluginsToLoad.parallelStream()
1448 .filter(x -> x.libraries.contains(newPluginURL)).findAny().orElse(null);
1449 if (tInfo != null) {
1450 Object tUpdatedPlugin = getPlugin(tInfo.name);
1451 if (tUpdatedPlugin instanceof Destroyable) {
1452 ((Destroyable) tUpdatedPlugin).destroy();
1453 PluginHandler.loadPlugins(getInfoPanel(), Collections.singleton(tInfo),
1454 NullProgressMonitor.INSTANCE);
1455 }
1456 }
1457 } catch (MalformedURLException e) {
1458 Logging.warn(e);
1459 }
1460 } else if (dowarn) {
1461 Logging.warn(tr("Failed to install plugin ''{0}'' from temporary download file ''{1}''. Renaming failed.",
1462 plugin.toString(), updatedPlugin.toString()));
1463 Logging.warn(tr("Failed to install already downloaded plugin ''{0}''. " +
1464 "Skipping installation. JOSM is still going to load the old plugin version.",
1465 pluginName));
1466 }
1467 }
1468 }
1469
1470 /**
1471 * Determines if the specified file is a valid and accessible JAR file.
1472 * @param jar The file to check
1473 * @return true if file can be opened as a JAR file.
1474 * @since 5723
1475 */
1476 public static boolean isValidJar(File jar) {
1477 if (jar != null && jar.exists() && jar.canRead()) {
1478 try {
1479 new JarFile(jar).close();
1480 } catch (IOException e) {
1481 Logging.warn(e);
1482 return false;
1483 }
1484 return true;
1485 } else if (jar != null) {
1486 Logging.debug("Invalid jar file ''"+jar+"'' (exists: "+jar.exists()+", canRead: "+jar.canRead()+')');
1487 }
1488 return false;
1489 }
1490
1491 /**
1492 * Replies the updated jar file for the given plugin name.
1493 * @param name The plugin name to find.
1494 * @return the updated jar file for the given plugin name. null if not found or not readable.
1495 * @since 5601
1496 */
1497 public static File findUpdatedJar(String name) {
1498 File pluginDir = Preferences.main().getPluginsDirectory();
1499 // Find the downloaded file. We have tried to install the downloaded plugins
1500 // (PluginHandler.installDownloadedPlugins). This succeeds depending on the platform.
1501 File downloadedPluginFile = new File(pluginDir, name + ".jar.new");
1502 if (!isValidJar(downloadedPluginFile)) {
1503 downloadedPluginFile = new File(pluginDir, name + ".jar");
1504 if (!isValidJar(downloadedPluginFile)) {
1505 return null;
1506 }
1507 }
1508 return downloadedPluginFile;
1509 }
1510
1511 /**
1512 * Refreshes the given PluginInformation objects with new contents read from their corresponding jar file.
1513 * @param updatedPlugins The PluginInformation objects to update.
1514 * @since 5601
1515 */
1516 public static void refreshLocalUpdatedPluginInfo(Collection<PluginInformation> updatedPlugins) {
1517 if (updatedPlugins == null) return;
1518 for (PluginInformation pi : updatedPlugins) {
1519 File downloadedPluginFile = findUpdatedJar(pi.name);
1520 if (downloadedPluginFile == null) {
1521 continue;
1522 }
1523 try {
1524 pi.updateFromJar(new PluginInformation(downloadedPluginFile, pi.name));
1525 } catch (PluginException e) {
1526 Logging.error(e);
1527 }
1528 }
1529 }
1530
1531 private static int askUpdateDisableKeepPluginAfterException(PluginProxy plugin) {
1532 final ButtonSpec[] options = {
1533 new ButtonSpec(
1534 tr("Update plugin"),
1535 new ImageProvider(DIALOGS, "refresh"),
1536 tr("Click to update the plugin ''{0}''", plugin.getPluginInformation().name),
1537 null /* no specific help context */
1538 ),
1539 new ButtonSpec(
1540 tr(DISABLE_PLUGIN),
1541 new ImageProvider(DIALOGS, "delete"),
1542 tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name),
1543 null /* no specific help context */
1544 ),
1545 new ButtonSpec(
1546 tr("Keep plugin"),
1547 new ImageProvider(CANCEL),
1548 tr("Click to keep the plugin ''{0}''", plugin.getPluginInformation().name),
1549 null /* no specific help context */
1550 )
1551 };
1552
1553 final StringBuilder msg = new StringBuilder(256);
1554 msg.append(HTML_START)
1555 .append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.",
1556 Utils.escapeReservedCharactersHTML(plugin.getPluginInformation().name)))
1557 .append("<br>");
1558 if (plugin.getPluginInformation().author != null) {
1559 msg.append(tr("According to the information within the plugin, the author is {0}.",
1560 Utils.escapeReservedCharactersHTML(plugin.getPluginInformation().author)))
1561 .append("<br>");
1562 }
1563 msg.append(tr("Try updating to the newest version of this plugin before reporting a bug."))
1564 .append(HTML_END);
1565
1566 try {
1567 FutureTask<Integer> task = new FutureTask<>(() -> HelpAwareOptionPane.showOptionDialog(
1568 MainApplication.getMainFrame(),
1569 msg.toString(),
1570 tr(UPDATE_PLUGINS),
1571 JOptionPane.QUESTION_MESSAGE,
1572 null,
1573 options,
1574 options[0],
1575 ht("/ErrorMessages#ErrorInPlugin")
1576 ));
1577 GuiHelper.runInEDT(task);
1578 return task.get();
1579 } catch (InterruptedException e) {
1580 Thread.currentThread().interrupt();
1581 Logging.warn(e);
1582 } catch (ExecutionException e) {
1583 Logging.warn(e);
1584 }
1585 return -1;
1586 }
1587
1588 /**
1589 * Replies the plugin which most likely threw the exception <code>ex</code>.
1590 *
1591 * @param ex the exception
1592 * @return the plugin; null, if the exception probably wasn't thrown from a plugin
1593 */
1594 private static PluginProxy getPluginCausingException(Throwable ex) {
1595 PluginProxy err = null;
1596 List<StackTraceElement> stack = new ArrayList<>();
1597 Set<Throwable> seen = new HashSet<>();
1598 Throwable current = ex;
1599 while (current != null) {
1600 seen.add(current);
1601 stack.addAll(Arrays.asList(current.getStackTrace()));
1602 Throwable cause = current.getCause();
1603 if (cause != null && seen.contains(cause)) {
1604 break; // circular reference
1605 }
1606 current = cause;
1607 }
1608
1609 // remember the error position, as multiple plugins may be involved, we search the topmost one
1610 int pos = stack.size();
1611 for (PluginProxy p : pluginList) {
1612 String baseClass = p.getPluginInformation().className;
1613 baseClass = baseClass.substring(0, baseClass.lastIndexOf('.'));
1614 for (int elpos = 0; elpos < pos; ++elpos) {
1615 if (stack.get(elpos).getClassName().startsWith(baseClass)) {
1616 pos = elpos;
1617 err = p;
1618 }
1619 }
1620 }
1621 return err;
1622 }
1623
1624 /**
1625 * Checks whether the exception <code>e</code> was thrown by a plugin. If so,
1626 * conditionally updates or deactivates the plugin, but asks the user first.
1627 *
1628 * @param e the exception
1629 * @return plugin download task if the plugin has been updated to a newer version, {@code null} if it has been disabled or kept as it
1630 */
1631 public static PluginDownloadTask updateOrdisablePluginAfterException(Throwable e) {
1632 PluginProxy plugin = null;
1633 // Check for an explicit problem when calling a plugin function
1634 if (e instanceof PluginException) {
1635 plugin = ((PluginException) e).plugin;
1636 }
1637 if (plugin == null) {
1638 plugin = getPluginCausingException(e);
1639 }
1640 if (plugin == null)
1641 // don't know what plugin threw the exception
1642 return null;
1643
1644 Set<String> plugins = new HashSet<>(Config.getPref().getList(PLUGINS));
1645 final PluginInformation pluginInfo = plugin.getPluginInformation();
1646 if (!plugins.contains(pluginInfo.name))
1647 // plugin not activated ? strange in this context but anyway, don't bother
1648 // the user with dialogs, skip conditional deactivation
1649 return null;
1650
1651 switch (askUpdateDisableKeepPluginAfterException(plugin)) {
1652 case 0:
1653 // update the plugin
1654 updatePlugins(MainApplication.getMainFrame(), Collections.singleton(pluginInfo), null, true);
1655 return pluginDownloadTask;
1656 case 1:
1657 // deactivate the plugin
1658 plugins.remove(plugin.getPluginInformation().name);
1659 Config.getPref().putList(PLUGINS, new ArrayList<>(plugins));
1660 GuiHelper.runInEDTAndWait(() -> JOptionPane.showMessageDialog(
1661 MainApplication.getMainFrame(),
1662 tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."),
1663 tr("Information"),
1664 JOptionPane.INFORMATION_MESSAGE
1665 ));
1666 return null;
1667 default:
1668 // user doesn't want to deactivate the plugin
1669 return null;
1670 }
1671 }
1672
1673 /**
1674 * Returns the list of loaded plugins as a {@code String} to be displayed in status report. Useful for bug reports.
1675 * @return The list of loaded plugins
1676 */
1677 public static Collection<String> getBugReportInformation() {
1678 final Collection<String> pl = new TreeSet<>(Config.getPref().getList(PLUGINS, new LinkedList<>()));
1679 for (final PluginProxy pp : pluginList) {
1680 PluginInformation pi = pp.getPluginInformation();
1681 pl.remove(pi.name);
1682 pl.add(pi.name + " (" + (!Utils.isEmpty(pi.localversion)
1683 ? pi.localversion : "unknown") + ')');
1684 }
1685 return pl;
1686 }
1687
1688 /**
1689 * Returns the list of loaded plugins as a {@code JPanel} to be displayed in About dialog.
1690 * @return The list of loaded plugins (one "line" of Swing components per plugin)
1691 */
1692 public static JPanel getInfoPanel() {
1693 JPanel pluginTab = new JPanel(new GridBagLayout());
1694 for (final PluginInformation info : getPlugins()) {
1695 String name = info.name
1696 + (!Utils.isEmpty(info.localversion) ? " Version: " + info.localversion : "");
1697 pluginTab.add(new JLabel(name), GBC.std());
1698 pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GridBagConstraints.HORIZONTAL));
1699 pluginTab.add(new JButton(new PluginInformationAction(info)), GBC.eol());
1700
1701 JosmTextArea description = new JosmTextArea(info.description == null ? tr("no description available")
1702 : info.description);
1703 description.setEditable(false);
1704 description.setFont(new JLabel().getFont().deriveFont(Font.ITALIC));
1705 description.setLineWrap(true);
1706 description.setWrapStyleWord(true);
1707 description.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
1708 description.setBackground(UIManager.getColor("Panel.background"));
1709 description.setCaretPosition(0);
1710
1711 pluginTab.add(description, GBC.eop().fill(GridBagConstraints.HORIZONTAL));
1712 }
1713 return pluginTab;
1714 }
1715
1716 /**
1717 * Returns the set of deprecated and unmaintained plugins.
1718 * @return set of deprecated and unmaintained plugins names.
1719 * @since 8938
1720 */
1721 public static Set<String> getDeprecatedAndUnmaintainedPlugins() {
1722 Set<String> result = new HashSet<>(DEPRECATED_PLUGINS.size() + UNMAINTAINED_PLUGINS.size());
1723 for (DeprecatedPlugin dp : DEPRECATED_PLUGINS) {
1724 result.add(dp.name);
1725 }
1726 result.addAll(UNMAINTAINED_PLUGINS);
1727 return result;
1728 }
1729
1730 private static class UpdatePluginsMessagePanel extends JPanel {
1731 private final JMultilineLabel lblMessage = new JMultilineLabel("");
1732 private final JCheckBox cbDontShowAgain = new JCheckBox(
1733 tr("Do not ask again and remember my decision (go to Preferences->Plugins to change it later)"));
1734
1735 UpdatePluginsMessagePanel() {
1736 build();
1737 }
1738
1739 protected final void build() {
1740 setLayout(new GridBagLayout());
1741 GridBagConstraints gc = new GridBagConstraints();
1742 gc.anchor = GridBagConstraints.NORTHWEST;
1743 gc.fill = GridBagConstraints.BOTH;
1744 gc.weightx = 1.0;
1745 gc.weighty = 1.0;
1746 gc.insets = new Insets(5, 5, 5, 5);
1747 add(lblMessage, gc);
1748 lblMessage.setFont(lblMessage.getFont().deriveFont(Font.PLAIN));
1749
1750 gc.gridy = 1;
1751 gc.fill = GridBagConstraints.HORIZONTAL;
1752 gc.weighty = 0.0;
1753 add(cbDontShowAgain, gc);
1754 cbDontShowAgain.setFont(cbDontShowAgain.getFont().deriveFont(Font.PLAIN));
1755 }
1756
1757 public void setMessage(String message) {
1758 lblMessage.setText(message);
1759 }
1760
1761 /**
1762 * Returns the text. Useful for logging in {@link HelpAwareOptionPane#showOptionDialog}
1763 * @return the text
1764 */
1765 @Override
1766 public String toString() {
1767 return Utils.stripHtml(lblMessage.getText());
1768 }
1769
1770 public void initDontShowAgain(String preferencesKey) {
1771 String policy = Config.getPref().get(preferencesKey, "ask");
1772 policy = policy.trim().toLowerCase(Locale.ENGLISH);
1773 cbDontShowAgain.setSelected(!"ask".equals(policy));
1774 }
1775
1776 public boolean isRememberDecision() {
1777 return cbDontShowAgain.isSelected();
1778 }
1779 }
1780
1781 /**
1782 * Remove deactivated plugins, returning true if JOSM should restart
1783 *
1784 * @param deactivatedPlugins The plugins to deactivate
1785 *
1786 * @return true if there was a plugin that requires a restart
1787 * @since 15508
1788 */
1789 public static boolean removePlugins(List<PluginInformation> deactivatedPlugins) {
1790 List<Destroyable> noRestart = deactivatedPlugins.parallelStream()
1791 .map(info -> PluginHandler.getPlugin(info.name)).filter(Destroyable.class::isInstance)
1792 .map(Destroyable.class::cast).collect(Collectors.toList());
1793 boolean restartNeeded;
1794 try {
1795 noRestart.forEach(Destroyable::destroy);
1796 new ArrayList<>(pluginList).stream().filter(proxy -> noRestart.contains(proxy.getPlugin()))
1797 .forEach(pluginList::remove);
1798 restartNeeded = deactivatedPlugins.size() != noRestart.size();
1799 } catch (Exception e) {
1800 Logging.error(e);
1801 restartNeeded = true;
1802 }
1803 return restartNeeded;
1804 }
1805}
Note: See TracBrowser for help on using the repository browser.