KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > Jar


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.ByteArrayInputStream JavaDoc;
22 import java.io.ByteArrayOutputStream JavaDoc;
23 import java.io.File JavaDoc;
24 import java.io.FileOutputStream JavaDoc;
25 import java.io.FileInputStream JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.InputStream JavaDoc;
28 import java.io.UnsupportedEncodingException JavaDoc;
29 import java.io.InputStreamReader JavaDoc;
30 import java.io.OutputStreamWriter JavaDoc;
31 import java.io.PrintWriter JavaDoc;
32 import java.io.Reader JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.Collections JavaDoc;
35 import java.util.Comparator JavaDoc;
36 import java.util.Enumeration JavaDoc;
37 import java.util.HashSet JavaDoc;
38 import java.util.Iterator JavaDoc;
39 import java.util.List JavaDoc;
40 import java.util.StringTokenizer JavaDoc;
41 import java.util.TreeMap JavaDoc;
42 import java.util.Vector JavaDoc;
43 import java.util.zip.ZipEntry JavaDoc;
44 import java.util.zip.ZipFile JavaDoc;
45 import org.apache.tools.ant.BuildException;
46 import org.apache.tools.ant.Project;
47 import org.apache.tools.ant.types.EnumeratedAttribute;
48 import org.apache.tools.ant.types.Path;
49 import org.apache.tools.ant.types.ResourceCollection;
50 import org.apache.tools.ant.types.ZipFileSet;
51 import org.apache.tools.ant.types.spi.Service;
52 import org.apache.tools.zip.JarMarker;
53 import org.apache.tools.zip.ZipExtraField;
54 import org.apache.tools.zip.ZipOutputStream;
55
56 /**
57  * Creates a JAR archive.
58  *
59  * @since Ant 1.1
60  *
61  * @ant.task category="packaging"
62  */

