KickJava   Java API By Example, From Geeks To Geeks.

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


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 package org.apache.tools.ant.taskdefs;
19
20 import java.io.ByteArrayInputStream JavaDoc;
21 import java.io.ByteArrayOutputStream JavaDoc;
22 import java.io.File JavaDoc;
23 import java.io.FileInputStream JavaDoc;
24 import java.io.FileOutputStream JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import java.util.Hashtable JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.Stack JavaDoc;
33 import java.util.Vector JavaDoc;
34 import java.util.zip.CRC32 JavaDoc;
35
36 import org.apache.tools.ant.BuildException;
37 import org.apache.tools.ant.DirectoryScanner;
38 import org.apache.tools.ant.FileScanner;
39 import org.apache.tools.ant.Project;
40 import org.apache.tools.ant.types.ArchiveFileSet;
41 import org.apache.tools.ant.types.EnumeratedAttribute;
42 import org.apache.tools.ant.types.FileSet;
43 import org.apache.tools.ant.types.PatternSet;
44 import org.apache.tools.ant.types.Resource;
45 import org.apache.tools.ant.types.ResourceCollection;
46 import org.apache.tools.ant.types.ZipFileSet;
47 import org.apache.tools.ant.types.ZipScanner;
48 import org.apache.tools.ant.types.resources.ArchiveResource;
49 import org.apache.tools.ant.types.resources.FileResource;
50 import org.apache.tools.ant.util.FileNameMapper;
51 import org.apache.tools.ant.util.FileUtils;
52 import org.apache.tools.ant.util.GlobPatternMapper;
53 import org.apache.tools.ant.util.IdentityMapper;
54 import org.apache.tools.ant.util.MergingMapper;
55 import org.apache.tools.ant.util.ResourceUtils;
56 import org.apache.tools.zip.ZipEntry;
57 import org.apache.tools.zip.ZipExtraField;
58 import org.apache.tools.zip.ZipFile;
59 import org.apache.tools.zip.ZipOutputStream;
60
61 /**
62  * Create a Zip file.
63  *
64  * @since Ant 1.1
65  *
66  * @ant.task category="packaging"
67  */

