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