63 public class Jar extends Zip {
64     /** The index file name. */
65     private static final String JavaDoc INDEX_NAME = "META-INF/INDEX.LIST";
66
67     /** The manifest file name. */
68     private static final String JavaDoc MANIFEST_NAME = "META-INF/MANIFEST.MF";
69
70     /**
71      * List of all known SPI Services
72      */

73     private List JavaDoc serviceList = new ArrayList JavaDoc();
74
75     /** merged manifests added through addConfiguredManifest */
76     private Manifest configuredManifest;
77     /** shadow of the above if upToDate check alters the value */
78     private Manifest savedConfiguredManifest;
79
80     /** merged manifests added through filesets */
81     private Manifest filesetManifest;
82
83     /**
84      * Manifest of original archive, will be set to null if not in
85      * update mode.
86      */

87     private Manifest originalManifest;
88
89     /**
90      * whether to merge fileset manifests;
91      * value is true if filesetmanifest is 'merge' or 'mergewithoutmain'
92      */

93     private FilesetManifestConfig filesetManifestConfig;
94
95     /**
96      * whether to merge the main section of fileset manifests;
97      * value is true if filesetmanifest is 'merge'
98      */

99     private boolean mergeManifestsMain = true;
100
101     /** the manifest specified by the 'manifest' attribute **/
102     private Manifest manifest;
103
104     /** The encoding to use when reading in a manifest file */
105     private String JavaDoc manifestEncoding;
106
107     /**
108      * The file found from the 'manifest' attribute. This can be
109      * either the location of a manifest, or the name of a jar added
110      * through a fileset. If its the name of an added jar, the
111      * manifest is looked for in META-INF/MANIFEST.MF
112      */

113     private File JavaDoc manifestFile;
114
115     /** jar index is JDK 1.3+ only */
116     private boolean index = false;
117
118     /**
119      * whether to really create the archive in createEmptyZip, will
120      * get set in getResourcesToAdd.
121      */

122     private boolean createEmpty = false;
123
124     /**
125      * Stores all files that are in the root of the archive (i.e. that
126      * have a name that doesn't contain a slash) so they can get
127      * listed in the index.
128      *
129      * Will not be filled unless the user has asked for an index.
130      *
131      * @since Ant 1.6
132      */

133     private Vector JavaDoc rootEntries;
134
135     /**
136      * Path containing jars that shall be indexed in addition to this archive.
137      *
138      * @since Ant 1.6.2
139      */

140     private Path indexJars;
141
142     /**
143      * Extra fields needed to make Solaris recognize the archive as a jar file.
144      *
145      * @since Ant 1.6.3
146      */

147     private static final ZipExtraField[] JAR_MARKER = new ZipExtraField[] {
148         JarMarker.getInstance()
149     };
150
151     // CheckStyle:VisibilityModifier OFF - bc
152
protected String JavaDoc emptyBehavior = "create";
153     // CheckStyle:VisibilityModifier ON
154

155     /** constructor */
156     public Jar() {
157         super();
158         archiveType = "jar";
159         emptyBehavior = "create";
160         setEncoding("UTF8");
161         rootEntries = new Vector JavaDoc();
162     }
163
164     /**
165      * Not used for jar files.
166      * @param we not used
167      * @ant.attribute ignore="true"
168      */

169     public void setWhenempty(WhenEmpty we) {
170         log("JARs are never empty, they contain at least a manifest file",
171             Project.MSG_WARN);
172     }
173
174     /**
175      * Indicates if a jar file should be created when it would only contain a
176      * manifest file.
177      * Possible values are: <code>fail</code> (throw an exception
178      * and halt the build); <code>skip</code> (do not create
179      * any archive, but issue a warning); <code>create</code>
180      * (make an archive with only a manifest file).
181      * Default is <code>create</code>;
182      * @param we a <code>WhenEmpty</code> enumerated value
183      */

184     public void setWhenmanifestonly(WhenEmpty we) {
185         emptyBehavior = we.getValue();
186     }
187
188     /**
189      * Set the destination file.
190      * @param jarFile the destination file
191      * @deprecated since 1.5.x.
192      * Use setDestFile(File) instead.
193      */

194     public void setJarfile(File JavaDoc jarFile) {
195         setDestFile(jarFile);
196     }
197
198     /**
199      * Set whether or not to create an index list for classes.
200      * This may speed up classloading in some cases.
201      * @param flag a <code>boolean</code> value
202      */

203     public void setIndex(boolean flag) {
204         index = flag;
205     }
206
207     /**
208      * The character encoding to use in the manifest file.
209      *
210      * @param manifestEncoding the character encoding
211      */

212     public void setManifestEncoding(String JavaDoc manifestEncoding) {
213         this.manifestEncoding = manifestEncoding;
214     }
215
216     /**
217      * Allows the manifest for the archive file to be provided inline
218      * in the build file rather than in an external file.
219      *
220      * @param newManifest an embedded manifest element
221      * @throws ManifestException on error
222      */

223     public void addConfiguredManifest(Manifest newManifest)
224         throws ManifestException {
225         if (configuredManifest == null) {
226             configuredManifest = newManifest;
227         } else {
228             configuredManifest.merge(newManifest);
229         }
230         savedConfiguredManifest = configuredManifest;
231     }
232
233     /**
234      * The manifest file to use. This can be either the location of a manifest,
235      * or the name of a jar added through a fileset. If its the name of an added
236      * jar, the task expects the manifest to be in the jar at META-INF/MANIFEST.MF.
237      *
238      * @param manifestFile the manifest file to use.
239      */

240     public void setManifest(File JavaDoc manifestFile) {
241         if (!manifestFile.exists()) {
242             throw new BuildException("Manifest file: " + manifestFile
243                                      + " does not exist.", getLocation());
244         }
245
246         this.manifestFile = manifestFile;
247     }
248
249     private Manifest getManifest(File JavaDoc manifestFile) {
250
251         Manifest newManifest = null;
252         FileInputStream JavaDoc fis = null;
253         InputStreamReader JavaDoc isr = null;
254         try {
255             fis = new FileInputStream JavaDoc(manifestFile);
256             if (manifestEncoding == null) {
257                 isr = new InputStreamReader JavaDoc(fis);
258             } else {
259                 isr = new InputStreamReader JavaDoc(fis, manifestEncoding);
260             }
261             newManifest = getManifest(isr);
262         } catch (UnsupportedEncodingException JavaDoc e) {
263             throw new BuildException("Unsupported encoding while reading manifest: "
264                                      + e.getMessage(), e);
265         } catch (IOException JavaDoc e) {
266             throw new BuildException("Unable to read manifest file: "
267                                      + manifestFile
268                                      + " (" + e.getMessage() + ")", e);
269         } finally {
270             if (isr != null) {
271                 try {
272                     isr.close();
273                 } catch (IOException JavaDoc e) {
274                     // do nothing
275
}
276             }
277         }
278         return newManifest;
279     }
280
281     /**
282      * @return null if jarFile doesn't contain a manifest, the
283      * manifest otherwise.
284      * @since Ant 1.5.2
285      */

286     private Manifest getManifestFromJar(File JavaDoc jarFile) throws IOException JavaDoc {
287         ZipFile JavaDoc zf = null;
288         try {
289             zf = new ZipFile JavaDoc(jarFile);
290
291             // must not use getEntry as "well behaving" applications
292
// must accept the manifest in any capitalization
293
Enumeration JavaDoc e = zf.entries();
294             while (e.hasMoreElements()) {
295                 ZipEntry JavaDoc ze = (ZipEntry JavaDoc) e.nextElement();
296                 if (ze.getName().equalsIgnoreCase(MANIFEST_NAME)) {
297                     InputStreamReader JavaDoc isr =
298                         new InputStreamReader JavaDoc(zf.getInputStream(ze), "UTF-8");
299                     return getManifest(isr);
300                 }
301             }
302             return null;
303         } finally {
304             if (zf != null) {
305                 try {
306                     zf.close();
307                 } catch (IOException JavaDoc e) {
308                     // XXX - log an error? throw an exception?
309
}
310             }
311         }
312     }
313
314     private Manifest getManifest(Reader JavaDoc r) {
315
316         Manifest newManifest = null;
317         try {
318             newManifest = new Manifest(r);
319         } catch (ManifestException e) {
320             log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR);
321             throw new BuildException("Invalid Manifest: " + manifestFile,
322                                      e, getLocation());
323         } catch (IOException JavaDoc e) {
324             throw new BuildException("Unable to read manifest file"
325                                      + " (" + e.getMessage() + ")", e);
326         }
327         return newManifest;
328     }
329
330     /**
331      * Behavior when a Manifest is found in a zipfileset or zipgroupfileset file.
332      * Valid values are "skip", "merge", and "mergewithoutmain".
333      * "merge" will merge all of manifests together, and merge this into any
334      * other specified manifests.
335      * "mergewithoutmain" merges everything but the Main section of the manifests.
336      * Default value is "skip".
337      *
338      * Note: if this attribute's value is not "skip", the created jar will not
339      * be readable by using java.util.jar.JarInputStream
340      *
341      * @param config setting for found manifest behavior.
342      */

