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

Last change on this file since 2817 was 2817, checked in by Gubaer, 14 years ago

fixed #3063: Downloading a plugin yields 3 dialogs at the same time: Downloading plugin / You should restart JOSM / Plugin downloaded
fixed #3628: JOSM blocking itself updating broken plugin
fixed #4187: JOSM deleted random files from disk after start (data loss)
fixed #4199: new version - plugins update vs josm start [should be fixed. Be careful if you have two JOSM instances running. Auto-update of plugins in the second instance will fail because plugin files are locked by the first instance]
fixed #4034: JOSM should auto-download plugin list when it hasn't been downloaded before [JOSM now displays a hint]

fixed: splash screen showing again even if plugins are auto-updated
new: progress indication integrated in splash screen
new: cancelable, asynchronous download of plugins from preferences
new: cancelable, asynchronous download of plugin list from plugin download sites
new: asynchronous loading of plugin information, launch of preferences dialog accelerated
refactored: clean up, documentation of plugin management code (PluginHandler)

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