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