68 public class Zip extends MatchingTask {
69     // CheckStyle:VisibilityModifier OFF - bc
70

71     protected File JavaDoc zipFile;
72     // use to scan own archive
73
private ZipScanner zs;
74     private File JavaDoc baseDir;
75     protected Hashtable JavaDoc entries = new Hashtable JavaDoc();
76     private Vector JavaDoc groupfilesets = new Vector JavaDoc();
77     private Vector JavaDoc filesetsFromGroupfilesets = new Vector JavaDoc();
78     protected String JavaDoc duplicate = "add";
79     private boolean doCompress = true;
80     private boolean doUpdate = false;
81     // shadow of the above if the value is altered in execute
82
private boolean savedDoUpdate = false;
83     private boolean doFilesonly = false;
84     protected String JavaDoc archiveType = "zip";
85
86     // For directories:
87
private static final long EMPTY_CRC = new CRC32 JavaDoc ().getValue ();
88     protected String JavaDoc emptyBehavior = "skip";
89     private Vector JavaDoc resources = new Vector JavaDoc();
90     protected Hashtable JavaDoc addedDirs = new Hashtable JavaDoc();
91     private Vector JavaDoc addedFiles = new Vector JavaDoc();
92
93     protected boolean doubleFilePass = false;
94     protected boolean skipWriting = false;
95
96     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
97
98     // CheckStyle:VisibilityModifier ON
99

100     /**
101      * true when we are adding new files into the Zip file, as opposed
102      * to adding back the unchanged files
103      */

104     private boolean addingNewFiles = false;
105
106     /**
107      * Encoding to use for filenames, defaults to the platform's
108      * default encoding.
109      */

110     private String JavaDoc encoding;
111
112     /**
113      * Whether the original compression of entries coming from a ZIP
114      * archive should be kept (for example when updating an archive).
115      *
116      * @since Ant 1.6
117      */

118     private boolean keepCompression = false;
119
120     /**
121      * Whether the file modification times will be rounded up to the
122      * next even number of seconds.
123      *
124      * @since Ant 1.6.2
125      */

126     private boolean roundUp = true;
127
128     /**
129      * Comment for the archive.
130      * @since Ant 1.6.3
131      */

132     private String JavaDoc comment = "";
133
134     private int level = ZipOutputStream.DEFAULT_COMPRESSION;
135
136     /**
137      * This is the name/location of where to
138      * create the .zip file.
139      * @param zipFile the path of the zipFile
140      * @deprecated since 1.5.x.
141      * Use setDestFile(File) instead.
142      * @ant.attribute ignore="true"
143      */

144     public void setZipfile(File JavaDoc zipFile) {
145         setDestFile(zipFile);
146     }
147
148     /**
149      * This is the name/location of where to
150      * create the file.
151      * @param file the path of the zipFile
152      * @since Ant 1.5
153      * @deprecated since 1.5.x.
154      * Use setDestFile(File) instead.
155      * @ant.attribute ignore="true"
156      */

157     public void setFile(File JavaDoc file) {
158         setDestFile(file);
159     }
160
161
162     /**
163      * The file to create; required.
164      * @since Ant 1.5
165      * @param destFile The new destination File
166      */

167     public void setDestFile(File JavaDoc destFile) {
168        this.zipFile = destFile;
169     }
170
171     /**
172      * The file to create.
173      * @return the destination file
174      * @since Ant 1.5.2
175      */

176     public File JavaDoc getDestFile() {
177         return zipFile;
178     }
179
180
181     /**
182      * Directory from which to archive files; optional.
183      * @param baseDir the base directory
184      */

185     public void setBasedir(File JavaDoc baseDir) {
186         this.baseDir = baseDir;
187     }
188
189     /**
190      * Whether we want to compress the files or only store them;
191      * optional, default=true;
192      * @param c if true, compress the files
193      */

194     public void setCompress(boolean c) {
195         doCompress = c;
196     }
197
198     /**
199      * Whether we want to compress the files or only store them;
200      * @return true if the files are to be compressed
201      * @since Ant 1.5.2
202      */

203     public boolean isCompress() {
204         return doCompress;
205     }
206
207     /**
208      * If true, emulate Sun's jar utility by not adding parent directories;
209      * optional, defaults to false.
210      * @param f if true, emulate sun's jar by not adding parent directories
211      */

212     public void setFilesonly(boolean f) {
213         doFilesonly = f;
214     }
215
216     /**
217      * If true, updates an existing file, otherwise overwrite
218      * any existing one; optional defaults to false.
219      * @param c if true, updates an existing zip file
220      */

221     public void setUpdate(boolean c) {
222         doUpdate = c;
223         savedDoUpdate = c;
224     }
225
226     /**
227      * Are we updating an existing archive?
228      * @return true if updating an existing archive
229      */

230     public boolean isInUpdateMode() {
231         return doUpdate;
232     }
233
234     /**
235      * Adds a set of files.
236      * @param set the fileset to add
237      */

238     public void addFileset(FileSet set) {
239         add(set);
240     }
241
242     /**
243      * Adds a set of files that can be
244      * read from an archive and be given a prefix/fullpath.
245      * @param set the zipfileset to add
246      */

247     public void addZipfileset(ZipFileSet set) {
248         add(set);
249     }
250
251     /**
252      * Add a collection of resources to be archived.
253      * @param a the resources to archive
254      * @since Ant 1.7
255      */

256     public void add(ResourceCollection a) {
257         resources.add(a);
258     }
259
260     /**
261      * Adds a group of zip files.
262      * @param set the group (a fileset) to add
263      */

264     public void addZipGroupFileset(FileSet set) {
265         groupfilesets.addElement(set);
266     }
267
268     /**
269      * Sets behavior for when a duplicate file is about to be added -
270      * one of <code>add</code>, <code>preserve</code> or <code>fail</code>.
271      * Possible values are: <code>add</code> (keep both
272      * of the files); <code>preserve</code> (keep the first version
273      * of the file found); <code>fail</code> halt a problem
274      * Default for zip tasks is <code>add</code>
275      * @param df a <code>Duplicate</code> enumerated value
276      */

277     public void setDuplicate(Duplicate df) {
278         duplicate = df.getValue();
279     }
280
281     /**
282      * Possible behaviors when there are no matching files for the task:
283      * "fail", "skip", or "create".
284      */

285     public static class WhenEmpty extends EnumeratedAttribute {
286         /**
287          * The string values for the enumerated value
288          * @return the values
289          */

290         public String JavaDoc[] getValues() {
291             return new String JavaDoc[] {"fail", "skip", "create"};
292         }
293     }
294
295     /**
296      * Sets behavior of the task when no files match.
297      * Possible values are: <code>fail</code> (throw an exception
298      * and halt the build); <code>skip</code> (do not create
299      * any archive, but issue a warning); <code>create</code>
300      * (make an archive with no entries).
301      * Default for zip tasks is <code>skip</code>;
302      * for jar tasks, <code>create</code>.
303      * @param we a <code>WhenEmpty</code> enumerated value
304      */

305     public void setWhenempty(WhenEmpty we) {
306         emptyBehavior = we.getValue();
307     }
308
309     /**
310      * Encoding to use for filenames, defaults to the platform's
311      * default encoding.
312      *
313      * <p>For a list of possible values see <a
314      * HREF="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.</p>
315      * @param encoding the encoding name
316      */

317     public void setEncoding(String JavaDoc encoding) {
318         this.encoding = encoding;
319     }
320
321     /**
322      * Encoding to use for filenames.
323      * @return the name of the encoding to use
324      * @since Ant 1.5.2
325      */

326     public String JavaDoc getEncoding() {
327         return encoding;
328     }
329
330     /**
331      * Whether the original compression of entries coming from a ZIP
332      * archive should be kept (for example when updating an archive).
333      * Default is false.
334      * @param keep if true, keep the original compression
335      * @since Ant 1.6
336      */

337     public void setKeepCompression(boolean keep) {
338         keepCompression = keep;
339     }
340
341     /**
342      * Comment to use for archive.
343      *
344      * @param comment The content of the comment.
345      * @since Ant 1.6.3
346      */

347     public void setComment(String JavaDoc comment) {
348         this.comment = comment;
349     }
350
351     /**
352      * Comment of the archive
353      *
354      * @return Comment of the archive.
355      * @since Ant 1.6.3
356      */

357     public String JavaDoc getComment() {
358         return comment;
359     }
360
361     /**
362      * Set the compression level to use. Default is
363      * ZipOutputStream.DEFAULT_COMPRESSION.
364      * @param level compression level.
365      * @since Ant 1.7
366      */

367     public void setLevel(int level) {
368         this.level = level;
369     }
370
371     /**
372      * Get the compression level.
373      * @return compression level.
374      * @since Ant 1.7
375      */

376     public int getLevel() {
377         return level;
378     }
379
380     /**
381      * Whether the file modification times will be rounded up to the
382      * next even number of seconds.
383      *
384      * <p>Zip archives store file modification times with a
385      * granularity of two seconds, so the times will either be rounded
386      * up or down. If you round down, the archive will always seem
387      * out-of-date when you rerun the task, so the default is to round
388      * up. Rounding up may lead to a different type of problems like
389      * JSPs inside a web archive that seem to be slightly more recent
390      * than precompiled pages, rendering precompilation useless.</p>
391      * @param r a <code>boolean</code> value
392      * @since Ant 1.6.2
393      */

394     public void setRoundUp(boolean r) {
395         roundUp = r;
396     }
397
398     /**
399      * validate and build
400      * @throws BuildException on error
401      */

402     public void execute() throws BuildException {
403
404         if (doubleFilePass) {
405             skipWriting = true;
406             executeMain();
407             skipWriting = false;
408             executeMain();
409         } else {
410             executeMain();
411         }
412     }
413
414     /**
415      * Build the zip file.
416      * This is called twice if doubleFilePass is true.
417      * @throws BuildException on error
418      */

419     public void executeMain() throws BuildException {
420
421         if (baseDir == null && resources.size() == 0
422             && groupfilesets.size() == 0 && "zip".equals(archiveType)) {
423             throw new BuildException("basedir attribute must be set, "
424                                      + "or at least one "
425                                      + "resource collection must be given!");
426         }
427
428         if (zipFile == null) {
429             throw new BuildException("You must specify the "
430                                      + archiveType + " file to create!");
431         }
432
433         if (zipFile.exists() && !zipFile.isFile()) {
434             throw new BuildException(zipFile + " is not a file.");
435         }
436
437         if (zipFile.exists() && !zipFile.canWrite()) {
438             throw new BuildException(zipFile + " is read-only.");
439         }
440
441         // Renamed version of original file, if it exists
442
File JavaDoc renamedFile = null;
443         addingNewFiles = true;
444
445         // Whether or not an actual update is required -
446
// we don't need to update if the original file doesn't exist
447
if (doUpdate && !zipFile.exists()) {
448             doUpdate = false;
449             log("ignoring update attribute as " + archiveType
450                 + " doesn't exist.", Project.MSG_DEBUG);
451         }
452
453         // Add the files found in groupfileset to fileset
454
for (int i = 0; i < groupfilesets.size(); i++) {
455
456             log("Processing groupfileset ", Project.MSG_VERBOSE);
457             FileSet fs = (FileSet) groupfilesets.elementAt(i);
458             FileScanner scanner = fs.getDirectoryScanner(getProject());
459             String JavaDoc[] files = scanner.getIncludedFiles();
460             File JavaDoc basedir = scanner.getBasedir();
461             for (int j = 0; j < files.length; j++) {
462
463                 log("Adding file " + files[j] + " to fileset",
464                     Project.MSG_VERBOSE);
465                 ZipFileSet zf = new ZipFileSet();
466                 zf.setProject(getProject());
467                 zf.setSrc(new File JavaDoc(basedir, files[j]));
468                 add(zf);
469                 filesetsFromGroupfilesets.addElement(zf);
470             }
471         }
472
473         // collect filesets to pass them to getResourcesToAdd
474
Vector JavaDoc vfss = new Vector JavaDoc();
475         if (baseDir != null) {
476             FileSet fs = (FileSet) getImplicitFileSet().clone();
477             fs.setDir(baseDir);
478             vfss.addElement(fs);
479         }
480         for (int i = 0; i < resources.size(); i++) {
481             ResourceCollection rc = (ResourceCollection) resources.elementAt(i);
482             vfss.addElement(rc);
483         }
484
485         ResourceCollection[] fss = new ResourceCollection[vfss.size()];
486         vfss.copyInto(fss);
487         boolean success = false;
488         try {
489             // can also handle empty archives
490
ArchiveState state = getResourcesToAdd(fss, zipFile, false);
491
492             // quick exit if the target is up to date
493
if (!state.isOutOfDate()) {
494                 return;
495             }
496
497             if (!zipFile.exists() && state.isWithoutAnyResources()) {
498                 createEmptyZip(zipFile);
499                 return;
500             }
501             Resource[][] addThem = state.getResourcesToAdd();
502
503             if (doUpdate) {
504                 renamedFile =
505                     FILE_UTILS.createTempFile("zip", ".tmp",
506                                               zipFile.getParentFile());
507                 renamedFile.deleteOnExit();
508
509                 try {
510                     FILE_UTILS.rename(zipFile, renamedFile);
511                 } catch (SecurityException JavaDoc e) {
512                     throw new BuildException(
513                         "Not allowed to rename old file ("
514                         + zipFile.getAbsolutePath()
515                         + ") to temporary file");
516                 } catch (IOException JavaDoc e) {
517                     throw new BuildException(
518                         "Unable to rename old file ("
519                         + zipFile.getAbsolutePath()
520                         + ") to temporary file");
521                 }
522             }
523
524             String JavaDoc action = doUpdate ? "Updating " : "Building ";
525
526             log(action + archiveType + ": " + zipFile.getAbsolutePath());
527
528             ZipOutputStream zOut = null;
529             try {
530                 if (!skipWriting) {
531                     zOut = new ZipOutputStream(zipFile);
532
533                     zOut.setEncoding(encoding);
534                     zOut.setMethod(doCompress
535                         ? ZipOutputStream.DEFLATED : ZipOutputStream.STORED);
536                     zOut.setLevel(level);
537                 }
538                 initZipOutputStream(zOut);
539
540                 // Add the explicit resource collections to the archive.
541
for (int i = 0; i < fss.length; i++) {
542                     if (addThem[i].length != 0) {
543                         addResources(fss[i], addThem[i], zOut);
544                     }
545                 }
546
547                 if (doUpdate) {
548                     addingNewFiles = false;
549                     ZipFileSet oldFiles = new ZipFileSet();
550                     oldFiles.setProject(getProject());
551                     oldFiles.setSrc(renamedFile);
552                     oldFiles.setDefaultexcludes(false);
553
554                     for (int i = 0; i < addedFiles.size(); i++) {
555                         PatternSet.NameEntry ne = oldFiles.createExclude();
556                         ne.setName((String JavaDoc) addedFiles.elementAt(i));
557                     }
558                     DirectoryScanner ds =
559                         oldFiles.getDirectoryScanner(getProject());
560                     ((ZipScanner) ds).setEncoding(encoding);
561
562                     String JavaDoc[] f = ds.getIncludedFiles();
563                     Resource[] r = new Resource[f.length];
564                     for (int i = 0; i < f.length; i++) {
565                         r[i] = ds.getResource(f[i]);
566                     }
567
568                     if (!doFilesonly) {
569                         String JavaDoc[] d = ds.getIncludedDirectories();
570                         Resource[] dr = new Resource[d.length];
571                         for (int i = 0; i < d.length; i++) {
572                             dr[i] = ds.getResource(d[i]);
573                         }
574                         Resource[] tmp = r;
575                         r = new Resource[tmp.length + dr.length];
576                         System.arraycopy(dr, 0, r, 0, dr.length);
577                         System.arraycopy(tmp, 0, r, dr.length, tmp.length);
578                     }
579                     addResources(oldFiles, r, zOut);
580                 }
581                 if (zOut != null) {
582                     zOut.setComment(comment);
583                 }
584                 finalizeZipOutputStream(zOut);
585
586                 // If we've been successful on an update, delete the
587
// temporary file
588
if (doUpdate) {
589                     if (!renamedFile.delete()) {
590                         log ("Warning: unable to delete temporary file "
591                             + renamedFile.getName(), Project.MSG_WARN);
592                     }
593                 }
594                 success = true;
595             } finally {
596                 // Close the output stream.
597
try {
598                     if (zOut != null) {
599                         zOut.close();
600                     }
601                 } catch (IOException JavaDoc ex) {
602                     // If we're in this finally clause because of an
603
// exception, we don't really care if there's an
604
// exception when closing the stream. E.g. if it
605
// throws "ZIP file must have at least one entry",
606
// because an exception happened before we added
607
// any files, then we must swallow this
608
// exception. Otherwise, the error that's reported
609
// will be the close() error, which is not the
610
// real cause of the problem.
611
if (success) {
612                         throw ex;
613                     }
614                 }
615             }
616         } catch (IOException JavaDoc ioe) {
617             String JavaDoc msg = "Problem creating " + archiveType + ": "
618                 + ioe.getMessage();
619
620             // delete a bogus ZIP file (but only if it's not the original one)
621
if ((!doUpdate || renamedFile != null) && !zipFile.delete()) {
622                 msg += " (and the archive is probably corrupt but I could not "
623                     + "delete it)";
624             }
625
626             if (doUpdate && renamedFile != null) {
627                 try {
628                     FILE_UTILS.rename(renamedFile, zipFile);
629                 } catch (IOException JavaDoc e) {
630                     msg += " (and I couldn't rename the temporary file "
631                             + renamedFile.getName() + " back)";
632                 }
633             }
634
635             throw new BuildException(msg, ioe, getLocation());
636         } finally {
637             cleanUp();
638         }
639     }
640
641     /**
642      * Indicates if the task is adding new files into the archive as opposed to
643      * copying back unchanged files from the backup copy
644      * @return true if adding new files
645      */

646     protected final boolean isAddingNewFiles() {
647         return addingNewFiles;
648     }
649
650     /**
651      * Add the given resources.
652      *
653      * @param fileset may give additional information like fullpath or
654      * permissions.
655      * @param resources the resources to add
656      * @param zOut the stream to write to
657      * @throws IOException on error
658      *
659      * @since Ant 1.5.2
660      */

661     protected final void addResources(FileSet fileset, Resource[] resources,
662                                       ZipOutputStream zOut)
663         throws IOException JavaDoc {
664
665         String JavaDoc prefix = "";
666         String JavaDoc fullpath = "";
667         int dirMode = ArchiveFileSet.DEFAULT_DIR_MODE;
668         int fileMode = ArchiveFileSet.DEFAULT_FILE_MODE;
669
670         ArchiveFileSet zfs = null;
671         if (fileset instanceof ArchiveFileSet) {
672             zfs = (ArchiveFileSet) fileset;
673             prefix = zfs.getPrefix(getProject());
674             fullpath = zfs.getFullpath(getProject());
675             dirMode = zfs.getDirMode(getProject());
676             fileMode = zfs.getFileMode(getProject());
677         }
678
679         if (prefix.length() > 0 && fullpath.length() > 0) {
680             throw new BuildException("Both prefix and fullpath attributes must"
681                                      + " not be set on the same fileset.");
682         }
683
684         if (resources.length != 1 && fullpath.length() > 0) {
685             throw new BuildException("fullpath attribute may only be specified"
686                                      + " for filesets that specify a single"
687                                      + " file.");
688         }
689
690         if (prefix.length() > 0) {
691             if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
692                 prefix += "/";
693             }
694             addParentDirs(null, prefix, zOut, "", dirMode);
695         }
696
697         ZipFile zf = null;
698         try {
699             boolean dealingWithFiles = false;
700             File JavaDoc base = null;
701
702             if (zfs == null || zfs.getSrc(getProject()) == null) {
703                 dealingWithFiles = true;
704                 base = fileset.getDir(getProject());
705             } else if (zfs instanceof ZipFileSet) {
706                 zf = new ZipFile(zfs.getSrc(getProject()), encoding);
707             }
708
709             for (int i = 0; i < resources.length; i++) {
710                 String JavaDoc name = null;
711                 if (fullpath.length() > 0) {
712                     name = fullpath;
713                 } else {
714                     name = resources[i].getName();
715                 }
716                 name = name.replace(File.separatorChar, '/');
717
718                 if ("".equals(name)) {
719                     continue;
720                 }
721                 if (resources[i].isDirectory() && !name.endsWith("/")) {
722                     name = name + "/";
723                 }
724
725                 if (!doFilesonly && !dealingWithFiles
726                     && resources[i].isDirectory()
727                     && !zfs.hasDirModeBeenSet()) {
728                     int nextToLastSlash = name.lastIndexOf("/",
729                                                            name.length() - 2);
730                     if (nextToLastSlash != -1) {
731                         addParentDirs(base, name.substring(0,
732                                                            nextToLastSlash + 1),
733                                       zOut, prefix, dirMode);
734                     }
735                     if (zf != null) {
736                         ZipEntry ze = zf.getEntry(resources[i].getName());
737                         addParentDirs(base, name, zOut, prefix,
738                                       ze.getUnixMode());
739                     } else {
740                         ArchiveResource tr = (ArchiveResource) resources[i];
741                         addParentDirs(base, name, zOut, prefix,
742                                       tr.getMode());
743                     }
744
745                 } else {
746                     addParentDirs(base, name, zOut, prefix, dirMode);
747                 }
748
749                 if (!resources[i].isDirectory() && dealingWithFiles) {
750                     File JavaDoc f = FILE_UTILS.resolveFile(base,
751                                                    resources[i].getName());
752                     zipFile(f, zOut, prefix + name, fileMode);
753                 } else if (!resources[i].isDirectory()) {
754                     if (zf != null) {
755                     ZipEntry ze = zf.getEntry(resources[i].getName());
756
757                     if (ze != null) {
758                         boolean oldCompress = doCompress;
759                         if (keepCompression) {
760                             doCompress = (ze.getMethod() == ZipEntry.DEFLATED);
761                         }
762                         try {
763                             zipFile(zf.getInputStream(ze), zOut, prefix + name,
764                                     ze.getTime(), zfs.getSrc(getProject()),
765                                     zfs.hasFileModeBeenSet() ? fileMode
766                                     : ze.getUnixMode());
767                         } finally {
768                             doCompress = oldCompress;
769                         }
770                     }
771                     } else {
772                         ArchiveResource tr = (ArchiveResource) resources[i];
773                         InputStream JavaDoc is = null;
774                         try {
775                             is = tr.getInputStream();
776                             zipFile(is, zOut, prefix + name,
777                                     resources[i].getLastModified(),
778                                     zfs.getSrc(getProject()),
779                                     zfs.hasFileModeBeenSet() ? fileMode
780                                     : tr.getMode());
781                         } finally {
782                             FileUtils.close(is);
783                         }
784                     }
785                 }
786             }
787         } finally {
788             if (zf != null) {
789                 zf.close();
790             }
791         }
792     }
793
794     /**
795      * Add the given resources.
796      *
797      * @param rc may give additional information like fullpath or
798      * permissions.
799      * @param resources the resources to add
800      * @param zOut the stream to write to
801      * @throws IOException on error
802      *
803      * @since Ant 1.7
804      */

