KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > nbbuild > MakeNBM


1 /*
2  * Sun Public License Notice
3  *
4  * The contents of this file are subject to the Sun Public License
5  * Version 1.0 (the "License"). You may not use this file except in
6  * compliance with the License. A copy of the License is available at
7  * http://www.sun.com/
8  *
9  * The Original Code is NetBeans. The Initial Developer of the Original
10  * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
11  * Microsystems, Inc. All Rights Reserved.
12  */

13
14 package org.netbeans.nbbuild;
15
16 import java.io.*;
17 import java.util.*;
18 import java.util.jar.*;
19 import java.util.zip.*;
20 import java.net.*;
21
22 import org.apache.tools.ant.BuildException;
23 import org.apache.tools.ant.Location;
24 import org.apache.tools.ant.Project;
25 import org.apache.tools.ant.taskdefs.MatchingTask;
26 import org.apache.tools.ant.taskdefs.Jar;
27 import org.apache.tools.ant.taskdefs.Zip;
28 import org.apache.tools.ant.taskdefs.SignJar;
29 import org.apache.tools.ant.types.FileSet;
30
31 /** Makes a <code>.nbm</code> (<b>N</b>et<b>B</b>eans <b>M</b>odule) file.
32  *
33  * @author Jesse Glick
34  */

