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