805     protected final void addResources(ResourceCollection rc,
806                                       Resource[] resources,
807                                       ZipOutputStream zOut)
808         throws IOException JavaDoc {
809         if (rc instanceof FileSet) {
810             addResources((FileSet) rc, resources, zOut);
811             return;
812         }
813         for (int i = 0; i < resources.length; i++) {
814             String JavaDoc name = resources[i].getName().replace(File.separatorChar,
815                                                          '/');
816             if ("".equals(name)) {
817                 continue;
818             }
819             if (resources[i].isDirectory() && doFilesonly) {
820                 continue;
821             }
822             File JavaDoc base = null;
823             if (resources[i] instanceof FileResource) {
824                 base = ((FileResource) resources[i]).getBaseDir();
825             }
826             if (resources[i].isDirectory()) {
827                 if (!name.endsWith("/")) {
828                     name = name + "/";
829                 }
830             }
831
832             addParentDirs(base, name, zOut, "",
833                           ArchiveFileSet.DEFAULT_DIR_MODE);
834
835             if (!resources[i].isDirectory()) {
836                 if (resources[i] instanceof FileResource) {
837                     File JavaDoc f = ((FileResource) resources[i]).getFile();
838                     zipFile(f, zOut, name, ArchiveFileSet.DEFAULT_FILE_MODE);
839                 } else {
840                     InputStream JavaDoc is = null;
841                     try {
842                         is = resources[i].getInputStream();
843                         zipFile(is, zOut, name,
844                                 resources[i].getLastModified(),
845                                 null, ArchiveFileSet.DEFAULT_FILE_MODE);
846                     } finally {
847                         FileUtils.close(is);
848                     }
849                 }
850             }
851         }
852     }
853
854     /**
855      * method for subclasses to override
856      * @param zOut the zip output stream
857      * @throws IOException on output error
858      * @throws BuildException on other errors
859      */