35 public class MakeNBM extends MatchingTask {
36
37     /** The same syntax may be used for either <samp>&lt;license&gt;</samp> or
38      * <samp>&lt;description&gt;</samp> subelements.
39      * <p>By setting the property <code>makenbm.nocdata</code> to <code>true</code>,
40      * you can avoid using XML <code>CDATA</code> (for compatibility with older versions
41      * of Auto Update which could not handle it).
42      */

43     public class Blurb {
44         /** You may embed a <samp>&lt;file&gt;</samp> element inside the blurb.
45          * If there is text on either side of it, that will be separated
46          * with a line of dashes automatically.
47          * But use nested <samp>&lt;text&gt;</samp> for this purpose.
48          */

49     public class FileInsert {
50             /** File location. */
51         public void setLocation (File file) throws BuildException {
52                 log("Including contents of " + file, Project.MSG_VERBOSE);
53         long lmod = file.lastModified ();
54         if (lmod > mostRecentInput) mostRecentInput = lmod;
55         addSeparator ();
56         try {
57             InputStream is = new FileInputStream (file);
58             try {
59             Reader r = new InputStreamReader (is);
60             char[] buf = new char[4096];
61             int len;
62             while ((len = r.read (buf)) != -1)
63                 text.append (buf, 0, len);
64             } finally {
65             is.close ();
66             }
67         } catch (IOException ioe) {
68                     throw new BuildException ("Exception reading blurb from " + file, ioe, getLocation ());
69         }
70         }
71     }
72     private StringBuffer JavaDoc text = new StringBuffer JavaDoc ();
73     private String JavaDoc name = null;
74         /** There may be freeform text inside the element. Prefer to use nested elements. */
75     public void addText (String JavaDoc t) {
76         addSeparator ();
77         // Strips indentation. Needed because of common style:
78
// <description>
79
// Some text here.
80
// And another line.
81
// </description>
82
t = t.trim ();
83         int min = Integer.MAX_VALUE;
84         StringTokenizer tok = new StringTokenizer (t, "\n");
85         boolean first = true;
86         while (tok.hasMoreTokens ()) {
87         String JavaDoc line = tok.nextToken ();
88         if (first) {
89             first = false;
90         } else {
91             int i;
92             for (i = 0;
93              i < line.length () &&
94                  Character.isWhitespace (line.charAt (i));
95              i++)
96             ;
97             if (i < min) min = i;
98         }
99         }
100         if (min == 0) {
101         text.append (t);
102         } else {
103         tok = new StringTokenizer (t, "\n");
104         first = true;
105         while (tok.hasMoreTokens ()) {
106             String JavaDoc line = tok.nextToken ();
107             if (first) {
108             first = false;
109             } else {
110             text.append ('\n');
111             line = line.substring (min);
112             }
113             text.append (line);
114         }
115         }
116     }
117         /** Contents of a file to include. */
118     public FileInsert createFile () {
119         return new FileInsert ();
120     }
121         /** Text to include literally. */
122         public class Text {
123             public void addText(String JavaDoc t) {
124                 Blurb.this.addText(t);
125             }
126         }
127         // At least on Ant 1.3, mixed content does not work: all the text is added
128
// first, then all the file inserts. Need to use subelements to be sure.
129
/** Include nested literal text. */
130         public Text createText() {
131             return new Text();
132         }
133     private void addSeparator () {
134         if (text.length () > 0) {
135         // some sort of separator
136
if (text.charAt (text.length () - 1) != '\n')
137             text.append ('\n');
138         text.append ("-----------------------------------------------------\n");
139         }
140     }
141     public String JavaDoc getText () {
142             String JavaDoc nocdata = getProject().getProperty("makenbm.nocdata");
143             if (nocdata != null && Project.toBoolean(nocdata)) {
144                 return xmlEscape(text.toString());
145             } else {
146                 return "<![CDATA[" + text.toString () + "]]>";
147             }
148     }
149         /** You can either set a name for the blurb, or using the <code>file</code> attribute does this.
150          * The name is mandatory for licenses, as this identifies the license in
151          * an update description.
152          */

153     public void setName (String JavaDoc name) {
154         this.name = name;
155     }
156     public String JavaDoc getName () {
157         return name;
158     }
159         /** Include a file (and set the license name according to its basename). */
160     public void setFile (File file) {
161         // This actually adds the text and so on:
162
new FileInsert ().setLocation (file);
163         // Default for the name too, as a convenience.
164
if (name == null) name = file.getName ();
165     }
166     }
167
168     public class ExternalPackage {
169     String JavaDoc name = null;
170     String JavaDoc targetName = null;
171     String JavaDoc startUrl = null;
172     String JavaDoc description = null;
173
174     public void setName(String JavaDoc n) {
175         this.name = n;
176     }
177
178     public void setTargetName(String JavaDoc t) {
179         this.targetName = t;
180     }
181
182     public void setStartURL(String JavaDoc u) {
183         this.startUrl = u;
184     }
185     
186     public void setDescription(String JavaDoc d) {
187         this.description = d;
188     }
189
190     }
191
192     // Similar to org.openide.xml.XMLUtil methods.
193
private static String JavaDoc xmlEscape(String JavaDoc s) {
194         int max = s.length();
195         StringBuffer JavaDoc s2 = new StringBuffer JavaDoc((int)(max * 1.1 + 1));
196         for (int i = 0; i < max; i++) {
197             char c = s.charAt(i);
198             switch (c) {
199                 case '<':
200                     s2.append("&lt;");
201                     break;
202                 case '>':
203                     s2.append("&gt;");
204                     break;
205                 case '&':
206                     s2.append("&amp;");
207                     break;
208                 case '"':
209                     s2.append("&quot;");
210                     break;
211                 default:
212                     s2.append(c);
213                     break;
214             }
215         }
216         return s2.toString();
217     }
218
219     /** <samp>&lt;signature&gt;</samp> subelement for signing the NBM. */
220     public /*static*/ class Signature {
221     public File keystore;
222     public String JavaDoc storepass, alias;
223         /** Path to the keystore (private key). */
224     public void setKeystore (File f) {
225         keystore = f;
226     }
227         /** Password for the keystore.
228          * If a question mark (<samp>?</samp>), the NBM will not be signed
229          * and a warning will be printed.
230          */

231     public void setStorepass (String JavaDoc s) {
232         storepass = s;
233     }
234         /** Alias for the private key. */
235     public void setAlias (String JavaDoc s) {
236         alias = s;
237     }
238     }
239
240     private File file = null;
241     private File topdir = null;
242     private File manifest = null;
243     /** see #13850 for explanation */
244     private File module = null;
245     private String JavaDoc homepage = null;
246     private String JavaDoc distribution = null;
247     private String JavaDoc needsrestart = null;
248     private Blurb license = null;
249     private Blurb description = null;
250     private Blurb notification = null;
251     private Signature signature = null;
252     private long mostRecentInput = 0L;
253     private boolean isStandardInclude = true;
254     private Vector externalPackages = null;
255
256     /** Include netbeans directory - default is true */
257     public void setIsStandardInclude(boolean isStandardInclude) {
258     this.isStandardInclude = isStandardInclude;
259     }
260
261     /** Name of resulting NBM file. */
262     public void setFile (File file) {
263     this.file = file;
264     }
265     /** Top directory.
266      * Expected to contain a subdirectory <samp>netbeans/</samp> with the
267      * desired contents of the NBM.
268      * Will create <samp>Info/info.xml</samp> with metadata.
269      */

270     public void setTopdir (File topdir) {
271     this.topdir = topdir;
272     }
273     /** Module manifest needed for versioning.
274      * @deprecated Use {@link #setModule} instead.
275      */

276     public void setManifest (File manifest) {
277     this.manifest = manifest;
278     long lmod = manifest.lastModified ();
279     if (lmod > mostRecentInput) mostRecentInput = lmod;
280         log(getLocation() + "The 'manifest' attr on <makenbm> is deprecated, please use 'module' instead", Project.MSG_WARN);
281     }
282     /** Module JAR needed for generating the info file.
283      * Information may be gotten either from its manifest,
284      * or if it declares OpenIDE-Module-Localizing-Bundle in its
285      * manifest, from that bundle.
286      * The base locale variant, if any, is also checked if necessary
287      * for the named bundle.
288      * Currently no other locale variants of the module are examined;
289      * the information is available but there is no published specification
290      * of what the resulting variant NBMs (or variant information within
291      * the NBM) should look like.
292      */

293     public void setModule(File module) {
294         this.module = module;
295         // mostRecentInput updated below...
296
}
297     /** URL to a home page describing the module. */
298     public void setHomepage (String JavaDoc homepage) {
299     this.homepage = homepage;
300     }
301     /** Does module need IDE restart to be installed? */
302     public void setNeedsrestart (String JavaDoc needsrestart) {
303         this.needsrestart = needsrestart;
304     }
305     /** URL where this NBM file is expected to be downloadable from. */
306     public void setDistribution (String JavaDoc distribution) throws MalformedURLException {
307         if (distribution.startsWith("http://")) {
308             this.distribution = distribution;
309         } else {
310             // workaround for typical bug in build script
311
this.distribution = "http://" + distribution;
312         }
313     }
314     public Blurb createLicense () {
315     return (license = new Blurb ());
316     }
317     public Blurb createNotification () {
318     return (notification = new Blurb ());
319     }
320     public Blurb createDescription () {
321         log(getLocation() + "The <description> subelement in <makenbm> is deprecated except for emergency patches, please ensure your module has an OpenIDE-Module-Long-Description instead", Project.MSG_WARN);
322     return (description = new Blurb ());
323     }
324     public Signature createSignature () {
325     return (signature = new Signature ());
326     }
327
328     public ExternalPackage createExternalPackage(){
329     ExternalPackage externalPackage = new ExternalPackage ();
330     if (externalPackages == null)
331         externalPackages = new Vector();
332     externalPackages.add( externalPackage );
333     return externalPackage;
334     }
335
336     public void execute () throws BuildException {
337     if (file == null)
338         throw new BuildException ("must set file for makenbm", location);
339         if (manifest == null && module == null)
340             throw new BuildException ("must set module for makenbm", location);
341         if (manifest != null && module != null)
342             throw new BuildException("cannot set both manifest and module for makenbm", location);
343     // Will create a file Info/info.xml to be stored alongside netbeans/ contents.
344
File infodir = new File (topdir, "Info");
345     infodir.mkdirs ();
346     File infofile = new File (infodir, "info.xml");
347         Attributes attr = null;
348         if (module != null) {
349             // The normal case; read attributes from its manifest and maybe bundle.
350
long mMod = module.lastModified();
351             if (mostRecentInput < mMod) mostRecentInput = mMod;
352             try {
353                 JarFile modulejar = new JarFile(module);
354                 try {
355                     attr = modulejar.getManifest().getMainAttributes();
356                     String JavaDoc bundlename = attr.getValue("OpenIDE-Module-Localizing-Bundle");
357                     if (bundlename != null) {
358                         Properties p = new Properties();
359                         ZipEntry bundleentry = modulejar.getEntry(bundlename);
360                         if (bundleentry != null) {
361                             InputStream is = modulejar.getInputStream(bundleentry);
362                             try {
363                                 p.load(is);
364                             } finally {
365                                 is.close();
366                             }
367                         } else {
368                             // Not found in main JAR, check locale variant JAR.
369
File variant = new File(new File(module.getParentFile(), "locale"), module.getName());
370                             if (! variant.isFile()) throw new BuildException(bundlename + " not found in " + module, location);
371                             long vmMod = variant.lastModified();
372                             if (mostRecentInput < vmMod) mostRecentInput = vmMod;
373                             ZipFile variantjar = new ZipFile(variant);
374                             try {
375                                 bundleentry = variantjar.getEntry(bundlename);
376                                 if (bundleentry == null) throw new BuildException(bundlename + " not found in " + module + " nor in " + variant, location);
377                                 InputStream is = variantjar.getInputStream(bundleentry);
378                                 try {
379                                     p.load(is);
380                                 } finally {
381                                     is.close();
382                                 }
383                             } finally {
384                                 variantjar.close();
385                             }
386                         }
387                         // Now pick up attributes from the bundle.
388
Iterator it = p.entrySet().iterator();
389                         while (it.hasNext()) {
390                             Map.Entry entry = (Map.Entry)it.next();
391                             String JavaDoc name = (String JavaDoc)entry.getKey();
392                             if (! name.startsWith("OpenIDE-Module-")) continue;
393                             attr.putValue(name, (String JavaDoc)entry.getValue());
394                         }
395                     } // else all loc attrs in main manifest, OK
396
} finally {
397                     modulejar.close();
398                 }
399             } catch (IOException ioe) {
400                 throw new BuildException("exception while reading " + module, ioe, location);
401             }
402         } // else we will read attr later if info file is out of date
403
boolean skipInfo = false;
404     if (infofile.exists ()) {
405         // Check for up-to-date w.r.t. manifest and maybe license file.
406
long iMod = infofile.lastModified ();
407         if (mostRecentInput < iMod)
408         skipInfo = true;
409     }
410     if (! skipInfo) {
411         log ("Creating NBM info file " + infofile);
412             if (manifest != null) {
413                 // Read module manifest for main attributes.
414
try {
415                     InputStream manifestStream = new FileInputStream (manifest);
416                     try {
417                         attr = new Manifest (manifestStream).getMainAttributes ();
418                     } finally {
419                         manifestStream.close ();
420                     }
421                 } catch (IOException e) {
422                     throw new BuildException ("exception when reading manifest " + manifest, e, location);
423                 }
424             } // else we read attr before
425
try {
426         OutputStream infoStream = new FileOutputStream (infofile);
427         try {
428                     PrintWriter ps = new PrintWriter(new OutputStreamWriter(infoStream, "UTF-8"));
429             // Begin writing XML.
430
ps.println ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
431                     ps.println("<!DOCTYPE module PUBLIC \"-//NetBeans//DTD Autoupdate Module Info 2.0//EN\" \"http://www.netbeans.org/dtds/autoupdate-info-2_0.dtd\">");
432             String JavaDoc codenamebase = attr.getValue ("OpenIDE-Module");
433             if (codenamebase == null)
434             throw new BuildException ("invalid manifest, does not contain OpenIDE-Module", location);
435             // Strip major release number if any.
436
int idx = codenamebase.lastIndexOf ('/');
437             if (idx != -1) codenamebase = codenamebase.substring (0, idx);
438             ps.println ("<module codenamebase=\"" + codenamebase + "\"");
439             if (homepage != null)
440                         ps.println (" homepage=\"" + xmlEscape(homepage) + "\"");
441             if (distribution != null)
442                         ps.println (" distribution=\"" + xmlEscape(distribution) + "\"");
443             // Here we only write a name for the license.
444
if (license != null) {
445             String JavaDoc name = license.getName ();
446             if (name == null)
447                 throw new BuildException ("Every license must have a name or file attribute", location);
448                         ps.println (" license=\"" + xmlEscape(name) + "\"");
449             }
450             ps.println (" downloadsize=\"0\"");
451                     if (needsrestart != null)
452                         ps.println (" needsrestart=\"" + needsrestart + "\"");
453             ps.println (">");
454             if (description != null) {
455             ps.print (" <description>");
456             ps.print (description.getText ());
457             ps.println ("</description>");
458                     }
459
460                     if (notification != null) {
461                         ps.print(" <module_notification>");
462                         ps.print(notification.getText());
463                         ps.println("</module_notification>");
464             }
465
466             if (externalPackages != null) {
467             Enumeration exp = externalPackages.elements();
468             while (exp.hasMoreElements()) {
469                 ExternalPackage externalPackage = (ExternalPackage) exp.nextElement();
470                 if (externalPackage.name == null ||
471                 externalPackage.targetName == null ||
472                 externalPackage.startUrl == null)
473                 throw new BuildException("Must define name, targetname, starturl for external package");
474                 ps.print(" <external_package ");
475                 ps.print("name=\""+externalPackage.name+"\" ");
476                 ps.print("target_name=\""+externalPackage.targetName+"\" ");
477                 ps.print("start_url=\""+externalPackage.startUrl+"\"");
478                 if (externalPackage.description != null)
479                 ps.print(" description=\""+externalPackage.description+"\"");
480                 ps.println("/>");
481             }
482             }
483             // Write manifest attributes.
484
ps.print (" <manifest ");
485             boolean firstline = true;
486                     List attrNames = new ArrayList(attr.size()); // List<String>
487
Iterator it = attr.keySet().iterator();
488                     while (it.hasNext()) {
489                         attrNames.add(((Attributes.Name)it.next()).toString());
490                     }
491                     Collections.sort(attrNames);
492                     it = attrNames.iterator();
493                     while (it.hasNext()) {
494                         String JavaDoc name = (String JavaDoc)it.next();
495                         // Ignore irrelevant attributes (cf. www/www/dtds/autoupdate-catalog-2_0.dtd
496
// and www/www/dtds/autoupdate-info-2_0.dtd):
497
if (! name.startsWith("OpenIDE-Module")) continue;
498                         if (name.equals("OpenIDE-Module-Localizing-Bundle")) continue;
499                         if (name.equals("OpenIDE-Module-Install")) continue;
500                         if (name.equals("OpenIDE-Module-Layer")) continue;
501                         if (name.equals("OpenIDE-Module-Description")) continue;
502                         if (name.equals("OpenIDE-Module-Package-Dependency-Message")) continue;
503                         if (name.equals("OpenIDE-Module-Public-Packages")) continue;
504             if (firstline)
505                 firstline = false;
506             else
507                 ps.print (" ");
508                         ps.println(name + "=\"" + xmlEscape(attr.getValue(name)) + "\"");
509             }
510             ps.println (" />");
511             // Maybe write out license text.
512
if (license != null) {
513                         ps.print (" <license name=\"" + xmlEscape(license.getName ()) + "\">");
514             ps.print (license.getText ());
515             ps.println ("</license>");
516             }
517             ps.println ("</module>");
518                     ps.flush();
519         } finally {
520             infoStream.close ();
521         }
522         } catch (IOException e) {
523         throw new BuildException ("exception when creating Info/info.xml", e, location);
524         }
525     }
526     // JAR it all up together.
527
long jarModified = file.lastModified (); // may be 0
528
//log ("Ensuring existence of NBM file " + file);
529
Jar jar = (Jar) project.createTask ("jar");
530     jar.setJarfile (file);
531     //jar.setBasedir (topdir.getAbsolutePath ());
532
jar.setCompress(true);
533     //jar.createInclude ().setName ("netbeans/");
534
//jar.createInclude ().setName ("Info/info.xml");
535
jar.addFileset (getFileSet());
536     jar.setLocation (location);
537     jar.init ();
538     jar.execute ();
539     // Maybe sign it.
540
if (signature != null && file.lastModified () != jarModified) {
541         if (signature.keystore == null)
542         throw new BuildException ("must define keystore attribute on <signature/>");
543         if (signature.storepass == null)
544         throw new BuildException ("must define storepass attribute on <signature/>");
545         if (signature.alias == null)
546         throw new BuildException ("must define alias attribute on <signature/>");
547             if (signature.storepass.equals ("?") || !signature.keystore.exists()) {
548                 log ("Not signing NBM file " + file + "; no stored-key password provided or keystore ("
549              + signature.keystore.toString() + ") doesn't exist", Project.MSG_WARN);
550             } else {
551                 log ("Signing NBM file " + file);
552                 SignJar signjar = (SignJar) project.createTask ("signjar");
553                 //I have to use Reflection API, because there was changed API in ANT1.5
554
try {
555                     try {
556                         Class JavaDoc[] paramsT = {String JavaDoc.class};
557                         Object JavaDoc[] paramsV1 = {signature.keystore.getAbsolutePath()};
558                         Object JavaDoc[] paramsV2 = {file.getAbsolutePath()};
559                         signjar.getClass().getDeclaredMethod( "setKeystore", paramsT ).invoke( signjar, paramsV1 );
560                         signjar.getClass().getDeclaredMethod( "setJar", paramsT ).invoke( signjar, paramsV2 );
561                     } catch (NoSuchMethodException JavaDoc ex1) {
562                         //Probably ANT 1.5
563
try {
564                             Class JavaDoc[] paramsT = {File.class};
565                             Object JavaDoc[] paramsV1 = {signature.keystore};
566                             Object JavaDoc[] paramsV2 = {file};
567                             signjar.getClass().getDeclaredMethod( "setKeystore", paramsT ).invoke( signjar, paramsV1 );
568                             signjar.getClass().getDeclaredMethod( "setJar", paramsT ).invoke( signjar, paramsV2 );
569                         }
570                         catch (NoSuchMethodException JavaDoc ex2) {
571                             //Probably ANT 1.5.2
572
try {
573                                 Class JavaDoc[] paramsFile = {File.class};
574                                 Class JavaDoc[] paramsString = {String JavaDoc.class};
575                                 Object JavaDoc[] paramsV1 = {signature.keystore.getAbsolutePath()};
576                                 Object JavaDoc[] paramsV2 = {file};
577                                 signjar.getClass().getDeclaredMethod( "setKeystore", paramsString ).invoke( signjar, paramsV1 );
578                                 signjar.getClass().getDeclaredMethod( "setJar", paramsFile ).invoke( signjar, paramsV2 );
579                             }
580                             catch (NoSuchMethodException JavaDoc ex3) {
581                                 ex3.printStackTrace(System.err);
582                                 throw new BuildException("Unknown ANT version, only ANT 1.4.1 is currently supported and ANT 1.4.1+ is acceptable.");
583                             }
584                         }
585                     }
586                 } catch (IllegalAccessException JavaDoc ex3) {
587                     throw new BuildException(ex3);
588                 } catch (java.lang.reflect.InvocationTargetException JavaDoc ex4) {
589                     throw new BuildException(ex4);
590                 }
591                 signjar.setStorepass (signature.storepass);
592                 signjar.setAlias (signature.alias);
593                 signjar.setLocation (location);
594                 signjar.init ();
595                 signjar.execute ();
596             }
597     }
598     }
599    
600     // Reflection access from MakeListOfNBM:
601

602     FileSet getFileSet() {
603         FileSet fs = fileset; //makes in apperance to excludes and includes files defined in XML
604
fs.setDir (topdir);
605
606     if (isStandardInclude)
607         fs.createInclude ().setName ("netbeans/");
608
609     fs.createInclude ().setName ("Info/info.xml");
610         return fs;
611     }
612
613     Attributes getAttributes() throws IOException {
614         if (manifest != null) {
615             InputStream is = new FileInputStream(manifest);
616             try {
617                 return new Manifest(is).getMainAttributes();
618             } finally {
619                 is.close();
620             }
621         } else if (module != null) {
622             JarFile jar = new JarFile(module);
623             try {
624                 return jar.getManifest().getMainAttributes();
625             } finally {
626                 jar.close();
627             }
628         } else {
629             throw new IOException(location + "must give either 'manifest' or 'module' on <makenbm>");
630         }
631     }
632 }
633
Popular Tags