343     public void setFilesetmanifest(FilesetManifestConfig config) {
344         filesetManifestConfig = config;
345         mergeManifestsMain = "merge".equals(config.getValue());
346
347         if (filesetManifestConfig != null
348             && !filesetManifestConfig.getValue().equals("skip")) {
349
350             doubleFilePass = true;
351         }
352     }
353
354     /**
355      * Adds a zipfileset to include in the META-INF directory.
356      *
357      * @param fs zipfileset to add
358      */

359     public void addMetainf(ZipFileSet fs) {
360         // We just set the prefix for this fileset, and pass it up.
361
fs.setPrefix("META-INF/");
362         super.addFileset(fs);
363     }
364
365     /**
366      * Add a path to index jars.
367      * @param p a path
368      * @since Ant 1.6.2
369      */

370     public void addConfiguredIndexJars(Path p) {
371         if (indexJars == null) {
372             indexJars = new Path(getProject());
373         }
374         indexJars.append(p);
375     }
376
377     /**
378      * A nested SPI service element.
379      * @param service the nested element.
380      * @since Ant 1.7
381      */

382     public void addConfiguredService(Service service) {
383         // Check if the service is configured correctly
384
service.check();
385         serviceList.add(service);
386     }
387
388     /**
389      * Write SPI Information to JAR
390      */