860     protected void initZipOutputStream(ZipOutputStream zOut)
861         throws IOException JavaDoc, BuildException {
862     }
863
864     /**
865      * method for subclasses to override
866      * @param zOut the zip output stream
867      * @throws IOException on output error
868      * @throws BuildException on other errors
869      */

870     protected void finalizeZipOutputStream(ZipOutputStream zOut)
871         throws IOException JavaDoc, BuildException {
872     }
873
874     /**
875      * Create an empty zip file
876      * @param zipFile the zip file
877      * @return true for historic reasons
878      * @throws BuildException on error
879      */

880     protected boolean createEmptyZip(File JavaDoc zipFile) throws BuildException {
881         // In this case using java.util.zip will not work
882
// because it does not permit a zero-entry archive.
883
// Must create it manually.
884
log("Note: creating empty " + archiveType + " archive " + zipFile,
885             Project.MSG_INFO);
886         OutputStream JavaDoc os = null;
887         try {
888             os = new FileOutputStream JavaDoc(zipFile);
889             // Cf. PKZIP specification.
890
byte[] empty = new byte[22];
891             empty[0] = 80; // P
892
empty[1] = 75; // K
893
empty[2] = 5;
894             empty[3] = 6;
895             // remainder zeros
896
os.write(empty);
897         } catch (IOException JavaDoc ioe) {
898             throw new BuildException("Could not create empty ZIP archive "
899                                      + "(" + ioe.getMessage() + ")", ioe,
900                                      getLocation());
901         } finally {
902             if (os != null) {
903                 try {
904                     os.close();
905                 } catch (IOException JavaDoc e) {
906                     //ignore
907
}
908             }
909         }
910         return true;
911     }
912
913     /**
914      * @since Ant 1.5.2
915      */

