source: josm/trunk/src/org/openstreetmap/josm/plugins/PluginInformation.java@ 6113

Last change on this file since 6113 was 6085, checked in by Don-vip, 11 years ago

see #8902 - c-like array definitions changed to java-like (patch by shinigami)

  • Property svn:eol-style set to native
File size: 19.5 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.plugins;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Image;
7import java.awt.image.BufferedImage;
8import java.io.File;
9import java.io.FileInputStream;
10import java.io.IOException;
11import java.io.InputStream;
12import java.lang.reflect.Constructor;
13import java.lang.reflect.InvocationTargetException;
14import java.net.MalformedURLException;
15import java.net.URL;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.TreeMap;
22import java.util.jar.Attributes;
23import java.util.jar.JarInputStream;
24import java.util.jar.Manifest;
25import javax.swing.ImageIcon;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.Version;
29import org.openstreetmap.josm.tools.ImageProvider;
30import org.openstreetmap.josm.tools.LanguageInfo;
31import org.openstreetmap.josm.tools.Utils;
32
33/**
34 * Encapsulate general information about a plugin. This information is available
35 * without the need of loading any class from the plugin jar file.
36 *
37 * @author imi
38 */
39public class PluginInformation {
40 public File file = null;
41 public String name = null;
42 public int mainversion = 0;
43 public int localmainversion = 0;
44 public String className = null;
45 public boolean oldmode = false;
46 public String requires = null;
47 public String localrequires = null;
48 public String link = null;
49 public String description = null;
50 public boolean early = false;
51 public String author = null;
52 public int stage = 50;
53 public String version = null;
54 public String localversion = null;
55 public String downloadlink = null;
56 public String iconPath;
57 public ImageIcon icon;
58 public List<URL> libraries = new LinkedList<URL>();
59 public final Map<String, String> attr = new TreeMap<String, String>();
60
61 private static final ImageIcon emptyIcon = new ImageIcon(new BufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB));
62
63 /**
64 * Creates a plugin information object by reading the plugin information from
65 * the manifest in the plugin jar.
66 *
67 * The plugin name is derived from the file name.
68 *
69 * @param file the plugin jar file
70 * @throws PluginException if reading the manifest fails
71 */
72 public PluginInformation(File file) throws PluginException{
73 this(file, file.getName().substring(0, file.getName().length()-4));
74 }
75
76 /**
77 * Creates a plugin information object for the plugin with name {@code name}.
78 * Information about the plugin is extracted from the manifest file in the plugin jar
79 * {@code file}.
80 * @param file the plugin jar
81 * @param name the plugin name
82 * @throws PluginException thrown if reading the manifest file fails
83 */
84 public PluginInformation(File file, String name) throws PluginException {
85 if (!PluginHandler.isValidJar(file)) {
86 throw new PluginException(name, tr("Invalid jar file ''{0}''", file));
87 }
88 this.name = name;
89 this.file = file;
90 FileInputStream fis = null;
91 JarInputStream jar = null;
92 try {
93 fis = new FileInputStream(file);
94 jar = new JarInputStream(fis);
95 Manifest manifest = jar.getManifest();
96 if (manifest == null)
97 throw new PluginException(name, tr("The plugin file ''{0}'' does not include a Manifest.", file.toString()));
98 scanManifest(manifest, false);
99 libraries.add(0, fileToURL(file));
100 } catch (IOException e) {
101 throw new PluginException(name, e);
102 } finally {
103 Utils.close(jar);
104 Utils.close(fis);
105 }
106 }
107
108 /**
109 * Creates a plugin information object by reading plugin information in Manifest format
110 * from the input stream {@code manifestStream}.
111 *
112 * @param manifestStream the stream to read the manifest from
113 * @param name the plugin name
114 * @param url the download URL for the plugin
115 * @throws PluginException thrown if the plugin information can't be read from the input stream
116 */
117 public PluginInformation(InputStream manifestStream, String name, String url) throws PluginException {
118 this.name = name;
119 try {
120 Manifest manifest = new Manifest();
121 manifest.read(manifestStream);
122 if(url != null) {
123 downloadlink = url;
124 }
125 scanManifest(manifest, url != null);
126 } catch (IOException e) {
127 throw new PluginException(name, e);
128 }
129 }
130
131 /**
132 * Updates the plugin information of this plugin information object with the
133 * plugin information in a plugin information object retrieved from a plugin
134 * update site.
135 *
136 * @param other the plugin information object retrieved from the update
137 * site
138 */
139 public void updateFromPluginSite(PluginInformation other) {
140 this.mainversion = other.mainversion;
141 this.className = other.className;
142 this.requires = other.requires;
143 this.link = other.link;
144 this.description = other.description;
145 this.early = other.early;
146 this.author = other.author;
147 this.stage = other.stage;
148 this.version = other.version;
149 this.downloadlink = other.downloadlink;
150 this.icon = other.icon;
151 this.iconPath = other.iconPath;
152 this.libraries = other.libraries;
153 this.attr.clear();
154 this.attr.putAll(other.attr);
155 }
156
157 /**
158 * Updates the plugin information of this plugin information object with the
159 * plugin information in a plugin information object retrieved from a plugin
160 * jar.
161 *
162 * @param other the plugin information object retrieved from the jar file
163 * @since 5601
164 */
165 public void updateFromJar(PluginInformation other) {
166 updateLocalInfo(other);
167 if (other.icon != null) {
168 this.icon = other.icon;
169 }
170 this.early = other.early;
171 this.className = other.className;
172 this.libraries = other.libraries;
173 this.stage = other.stage;
174 }
175
176 private void scanManifest(Manifest manifest, boolean oldcheck){
177 String lang = LanguageInfo.getLanguageCodeManifest();
178 Attributes attr = manifest.getMainAttributes();
179 className = attr.getValue("Plugin-Class");
180 String s = attr.getValue(lang+"Plugin-Link");
181 if(s == null) {
182 s = attr.getValue("Plugin-Link");
183 }
184 if(s != null) {
185 try {
186 URL url = new URL(s);
187 } catch (MalformedURLException e) {
188 System.out.println(tr("Invalid URL ''{0}'' in plugin {1}", s, name));
189 s = null;
190 }
191 }
192 link = s;
193 requires = attr.getValue("Plugin-Requires");
194 s = attr.getValue(lang+"Plugin-Description");
195 if(s == null)
196 {
197 s = attr.getValue("Plugin-Description");
198 if(s != null) {
199 try {
200 s = tr(s);
201 } catch (IllegalArgumentException e) {
202 System.out.println(tr("Invalid plugin description ''{0}'' in plugin {1}", s, name));
203 }
204 }
205 }
206 description = s;
207 early = Boolean.parseBoolean(attr.getValue("Plugin-Early"));
208 String stageStr = attr.getValue("Plugin-Stage");
209 stage = stageStr == null ? 50 : Integer.parseInt(stageStr);
210 version = attr.getValue("Plugin-Version");
211 try { mainversion = Integer.parseInt(attr.getValue("Plugin-Mainversion")); }
212 catch(NumberFormatException e) {}
213 author = attr.getValue("Author");
214 iconPath = attr.getValue("Plugin-Icon");
215 if (iconPath != null && file != null) {
216 // extract icon from the plugin jar file
217 icon = new ImageProvider(iconPath).setArchive(file).setMaxWidth(24).setMaxHeight(24).setOptional(true).get();
218 }
219 if(oldcheck && mainversion > Version.getInstance().getVersion())
220 {
221 int myv = Version.getInstance().getVersion();
222 for(Map.Entry<Object, Object> entry : attr.entrySet())
223 {
224 try {
225 String key = ((Attributes.Name)entry.getKey()).toString();
226 if(key.endsWith("_Plugin-Url"))
227 {
228 int mv = Integer.parseInt(key.substring(0,key.length()-11));
229 if(mv <= myv && (mv > mainversion || mainversion > myv))
230 {
231 String v = (String)entry.getValue();
232 int i = v.indexOf(';');
233 if(i > 0)
234 {
235 downloadlink = v.substring(i+1);
236 mainversion = mv;
237 version = v.substring(0,i);
238 oldmode = true;
239 }
240 }
241 }
242 }
243 catch(Exception e) { e.printStackTrace(); }
244 }
245 }
246
247 String classPath = attr.getValue(Attributes.Name.CLASS_PATH);
248 if (classPath != null) {
249 for (String entry : classPath.split(" ")) {
250 File entryFile;
251 if (new File(entry).isAbsolute() || file == null) {
252 entryFile = new File(entry);
253 } else {
254 entryFile = new File(file.getParent(), entry);
255 }
256
257 libraries.add(fileToURL(entryFile));
258 }
259 }
260 for (Object o : attr.keySet()) {
261 this.attr.put(o.toString(), attr.getValue(o.toString()));
262 }
263 }
264
265 /**
266 * Replies the description as HTML document, including a link to a web page with
267 * more information, provided such a link is available.
268 *
269 * @return the description as HTML document
270 */
271 public String getDescriptionAsHtml() {
272 StringBuilder sb = new StringBuilder();
273 sb.append("<html><body>");
274 sb.append(description == null ? tr("no description available") : description);
275 if (link != null) {
276 sb.append(" <a href=\"").append(link).append("\">").append(tr("More info...")).append("</a>");
277 }
278 if (downloadlink != null && !downloadlink.startsWith("http://svn.openstreetmap.org/applications/editors/josm/dist/")
279 && !downloadlink.startsWith("http://trac.openstreetmap.org/browser/applications/editors/josm/dist/")) {
280 sb.append("<p>&nbsp;</p><p>"+tr("<b>Plugin provided by an external source:</b> {0}", downloadlink)+"</p>");
281 }
282 sb.append("</body></html>");
283 return sb.toString();
284 }
285
286 /**
287 * Load and instantiate the plugin
288 *
289 * @param klass the plugin class
290 * @return the instantiated and initialized plugin
291 */
292 public PluginProxy load(Class<?> klass) throws PluginException{
293 try {
294 Constructor<?> c = klass.getConstructor(PluginInformation.class);
295 Object plugin = c.newInstance(this);
296 return new PluginProxy(plugin, this);
297 } catch(NoSuchMethodException e) {
298 throw new PluginException(name, e);
299 } catch(IllegalAccessException e) {
300 throw new PluginException(name, e);
301 } catch (InstantiationException e) {
302 throw new PluginException(name, e);
303 } catch(InvocationTargetException e) {
304 throw new PluginException(name, e);
305 }
306 }
307
308 /**
309 * Load the class of the plugin
310 *
311 * @param classLoader the class loader to use
312 * @return the loaded class
313 */
314 public Class<?> loadClass(ClassLoader classLoader) throws PluginException {
315 if (className == null)
316 return null;
317 try{
318 Class<?> realClass = Class.forName(className, true, classLoader);
319 return realClass;
320 } catch (ClassNotFoundException e) {
321 throw new PluginException(name, e);
322 } catch (ClassCastException e) {
323 throw new PluginException(name, e);
324 }
325 }
326
327 public static URL fileToURL(File f) {
328 try {
329 return f.toURI().toURL();
330 } catch (MalformedURLException ex) {
331 return null;
332 }
333 }
334
335 /**
336 * Try to find a plugin after some criterias. Extract the plugin-information
337 * from the plugin and return it. The plugin is searched in the following way:
338 *
339 *<li>first look after an MANIFEST.MF in the package org.openstreetmap.josm.plugins.<plugin name>
340 * (After removing all fancy characters from the plugin name).
341 * If found, the plugin is loaded using the bootstrap classloader.
342 *<li>If not found, look for a jar file in the user specific plugin directory
343 * (~/.josm/plugins/<plugin name>.jar)
344 *<li>If not found and the environment variable JOSM_RESOURCES + "/plugins/" exist, look there.
345 *<li>Try for the java property josm.resources + "/plugins/" (set via java -Djosm.plugins.path=...)
346 *<li>If the environment variable ALLUSERSPROFILE and APPDATA exist, look in
347 * ALLUSERSPROFILE/<the last stuff from APPDATA>/JOSM/plugins.
348 * (*sic* There is no easy way under Windows to get the All User's application
349 * directory)
350 *<li>Finally, look in some typical unix paths:<ul>
351 * <li>/usr/local/share/josm/plugins/
352 * <li>/usr/local/lib/josm/plugins/
353 * <li>/usr/share/josm/plugins/
354 * <li>/usr/lib/josm/plugins/
355 *
356 * If a plugin class or jar file is found earlier in the list but seem not to
357 * be working, an PluginException is thrown rather than continuing the search.
358 * This is so JOSM can detect broken user-provided plugins and do not go silently
359 * ignore them.
360 *
361 * The plugin is not initialized. If the plugin is a .jar file, it is not loaded
362 * (only the manifest is extracted). In the classloader-case, the class is
363 * bootstraped (e.g. static {} - declarations will run. However, nothing else is done.
364 *
365 * @param pluginName The name of the plugin (in all lowercase). E.g. "lang-de"
366 * @return Information about the plugin or <code>null</code>, if the plugin
367 * was nowhere to be found.
368 * @throws PluginException In case of broken plugins.
369 */
370 public static PluginInformation findPlugin(String pluginName) throws PluginException {
371 String name = pluginName;
372 name = name.replaceAll("[-. ]", "");
373 InputStream manifestStream = PluginInformation.class.getResourceAsStream("/org/openstreetmap/josm/plugins/"+name+"/MANIFEST.MF");
374 if (manifestStream != null)
375 return new PluginInformation(manifestStream, pluginName, null);
376
377 Collection<String> locations = getPluginLocations();
378
379 for (String s : locations) {
380 File pluginFile = new File(s, pluginName + ".jar");
381 if (pluginFile.exists()) {
382 PluginInformation info = new PluginInformation(pluginFile);
383 return info;
384 }
385 }
386 return null;
387 }
388
389 public static Collection<String> getPluginLocations() {
390 Collection<String> locations = Main.pref.getAllPossiblePreferenceDirs();
391 Collection<String> all = new ArrayList<String>(locations.size());
392 for (String s : locations) {
393 all.add(s+"plugins");
394 }
395 return all;
396 }
397
398 /**
399 * Replies true if the plugin with the given information is most likely outdated with
400 * respect to the referenceVersion.
401 *
402 * @param referenceVersion the reference version. Can be null if we don't know a
403 * reference version
404 *
405 * @return true, if the plugin needs to be updated; false, otherweise
406 */
407 public boolean isUpdateRequired(String referenceVersion) {
408 if (this.downloadlink == null) return false;
409 if (this.version == null && referenceVersion!= null)
410 return true;
411 if (this.version != null && !this.version.equals(referenceVersion))
412 return true;
413 return false;
414 }
415
416 /**
417 * Replies true if this this plugin should be updated/downloaded because either
418 * it is not available locally (its local version is null) or its local version is
419 * older than the available version on the server.
420 *
421 * @return true if the plugin should be updated
422 */
423 public boolean isUpdateRequired() {
424 if (this.downloadlink == null) return false;
425 if (this.localversion == null) return true;
426 return isUpdateRequired(this.localversion);
427 }
428
429 protected boolean matches(String filter, String value) {
430 if (filter == null) return true;
431 if (value == null) return false;
432 return value.toLowerCase().contains(filter.toLowerCase());
433 }
434
435 /**
436 * Replies true if either the name, the description, or the version match (case insensitive)
437 * one of the words in filter. Replies true if filter is null.
438 *
439 * @param filter the filter expression
440 * @return true if this plugin info matches with the filter
441 */
442 public boolean matches(String filter) {
443 if (filter == null) return true;
444 String[] words = filter.split("\\s+");
445 for (String word: words) {
446 if (matches(word, name)
447 || matches(word, description)
448 || matches(word, version)
449 || matches(word, localversion))
450 return true;
451 }
452 return false;
453 }
454
455 /**
456 * Replies the name of the plugin
457 */
458 public String getName() {
459 return name;
460 }
461
462 /**
463 * Sets the name
464 * @param name
465 */
466 public void setName(String name) {
467 this.name = name;
468 }
469
470 /**
471 * Replies the plugin icon, scaled to 24x24 pixels.
472 * @return the plugin icon, scaled to 24x24 pixels.
473 */
474 public ImageIcon getScaledIcon() {
475 if (icon == null)
476 return emptyIcon;
477 return new ImageIcon(icon.getImage().getScaledInstance(24, 24, Image.SCALE_SMOOTH));
478 }
479
480 @Override
481 public String toString() {
482 return getName();
483 }
484
485 private static List<String> getRequiredPlugins(String pluginList) {
486 List<String> requiredPlugins = new ArrayList<String>();
487 if (pluginList != null) {
488 for (String s : pluginList.split(";")) {
489 String plugin = s.trim();
490 if (!plugin.isEmpty()) {
491 requiredPlugins.add(plugin);
492 }
493 }
494 }
495 return requiredPlugins;
496 }
497
498 /**
499 * Replies the list of plugins required by the up-to-date version of this plugin.
500 * @return List of plugins required. Empty if no plugin is required.
501 * @since 5601
502 */
503 public List<String> getRequiredPlugins() {
504 return getRequiredPlugins(requires);
505 }
506
507 /**
508 * Replies the list of plugins required by the local instance of this plugin.
509 * @return List of plugins required. Empty if no plugin is required.
510 * @since 5601
511 */
512 public List<String> getLocalRequiredPlugins() {
513 return getRequiredPlugins(localrequires);
514 }
515
516 /**
517 * Updates the local fields ({@link #localversion}, {@link #localmainversion}, {@link #localrequires})
518 * to values contained in the up-to-date fields ({@link #version}, {@link #mainversion}, {@link #requires})
519 * of the given PluginInformation.
520 * @param info The plugin information to get the data from.
521 * @since 5601
522 */
523 public void updateLocalInfo(PluginInformation info) {
524 if (info != null) {
525 this.localversion = info.version;
526 this.localmainversion = info.mainversion;
527 this.localrequires = info.requires;
528 }
529 }
530}
Note: See TracBrowser for help on using the repository browser.