391     private void writeServices(ZipOutputStream zOut) throws IOException JavaDoc {
392         Iterator JavaDoc serviceIterator;
393         Service service;
394
395         serviceIterator = serviceList.iterator();
396         while (serviceIterator.hasNext()) {
397            service = (Service) serviceIterator.next();
398            //stolen from writeManifest
399
super.zipFile(service.getAsStream(), zOut,
400                          "META-INF/service/" + service.getType(),
401                          System.currentTimeMillis(), null,
402                          ZipFileSet.DEFAULT_FILE_MODE);
403         }
404     }
405
406
407     /**
408      * Initialize the zip output stream.
409      * @param zOut the zip output stream
410      * @throws IOException on I/O errors
411      * @throws BuildException on other errors
412      */

413     protected void initZipOutputStream(ZipOutputStream zOut)
414         throws IOException JavaDoc, BuildException {
415
416         if (!skipWriting) {
417             Manifest jarManifest = createManifest();
418             writeManifest(zOut, jarManifest);
419             writeServices(zOut);
420         }
421     }
422
423     private Manifest createManifest()
424         throws BuildException {
425         try {
426             Manifest finalManifest = Manifest.getDefaultManifest();
427
428             if (manifest == null) {
429                 if (manifestFile != null) {
430                     // if we haven't got the manifest yet, attempt to
431
// get it now and have manifest be the final merge
432
manifest = getManifest(manifestFile);
433                 }
434             }
435
436             /*
437              * Precedence: manifestFile wins over inline manifest,
438              * over manifests read from the filesets over the original
439              * manifest.
440              *
441              * merge with null argument is a no-op
442              */

443
444             if (isInUpdateMode()) {
445                 finalManifest.merge(originalManifest);
446             }
447             finalManifest.merge(filesetManifest);
448             finalManifest.merge(configuredManifest);
449             finalManifest.merge(manifest, !mergeManifestsMain);
450
451             return finalManifest;
452
453         } catch (ManifestException e) {
454             log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR);
455             throw new BuildException("Invalid Manifest", e, getLocation());
456         }
457     }
458
459     private void writeManifest(ZipOutputStream zOut, Manifest manifest)
460         throws IOException JavaDoc {
461         for (Enumeration JavaDoc e = manifest.getWarnings();
462              e.hasMoreElements();) {
463             log("Manifest warning: " + (String JavaDoc) e.nextElement(),
464                 Project.MSG_WARN);
465         }
466
467         zipDir(null, zOut, "META-INF/", ZipFileSet.DEFAULT_DIR_MODE,
468                JAR_MARKER);
469         // time to write the manifest
470
ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
471         OutputStreamWriter JavaDoc osw = new OutputStreamWriter JavaDoc(baos, Manifest.JAR_ENCODING);
472         PrintWriter JavaDoc writer = new PrintWriter JavaDoc(osw);
473         manifest.write(writer);
474         writer.flush();
475
476         ByteArrayInputStream JavaDoc bais =
477             new ByteArrayInputStream JavaDoc(baos.toByteArray());
478         super.zipFile(bais, zOut, MANIFEST_NAME,
479                       System.currentTimeMillis(), null,
480                       ZipFileSet.DEFAULT_FILE_MODE);
481         super.initZipOutputStream(zOut);
482     }
483
484     /**
485      * Finalize the zip output stream.
486      * This creates an index list if the index attribute is true.
487      * @param zOut the zip output stream
488      * @throws IOException on I/O errors
489      * @throws BuildException on other errors
490      */

491     protected void finalizeZipOutputStream(ZipOutputStream zOut)
492         throws IOException JavaDoc, BuildException {
493
494         if (index) {
495             createIndexList(zOut);
496         }
497     }
498
499     /**
500      * Create the index list to speed up classloading.
501      * This is a JDK 1.3+ specific feature and is enabled by default. See
502      * <a HREF="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index">
503      * the JAR index specification</a> for more details.
504      *
505      * @param zOut the zip stream representing the jar being built.
506      * @throws IOException thrown if there is an error while creating the
507      * index and adding it to the zip stream.
508      */