916     private synchronized ZipScanner getZipScanner() {
917         if (zs == null) {
918             zs = new ZipScanner();
919             zs.setEncoding(encoding);
920             zs.setSrc(zipFile);
921         }
922         return zs;
923     }
924
925     /**
926      * Collect the resources that are newer than the corresponding
927      * entries (or missing) in the original archive.
928      *
929      * <p>If we are going to recreate the archive instead of updating
930      * it, all resources should be considered as new, if a single one
931      * is. Because of this, subclasses overriding this method must
932      * call <code>super.getResourcesToAdd</code> and indicate with the
933      * third arg if they already know that the archive is
934      * out-of-date.</p>
935      *
936      * <p>This method first delegates to getNonFileSetResourceToAdd
937      * and then invokes the FileSet-arg version. All this to keep
938      * backwards compatibility for subclasses that don't know how to
939      * deal with non-FileSet ResourceCollections.</p>
940      *
941      * @param rcs The resource collections to grab resources from
942      * @param zipFile intended archive file (may or may not exist)
943      * @param needsUpdate whether we already know that the archive is
944      * out-of-date. Subclasses overriding this method are supposed to
945      * set this value correctly in their call to
946      * <code>super.getResourcesToAdd</code>.
947      * @return an array of resources to add for each fileset passed in as well
948      * as a flag that indicates whether the archive is uptodate.
949      *
950      * @exception BuildException if it likes
951      * @since Ant 1.7
952      */

953     protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs,
954                                              File JavaDoc zipFile,
955                                              boolean needsUpdate)
956         throws BuildException {
957         ArrayList JavaDoc filesets = new ArrayList JavaDoc();
958         ArrayList JavaDoc rest = new ArrayList JavaDoc();
959         for (int i = 0; i < rcs.length; i++) {
960             if (rcs[i] instanceof FileSet) {
961                 filesets.add(rcs[i]);
962             } else {
963                 rest.add(rcs[i]);
964             }
965         }
966         ResourceCollection[] rc = (ResourceCollection[])
967             rest.toArray(new ResourceCollection[rest.size()]);
968         ArchiveState as = getNonFileSetResourcesToAdd(rc, zipFile,
969                                                       needsUpdate);
970
971         FileSet[] fs = (FileSet[]) filesets.toArray(new FileSet[filesets
972                                                                 .size()]);
973         ArchiveState as2 = getResourcesToAdd(fs, zipFile, as.isOutOfDate());
974         if (!as.isOutOfDate() && as2.isOutOfDate()) {
975             /*
976              * Bad luck.
977              *
978              * There are resources in the filesets that make the
979              * archive out of date, but not in the non-fileset
980              * resources. We need to rescan the non-FileSets to grab
981              * all of them now.
982              */

983             as = getNonFileSetResourcesToAdd(rc, zipFile, true);
984         }
985
986         Resource[][] toAdd = new Resource[rcs.length][];
987         int fsIndex = 0;
988         int restIndex = 0;
989         for (int i = 0; i < rcs.length; i++) {
990             if (rcs[i] instanceof FileSet) {
991                 toAdd[i] = as2.getResourcesToAdd()[fsIndex++];
992             } else {
993                 toAdd[i] = as.getResourcesToAdd()[restIndex++];
994             }
995         }
996         return new ArchiveState(as2.isOutOfDate(), toAdd);
997     }
998
999     /**
1000     * Collect the resources that are newer than the corresponding
1001     * entries (or missing) in the original archive.
1002     *
1003     * <p>If we are going to recreate the archive instead of updating
1004     * it, all resources should be considered as new, if a single one
1005     * is. Because of this, subclasses overriding this method must
1006     * call <code>super.getResourcesToAdd</code> and indicate with the
1007     * third arg if they already know that the archive is
1008     * out-of-date.</p>
1009     *
1010     * @param filesets The filesets to grab resources from
1011     * @param zipFile intended archive file (may or may not exist)
1012     * @param needsUpdate whether we already know that the archive is
1013     * out-of-date. Subclasses overriding this method are supposed to
1014     * set this value correctly in their call to
1015     * <code>super.getResourcesToAdd</code>.
1016     * @return an array of resources to add for each fileset passed in as well
1017     * as a flag that indicates whether the archive is uptodate.
1018     *
1019     * @exception BuildException if it likes
1020     */

1021    protected ArchiveState getResourcesToAdd(FileSet[] filesets,
1022                                             File JavaDoc zipFile,
1023                                             boolean needsUpdate)
1024        throws BuildException {
1025
1026        Resource[][] initialResources = grabResources(filesets);
1027        if (isEmpty(initialResources)) {
1028            if (needsUpdate && doUpdate) {
1029                /*
1030                 * This is a rather hairy case.
1031                 *
1032                 * One of our subclasses knows that we need to update the
1033                 * archive, but at the same time, there are no resources
1034                 * known to us that would need to be added. Only the
1035                 * subclass seems to know what's going on.
1036                 *
1037                 * This happens if <jar> detects that the manifest has changed,
1038                 * for example. The manifest is not part of any resources
1039                 * because of our support for inline <manifest>s.
1040                 *
1041                 * If we invoke createEmptyZip like Ant 1.5.2 did,
1042                 * we'll loose all stuff that has been in the original
1043                 * archive (bugzilla report 17780).
1044                 */

1045                return new ArchiveState(true, initialResources);
1046            }
1047
1048            if (emptyBehavior.equals("skip")) {
1049                if (doUpdate) {
1050                    log(archiveType + " archive " + zipFile
1051                        + " not updated because no new files were included.",
1052                        Project.MSG_VERBOSE);
1053                } else {
1054                    log("Warning: skipping " + archiveType + " archive "
1055                        + zipFile + " because no files were included.",
1056                        Project.MSG_WARN);
1057                }
1058            } else if (emptyBehavior.equals("fail")) {
1059                throw new BuildException("Cannot create " + archiveType
1060                                         + " archive " + zipFile
1061                                         + ": no files were included.",
1062                                         getLocation());
1063            } else {
1064                // Create.
1065
if (!zipFile.exists()) {
1066                    needsUpdate = true;
1067                }
1068            }
1069            return new ArchiveState(needsUpdate, initialResources);
1070        }
1071
1072        // initialResources is not empty
1073

1074        if (!zipFile.exists()) {
1075            return new ArchiveState(true, initialResources);
1076        }
1077
1078        if (needsUpdate && !doUpdate) {
1079            // we are recreating the archive, need all resources
1080
return new ArchiveState(true, initialResources);
1081        }
1082
1083        Resource[][] newerResources = new Resource[filesets.length][];
1084
1085        for (int i = 0; i < filesets.length; i++) {
1086            if (!(fileset instanceof ZipFileSet)
1087                || ((ZipFileSet) fileset).getSrc(getProject()) == null) {
1088                File JavaDoc base = filesets[i].getDir(getProject());
1089
1090                for (int j = 0; j < initialResources[i].length; j++) {
1091                    File JavaDoc resourceAsFile =
1092                        FILE_UTILS.resolveFile(base,
1093                                              initialResources[i][j].getName());
1094                    if (resourceAsFile.equals(zipFile)) {
1095                        throw new BuildException("A zip file cannot include "
1096                                                 + "itself", getLocation());
1097                    }
1098                }
1099            }
1100        }
1101
1102        for (int i = 0; i < filesets.length; i++) {
1103            if (initialResources[i].length == 0) {
1104                newerResources[i] = new Resource[] {};
1105                continue;
1106            }
1107
1108            FileNameMapper myMapper = new IdentityMapper();
1109            if (filesets[i] instanceof ZipFileSet) {
1110                ZipFileSet zfs = (ZipFileSet) filesets[i];
1111                if (zfs.getFullpath(getProject()) != null
1112                    && !zfs.getFullpath(getProject()).equals("")) {
1113                    // in this case all files from origin map to
1114
// the fullPath attribute of the zipfileset at
1115
// destination
1116
MergingMapper fm = new MergingMapper();
1117                    fm.setTo(zfs.getFullpath(getProject()));
1118                    myMapper = fm;
1119
1120                } else if (zfs.getPrefix(getProject()) != null
1121                           && !zfs.getPrefix(getProject()).equals("")) {
1122                    GlobPatternMapper gm = new GlobPatternMapper();
1123                    gm.setFrom("*");
1124                    String JavaDoc prefix = zfs.getPrefix(getProject());
1125                    if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
1126                        prefix += "/";
1127                    }
1128                    gm.setTo(prefix + "*");
1129                    myMapper = gm;
1130                }
1131            }
1132
1133            Resource[] resources = initialResources[i];
1134            if (doFilesonly) {
1135                resources = selectFileResources(resources);
1136            }
1137
1138            newerResources[i] =
1139                ResourceUtils.selectOutOfDateSources(this,
1140                                                     resources,
1141                                                     myMapper,
1142                                                     getZipScanner());
1143            needsUpdate = needsUpdate || (newerResources[i].length > 0);
1144
1145            if (needsUpdate && !doUpdate) {
1146                // we will return initialResources anyway, no reason
1147
// to scan further.
1148
break;
1149            }
1150        }
1151
1152        if (needsUpdate && !doUpdate) {
1153            // we are recreating the archive, need all resources
1154
return new ArchiveState(true, initialResources);
1155        }
1156
1157        return new ArchiveState(needsUpdate, newerResources);
1158    }
1159
1160    /**
1161     * Collect the resources that are newer than the corresponding
1162     * entries (or missing) in the original archive.
1163     *
1164     * <p>If we are going to recreate the archive instead of updating
1165     * it, all resources should be considered as new, if a single one
1166     * is. Because of this, subclasses overriding this method must
1167     * call <code>super.getResourcesToAdd</code> and indicate with the
1168     * third arg if they already know that the archive is
1169     * out-of-date.</p>
1170     *
1171     * @param rcs The filesets to grab resources from
1172     * @param zipFile intended archive file (may or may not exist)
1173     * @param needsUpdate whether we already know that the archive is
1174     * out-of-date. Subclasses overriding this method are supposed to
1175     * set this value correctly in their call to
1176     * <code>super.getResourcesToAdd</code>.
1177     * @return an array of resources to add for each fileset passed in as well
1178     * as a flag that indicates whether the archive is uptodate.
1179     *
1180     * @exception BuildException if it likes
1181     */

