| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.plugins;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 5 |
|
|---|
| 6 | import java.io.File;
|
|---|
| 7 | import java.io.IOException;
|
|---|
| 8 | import java.io.InputStream;
|
|---|
| 9 | import java.lang.reflect.Constructor;
|
|---|
| 10 | import java.net.URL;
|
|---|
| 11 | import java.nio.file.Files;
|
|---|
| 12 | import java.nio.file.InvalidPathException;
|
|---|
| 13 | import java.text.MessageFormat;
|
|---|
| 14 | import java.util.ArrayList;
|
|---|
| 15 | import java.util.Collection;
|
|---|
| 16 | import java.util.LinkedList;
|
|---|
| 17 | import java.util.List;
|
|---|
| 18 | import java.util.Locale;
|
|---|
| 19 | import java.util.Map;
|
|---|
| 20 | import java.util.Optional;
|
|---|
| 21 | import java.util.jar.Attributes;
|
|---|
| 22 | import java.util.jar.JarInputStream;
|
|---|
| 23 | import java.util.jar.Manifest;
|
|---|
| 24 | import java.util.logging.Level;
|
|---|
| 25 |
|
|---|
| 26 | import javax.swing.ImageIcon;
|
|---|
| 27 |
|
|---|
| 28 | import org.openstreetmap.josm.data.Preferences;
|
|---|
| 29 | import org.openstreetmap.josm.data.Version;
|
|---|
| 30 | import org.openstreetmap.josm.tools.ImageProvider;
|
|---|
| 31 | import org.openstreetmap.josm.tools.LanguageInfo;
|
|---|
| 32 | import org.openstreetmap.josm.tools.Logging;
|
|---|
| 33 | import org.openstreetmap.josm.tools.Platform;
|
|---|
| 34 | import org.openstreetmap.josm.tools.PlatformManager;
|
|---|
| 35 | import org.openstreetmap.josm.tools.Utils;
|
|---|
| 36 |
|
|---|
| 37 | /**
|
|---|
| 38 | * Encapsulate general information about a plugin. This information is available
|
|---|
| 39 | * without the need of loading any class from the plugin jar file.
|
|---|
| 40 | *
|
|---|
| 41 | * @author imi
|
|---|
| 42 | * @since 153
|
|---|
| 43 | */
|
|---|
| 44 | public class PluginInformation {
|
|---|
| 45 |
|
|---|
| 46 | /** The plugin jar file. */
|
|---|
| 47 | public File file;
|
|---|
| 48 | /** The plugin name. */
|
|---|
| 49 | public String name;
|
|---|
| 50 | /** The lowest JOSM version required by this plugin (from plugin list). **/
|
|---|
| 51 | public int mainversion;
|
|---|
| 52 | /** The lowest JOSM version required by this plugin (from locally available jar). **/
|
|---|
| 53 | public int localmainversion;
|
|---|
| 54 | /** The lowest Java version required by this plugin (from plugin list). **/
|
|---|
| 55 | public int minjavaversion;
|
|---|
| 56 | /** The lowest Java version required by this plugin (from locally available jar). **/
|
|---|
| 57 | public int localminjavaversion;
|
|---|
| 58 | /** The plugin class name. */
|
|---|
| 59 | public String className;
|
|---|
| 60 | /** Determines if the plugin is an old version loaded for incompatibility with latest JOSM (from plugin list) */
|
|---|
| 61 | public boolean oldmode;
|
|---|
| 62 | /** The list of required plugins, separated by ';' (from plugin list). */
|
|---|
| 63 | public String requires;
|
|---|
| 64 | /** The list of required plugins, separated by ';' (from locally available jar). */
|
|---|
| 65 | public String localrequires;
|
|---|
| 66 | /** The plugin platform on which it is meant to run (windows, osx, unixoid). */
|
|---|
| 67 | public String platform;
|
|---|
| 68 | /** The virtual plugin provided by this plugin, if native for a given platform. */
|
|---|
| 69 | public String provides;
|
|---|
| 70 | /** The plugin link (for documentation). */
|
|---|
| 71 | public String link;
|
|---|
| 72 | /** The plugin description. */
|
|---|
| 73 | public String description;
|
|---|
| 74 | /** Determines if the plugin must be loaded early or not. */
|
|---|
| 75 | public boolean early;
|
|---|
| 76 | /** The plugin author. */
|
|---|
| 77 | public String author;
|
|---|
| 78 | /** The plugin stage, determining the loading sequence order of plugins. */
|
|---|
| 79 | public int stage = 50;
|
|---|
| 80 | /** The plugin version (from plugin list). **/
|
|---|
| 81 | public String version;
|
|---|
| 82 | /** The plugin version (from locally available jar). **/
|
|---|
| 83 | public String localversion;
|
|---|
| 84 | /** The plugin download link. */
|
|---|
| 85 | public String downloadlink;
|
|---|
| 86 | /** The plugin icon path inside jar. */
|
|---|
| 87 | public String iconPath;
|
|---|
| 88 | /** The plugin icon. */
|
|---|
| 89 | private ImageProvider icon;
|
|---|
| 90 | /** Plugin can be loaded at any time and not just at start. */
|
|---|
| 91 | public boolean canloadatruntime;
|
|---|
| 92 | /** The libraries referenced in Class-Path manifest attribute. */
|
|---|
| 93 | public List<URL> libraries = new LinkedList<>();
|
|---|
| 94 | /** All manifest attributes. */
|
|---|
| 95 | public Attributes attr;
|
|---|
| 96 | /** Invalid manifest entries */
|
|---|
| 97 | final List<String> invalidManifestEntries = new ArrayList<>();
|
|---|
| 98 | /** Empty icon for these plugins which have none */
|
|---|
| 99 | private static final ImageIcon emptyIcon = ImageProvider.getEmpty(ImageProvider.ImageSizes.LARGEICON);
|
|---|
| 100 |
|
|---|
| 101 | /**
|
|---|
| 102 | * Creates a plugin information object by reading the plugin information from
|
|---|
| 103 | * the manifest in the plugin jar.
|
|---|
| 104 | *
|
|---|
| 105 | * The plugin name is derived from the file name.
|
|---|
| 106 | *
|
|---|
| 107 | * @param file the plugin jar file
|
|---|
| 108 | * @throws PluginException if reading the manifest fails
|
|---|
| 109 | */
|
|---|
| 110 | public PluginInformation(File file) throws PluginException {
|
|---|
| 111 | this(file, file.getName().substring(0, file.getName().length()-4));
|
|---|
| 112 | }
|
|---|
| 113 |
|
|---|
| 114 | /**
|
|---|
| 115 | * Creates a plugin information object for the plugin with name {@code name}.
|
|---|
| 116 | * Information about the plugin is extracted from the manifest file in the plugin jar
|
|---|
| 117 | * {@code file}.
|
|---|
| 118 | * @param file the plugin jar
|
|---|
| 119 | * @param name the plugin name
|
|---|
| 120 | * @throws PluginException if reading the manifest file fails
|
|---|
| 121 | */
|
|---|
| 122 | public PluginInformation(File file, String name) throws PluginException {
|
|---|
| 123 | if (!PluginHandler.isValidJar(file)) {
|
|---|
| 124 | throw new PluginException(tr("Invalid jar file ''{0}''", file));
|
|---|
| 125 | }
|
|---|
| 126 | this.name = name;
|
|---|
| 127 | this.file = file;
|
|---|
| 128 | try (
|
|---|
| 129 | InputStream fis = Files.newInputStream(file.toPath());
|
|---|
| 130 | JarInputStream jar = new JarInputStream(fis)
|
|---|
| 131 | ) {
|
|---|
| 132 | Manifest manifest = jar.getManifest();
|
|---|
| 133 | if (manifest == null)
|
|---|
| 134 | throw new PluginException(tr("The plugin file ''{0}'' does not include a Manifest.", file.toString()));
|
|---|
| 135 | scanManifest(manifest.getMainAttributes(), false);
|
|---|
| 136 | libraries.add(0, Utils.fileToURL(file));
|
|---|
| 137 | } catch (IOException | InvalidPathException e) {
|
|---|
| 138 | throw new PluginException(name, e);
|
|---|
| 139 | }
|
|---|
| 140 | }
|
|---|
| 141 |
|
|---|
| 142 | /**
|
|---|
| 143 | * Creates a plugin information object by reading plugin information in Manifest format
|
|---|
| 144 | * from the input stream {@code manifestStream}.
|
|---|
| 145 | *
|
|---|
| 146 | * @param manifestStream the stream to read the manifest from
|
|---|
| 147 | * @param name the plugin name
|
|---|
| 148 | * @param url the download URL for the plugin
|
|---|
| 149 | * @throws PluginException if the plugin information can't be read from the input stream
|
|---|
| 150 | */
|
|---|
| 151 | public PluginInformation(InputStream manifestStream, String name, String url) throws PluginException {
|
|---|
| 152 | this.name = name;
|
|---|
| 153 | try {
|
|---|
| 154 | Manifest manifest = new Manifest();
|
|---|
| 155 | manifest.read(manifestStream);
|
|---|
| 156 | if (url != null) {
|
|---|
| 157 | downloadlink = url;
|
|---|
| 158 | }
|
|---|
| 159 | scanManifest(manifest.getMainAttributes(), url != null);
|
|---|
| 160 | } catch (IOException e) {
|
|---|
| 161 | throw new PluginException(name, e);
|
|---|
| 162 | }
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | /**
|
|---|
| 166 | * Creates a plugin information object by reading plugin information in Manifest format
|
|---|
| 167 | * from the input stream {@code manifestStream}.
|
|---|
| 168 | *
|
|---|
| 169 | * @param attr the manifest attributes
|
|---|
| 170 | * @param name the plugin name
|
|---|
| 171 | * @param url the download URL for the plugin
|
|---|
| 172 | * @throws PluginException if the plugin information can't be read from the input stream
|
|---|
| 173 | */
|
|---|
| 174 | public PluginInformation(Attributes attr, String name, String url) throws PluginException {
|
|---|
| 175 | this.name = name;
|
|---|
| 176 | if (url != null) {
|
|---|
| 177 | downloadlink = url;
|
|---|
| 178 | }
|
|---|
| 179 | scanManifest(attr, url != null);
|
|---|
| 180 | }
|
|---|
| 181 |
|
|---|
| 182 | /**
|
|---|
| 183 | * Updates the plugin information of this plugin information object with the
|
|---|
| 184 | * plugin information in a plugin information object retrieved from a plugin
|
|---|
| 185 | * update site.
|
|---|
| 186 | *
|
|---|
| 187 | * @param other the plugin information object retrieved from the update site
|
|---|
| 188 | */
|
|---|
| 189 | public void updateFromPluginSite(PluginInformation other) {
|
|---|
| 190 | this.mainversion = other.mainversion;
|
|---|
| 191 | this.minjavaversion = other.minjavaversion;
|
|---|
| 192 | this.className = other.className;
|
|---|
| 193 | this.requires = other.requires;
|
|---|
| 194 | this.provides = other.provides;
|
|---|
| 195 | this.platform = other.platform;
|
|---|
| 196 | this.link = other.link;
|
|---|
| 197 | this.description = other.description;
|
|---|
| 198 | this.early = other.early;
|
|---|
| 199 | this.author = other.author;
|
|---|
| 200 | this.stage = other.stage;
|
|---|
| 201 | this.version = other.version;
|
|---|
| 202 | this.downloadlink = other.downloadlink;
|
|---|
| 203 | this.icon = other.icon;
|
|---|
| 204 | this.iconPath = other.iconPath;
|
|---|
| 205 | this.canloadatruntime = other.canloadatruntime;
|
|---|
| 206 | this.attr = new Attributes(other.attr);
|
|---|
| 207 | this.invalidManifestEntries.clear();
|
|---|
| 208 | this.invalidManifestEntries.addAll(other.invalidManifestEntries);
|
|---|
| 209 | }
|
|---|
| 210 |
|
|---|
| 211 | /**
|
|---|
| 212 | * Updates the plugin information of this plugin information object with the
|
|---|
| 213 | * plugin information in a plugin information object retrieved from a plugin jar.
|
|---|
| 214 | *
|
|---|
| 215 | * @param other the plugin information object retrieved from the jar file
|
|---|
| 216 | * @since 5601
|
|---|
| 217 | */
|
|---|
| 218 | public void updateFromJar(PluginInformation other) {
|
|---|
| 219 | updateLocalInfo(other);
|
|---|
| 220 | if (other.icon != null) {
|
|---|
| 221 | this.icon = other.icon;
|
|---|
| 222 | }
|
|---|
| 223 | this.early = other.early;
|
|---|
| 224 | this.className = other.className;
|
|---|
| 225 | this.canloadatruntime = other.canloadatruntime;
|
|---|
| 226 | this.libraries = other.libraries;
|
|---|
| 227 | this.stage = other.stage;
|
|---|
| 228 | this.file = other.file;
|
|---|
| 229 | }
|
|---|
| 230 |
|
|---|
| 231 | private void scanManifest(Attributes attr, boolean oldcheck) {
|
|---|
| 232 | String lang = LanguageInfo.getLanguageCodeManifest();
|
|---|
| 233 | className = attr.getValue("Plugin-Class");
|
|---|
| 234 | String s = Optional.ofNullable(attr.getValue(lang+"Plugin-Link")).orElseGet(() -> attr.getValue("Plugin-Link"));
|
|---|
| 235 | if (s != null && !Utils.isValidUrl(s)) {
|
|---|
| 236 | Logging.info(tr("Invalid URL ''{0}'' in plugin {1}", s, name));
|
|---|
| 237 | s = null;
|
|---|
| 238 | }
|
|---|
| 239 | link = s;
|
|---|
| 240 | platform = attr.getValue("Plugin-Platform");
|
|---|
| 241 | provides = attr.getValue("Plugin-Provides");
|
|---|
| 242 | requires = attr.getValue("Plugin-Requires");
|
|---|
| 243 | s = attr.getValue(lang+"Plugin-Description");
|
|---|
| 244 | if (s == null) {
|
|---|
| 245 | s = attr.getValue("Plugin-Description");
|
|---|
| 246 | if (s != null) {
|
|---|
| 247 | try {
|
|---|
| 248 | s = tr(s);
|
|---|
| 249 | } catch (IllegalArgumentException e) {
|
|---|
| 250 | Logging.debug(e);
|
|---|
| 251 | Logging.info(tr("Invalid plugin description ''{0}'' in plugin {1}", s, name));
|
|---|
| 252 | }
|
|---|
| 253 | }
|
|---|
| 254 | } else {
|
|---|
| 255 | s = MessageFormat.format(s, (Object[]) null);
|
|---|
| 256 | }
|
|---|
| 257 | description = s;
|
|---|
| 258 | early = Boolean.parseBoolean(attr.getValue("Plugin-Early"));
|
|---|
| 259 | String stageStr = attr.getValue("Plugin-Stage");
|
|---|
| 260 | stage = stageStr == null ? 50 : Integer.parseInt(stageStr);
|
|---|
| 261 | version = attr.getValue("Plugin-Version");
|
|---|
| 262 | if (!Utils.isEmpty(version) && version.charAt(0) == '$') {
|
|---|
| 263 | invalidManifestEntries.add("Plugin-Version");
|
|---|
| 264 | }
|
|---|
| 265 | s = attr.getValue("Plugin-Mainversion");
|
|---|
| 266 | if (s != null) {
|
|---|
| 267 | try {
|
|---|
| 268 | mainversion = Integer.parseInt(s);
|
|---|
| 269 | } catch (NumberFormatException e) {
|
|---|
| 270 | Logging.warn(tr("Invalid plugin main version ''{0}'' in plugin {1}", s, name));
|
|---|
| 271 | Logging.trace(e);
|
|---|
| 272 | }
|
|---|
| 273 | } else {
|
|---|
| 274 | Logging.warn(tr("Missing plugin main version in plugin {0}", name));
|
|---|
| 275 | }
|
|---|
| 276 | s = attr.getValue("Plugin-Minimum-Java-Version");
|
|---|
| 277 | if (s != null) {
|
|---|
| 278 | try {
|
|---|
| 279 | minjavaversion = Integer.parseInt(s);
|
|---|
| 280 | } catch (NumberFormatException e) {
|
|---|
| 281 | Logging.warn(tr("Invalid Java version ''{0}'' in plugin {1}", s, name));
|
|---|
| 282 | Logging.trace(e);
|
|---|
| 283 | }
|
|---|
| 284 | }
|
|---|
| 285 | author = attr.getValue("Author");
|
|---|
| 286 | iconPath = attr.getValue("Plugin-Icon");
|
|---|
| 287 | if (iconPath != null) {
|
|---|
| 288 | if (file != null) {
|
|---|
| 289 | // extract icon from the plugin jar file
|
|---|
| 290 | icon = new ImageProvider(iconPath).setArchive(file).setMaxSize(ImageProvider.ImageSizes.LARGEICON).setOptional(true);
|
|---|
| 291 | } else if (iconPath.startsWith("data:")) {
|
|---|
| 292 | icon = new ImageProvider(iconPath).setMaxSize(ImageProvider.ImageSizes.LARGEICON).setOptional(true);
|
|---|
| 293 | }
|
|---|
| 294 | }
|
|---|
| 295 | canloadatruntime = Boolean.parseBoolean(attr.getValue("Plugin-Canloadatruntime"));
|
|---|
| 296 | int myv = Version.getInstance().getVersion();
|
|---|
| 297 | for (Map.Entry<Object, Object> entry : attr.entrySet()) {
|
|---|
| 298 | String key = ((Attributes.Name) entry.getKey()).toString();
|
|---|
| 299 | if (key.endsWith("_Plugin-Url")) {
|
|---|
| 300 | try {
|
|---|
| 301 | int mv = Integer.parseInt(key.substring(0, key.length()-11));
|
|---|
| 302 | String v = (String) entry.getValue();
|
|---|
| 303 | int i = v.indexOf(';');
|
|---|
| 304 | if (i <= 0) {
|
|---|
| 305 | invalidManifestEntries.add(key);
|
|---|
| 306 | } else if (oldcheck &&
|
|---|
| 307 | mv <= myv && (mv > mainversion || mainversion > myv)) {
|
|---|
| 308 | downloadlink = v.substring(i+1);
|
|---|
| 309 | mainversion = mv;
|
|---|
| 310 | version = v.substring(0, i);
|
|---|
| 311 | oldmode = true;
|
|---|
| 312 | }
|
|---|
| 313 | } catch (NumberFormatException | IndexOutOfBoundsException e) {
|
|---|
| 314 | invalidManifestEntries.add(key);
|
|---|
| 315 | Logging.error(e);
|
|---|
| 316 | }
|
|---|
| 317 | }
|
|---|
| 318 | }
|
|---|
| 319 |
|
|---|
| 320 | String classPath = attr.getValue(Attributes.Name.CLASS_PATH);
|
|---|
| 321 | if (classPath != null) {
|
|---|
| 322 | for (String entry : classPath.split(" ", -1)) {
|
|---|
| 323 | File entryFile;
|
|---|
| 324 | if (new File(entry).isAbsolute() || file == null) {
|
|---|
| 325 | entryFile = new File(entry);
|
|---|
| 326 | } else {
|
|---|
| 327 | entryFile = new File(file.getParent(), entry);
|
|---|
| 328 | }
|
|---|
| 329 |
|
|---|
| 330 | libraries.add(Utils.fileToURL(entryFile));
|
|---|
| 331 | }
|
|---|
| 332 | }
|
|---|
| 333 | this.attr = attr;
|
|---|
| 334 | }
|
|---|
| 335 |
|
|---|
| 336 | /**
|
|---|
| 337 | * Replies the description as HTML document, including a link to a web page with
|
|---|
| 338 | * more information, provided such a link is available.
|
|---|
| 339 | *
|
|---|
| 340 | * @return the description as HTML document
|
|---|
| 341 | */
|
|---|
| 342 | public String getDescriptionAsHtml() {
|
|---|
| 343 | StringBuilder sb = new StringBuilder(128);
|
|---|
| 344 | sb.append("<html><body>")
|
|---|
| 345 | .append(description == null ? tr("no description available") : Utils.escapeReservedCharactersHTML(description));
|
|---|
| 346 | if (link != null) {
|
|---|
| 347 | sb.append(" <a href=\"").append(link).append("\">").append(tr("More info...")).append("</a>");
|
|---|
| 348 | }
|
|---|
| 349 | if (isExternal()) {
|
|---|
| 350 | sb.append("<p> </p><p>").append(tr("<b>Plugin provided by an external source:</b> {0}", downloadlink)).append("</p>");
|
|---|
| 351 | }
|
|---|
| 352 | sb.append("</body></html>");
|
|---|
| 353 | return sb.toString();
|
|---|
| 354 | }
|
|---|
| 355 |
|
|---|
| 356 | /**
|
|---|
| 357 | * Determines if this plugin comes from an external, non-official source.
|
|---|
| 358 | * @return {@code true} if this plugin comes from an external, non-official source.
|
|---|
| 359 | * @since 18267
|
|---|
| 360 | */
|
|---|
| 361 | public boolean isExternal() {
|
|---|
| 362 | return downloadlink != null
|
|---|
| 363 | && !downloadlink.startsWith("https://josm.openstreetmap.de/osmsvn/applications/editors/josm/dist/")
|
|---|
| 364 | && !downloadlink.startsWith("https://github.com/JOSM/");
|
|---|
| 365 | }
|
|---|
| 366 |
|
|---|
| 367 | /**
|
|---|
| 368 | * Loads and instantiates the plugin.
|
|---|
| 369 | *
|
|---|
| 370 | * @param klass the plugin class
|
|---|
| 371 | * @param classLoader the class loader for the plugin
|
|---|
| 372 | * @return the instantiated and initialized plugin
|
|---|
| 373 | * @throws PluginException if the plugin cannot be loaded or instanciated
|
|---|
| 374 | * @since 12322
|
|---|
| 375 | */
|
|---|
| 376 | public PluginProxy load(Class<?> klass, PluginClassLoader classLoader) throws PluginException {
|
|---|
| 377 | try {
|
|---|
| 378 | Constructor<?> c = klass.getConstructor(PluginInformation.class);
|
|---|
| 379 | ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
|---|
| 380 | Thread.currentThread().setContextClassLoader(classLoader);
|
|---|
| 381 | try {
|
|---|
| 382 | return new PluginProxy(c.newInstance(this), this, classLoader);
|
|---|
| 383 | } finally {
|
|---|
| 384 | Thread.currentThread().setContextClassLoader(contextClassLoader);
|
|---|
| 385 | }
|
|---|
| 386 | } catch (ReflectiveOperationException e) {
|
|---|
| 387 | throw new PluginException(name, e);
|
|---|
| 388 | }
|
|---|
| 389 | }
|
|---|
| 390 |
|
|---|
| 391 | /**
|
|---|
| 392 | * Loads the class of the plugin.
|
|---|
| 393 | *
|
|---|
| 394 | * @param classLoader the class loader to use
|
|---|
| 395 | * @return the loaded class
|
|---|
| 396 | * @throws PluginException if the class cannot be loaded
|
|---|
| 397 | */
|
|---|
| 398 | public Class<?> loadClass(ClassLoader classLoader) throws PluginException {
|
|---|
| 399 | if (className == null)
|
|---|
| 400 | return null;
|
|---|
| 401 | try {
|
|---|
| 402 | return Class.forName(className, true, classLoader);
|
|---|
| 403 | } catch (NoClassDefFoundError | ClassNotFoundException | ClassCastException e) {
|
|---|
| 404 | Logging.logWithStackTrace(Level.SEVERE, e,
|
|---|
| 405 | "Unable to load class {0} from plugin {1} using classloader {2}", className, name, classLoader);
|
|---|
| 406 | throw new PluginException(name, e);
|
|---|
| 407 | }
|
|---|
| 408 | }
|
|---|
| 409 |
|
|---|
| 410 | /**
|
|---|
| 411 | * Try to find a plugin after some criteria. Extract the plugin-information
|
|---|
| 412 | * from the plugin and return it. The plugin is searched in the following way:
|
|---|
| 413 | *<ol>
|
|---|
| 414 | *<li>first look after an MANIFEST.MF in the package org.openstreetmap.josm.plugins.<plugin name>
|
|---|
| 415 | * (After removing all fancy characters from the plugin name).
|
|---|
| 416 | * If found, the plugin is loaded using the bootstrap classloader.</li>
|
|---|
| 417 | *<li>If not found, look for a jar file in the user specific plugin directory
|
|---|
| 418 | * (~/.josm/plugins/<plugin name>.jar)</li>
|
|---|
| 419 | *<li>If not found and the environment variable JOSM_RESOURCES + "/plugins/" exist, look there.</li>
|
|---|
| 420 | *<li>Try for the java property josm.resources + "/plugins/" (set via java -Djosm.plugins.path=...)</li>
|
|---|
| 421 | *<li>If the environment variable ALLUSERSPROFILE and APPDATA exist, look in
|
|---|
| 422 | * ALLUSERSPROFILE/<the last stuff from APPDATA>/JOSM/plugins.
|
|---|
| 423 | * (*sic* There is no easy way under Windows to get the All User's application
|
|---|
| 424 | * directory)</li>
|
|---|
| 425 | *<li>Finally, look in some typical unix paths:<ul>
|
|---|
| 426 | * <li>/usr/local/share/josm/plugins/</li>
|
|---|
| 427 | * <li>/usr/local/lib/josm/plugins/</li>
|
|---|
| 428 | * <li>/usr/share/josm/plugins/</li>
|
|---|
| 429 | * <li>/usr/lib/josm/plugins/</li></ul></li>
|
|---|
| 430 | *</ol>
|
|---|
| 431 | * If a plugin class or jar file is found earlier in the list but seem not to
|
|---|
| 432 | * be working, an PluginException is thrown rather than continuing the search.
|
|---|
| 433 | * This is so JOSM can detect broken user-provided plugins and do not go silently
|
|---|
| 434 | * ignore them.
|
|---|
| 435 | *
|
|---|
| 436 | * The plugin is not initialized. If the plugin is a .jar file, it is not loaded
|
|---|
| 437 | * (only the manifest is extracted). In the classloader-case, the class is
|
|---|
| 438 | * bootstraped (e.g. static {} - declarations will run. However, nothing else is done.
|
|---|
| 439 | *
|
|---|
| 440 | * @param pluginName The name of the plugin (in all lowercase). E.g. "lang-de"
|
|---|
| 441 | * @return Information about the plugin or <code>null</code>, if the plugin
|
|---|
| 442 | * was nowhere to be found.
|
|---|
| 443 | * @throws PluginException In case of broken plugins.
|
|---|
| 444 | */
|
|---|
| 445 | public static PluginInformation findPlugin(String pluginName) throws PluginException {
|
|---|
| 446 | String name = pluginName;
|
|---|
| 447 | name = name.replaceAll("[-. ]", "");
|
|---|
| 448 | try (InputStream manifestStream = Utils.getResourceAsStream(
|
|---|
| 449 | PluginInformation.class, "/org/openstreetmap/josm/plugins/"+name+"/MANIFEST.MF")) {
|
|---|
| 450 | if (manifestStream != null) {
|
|---|
| 451 | return new PluginInformation(manifestStream, pluginName, null);
|
|---|
| 452 | }
|
|---|
| 453 | } catch (IOException e) {
|
|---|
| 454 | Logging.warn(e);
|
|---|
| 455 | }
|
|---|
| 456 |
|
|---|
| 457 | Collection<String> locations = getPluginLocations();
|
|---|
| 458 |
|
|---|
| 459 | String[] nameCandidates = {
|
|---|
| 460 | pluginName,
|
|---|
| 461 | pluginName + "-" + PlatformManager.getPlatform().getPlatform().name().toLowerCase(Locale.ENGLISH)};
|
|---|
| 462 | for (String s : locations) {
|
|---|
| 463 | for (String nameCandidate: nameCandidates) {
|
|---|
| 464 | File pluginFile = new File(s, nameCandidate + ".jar");
|
|---|
| 465 | if (pluginFile.exists()) {
|
|---|
| 466 | return new PluginInformation(pluginFile);
|
|---|
| 467 | }
|
|---|
| 468 | }
|
|---|
| 469 | }
|
|---|
| 470 | return null;
|
|---|
| 471 | }
|
|---|
| 472 |
|
|---|
| 473 | /**
|
|---|
| 474 | * Returns all possible plugin locations.
|
|---|
| 475 | * @return all possible plugin locations.
|
|---|
| 476 | */
|
|---|
| 477 | public static Collection<String> getPluginLocations() {
|
|---|
| 478 | Collection<String> locations = Preferences.getAllPossiblePreferenceDirs();
|
|---|
| 479 | Collection<String> all = new ArrayList<>(locations.size());
|
|---|
| 480 | for (String s : locations) {
|
|---|
| 481 | all.add(s+"plugins");
|
|---|
| 482 | }
|
|---|
| 483 | return all;
|
|---|
| 484 | }
|
|---|
| 485 |
|
|---|
| 486 | /**
|
|---|
| 487 | * Replies true if the plugin with the given information is most likely outdated with
|
|---|
| 488 | * respect to the referenceVersion.
|
|---|
| 489 | *
|
|---|
| 490 | * @param referenceVersion the reference version. Can be null if we don't know a
|
|---|
| 491 | * reference version
|
|---|
| 492 | *
|
|---|
| 493 | * @return true, if the plugin needs to be updated; false, otherweise
|
|---|
| 494 | */
|
|---|
| 495 | public boolean isUpdateRequired(String referenceVersion) {
|
|---|
| 496 | if (this.downloadlink == null) return false;
|
|---|
| 497 | if (this.version == null && referenceVersion != null)
|
|---|
| 498 | return true;
|
|---|
| 499 | return this.version != null && !this.version.equals(referenceVersion);
|
|---|
| 500 | }
|
|---|
| 501 |
|
|---|
| 502 | /**
|
|---|
| 503 | * Replies true if this this plugin should be updated/downloaded because either
|
|---|
| 504 | * it is not available locally (its local version is null) or its local version is
|
|---|
| 505 | * older than the available version on the server.
|
|---|
| 506 | *
|
|---|
| 507 | * @return true if the plugin should be updated
|
|---|
| 508 | */
|
|---|
| 509 | public boolean isUpdateRequired() {
|
|---|
| 510 | if (this.downloadlink == null) return false;
|
|---|
| 511 | if (this.localversion == null) return true;
|
|---|
| 512 | return isUpdateRequired(this.localversion);
|
|---|
| 513 | }
|
|---|
| 514 |
|
|---|
| 515 | protected boolean matches(String filter, String value) {
|
|---|
| 516 | if (filter == null) return true;
|
|---|
| 517 | if (value == null) return false;
|
|---|
| 518 | return value.toLowerCase(Locale.ENGLISH).contains(filter.toLowerCase(Locale.ENGLISH));
|
|---|
| 519 | }
|
|---|
| 520 |
|
|---|
| 521 | /**
|
|---|
| 522 | * Replies true if either the name, the description, or the version match (case insensitive)
|
|---|
| 523 | * one of the words in filter. Replies true if filter is null.
|
|---|
| 524 | *
|
|---|
| 525 | * @param filter the filter expression
|
|---|
| 526 | * @return true if this plugin info matches with the filter
|
|---|
| 527 | */
|
|---|
| 528 | public boolean matches(String filter) {
|
|---|
| 529 | if (filter == null) return true;
|
|---|
| 530 | String[] words = filter.split("\\s+", -1);
|
|---|
| 531 | for (String word: words) {
|
|---|
| 532 | if (matches(word, name)
|
|---|
| 533 | || matches(word, description)
|
|---|
| 534 | || matches(word, version)
|
|---|
| 535 | || matches(word, localversion))
|
|---|
| 536 | return true;
|
|---|
| 537 | }
|
|---|
| 538 | return false;
|
|---|
| 539 | }
|
|---|
| 540 |
|
|---|
| 541 | /**
|
|---|
| 542 | * Replies the name of the plugin.
|
|---|
| 543 | * @return The plugin name
|
|---|
| 544 | */
|
|---|
| 545 | public String getName() {
|
|---|
| 546 | return name;
|
|---|
| 547 | }
|
|---|
| 548 |
|
|---|
| 549 | /**
|
|---|
| 550 | * Sets the name
|
|---|
| 551 | * @param name Plugin name
|
|---|
| 552 | */
|
|---|
| 553 | public void setName(String name) {
|
|---|
| 554 | this.name = name;
|
|---|
| 555 | }
|
|---|
| 556 |
|
|---|
| 557 | /**
|
|---|
| 558 | * Replies the plugin icon, scaled to LARGE_ICON size.
|
|---|
| 559 | * @return the plugin icon, scaled to LARGE_ICON size.
|
|---|
| 560 | */
|
|---|
| 561 | public ImageIcon getScaledIcon() {
|
|---|
| 562 | ImageIcon img = (icon != null) ? icon.get() : null;
|
|---|
| 563 | if (img == null)
|
|---|
| 564 | return emptyIcon;
|
|---|
| 565 | return img;
|
|---|
| 566 | }
|
|---|
| 567 |
|
|---|
| 568 | @Override
|
|---|
| 569 | public final String toString() {
|
|---|
| 570 | return getName();
|
|---|
| 571 | }
|
|---|
| 572 |
|
|---|
| 573 | private static List<String> getRequiredPlugins(String pluginList) {
|
|---|
| 574 | List<String> requiredPlugins = new ArrayList<>();
|
|---|
| 575 | if (pluginList != null) {
|
|---|
| 576 | for (String s : pluginList.split(";", -1)) {
|
|---|
| 577 | String plugin = s.trim();
|
|---|
| 578 | if (!plugin.isEmpty()) {
|
|---|
| 579 | requiredPlugins.add(plugin);
|
|---|
| 580 | }
|
|---|
| 581 | }
|
|---|
| 582 | }
|
|---|
| 583 | return requiredPlugins;
|
|---|
| 584 | }
|
|---|
| 585 |
|
|---|
| 586 | /**
|
|---|
| 587 | * Replies the list of plugins required by the up-to-date version of this plugin.
|
|---|
| 588 | * @return List of plugins required. Empty if no plugin is required.
|
|---|
| 589 | * @since 5601
|
|---|
| 590 | */
|
|---|
| 591 | public List<String> getRequiredPlugins() {
|
|---|
| 592 | return getRequiredPlugins(requires);
|
|---|
| 593 | }
|
|---|
| 594 |
|
|---|
| 595 | /**
|
|---|
| 596 | * Replies the list of plugins required by the local instance of this plugin.
|
|---|
| 597 | * @return List of plugins required. Empty if no plugin is required.
|
|---|
| 598 | * @since 5601
|
|---|
| 599 | */
|
|---|
| 600 | public List<String> getLocalRequiredPlugins() {
|
|---|
| 601 | return getRequiredPlugins(localrequires);
|
|---|
| 602 | }
|
|---|
| 603 |
|
|---|
| 604 | /**
|
|---|
| 605 | * Updates the local fields
|
|---|
| 606 | * ({@link #localversion}, {@link #localmainversion}, {@link #localminjavaversion}, {@link #localrequires})
|
|---|
| 607 | * to values contained in the up-to-date fields
|
|---|
| 608 | * ({@link #version}, {@link #mainversion}, {@link #minjavaversion}, {@link #requires})
|
|---|
| 609 | * of the given PluginInformation.
|
|---|
| 610 | * @param info The plugin information to get the data from.
|
|---|
| 611 | * @since 5601
|
|---|
| 612 | */
|
|---|
| 613 | public void updateLocalInfo(PluginInformation info) {
|
|---|
| 614 | if (info != null) {
|
|---|
| 615 | this.localversion = info.version;
|
|---|
| 616 | this.localmainversion = info.mainversion;
|
|---|
| 617 | this.localminjavaversion = info.minjavaversion;
|
|---|
| 618 | this.localrequires = info.requires;
|
|---|
| 619 | }
|
|---|
| 620 | }
|
|---|
| 621 |
|
|---|
| 622 | /**
|
|---|
| 623 | * Determines if this plugin can be run on the current platform.
|
|---|
| 624 | * @return {@code true} if this plugin can be run on the current platform
|
|---|
| 625 | * @since 14384
|
|---|
| 626 | */
|
|---|
| 627 | public boolean isForCurrentPlatform() {
|
|---|
| 628 | try {
|
|---|
| 629 | return platform == null || PlatformManager.getPlatform().getPlatform() == Platform.valueOf(platform.toUpperCase(Locale.ENGLISH));
|
|---|
| 630 | } catch (IllegalArgumentException e) {
|
|---|
| 631 | Logging.warn(e);
|
|---|
| 632 | return true;
|
|---|
| 633 | }
|
|---|
| 634 | }
|
|---|
| 635 | }
|
|---|