509     private void createIndexList(ZipOutputStream zOut) throws IOException JavaDoc {
510         ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
511         // encoding must be UTF8 as specified in the specs.
512
PrintWriter JavaDoc writer = new PrintWriter JavaDoc(new OutputStreamWriter JavaDoc(baos,
513                                                                     "UTF8"));
514
515         // version-info blankline
516
writer.println("JarIndex-Version: 1.0");
517         writer.println();
518
519         // header newline
520
writer.println(zipFile.getName());
521
522         writeIndexLikeList(new ArrayList JavaDoc(addedDirs.keySet()),
523                            rootEntries, writer);
524         writer.println();
525
526         if (indexJars != null) {
527             Manifest mf = createManifest();
528             Manifest.Attribute classpath =
529                 mf.getMainSection().getAttribute(Manifest.ATTRIBUTE_CLASSPATH);
530             String JavaDoc[] cpEntries = null;
531             if (classpath != null && classpath.getValue() != null) {
532                 StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(classpath.getValue(),
533                                                           " ");
534                 cpEntries = new String JavaDoc[tok.countTokens()];
535                 int c = 0;
536                 while (tok.hasMoreTokens()) {
537                     cpEntries[c++] = tok.nextToken();
538                 }
539             }
540             String JavaDoc[] indexJarEntries = indexJars.list();
541             for (int i = 0; i < indexJarEntries.length; i++) {
542                 String JavaDoc name = findJarName(indexJarEntries[i], cpEntries);
543                 if (name != null) {
544                     ArrayList JavaDoc dirs = new ArrayList JavaDoc();
545                     ArrayList JavaDoc files = new ArrayList JavaDoc();
546                     grabFilesAndDirs(indexJarEntries[i], dirs, files);
547                     if (dirs.size() + files.size() > 0) {
548                         writer.println(name);
549                         writeIndexLikeList(dirs, files, writer);
550                         writer.println();
551                     }
552                 }
553             }
554         }
555
556         writer.flush();
557         ByteArrayInputStream JavaDoc bais =
558             new ByteArrayInputStream JavaDoc(baos.toByteArray());
559         super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), null,
560                       ZipFileSet.DEFAULT_FILE_MODE);
561     }
562
563     /**
564      * Overridden from Zip class to deal with manifests and index lists.
565      * @param is the input stream
566      * @param zOut the zip output stream
567      * @param vPath the name this entry shall have in the archive
568      * @param lastModified last modification time for the entry.
569      * @param fromArchive the original archive we are copying this
570      * entry from, will be null if we are not copying from an archive.
571      * @param mode the Unix permissions to set.
572      * @throws IOException on error
573      */