1182    protected ArchiveState getNonFileSetResourcesToAdd(ResourceCollection[] rcs,
1183                                                       File JavaDoc zipFile,
1184                                                       boolean needsUpdate)
1185        throws BuildException {
1186        /*
1187         * Backwards compatibility forces us to repeat the logic of
1188         * getResourcesToAdd(FileSet[], ...) here once again.
1189         */

1190
1191        Resource[][] initialResources = grabNonFileSetResources(rcs);
1192        if (isEmpty(initialResources)) {
1193            // no emptyBehavior handling since the FileSet version
1194
// will take care of it.
1195
return new ArchiveState(needsUpdate, initialResources);
1196        }
1197
1198        // initialResources is not empty
1199

1200        if (!zipFile.exists()) {
1201            return new ArchiveState(true, initialResources);
1202        }
1203
1204        if (needsUpdate && !doUpdate) {
1205            // we are recreating the archive, need all resources
1206
return new ArchiveState(true, initialResources);
1207        }
1208
1209        Resource[][] newerResources = new Resource[rcs.length][];
1210
1211        for (int i = 0; i < rcs.length; i++) {
1212            if (initialResources[i].length == 0) {
1213                newerResources[i] = new Resource[] {};
1214                continue;
1215            }
1216
1217            for (int j = 0; j < initialResources[i].length; j++) {
1218                if (initialResources[i][j] instanceof FileResource
1219                    && zipFile.equals(((FileResource)
1220                                       initialResources[i][j]).getFile())) {
1221                    throw new BuildException("A zip file cannot include "
1222                                             + "itself", getLocation());
1223                }
1224            }
1225
1226            Resource[] rs = initialResources[i];
1227            if (doFilesonly) {
1228                rs = selectFileResources(rs);
1229            }
1230
1231            newerResources[i] =
1232                ResourceUtils.selectOutOfDateSources(this,
1233                                                     rs,
1234                                                     new IdentityMapper(),
1235                                                     getZipScanner());
1236            needsUpdate = needsUpdate || (newerResources[i].length > 0);
1237
1238            if (needsUpdate && !doUpdate) {
1239                // we will return initialResources anyway, no reason
1240
// to scan further.
1241
break;
1242            }
1243        }
1244
1245        if (needsUpdate && !doUpdate) {
1246            // we are recreating the archive, need all resources
1247
return new ArchiveState(true, initialResources);
1248        }
1249
1250        return new ArchiveState(needsUpdate, newerResources);
1251    }
1252
1253    /**
1254     * Fetch all included and not excluded resources from the sets.
1255     *
1256     * <p>Included directories will precede included files.</p>
1257     * @param filesets an array of filesets
1258     * @return the resources included
1259     * @since Ant 1.5.2
1260     */

1261    protected Resource[][] grabResources(FileSet[] filesets) {
1262        Resource[][] result = new Resource[filesets.length][];
1263        for (int i = 0; i < filesets.length; i++) {
1264            boolean skipEmptyNames = true;
1265            if (filesets[i] instanceof ZipFileSet) {
1266                ZipFileSet zfs = (ZipFileSet) filesets[i];
1267                skipEmptyNames = zfs.getPrefix(getProject()).equals("")
1268                    && zfs.getFullpath(getProject()).equals("");
1269            }
1270            DirectoryScanner rs =
1271                filesets[i].getDirectoryScanner(getProject());
1272            if (rs instanceof ZipScanner) {
1273                ((ZipScanner) rs).setEncoding(encoding);
1274            }
1275            Vector JavaDoc resources = new Vector JavaDoc();
1276            if (!doFilesonly) {
1277                String JavaDoc[] directories = rs.getIncludedDirectories();
1278                for (int j = 0; j < directories.length; j++) {
1279                    if (!"".equals(directories[j]) || !skipEmptyNames) {
1280                        resources.addElement(rs.getResource(directories[j]));
1281                    }
1282                }
1283            }
1284            String JavaDoc[] files = rs.getIncludedFiles();
1285            for (int j = 0; j < files.length; j++) {
1286                if (!"".equals(files[j]) || !skipEmptyNames) {
1287                    resources.addElement(rs.getResource(files[j]));
1288                }
1289            }
1290
1291            result[i] = new Resource[resources.size()];
1292            resources.copyInto(result[i]);
1293        }
1294        return result;
1295    }
1296
1297    /**
1298     * Fetch all included and not excluded resources from the collections.
1299     *
1300     * <p>Included directories will precede included files.</p>
1301     * @param rcs an array of resource collections
1302     * @return the resources included
1303     * @since Ant 1.7
1304     */

