KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > tools > eclipse > EclipseClasspath


1 /*
2  * Generate a Java classpath from an Eclipse plugin.xml file
3  * Copyright (C) 2004, University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.tools.eclipse;
21
22 import java.io.BufferedInputStream JavaDoc;
23 import java.io.BufferedReader JavaDoc;
24 import java.io.File JavaDoc;
25 import java.io.FileFilter JavaDoc;
26 import java.io.FileInputStream JavaDoc;
27 import java.io.FileReader JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.io.Reader JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.LinkedList JavaDoc;
33 import java.util.List JavaDoc;
34 import java.util.Map JavaDoc;
35 import java.util.StringTokenizer JavaDoc;
36 import java.util.jar.Attributes JavaDoc;
37 import java.util.jar.Manifest JavaDoc;
38 import java.util.regex.Matcher JavaDoc;
39 import java.util.regex.Pattern JavaDoc;
40
41 import org.dom4j.Document;
42 import org.dom4j.DocumentException;
43 import org.dom4j.Node;
44 import org.dom4j.io.SAXReader;
45
46 /**
47  * Starting from an Eclipse plugin, finds all required plugins
48  * (in an Eclipse installation) and recursively finds the classpath
49  * required to compile the original plugin. Different Eclipse
50  * releases will generally have different version numbers on the
51  * plugins they contain, which makes this task slightly difficult.
52  *
53  * <p> Basically, this is a big complicated hack to allow compilation
54  * of the FindBugs Eclipse plugin outside of the Eclipse workspace,
55  * in a way that doesn't depend on any specific release of Eclipse.
56  *
57  * @author David Hovemeyer
58  */