574     protected void zipFile(InputStream JavaDoc is, ZipOutputStream zOut, String JavaDoc vPath,
575                            long lastModified, File JavaDoc fromArchive, int mode)
576         throws IOException JavaDoc {
577         if (MANIFEST_NAME.equalsIgnoreCase(vPath)) {
578             if (!doubleFilePass || (doubleFilePass && skipWriting)) {
579                 filesetManifest(fromArchive, is);
580             }
581         } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) {
582             log("Warning: selected " + archiveType
583                 + " files include a META-INF/INDEX.LIST which will"
584                 + " be replaced by a newly generated one.", Project.MSG_WARN);
585         } else {
586             if (index && vPath.indexOf("/") == -1) {
587                 rootEntries.addElement(vPath);
588             }
589             super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode);
590         }
591     }
592
593     private void filesetManifest(File JavaDoc file, InputStream JavaDoc is) throws IOException JavaDoc {
594         if (manifestFile != null && manifestFile.equals(file)) {
595             // If this is the same name specified in 'manifest', this
596
// is the manifest to use
597
log("Found manifest " + file, Project.MSG_VERBOSE);
598             try {
599                 if (is != null) {
600                     InputStreamReader JavaDoc isr;
601                     if (manifestEncoding == null) {
602                         isr = new InputStreamReader JavaDoc(is);
603                     } else {
604                         isr = new InputStreamReader JavaDoc(is, manifestEncoding);
605                     }
606                     manifest = getManifest(isr);
607                 } else {
608                     manifest = getManifest(file);
609                 }
610             } catch (UnsupportedEncodingException JavaDoc e) {
611                 throw new BuildException("Unsupported encoding while reading "
612                     + "manifest: " + e.getMessage(), e);
613             }
614         } else if (filesetManifestConfig != null
615                     && !filesetManifestConfig.getValue().equals("skip")) {
616             // we add this to our group of fileset manifests
617
log("Found manifest to merge in file " + file,
618                 Project.MSG_VERBOSE);
619
620             try {
621                 Manifest newManifest = null;
622                 if (is != null) {
623                     InputStreamReader JavaDoc isr;
624                     if (manifestEncoding == null) {
625                         isr = new InputStreamReader JavaDoc(is);
626                     } else {
627                         isr = new InputStreamReader JavaDoc(is, manifestEncoding);
628                     }
629                     newManifest = getManifest(isr);
630                 } else {
631                     newManifest = getManifest(file);
632                 }
633
634                 if (filesetManifest == null) {
635                     filesetManifest = newManifest;
636                 } else {
637                     filesetManifest.merge(newManifest);
638                 }
639             } catch (UnsupportedEncodingException JavaDoc e) {
640                 throw new BuildException("Unsupported encoding while reading "
641                     + "manifest: " + e.getMessage(), e);
642             } catch (ManifestException e) {
643                 log("Manifest in file " + file + " is invalid: "
644                     + e.getMessage(), Project.MSG_ERR);
645                 throw new BuildException("Invalid Manifest", e, getLocation());
646             }
647         } else {
648             // assuming 'skip' otherwise
649
// don't warn if skip has been requested explicitly, warn if user
650
// didn't set the attribute
651

652             // Hide warning also as it makes no sense since
653
// the filesetmanifest attribute itself has been
654
// hidden
655

656             //int logLevel = filesetManifestConfig == null ?
657
// Project.MSG_WARN : Project.MSG_VERBOSE;
658
//log("File " + file
659
// + " includes a META-INF/MANIFEST.MF which will be ignored. "
660
// + "To include this file, set filesetManifest to a value other "
661
// + "than 'skip'.", logLevel);
662
}
663     }
664
665     /**
666      * Collect the resources that are newer than the corresponding
667      * entries (or missing) in the original archive.
668      *
669      * <p>If we are going to recreate the archive instead of updating
670      * it, all resources should be considered as new, if a single one
671      * is. Because of this, subclasses overriding this method must
672      * call <code>super.getResourcesToAdd</code> and indicate with the
673      * third arg if they already know that the archive is
674      * out-of-date.</p>
675      *
676      * @param rcs The resource collections to grab resources from
677      * @param zipFile intended archive file (may or may not exist)
678      * @param needsUpdate whether we already know that the archive is
679      * out-of-date. Subclasses overriding this method are supposed to
680      * set this value correctly in their call to
681      * super.getResourcesToAdd.
682      * @return an array of resources to add for each fileset passed in as well
683      * as a flag that indicates whether the archive is uptodate.
684      *
685      * @exception BuildException if it likes
686      */

687     protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs,
688                                              File JavaDoc zipFile,
689                                              boolean needsUpdate)
690         throws BuildException {
691
692         // need to handle manifest as a special check
693
if (zipFile.exists()) {
694             // if it doesn't exist, it will get created anyway, don't
695
// bother with any up-to-date checks.
696

697             try {
698                 originalManifest = getManifestFromJar(zipFile);
699                 if (originalManifest == null) {
700                     log("Updating jar since the current jar has no manifest",
701                         Project.MSG_VERBOSE);
702                     needsUpdate = true;
703                 } else {
704                     Manifest mf = createManifest();
705                     if (!mf.equals(originalManifest)) {
706                         log("Updating jar since jar manifest has changed",
707                             Project.MSG_VERBOSE);
708                         needsUpdate = true;
709                     }
710                 }
711             } catch (Throwable JavaDoc t) {
712                 log("error while reading original manifest in file: "
713                     + zipFile.toString() + t.getMessage(),
714                     Project.MSG_WARN);
715                 needsUpdate = true;
716             }
717
718         } else {
719             // no existing archive
720
needsUpdate = true;
721         }
722
723         createEmpty = needsUpdate;
724         return super.getResourcesToAdd(rcs, zipFile, needsUpdate);
725     }
726
727     /**
728      * Create an empty jar file.
729      * @param zipFile the file to create
730      * @return true for historic reasons
731      * @throws BuildException on error
732      */