1305    protected Resource[][] grabNonFileSetResources(ResourceCollection[] rcs) {
1306        Resource[][] result = new Resource[rcs.length][];
1307        for (int i = 0; i < rcs.length; i++) {
1308            Iterator JavaDoc iter = rcs[i].iterator();
1309            ArrayList JavaDoc rs = new ArrayList JavaDoc();
1310            int lastDir = 0;
1311            while (iter.hasNext()) {
1312                Resource r = (Resource) iter.next();
1313                if (r.isExists()) {
1314                    if (r.isDirectory()) {
1315                        rs.add(lastDir++, r);
1316                    } else {
1317                        rs.add(r);
1318                    }
1319                }
1320            }
1321            result[i] = (Resource[]) rs.toArray(new Resource[rs.size()]);
1322        }
1323        return result;
1324    }
1325
1326    /**
1327     * Add a directory to the zip stream.
1328     * @param dir the directort to add to the archive
1329     * @param zOut the stream to write to
1330     * @param vPath the name this entry shall have in the archive
1331     * @param mode the Unix permissions to set.
1332     * @throws IOException on error
1333     * @since Ant 1.5.2
1334     */

1335    protected void zipDir(File JavaDoc dir, ZipOutputStream zOut, String JavaDoc vPath,
1336                          int mode)
1337        throws IOException JavaDoc {
1338        zipDir(dir, zOut, vPath, mode, null);
1339    }
1340
1341    /**
1342     * Add a directory to the zip stream.
1343     * @param dir the directort to add to the archive
1344     * @param zOut the stream to write to
1345     * @param vPath the name this entry shall have in the archive
1346     * @param mode the Unix permissions to set.
1347     * @param extra ZipExtraFields to add
1348     * @throws IOException on error
1349     * @since Ant 1.6.3
1350     */

1351    protected void zipDir(File JavaDoc dir, ZipOutputStream zOut, String JavaDoc vPath,
1352                          int mode, ZipExtraField[] extra)
1353        throws IOException JavaDoc {
1354        if (doFilesonly) {
1355            log("skipping directory " + vPath + " for file-only archive",
1356                    Project.MSG_VERBOSE);
1357            return;
1358        }
1359        if (addedDirs.get(vPath) != null) {
1360            // don't add directories we've already added.
1361
// no warning if we try, it is harmless in and of itself
1362
return;
1363        }
1364
1365        log("adding directory " + vPath, Project.MSG_VERBOSE);
1366        addedDirs.put(vPath, vPath);
1367
1368        if (!skipWriting) {
1369            ZipEntry ze = new ZipEntry (vPath);
1370            if (dir != null && dir.exists()) {
1371                // ZIPs store time with a granularity of 2 seconds, round up
1372
ze.setTime(dir.lastModified() + (roundUp ? 1999 : 0));
1373            } else {
1374                // ZIPs store time with a granularity of 2 seconds, round up
1375
ze.setTime(System.currentTimeMillis() + (roundUp ? 1999 : 0));
1376            }
1377            ze.setSize (0);
1378            ze.setMethod (ZipEntry.STORED);
1379            // This is faintly ridiculous:
1380
ze.setCrc (EMPTY_CRC);
1381            ze.setUnixMode(mode);
1382
1383            if (extra != null) {
1384                ze.setExtraFields(extra);
1385            }
1386
1387            zOut.putNextEntry(ze);
1388        }
1389    }
1390
1391    /**
1392     * Adds a new entry to the archive, takes care of duplicates as well.
1393     *
1394     * @param in the stream to read data for the entry from.
1395     * @param zOut the stream to write to.
1396     * @param vPath the name this entry shall have in the archive.
1397     * @param lastModified last modification time for the entry.
1398     * @param fromArchive the original archive we are copying this
1399     * entry from, will be null if we are not copying from an archive.
1400     * @param mode the Unix permissions to set.
1401     *
1402     * @since Ant 1.5.2
1403     * @throws IOException on error
1404     */

1405    protected void zipFile(InputStream JavaDoc in, ZipOutputStream zOut, String JavaDoc vPath,
1406                           long lastModified, File JavaDoc fromArchive, int mode)
1407        throws IOException JavaDoc {
1408        if (entries.contains(vPath)) {
1409
1410            if (duplicate.equals("preserve")) {
1411                log(vPath + " already added, skipping", Project.MSG_INFO);
1412                return;
1413            } else if (duplicate.equals("fail")) {
1414                throw new BuildException("Duplicate file " + vPath
1415                                         + " was found and the duplicate "
1416                                         + "attribute is 'fail'.");
1417            } else {
1418                // duplicate equal to add, so we continue
1419
log("duplicate file " + vPath
1420                    + " found, adding.", Project.MSG_VERBOSE);
1421            }
1422        } else {
1423            log("adding entry " + vPath, Project.MSG_VERBOSE);
1424        }
1425
1426        entries.put(vPath, vPath);
1427
1428        if (!skipWriting) {
1429            ZipEntry ze = new ZipEntry(vPath);
1430            ze.setTime(lastModified);
1431            ze.setMethod(doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED);
1432
1433            /*
1434             * ZipOutputStream.putNextEntry expects the ZipEntry to
1435             * know its size and the CRC sum before you start writing
1436             * the data when using STORED mode - unless it is seekable.
1437             *
1438             * This forces us to process the data twice.
1439             */

1440            if (!zOut.isSeekable() && !doCompress) {
1441                long size = 0;
1442                CRC32 JavaDoc cal = new CRC32 JavaDoc();
1443                if (!in.markSupported()) {
1444                    // Store data into a byte[]
1445
ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
1446
1447                    byte[] buffer = new byte[8 * 1024];
1448                    int count = 0;
1449                    do {
1450                        size += count;
1451                        cal.update(buffer, 0, count);
1452                        bos.write(buffer, 0, count);
1453                        count = in.read(buffer, 0, buffer.length);
1454                    } while (count != -1);
1455                    in = new ByteArrayInputStream JavaDoc(bos.toByteArray());
1456
1457                } else {
1458                    in.mark(Integer.MAX_VALUE);
1459                    byte[] buffer = new byte[8 * 1024];
1460                    int count = 0;
1461                    do {
1462                        size += count;
1463                        cal.update(buffer, 0, count);
1464                        count = in.read(buffer, 0, buffer.length);
1465                    } while (count != -1);
1466                    in.reset();
1467                }
1468                ze.setSize(size);
1469                ze.setCrc(cal.getValue());
1470            }
1471
1472            ze.setUnixMode(mode);
1473            zOut.putNextEntry(ze);
1474
1475            byte[] buffer = new byte[8 * 1024];
1476            int count = 0;
1477            do {
1478                if (count != 0) {
1479                    zOut.write(buffer, 0, count);
1480                }
1481                count = in.read(buffer, 0, buffer.length);
1482            } while (count != -1);
1483        }
1484        addedFiles.addElement(vPath);
1485    }
1486
1487    /**
1488     * Method that gets called when adding from <code>java.io.File</code> instances.
1489     *
1490     * <p>This implementation delegates to the six-arg version.</p>
1491     *
1492     * @param file the file to add to the archive
1493     * @param zOut the stream to write to
1494     * @param vPath the name this entry shall have in the archive
1495     * @param mode the Unix permissions to set.
1496     * @throws IOException on error
1497     *
1498     * @since Ant 1.5.2
1499     */