59 public class EclipseClasspath {
60
61     public static class EclipseClasspathException extends Exception JavaDoc {
62         /**
63          *
64          */

65         private static final long serialVersionUID = 1L;
66
67         public EclipseClasspathException(String JavaDoc msg) {
68             super(msg);
69         }
70
71         public EclipseClasspathException(String JavaDoc msg, Throwable JavaDoc e) {
72             super(msg, e);
73         }
74     }
75
76     /**
77      * A customized Reader for Eclipse plugin descriptor files.
78      * The problem is that Eclipse uses invalid XML in the form of
79      * directives like
80      * <pre>
81      * &lt;?eclipse version="3.0"?&gt;
82      * </pre>
83      * outside of the root element. This Reader class
84      * filters out this crap.
85      */

86     private static class EclipseXMLReader extends Reader JavaDoc {
87         private BufferedReader JavaDoc reader;
88         private LinkedList JavaDoc<String JavaDoc> lineList;
89
90         public EclipseXMLReader(Reader JavaDoc reader) {
91             this.reader = new BufferedReader JavaDoc(reader);
92             this.lineList = new LinkedList JavaDoc<String JavaDoc>();
93         }
94
95         @Override JavaDoc
96         public int read(char[] cbuf, int off, int len) throws IOException JavaDoc {
97             if (!fill())
98                 return -1;
99             String JavaDoc line = lineList.getFirst();
100             if (len > line.length())
101                 len = line.length();
102             for (int i = 0; i < len; ++i)
103                 cbuf[i+off] = line.charAt(i);
104             if (len == line.length())
105                 lineList.removeFirst();
106             else
107                 lineList.set(0, line.substring(len));
108             return len;
109         }
110
111         @Override JavaDoc
112         public void close() throws IOException JavaDoc {
113             reader.close();
114         }
115
116         private boolean fill() throws IOException JavaDoc {
117             if (!lineList.isEmpty())
118                 return true;
119
120             String JavaDoc line;
121             do {
122                 line = reader.readLine();
123                 if (line == null)
124                     return false;
125             } while (isIllegal(line));
126             lineList.add(line+"\n");
127             return true;
128         }
129
130         private boolean isIllegal(String JavaDoc line) {
131             return line.startsWith("<?eclipse");
132         }
133     }
134
135     private class Plugin {
136         private String JavaDoc directory;
137         private boolean isDependent;
138         private String JavaDoc pluginId;
139         private String JavaDoc pluginVersion;
140         private List JavaDoc<String JavaDoc> requiredPluginIdList;
141         private List JavaDoc<String JavaDoc> exportedLibraryList;
142
143         public Plugin(String JavaDoc directory, boolean isDependent)
144                 throws DocumentException, EclipseClasspathException, IOException JavaDoc {
145             this.directory = directory;
146             this.isDependent = isDependent;
147             this.requiredPluginIdList = new LinkedList JavaDoc<String JavaDoc>();
148             this.exportedLibraryList = new LinkedList JavaDoc<String JavaDoc>();
149
150             // Figure out whether this is an old-style (Eclipse 2.1.x)
151
// or new-style (3.0, OSGI-based) plugin.
152

153             boolean oldStyle = false;
154
155             Document document = null;
156             File JavaDoc pluginDescriptorFile = new File JavaDoc(directory + File.separator + "plugin.xml");
157             if (pluginDescriptorFile.isFile()) {
158                 SAXReader reader = new SAXReader();
159                 document = reader.read(new EclipseXMLReader(new FileReader JavaDoc(pluginDescriptorFile)));
160
161                 Node plugin = document.selectSingleNode("/plugin");
162                 if (plugin == null)
163                     throw new EclipseClasspathException("No plugin node in plugin descriptor");
164
165                 oldStyle = !plugin.valueOf("@id").equals("");
166             }
167
168             // Get the plugin id
169

170             if (oldStyle) {
171                 parseOldPluginDescriptor(directory, document, isDependent);
172             } else {
173                 parseNewPluginDescriptor(directory, isDependent);
174             }
175         }
176
177         public String JavaDoc getDirectory() {
178             return directory;
179         }
180
181         public boolean isDependent() {
182             return isDependent;
183         }
184
185         public String JavaDoc getId() {
186             return pluginId;
187         }
188
189         public String JavaDoc getVersion() {
190             return pluginVersion;
191         }
192
193         public Iterator JavaDoc<String JavaDoc> requiredPluginIdIterator() {
194             return requiredPluginIdList.iterator();
195         }
196
197         public Iterator JavaDoc<String JavaDoc> exportedLibraryIterator() {
198             return exportedLibraryList.iterator();
199         }
200
201         private void parseOldPluginDescriptor(String JavaDoc directory, Document document, boolean isDependent)
202             throws DocumentException, EclipseClasspathException {
203             // In Eclipse 2.1.x, all of the information we need
204
// is in plugin.xml.
205

206             Node plugin = document.selectSingleNode("/plugin");
207
208             pluginId = plugin.valueOf("@id");
209             //System.out.println("Plugin id is " + pluginId);
210
pluginVersion = plugin.valueOf("@version");
211             if (pluginVersion.equals(""))
212                 throw new EclipseClasspathException("Cannot determine plugin version");
213
214             // Extract required plugins
215
List JavaDoc<Node> requiredPluginNodeList = (List JavaDoc<Node>) document.selectNodes("/plugin/requires/import");
216             for (Node node : requiredPluginNodeList) {
217                 String JavaDoc requiredPluginId = node.valueOf("@plugin");
218                 if (requiredPluginId.equals(""))
219                     throw new EclipseClasspathException("Import has no plugin id");
220                 //System.out.println(" Required plugin ==> " + requiredPluginId);
221
requiredPluginIdList.add(requiredPluginId);
222             }
223
224             // Extract exported libraries
225
List JavaDoc<Node> exportedLibraryNodeList = (List JavaDoc<Node>) document.selectNodes("/plugin/runtime/library");
226             for (Node node : exportedLibraryNodeList) {
227                 String JavaDoc jarName = node.valueOf("@name");
228                 if (jarName.equals(""))
229                     throw new EclipseClasspathException("Could not get name of exported library");
230
231                 jarName = replaceSpecial(jarName);
232                 File JavaDoc jarFile = new File JavaDoc(jarName);
233                 if (!jarFile.isAbsolute()) {
234                     // Make relative to plugin directory
235
jarFile = new File JavaDoc(directory, jarName);
236                 }
237                 exportedLibraryList.add(jarFile.getPath());
238             }
239         }
240
241         private void parseNewPluginDescriptor(String JavaDoc directory, boolean isDependent)
242             throws DocumentException, EclipseClasspathException {
243             // In Eclipse 3.x, we need to parse the plugin's MANIFEST.MF
244

245             BufferedInputStream JavaDoc in = null;
246             try {
247                 String JavaDoc manifestFileName = directory + File.separator + "META-INF/MANIFEST.MF";
248                 in = new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(manifestFileName));
249
250                 Manifest JavaDoc manifest = new Manifest JavaDoc(in);
251                 Attributes JavaDoc attributes = manifest.getMainAttributes();
252
253                 // Get the plugin id
254
pluginId = attributes.getValue("Bundle-SymbolicName");
255                 if (pluginId == null)
256                     throw new EclipseClasspathException("Missing Bundle-SymbolicName in " + manifestFileName);
257
258                 pluginId = stripSemiPart(pluginId);
259
260                 // Get the plugin version
261
pluginVersion = attributes.getValue("Bundle-Version");
262                 if (pluginVersion == null)
263                     throw new EclipseClasspathException("Missing Bundle-Version in " + manifestFileName);
264
265                 // Get required plugins
266
String JavaDoc required = attributes.getValue("Require-Bundle");
267                 if (required != null) {
268                     StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(required, ",");
269                     while (tok.hasMoreTokens()) {
270                         String JavaDoc requiredPlugin = tok.nextToken();
271                         requiredPlugin = requiredPlugin.trim();
272                         requiredPlugin = stripSemiPart(requiredPlugin);
273                         //System.out.println("Required plugin=" +requiredPlugin);
274
requiredPluginIdList.add(requiredPlugin);
275                     }
276                 }
277
278                 // Get exported libraries
279
String JavaDoc exported = attributes.getValue("Bundle-ClassPath");
280                 if (exported != null) {
281                     StringTokenizer JavaDoc tok2 = new StringTokenizer JavaDoc(exported, ",");
282                     while (tok2.hasMoreTokens()) {
283                         String JavaDoc jar = tok2.nextToken();
284                         jar = jar.trim();
285                         jar = stripSemiPart(jar);
286                         exportedLibraryList.add(directory + File.separator + jar);
287                     }
288                 }
289
290             } catch (IOException JavaDoc e) {
291                 throw new EclipseClasspathException("Could not parse Eclipse 3.0 plugin in " + directory, e);
292             } finally {
293                 try {
294                     if (in != null)
295                         in.close();
296                 } catch (IOException JavaDoc e) {
297                     // Ignore
298
}
299             }
300         }
301
302         private String JavaDoc stripSemiPart(String JavaDoc s) {
303             int semi = s.indexOf(';');
304             if (semi >= 0)
305                 s = s.substring(0, semi);
306             return s;
307         }
308     }
309
310     private static final Pattern JavaDoc pluginDirPattern = Pattern.compile("^(\\w+(\\.\\w+)*)_(\\w+(\\.\\w+)*)$");
311     private static final int PLUGIN_ID_GROUP = 1;
312
313     private String JavaDoc eclipseDir;
314     private String JavaDoc rootPluginDir;
315     private Map JavaDoc<String JavaDoc, File JavaDoc> pluginDirectoryMap;
316     private Map JavaDoc<String JavaDoc, String JavaDoc> varMap;
317     private Plugin rootPlugin;
318     private List JavaDoc<String JavaDoc> importList;
319
320     public EclipseClasspath(String JavaDoc eclipseDir, String JavaDoc rootPluginDir) {
321         this.eclipseDir = eclipseDir;
322         this.rootPluginDir = rootPluginDir;
323         this.pluginDirectoryMap = new HashMap JavaDoc<String JavaDoc, File JavaDoc>();
324         this.varMap = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
325     }
326
327     public void addRequiredPlugin(String JavaDoc pluginId, String JavaDoc pluginDir) {
328         pluginDirectoryMap.put(pluginId, new File JavaDoc(pluginDir));
329     }
330
331     private static class WorkListItem {
332         private String JavaDoc directory;
333         private boolean isDependent;
334
335         public WorkListItem(String JavaDoc directory, boolean isDependent) {
336             this.directory = directory;
337             this.isDependent = isDependent;
338         }
339
340         public String JavaDoc getDirectory() { return directory; }
341         public boolean isDependent() { return isDependent; }
342     }
343
344     public EclipseClasspath execute() throws EclipseClasspathException, DocumentException, IOException JavaDoc {
345
346         // Build plugin directory map
347
File JavaDoc pluginDir = new File JavaDoc(eclipseDir, "plugins");
348         File JavaDoc[] dirList = pluginDir.listFiles(new FileFilter JavaDoc() {
349             public boolean accept(File JavaDoc pathname) {
350                 return pathname.isDirectory();
351             }
352         });
353         if (dirList == null)
354             throw new EclipseClasspathException("Could not list plugins in directory " + pluginDir);
355         for (File JavaDoc aDirList : dirList) {
356             String JavaDoc dirName = aDirList.getName();
357             String JavaDoc pluginId = getPluginId(dirName);
358             if (pluginId != null) {
359                 //System.out.println(pluginId + " ==> " + dirList[i]);
360
pluginDirectoryMap.put(pluginId, aDirList);
361
362                 // HACK - see if we can deduce the value of the special "ws" variable.
363
if (pluginId.startsWith("org.eclipse.swt.")) {
364                     String JavaDoc ws = pluginId.substring("org.eclipse.swt.".length());
365                     if (ws.equals("gtk64"))
366                         ws = "gtk";
367                     varMap.put("ws", new File JavaDoc(aDirList + File.separator + "ws" + File.separator + ws).getPath().replace('\\', '/'));
368                 }
369             }
370         }
371
372         Map JavaDoc<String JavaDoc, Plugin> requiredPluginMap = new HashMap JavaDoc<String JavaDoc, Plugin>();
373
374         LinkedList JavaDoc<WorkListItem> workList = new LinkedList JavaDoc<WorkListItem>();
375         workList.add(new WorkListItem(rootPluginDir, false));
376
377         while (!workList.isEmpty()) {
378             WorkListItem item = workList.removeFirst();
379
380             Plugin plugin = new Plugin(item.getDirectory(), item.isDependent());
381             requiredPluginMap.put(plugin.getId(), plugin);
382
383             if (!plugin.isDependent()) {
384                 if (rootPlugin != null) throw new IllegalStateException JavaDoc("multiple root plugins");
385                 this.rootPlugin = plugin;
386             }
387
388             // Add unresolved required plugins to the worklist
389
for (Iterator JavaDoc<String JavaDoc> i = plugin.requiredPluginIdIterator(); i.hasNext(); ) {
390                 String JavaDoc requiredPluginId = i.next();
391                 if (requiredPluginMap.get(requiredPluginId) == null) {
392                     // Find the plugin's directory
393
File JavaDoc requiredPluginDir = pluginDirectoryMap.get(requiredPluginId);
394                     if (requiredPluginDir == null)
395                         throw new EclipseClasspathException("Unable to find plugin " + requiredPluginId);
396                     workList.add(new WorkListItem(requiredPluginDir.getPath(), true));
397                 }
398             }
399         }
400
401         //System.out.println("Found " + requiredPluginMap.size() + " required plugins");
402

403         importList = new LinkedList JavaDoc<String JavaDoc>();
404         for (Plugin plugin : requiredPluginMap.values()) {
405             if (plugin.isDependent()) {
406                 for (Iterator JavaDoc<String JavaDoc> j = plugin.exportedLibraryIterator(); j.hasNext();) {
407                     //System.out.println("Import: " + j.next());
408
importList.add(j.next());
409                 }
410             }
411         }
412
413         return this;
414     }
415
416     public Iterator JavaDoc<String JavaDoc> importListIterator() {
417         return importList.iterator();
418     }
419
420     public String JavaDoc getClasspath() {
421         if (importList == null) throw new IllegalStateException JavaDoc("execute() has not been called");
422
423         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
424         boolean first = true;
425
426         Iterator JavaDoc <String JavaDoc> i = importListIterator();
427         while (i.hasNext()) {
428             if (first)
429                 first = false;
430             else
431                 buf.append(File.pathSeparator);
432
433             buf.append(i.next());
434         }
435
436         // Convert backslashes to forward slashes,
437
// since raw backslashes cause problems in .properties files.
438
String JavaDoc s = buf.toString();
439         s = s.replace('\\', '/');
440
441         return s;
442     }
443
444     public Plugin getRootPlugin() {
445         return rootPlugin;
446     }
447
448     /**
449      * Get the plugin id for given directory name.
450      * Returns null if the directory name does not seem to
451      * be a plugin.
452      */