733     protected boolean createEmptyZip(File JavaDoc zipFile) throws BuildException {
734         if (!createEmpty) {
735             return true;
736         }
737
738         if (emptyBehavior.equals("skip")) {
739                 log("Warning: skipping " + archiveType + " archive "
740                     + zipFile + " because no files were included.",
741                     Project.MSG_WARN);
742                 return true;
743         } else if (emptyBehavior.equals("fail")) {
744             throw new BuildException("Cannot create " + archiveType
745                                      + " archive " + zipFile
746                                      + ": no files were included.",
747                                      getLocation());
748         }
749
750         ZipOutputStream zOut = null;
751         try {
752             log("Building MANIFEST-only jar: "
753                 + getDestFile().getAbsolutePath());
754             zOut = new ZipOutputStream(new FileOutputStream JavaDoc(getDestFile()));
755
756             zOut.setEncoding(getEncoding());
757             if (isCompress()) {
758                 zOut.setMethod(ZipOutputStream.DEFLATED);
759             } else {
760                 zOut.setMethod(ZipOutputStream.STORED);
761             }
762             initZipOutputStream(zOut);
763             finalizeZipOutputStream(zOut);
764         } catch (IOException JavaDoc ioe) {
765             throw new BuildException("Could not create almost empty JAR archive"
766                                      + " (" + ioe.getMessage() + ")", ioe,
767                                      getLocation());
768         } finally {
769             // Close the output stream.
770
try {
771                 if (zOut != null) {
772                     zOut.close();
773                 }
774             } catch (IOException JavaDoc ex) {
775                 // Ignore close exception
776
}
777             createEmpty = false;
778         }
779         return true;
780     }
781
782     /**
783      * Make sure we don't think we already have a MANIFEST next time this task
784      * gets executed.
785      *
786      * @see Zip#cleanUp
787      */

788     protected void cleanUp() {
789         super.cleanUp();
790
791         // we want to save this info if we are going to make another pass
792
if (!doubleFilePass || (doubleFilePass && !skipWriting)) {
793             manifest = null;
794             configuredManifest = savedConfiguredManifest;
795             filesetManifest = null;
796             originalManifest = null;
797         }
798         rootEntries.removeAllElements();
799     }
800
801     /**
802      * reset to default values.
803      *
804      * @see Zip#reset
805      *
806      * @since 1.44, Ant 1.5
807      */

808     public void reset() {
809         super.reset();
810         emptyBehavior = "create";
811         configuredManifest = null;
812         filesetManifestConfig = null;
813         mergeManifestsMain = false;
814         manifestFile = null;
815         index = false;
816     }
817
818     /**
819      * The manifest config enumerated type.
820      */

821     public static class FilesetManifestConfig extends EnumeratedAttribute {
822         /**
823          * Get the list of valid strings.
824          * @return the list of values - "skip", "merge" and "mergewithoutmain"
825          */

826         public String JavaDoc[] getValues() {
827             return new String JavaDoc[] {"skip", "merge", "mergewithoutmain"};
828         }
829     }
830
831     /**
832      * Writes the directory entries from the first and the filenames
833      * from the second list to the given writer, one entry per line.
834      *
835      * @param dirs a list of directories
836      * @param files a list of files
837      * @param writer the writer to write to
838      * @throws IOException on error
839      * @since Ant 1.6.2
840      */

841     protected final void writeIndexLikeList(List JavaDoc dirs, List JavaDoc files,
842                                             PrintWriter JavaDoc writer)
843         throws IOException JavaDoc {
844         // JarIndex is sorting the directories by ascending order.
845
// it has no value but cosmetic since it will be read into a
846
// hashtable by the classloader, but we'll do so anyway.
847
Collections.sort(dirs);
848         Collections.sort(files);
849         Iterator JavaDoc iter = dirs.iterator();
850         while (iter.hasNext()) {
851             String JavaDoc dir = (String JavaDoc) iter.next();
852
853             // try to be smart, not to be fooled by a weird directory name
854
dir = dir.replace('\\', '/');
855             if (dir.startsWith("./")) {
856                 dir = dir.substring(2);
857             }
858             while (dir.startsWith("/")) {
859                 dir = dir.substring(1);
860             }
861             int pos = dir.lastIndexOf('/');
862             if (pos != -1) {
863                 dir = dir.substring(0, pos);
864             }
865
866             // looks like nothing from META-INF should be added
867
// and the check is not case insensitive.
868
// see sun.misc.JarIndex
869
if (dir.startsWith("META-INF")) {
870                 continue;
871             }
872             // name newline
873
writer.println(dir);
874         }
875
876         iter = files.iterator();
877         while (iter.hasNext()) {
878             writer.println(iter.next());
879         }
880     }
881
882     /**
883      * try to guess the name of the given file.
884      *
885      * <p>If this jar has a classpath attribute in its manifest, we
886      * can assume that it will only require an index of jars listed
887      * there. try to find which classpath entry is most likely the
888      * one the given file name points to.</p>
889      *
890      * <p>In the absence of a classpath attribute, assume the other
891      * files will be placed inside the same directory as this jar and
892      * use their basename.</p>
893      *
894      * <p>if there is a classpath and the given file doesn't match any
895      * of its entries, return null.</p>
896      *
897      * @param fileName the name to look for
898      * @param classpath the classpath to look in (may be null)
899      * @return the matching entry, or null if the file is not found
900      * @since Ant 1.6.2
901      */