1500    protected void zipFile(File JavaDoc file, ZipOutputStream zOut, String JavaDoc vPath,
1501                           int mode)
1502        throws IOException JavaDoc {
1503        if (file.equals(zipFile)) {
1504            throw new BuildException("A zip file cannot include itself",
1505                                     getLocation());
1506        }
1507
1508        FileInputStream JavaDoc fIn = new FileInputStream JavaDoc(file);
1509        try {
1510            // ZIPs store time with a granularity of 2 seconds, round up
1511
zipFile(fIn, zOut, vPath,
1512                    file.lastModified() + (roundUp ? 1999 : 0),
1513                    null, mode);
1514        } finally {
1515            fIn.close();
1516        }
1517    }
1518
1519    /**
1520     * Ensure all parent dirs of a given entry have been added.
1521     * @param baseDir the base directory to use (may be null)
1522     * @param entry the entry name to create directories from
1523     * @param zOut the stream to write to
1524     * @param prefix a prefix to place on the created entries
1525     * @param dirMode the directory mode
1526     * @throws IOException on error
1527     * @since Ant 1.5.2
1528     */

1529    protected final void addParentDirs(File JavaDoc baseDir, String JavaDoc entry,
1530                                       ZipOutputStream zOut, String JavaDoc prefix,
1531                                       int dirMode)
1532        throws IOException JavaDoc {
1533        if (!doFilesonly) {
1534            Stack JavaDoc directories = new Stack JavaDoc();
1535            int slashPos = entry.length();
1536
1537            while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) {
1538                String JavaDoc dir = entry.substring(0, slashPos + 1);
1539                if (addedDirs.get(prefix + dir) != null) {
1540                    break;
1541                }
1542                directories.push(dir);
1543            }
1544
1545            while (!directories.isEmpty()) {
1546                String JavaDoc dir = (String JavaDoc) directories.pop();
1547                File JavaDoc f = null;
1548                if (baseDir != null) {
1549                    f = new File JavaDoc(baseDir, dir);
1550                } else {
1551                    f = new File JavaDoc(dir);
1552                }
1553                zipDir(f, zOut, prefix + dir, dirMode);
1554            }
1555        }
1556    }
1557
1558    /**
1559     * Do any clean up necessary to allow this instance to be used again.
1560     *
1561     * <p>When we get here, the Zip file has been closed and all we
1562     * need to do is to reset some globals.</p>
1563     *
1564     * <p>This method will only reset globals that have been changed
1565     * during execute(), it will not alter the attributes or nested
1566     * child elements. If you want to reset the instance so that you
1567     * can later zip a completely different set of files, you must use
1568     * the reset method.</p>
1569     *
1570     * @see #reset
1571     */

1572    protected void cleanUp() {
1573        addedDirs.clear();
1574        addedFiles.removeAllElements();
1575        entries.clear();
1576        addingNewFiles = false;
1577        doUpdate = savedDoUpdate;
1578        Enumeration JavaDoc e = filesetsFromGroupfilesets.elements();
1579        while (e.hasMoreElements()) {
1580            ZipFileSet zf = (ZipFileSet) e.nextElement();
1581            resources.removeElement(zf);
1582        }
1583        filesetsFromGroupfilesets.removeAllElements();
1584    }
1585
1586    /**
1587     * Makes this instance reset all attributes to their default
1588     * values and forget all children.
1589     *
1590     * @since Ant 1.5
1591     *
1592     * @see #cleanUp
1593     */

1594    public void reset() {
1595        resources.removeAllElements();
1596        zipFile = null;
1597        baseDir = null;
1598        groupfilesets.removeAllElements();
1599        duplicate = "add";
1600        archiveType = "zip";
1601        doCompress = true;
1602        emptyBehavior = "skip";
1603        doUpdate = false;
1604        doFilesonly = false;
1605        encoding = null;
1606    }
1607
1608    /**
1609     * Check is the resource arrays are empty.
1610     * @param r the arrays to check
1611     * @return true if all individual arrays are empty
1612     *
1613     * @since Ant 1.5.2
1614     */

1615    protected static final boolean isEmpty(Resource[][] r) {
1616        for (int i = 0; i < r.length; i++) {
1617            if (r[i].length > 0) {
1618                return false;
1619            }
1620        }
1621        return true;
1622    }
1623
1624    /**
1625     * Drops all non-file resources from the given array.
1626     * @param orig the resources to filter
1627     * @return the filters resources
1628     * @since Ant 1.6
1629     */

1630    protected Resource[] selectFileResources(Resource[] orig) {
1631        if (orig.length == 0) {
1632            return orig;
1633        }
1634
1635        Vector JavaDoc v = new Vector JavaDoc(orig.length);
1636        for (int i = 0; i < orig.length; i++) {
1637            if (!orig[i].isDirectory()) {
1638                v.addElement(orig[i]);
1639            } else {
1640                log("Ignoring directory " + orig[i].getName()
1641                    + " as only files will be added.", Project.MSG_VERBOSE);
1642            }
1643        }
1644
1645        if (v.size() != orig.length) {
1646            Resource[] r = new Resource[v.size()];
1647            v.copyInto(r);
1648            return r;
1649        }
1650        return orig;
1651    }
1652
1653    /**
1654     * Possible behaviors when a duplicate file is added:
1655     * "add", "preserve" or "fail"
1656     */

1657    public static class Duplicate extends EnumeratedAttribute {
1658        /**
1659         * @see EnumeratedAttribute#getValues()
1660         */

1661        /** {@inheritDoc} */
1662        public String JavaDoc[] getValues() {
1663            return new String JavaDoc[] {"add", "preserve", "fail"};
1664        }
1665    }
1666
1667    /**
1668     * Holds the up-to-date status and the out-of-date resources of
1669     * the original archive.
1670     *
1671     * @since Ant 1.5.3
1672     */

1673    public static class ArchiveState {
1674        private boolean outOfDate;
1675        private Resource[][] resourcesToAdd;
1676
1677        ArchiveState(boolean state, Resource[][] r) {
1678            outOfDate = state;
1679            resourcesToAdd = r;
1680        }
1681
1682        /**
1683         * Return the outofdate status.
1684         * @return the outofdate status
1685         */

1686        public boolean isOutOfDate() {
1687            return outOfDate;
1688        }
1689
1690        /**
1691         * Get the resources to add.
1692         * @return the resources to add
1693         */

1694        public Resource[][] getResourcesToAdd() {
1695            return resourcesToAdd;
1696        }
1697        /**
1698         * find out if there are absolutely no resources to add
1699         * @since Ant 1.6.3
1700         * @return true if there are no resources to add
1701         */

1702        public boolean isWithoutAnyResources() {
1703            if (resourcesToAdd == null) {
1704                return true;
1705            }
1706            for (int counter = 0; counter < resourcesToAdd.length; counter++) {
1707                if (resourcesToAdd[counter] != null) {
1708                    if (resourcesToAdd[counter].length > 0) {
1709                        return false;
1710                    }
1711                }
1712            }
1713            return true;
1714        }
1715    }
1716}
1717
Popular Tags