453     private String JavaDoc getPluginId(String JavaDoc dirName) {
454         Matcher JavaDoc m = pluginDirPattern.matcher(dirName);
455         return m.matches() ? m.group(PLUGIN_ID_GROUP) : null;
456     }
457
458     /**
459      * Expand variables of the form $varname$ in library names.
460      * This is used to handle the "$ws$" substitution used to refer to
461      * the plugin containing swt.jar.
462      */

463     private String JavaDoc replaceSpecial(String JavaDoc value) {
464         if (!varMap.isEmpty()) {
465             for (String JavaDoc key : varMap.keySet()) {
466                 String JavaDoc replace = varMap.get(key);
467                 Pattern JavaDoc pat = Pattern.compile("\\$\\Q" + key + "\\E\\$");
468                 Matcher JavaDoc m = pat.matcher(value);
469                 StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
470
471                 while (m.find())
472                     m.appendReplacement(buf, replace);
473                 m.appendTail(buf);
474
475                 value = buf.toString();
476             }
477         }
478         return value;
479     }
480
481     public static void main(String JavaDoc[] argv) throws Exception JavaDoc {
482         if (argv.length < 2 || (argv.length % 2 != 0)) {
483             System.err.println("Usage: " + EclipseClasspath.class.getName() +
484                 " <eclipse dir> <root plugin directory> [<required plugin id> <required plugin dir> ...]");
485             System.exit(1);
486         }
487
488         EclipseClasspath ec = new EclipseClasspath(argv[0], argv[1]);
489         for (int i = 2; i < argv.length; i += 2) {
490             ec.addRequiredPlugin(argv[i], new File JavaDoc(argv[i+1]).getAbsolutePath());
491         }
492
493         // Generate a build.properties file which communicates to Ant:
494
// - what the build classpath should be
495
// - what the plugin id and version are
496
ec.execute();
497         System.out.println("plugin.build.classpath=" + ec.getClasspath());
498         System.out.println("plugin.id=" + ec.getRootPlugin().getId());
499         System.out.println("plugin.version=" + ec.getRootPlugin().getVersion());
500     }
501 }
502
503 // vim:ts=4
504
Popular Tags