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

Last change on this file since 3965 was 3936, checked in by bastiK, 13 years ago

warning in the plugin list, when plugin is not provided by normal svn but from an external source

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