902     protected static final String JavaDoc findJarName(String JavaDoc fileName,
903                                               String JavaDoc[] classpath) {
904         if (classpath == null) {
905             return (new File JavaDoc(fileName)).getName();
906         }
907         fileName = fileName.replace(File.separatorChar, '/');
908         TreeMap JavaDoc matches = new TreeMap JavaDoc(new Comparator JavaDoc() {
909                 // longest match comes first
910
public int compare(Object JavaDoc o1, Object JavaDoc o2) {
911                     if (o1 instanceof String JavaDoc && o2 instanceof String JavaDoc) {
912                         return ((String JavaDoc) o2).length()
913                             - ((String JavaDoc) o1).length();
914                     }
915                     return 0;
916                 }
917             });
918
919         for (int i = 0; i < classpath.length; i++) {
920             if (fileName.endsWith(classpath[i])) {
921                 matches.put(classpath[i], classpath[i]);
922             } else {
923                 int slash = classpath[i].indexOf("/");
924                 String JavaDoc candidate = classpath[i];
925                 while (slash > -1) {
926                     candidate = candidate.substring(slash + 1);
927                     if (fileName.endsWith(candidate)) {
928                         matches.put(candidate, classpath[i]);
929                         break;
930                     }
931                     slash = candidate.indexOf("/");
932                 }
933             }
934         }
935
936         return matches.size() == 0
937             ? null : (String JavaDoc) matches.get(matches.firstKey());
938     }
939
940     /**
941      * Grab lists of all root-level files and all directories
942      * contained in the given archive.
943      * @param file the zip file to examine
944      * @param dirs where to place the directories found
945      * @param files where to place the files found
946      * @since Ant 1.7
947      * @throws IOException on error
948      */

949     protected static final void grabFilesAndDirs(String JavaDoc file, List JavaDoc dirs,
950                                                  List JavaDoc files)
951         throws IOException JavaDoc {
952         org.apache.tools.zip.ZipFile zf = null;
953         try {
954             zf = new org.apache.tools.zip.ZipFile(file, "utf-8");
955             Enumeration JavaDoc entries = zf.getEntries();
956             HashSet JavaDoc dirSet = new HashSet JavaDoc();
957             while (entries.hasMoreElements()) {
958                 org.apache.tools.zip.ZipEntry ze =
959                     (org.apache.tools.zip.ZipEntry) entries.nextElement();
960                 String JavaDoc name = ze.getName();
961                 // META-INF would be skipped anyway, avoid index for
962
// manifest-only jars.
963
if (!name.startsWith("META-INF/")) {
964                     if (ze.isDirectory()) {
965                         dirSet.add(name);
966                     } else if (name.indexOf("/") == -1) {
967                         files.add(name);
968                     } else {
969                         // a file, not in the root
970
// since the jar may be one without directory
971
// entries, add the parent dir of this file as
972
// well.
973
dirSet.add(name.substring(0,
974                                                   name.lastIndexOf("/") + 1));
975                     }
976                 }
977             }
978             dirs.addAll(dirSet);
979         } finally {
980             if (zf != null) {
981                 zf.close();
982             }
983         }
984     }
985 }
986
Popular Tags