source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/Zip.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 46.2 KB
Line 
1/*
2 * Copyright 2000-2005 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17package org.apache.tools.ant.taskdefs;
18
19import java.io.ByteArrayInputStream;
20import java.io.ByteArrayOutputStream;
21import java.io.File;
22import java.io.FileInputStream;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.OutputStream;
27import java.util.Enumeration;
28import java.util.Hashtable;
29import java.util.Stack;
30import java.util.Vector;
31import java.util.zip.CRC32;
32
33import org.apache.tools.ant.BuildException;
34import org.apache.tools.ant.DirectoryScanner;
35import org.apache.tools.ant.FileScanner;
36import org.apache.tools.ant.Project;
37import org.apache.tools.ant.types.EnumeratedAttribute;
38import org.apache.tools.ant.types.FileSet;
39import org.apache.tools.ant.types.PatternSet;
40import org.apache.tools.ant.types.Resource;
41import org.apache.tools.ant.types.ZipFileSet;
42import org.apache.tools.ant.types.ZipScanner;
43import org.apache.tools.ant.util.FileNameMapper;
44import org.apache.tools.ant.util.FileUtils;
45import org.apache.tools.ant.util.GlobPatternMapper;
46import org.apache.tools.ant.util.IdentityMapper;
47import org.apache.tools.ant.util.MergingMapper;
48import org.apache.tools.ant.util.ResourceUtils;
49import org.apache.tools.zip.ZipEntry;
50import org.apache.tools.zip.ZipExtraField;
51import org.apache.tools.zip.ZipFile;
52import org.apache.tools.zip.ZipOutputStream;
53
54/**
55 * Create a Zip file.
56 *
57 * @since Ant 1.1
58 *
59 * @ant.task category="packaging"
60 */
61public class Zip extends MatchingTask {
62
63 protected File zipFile;
64 // use to scan own archive
65 private ZipScanner zs;
66 private File baseDir;
67 protected Hashtable entries = new Hashtable();
68 private Vector groupfilesets = new Vector();
69 private Vector filesetsFromGroupfilesets = new Vector();
70 protected String duplicate = "add";
71 private boolean doCompress = true;
72 private boolean doUpdate = false;
73 // shadow of the above if the value is altered in execute
74 private boolean savedDoUpdate = false;
75 private boolean doFilesonly = false;
76 protected String archiveType = "zip";
77
78 // For directories:
79 private static final long EMPTY_CRC = new CRC32 ().getValue ();
80 protected String emptyBehavior = "skip";
81 private Vector filesets = new Vector ();
82 protected Hashtable addedDirs = new Hashtable();
83 private Vector addedFiles = new Vector();
84
85 protected boolean doubleFilePass = false;
86 protected boolean skipWriting = false;
87
88 private static FileUtils fileUtils = FileUtils.newFileUtils();
89
90 /**
91 * true when we are adding new files into the Zip file, as opposed
92 * to adding back the unchanged files
93 */
94 private boolean addingNewFiles = false;
95
96 /**
97 * Encoding to use for filenames, defaults to the platform's
98 * default encoding.
99 */
100 private String encoding;
101
102 /**
103 * Whether the original compression of entries coming from a ZIP
104 * archive should be kept (for example when updating an archive).
105 *
106 * @since Ant 1.6
107 */
108 private boolean keepCompression = false;
109
110 /**
111 * Whether the file modification times will be rounded up to the
112 * next even number of seconds.
113 *
114 * @since Ant 1.6.2
115 */
116 private boolean roundUp = true;
117
118 /**
119 * Comment for the archive.
120 * @since Ant 1.6.3
121 */
122 private String comment = "";
123
124 /**
125 * This is the name/location of where to
126 * create the .zip file.
127 *
128 * @deprecated Use setDestFile(File) instead.
129 * @ant.attribute ignore="true"
130 */
131 public void setZipfile(File zipFile) {
132 setDestFile(zipFile);
133 }
134
135 /**
136 * This is the name/location of where to
137 * create the file.
138 * @since Ant 1.5
139 * @deprecated Use setDestFile(File) instead
140 * @ant.attribute ignore="true"
141 */
142 public void setFile(File file) {
143 setDestFile(file);
144 }
145
146
147 /**
148 * The file to create; required.
149 * @since Ant 1.5
150 * @param destFile The new destination File
151 */
152 public void setDestFile(File destFile) {
153 this.zipFile = destFile;
154 }
155
156 /**
157 * The file to create.
158 * @since Ant 1.5.2
159 */
160 public File getDestFile() {
161 return zipFile;
162 }
163
164
165 /**
166 * Directory from which to archive files; optional.
167 */
168 public void setBasedir(File baseDir) {
169 this.baseDir = baseDir;
170 }
171
172 /**
173 * Whether we want to compress the files or only store them;
174 * optional, default=true;
175 */
176 public void setCompress(boolean c) {
177 doCompress = c;
178 }
179
180 /**
181 * Whether we want to compress the files or only store them;
182 *
183 * @since Ant 1.5.2
184 */
185 public boolean isCompress() {
186 return doCompress;
187 }
188
189 /**
190 * If true, emulate Sun's jar utility by not adding parent directories;
191 * optional, defaults to false.
192 */
193 public void setFilesonly(boolean f) {
194 doFilesonly = f;
195 }
196
197 /**
198 * If true, updates an existing file, otherwise overwrite
199 * any existing one; optional defaults to false.
200 */
201 public void setUpdate(boolean c) {
202 doUpdate = c;
203 savedDoUpdate = c;
204 }
205
206 /**
207 * Are we updating an existing archive?
208 */
209 public boolean isInUpdateMode() {
210 return doUpdate;
211 }
212
213 /**
214 * Adds a set of files.
215 */
216 public void addFileset(FileSet set) {
217 filesets.addElement(set);
218 }
219
220 /**
221 * Adds a set of files that can be
222 * read from an archive and be given a prefix/fullpath.
223 */
224 public void addZipfileset(ZipFileSet set) {
225 filesets.addElement(set);
226 }
227
228 /**
229 * Adds a group of zip files.
230 */
231 public void addZipGroupFileset(FileSet set) {
232 groupfilesets.addElement(set);
233 }
234
235 /**
236 * Sets behavior for when a duplicate file is about to be added -
237 * one of <code>keep</code>, <code>skip</code> or <code>overwrite</code>.
238 * Possible values are: <code>keep</code> (keep both
239 * of the files); <code>skip</code> (keep the first version
240 * of the file found); <code>overwrite</code> overwrite the file
241 * with the new file
242 * Default for zip tasks is <code>keep</code>
243 */
244 public void setDuplicate(Duplicate df) {
245 duplicate = df.getValue();
246 }
247
248 /**
249 * Possible behaviors when there are no matching files for the task:
250 * "fail", "skip", or "create".
251 */
252 public static class WhenEmpty extends EnumeratedAttribute {
253 public String[] getValues() {
254 return new String[] {"fail", "skip", "create"};
255 }
256 }
257
258 /**
259 * Sets behavior of the task when no files match.
260 * Possible values are: <code>fail</code> (throw an exception
261 * and halt the build); <code>skip</code> (do not create
262 * any archive, but issue a warning); <code>create</code>
263 * (make an archive with no entries).
264 * Default for zip tasks is <code>skip</code>;
265 * for jar tasks, <code>create</code>.
266 */
267 public void setWhenempty(WhenEmpty we) {
268 emptyBehavior = we.getValue();
269 }
270
271 /**
272 * Encoding to use for filenames, defaults to the platform's
273 * default encoding.
274 *
275 * <p>For a list of possible values see <a
276 * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.</p>
277 */
278 public void setEncoding(String encoding) {
279 this.encoding = encoding;
280 }
281
282 /**
283 * Encoding to use for filenames.
284 *
285 * @since Ant 1.5.2
286 */
287 public String getEncoding() {
288 return encoding;
289 }
290
291 /**
292 * Whether the original compression of entries coming from a ZIP
293 * archive should be kept (for example when updating an archive).
294 *
295 * @since Ant 1.6
296 */
297 public void setKeepCompression(boolean keep) {
298 keepCompression = keep;
299 }
300
301 /**
302 * Comment to use for archive.
303 *
304 * @param comment The content of the comment.
305 * @since Ant 1.6.3
306 */
307 public void setComment(String comment) {
308 this.comment = comment;
309 }
310
311 /**
312 * Comment of the archive
313 *
314 * @return Comment of the archive.
315 * @since Ant 1.6.3
316 */
317 public String getComment() {
318 return comment;
319 }
320
321 /**
322 * Whether the file modification times will be rounded up to the
323 * next even number of seconds.
324 *
325 * <p>Zip archives store file modification times with a
326 * granularity of two seconds, so the times will either be rounded
327 * up or down. If you round down, the archive will always seem
328 * out-of-date when you rerun the task, so the default is to round
329 * up. Rounding up may lead to a different type of problems like
330 * JSPs inside a web archive that seem to be slightly more recent
331 * than precompiled pages, rendering precompilation useless.</p>
332 *
333 * @since Ant 1.6.2
334 */
335 public void setRoundUp(boolean r) {
336 roundUp = r;
337 }
338
339 /**
340 * validate and build
341 */
342 public void execute() throws BuildException {
343
344 if (doubleFilePass) {
345 skipWriting = true;
346 executeMain();
347 skipWriting = false;
348 executeMain();
349 } else {
350 executeMain();
351 }
352 }
353
354 public void executeMain() throws BuildException {
355
356 if (baseDir == null && filesets.size() == 0
357 && groupfilesets.size() == 0 && "zip".equals(archiveType)) {
358 throw new BuildException("basedir attribute must be set, "
359 + "or at least "
360 + "one fileset must be given!");
361 }
362
363 if (zipFile == null) {
364 throw new BuildException("You must specify the "
365 + archiveType + " file to create!");
366 }
367
368 if (zipFile.exists() && !zipFile.isFile()) {
369 throw new BuildException(zipFile + " is not a file.");
370 }
371
372 if (zipFile.exists() && !zipFile.canWrite()) {
373 throw new BuildException(zipFile + " is read-only.");
374 }
375
376 // Renamed version of original file, if it exists
377 File renamedFile = null;
378 // Whether or not an actual update is required -
379 // we don't need to update if the original file doesn't exist
380
381 addingNewFiles = true;
382 if (doUpdate && !zipFile.exists()) {
383 doUpdate = false;
384 log("ignoring update attribute as " + archiveType
385 + " doesn't exist.", Project.MSG_DEBUG);
386 }
387
388 // Add the files found in groupfileset to fileset
389 for (int i = 0; i < groupfilesets.size(); i++) {
390
391 log("Processing groupfileset ", Project.MSG_VERBOSE);
392 FileSet fs = (FileSet) groupfilesets.elementAt(i);
393 FileScanner scanner = fs.getDirectoryScanner(getProject());
394 String[] files = scanner.getIncludedFiles();
395 File basedir = scanner.getBasedir();
396 for (int j = 0; j < files.length; j++) {
397
398 log("Adding file " + files[j] + " to fileset",
399 Project.MSG_VERBOSE);
400 ZipFileSet zf = new ZipFileSet();
401 zf.setProject(getProject());
402 zf.setSrc(new File(basedir, files[j]));
403 filesets.addElement(zf);
404 filesetsFromGroupfilesets.addElement(zf);
405 }
406 }
407
408 // collect filesets to pass them to getResourcesToAdd
409 Vector vfss = new Vector();
410 if (baseDir != null) {
411 FileSet fs = (FileSet) getImplicitFileSet().clone();
412 fs.setDir(baseDir);
413 vfss.addElement(fs);
414 }
415 for (int i = 0; i < filesets.size(); i++) {
416 FileSet fs = (FileSet) filesets.elementAt(i);
417 vfss.addElement(fs);
418 }
419
420 FileSet[] fss = new FileSet[vfss.size()];
421 vfss.copyInto(fss);
422 boolean success = false;
423 try {
424 // can also handle empty archives
425 ArchiveState state = getResourcesToAdd(fss, zipFile, false);
426
427 // quick exit if the target is up to date
428 if (!state.isOutOfDate()) {
429 return;
430 }
431
432 if (!zipFile.exists() && state.isWithoutAnyResources()) {
433 createEmptyZip(zipFile);
434 return;
435 }
436 Resource[][] addThem = state.getResourcesToAdd();
437
438 if (doUpdate) {
439 renamedFile =
440 fileUtils.createTempFile("zip", ".tmp",
441 fileUtils.getParentFile(zipFile));
442 renamedFile.deleteOnExit();
443
444 try {
445 fileUtils.rename(zipFile, renamedFile);
446 } catch (SecurityException e) {
447 throw new BuildException(
448 "Not allowed to rename old file ("
449 + zipFile.getAbsolutePath()
450 + ") to temporary file");
451 } catch (IOException e) {
452 throw new BuildException(
453 "Unable to rename old file ("
454 + zipFile.getAbsolutePath()
455 + ") to temporary file");
456 }
457 }
458
459 String action = doUpdate ? "Updating " : "Building ";
460
461 log(action + archiveType + ": " + zipFile.getAbsolutePath());
462
463 ZipOutputStream zOut = null;
464 try {
465
466 if (!skipWriting) {
467 zOut = new ZipOutputStream(zipFile);
468
469 zOut.setEncoding(encoding);
470 if (doCompress) {
471 zOut.setMethod(ZipOutputStream.DEFLATED);
472 } else {
473 zOut.setMethod(ZipOutputStream.STORED);
474 }
475 }
476 initZipOutputStream(zOut);
477
478 // Add the explicit filesets to the archive.
479 for (int i = 0; i < fss.length; i++) {
480 if (addThem[i].length != 0) {
481 addResources(fss[i], addThem[i], zOut);
482 }
483 }
484
485 if (doUpdate) {
486 addingNewFiles = false;
487 ZipFileSet oldFiles = new ZipFileSet();
488 oldFiles.setProject(getProject());
489 oldFiles.setSrc(renamedFile);
490 oldFiles.setDefaultexcludes(false);
491
492 for (int i = 0; i < addedFiles.size(); i++) {
493 PatternSet.NameEntry ne = oldFiles.createExclude();
494 ne.setName((String) addedFiles.elementAt(i));
495 }
496 DirectoryScanner ds =
497 oldFiles.getDirectoryScanner(getProject());
498 ((ZipScanner) ds).setEncoding(encoding);
499
500 String[] f = ds.getIncludedFiles();
501 Resource[] r = new Resource[f.length];
502 for (int i = 0; i < f.length; i++) {
503 r[i] = ds.getResource(f[i]);
504 }
505
506 if (!doFilesonly) {
507 String[] d = ds.getIncludedDirectories();
508 Resource[] dr = new Resource[d.length];
509 for (int i = 0; i < d.length; i++) {
510 dr[i] = ds.getResource(d[i]);
511 }
512 Resource[] tmp = r;
513 r = new Resource[tmp.length + dr.length];
514 System.arraycopy(dr, 0, r, 0, dr.length);
515 System.arraycopy(tmp, 0, r, dr.length, tmp.length);
516 }
517 addResources(oldFiles, r, zOut);
518 }
519 if (zOut != null) {
520 zOut.setComment(comment);
521 }
522 finalizeZipOutputStream(zOut);
523
524 // If we've been successful on an update, delete the
525 // temporary file
526 if (doUpdate) {
527 if (!renamedFile.delete()) {
528 log ("Warning: unable to delete temporary file "
529 + renamedFile.getName(), Project.MSG_WARN);
530 }
531 }
532 success = true;
533 } finally {
534 // Close the output stream.
535 try {
536 if (zOut != null) {
537 zOut.close();
538 }
539 } catch (IOException ex) {
540 // If we're in this finally clause because of an
541 // exception, we don't really care if there's an
542 // exception when closing the stream. E.g. if it
543 // throws "ZIP file must have at least one entry",
544 // because an exception happened before we added
545 // any files, then we must swallow this
546 // exception. Otherwise, the error that's reported
547 // will be the close() error, which is not the
548 // real cause of the problem.
549 if (success) {
550 throw ex;
551 }
552 }
553 }
554 } catch (IOException ioe) {
555 String msg = "Problem creating " + archiveType + ": "
556 + ioe.getMessage();
557
558 // delete a bogus ZIP file (but only if it's not the original one)
559 if ((!doUpdate || renamedFile != null) && !zipFile.delete()) {
560 msg += " (and the archive is probably corrupt but I could not "
561 + "delete it)";
562 }
563
564 if (doUpdate && renamedFile != null) {
565 try {
566 fileUtils.rename(renamedFile, zipFile);
567 } catch (IOException e) {
568 msg += " (and I couldn't rename the temporary file "
569 + renamedFile.getName() + " back)";
570 }
571 }
572
573 throw new BuildException(msg, ioe, getLocation());
574 } finally {
575 cleanUp();
576 }
577 }
578
579 /**
580 * Indicates if the task is adding new files into the archive as opposed to
581 * copying back unchanged files from the backup copy
582 */
583 protected final boolean isAddingNewFiles() {
584 return addingNewFiles;
585 }
586
587 /**
588 * Add the given resources.
589 *
590 * @param fileset may give additional information like fullpath or
591 * permissions.
592 * @param resources the resources to add
593 * @param zOut the stream to write to
594 *
595 * @since Ant 1.5.2
596 */
597 protected final void addResources(FileSet fileset, Resource[] resources,
598 ZipOutputStream zOut)
599 throws IOException {
600
601 String prefix = "";
602 String fullpath = "";
603 int dirMode = ZipFileSet.DEFAULT_DIR_MODE;
604 int fileMode = ZipFileSet.DEFAULT_FILE_MODE;
605
606 ZipFileSet zfs = null;
607 if (fileset instanceof ZipFileSet) {
608 zfs = (ZipFileSet) fileset;
609 prefix = zfs.getPrefix(getProject());
610 fullpath = zfs.getFullpath(getProject());
611 dirMode = zfs.getDirMode(getProject());
612 fileMode = zfs.getFileMode(getProject());
613 }
614
615 if (prefix.length() > 0 && fullpath.length() > 0) {
616 throw new BuildException("Both prefix and fullpath attributes must"
617 + " not be set on the same fileset.");
618 }
619
620 if (resources.length != 1 && fullpath.length() > 0) {
621 throw new BuildException("fullpath attribute may only be specified"
622 + " for filesets that specify a single"
623 + " file.");
624 }
625
626 if (prefix.length() > 0) {
627 if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
628 prefix += "/";
629 }
630 addParentDirs(null, prefix, zOut, "", dirMode);
631 }
632
633 ZipFile zf = null;
634 try {
635 boolean dealingWithFiles = false;
636 File base = null;
637
638 if (zfs == null || zfs.getSrc(getProject()) == null) {
639 dealingWithFiles = true;
640 base = fileset.getDir(getProject());
641 } else {
642 zf = new ZipFile(zfs.getSrc(getProject()), encoding);
643 }
644
645 for (int i = 0; i < resources.length; i++) {
646 String name = null;
647 if (fullpath.length() > 0) {
648 name = fullpath;
649 } else {
650 name = resources[i].getName();
651 }
652 name = name.replace(File.separatorChar, '/');
653
654 if ("".equals(name)) {
655 continue;
656 }
657 if (resources[i].isDirectory() && !name.endsWith("/")) {
658 name = name + "/";
659 }
660
661 if (!doFilesonly && !dealingWithFiles
662 && resources[i].isDirectory()
663 && !zfs.hasDirModeBeenSet()) {
664 int nextToLastSlash = name.lastIndexOf("/",
665 name.length() - 2);
666 if (nextToLastSlash != -1) {
667 addParentDirs(base, name.substring(0,
668 nextToLastSlash + 1),
669 zOut, prefix, dirMode);
670 }
671 ZipEntry ze = zf.getEntry(resources[i].getName());
672 addParentDirs(base, name, zOut, prefix, ze.getUnixMode());
673
674 } else {
675 addParentDirs(base, name, zOut, prefix, dirMode);
676 }
677
678 if (!resources[i].isDirectory() && dealingWithFiles) {
679 File f = fileUtils.resolveFile(base,
680 resources[i].getName());
681 zipFile(f, zOut, prefix + name, fileMode);
682 } else if (!resources[i].isDirectory()) {
683 ZipEntry ze = zf.getEntry(resources[i].getName());
684
685 if (ze != null) {
686 boolean oldCompress = doCompress;
687 if (keepCompression) {
688 doCompress = (ze.getMethod() == ZipEntry.DEFLATED);
689 }
690 try {
691 zipFile(zf.getInputStream(ze), zOut, prefix + name,
692 ze.getTime(), zfs.getSrc(getProject()),
693 zfs.hasFileModeBeenSet() ? fileMode
694 : ze.getUnixMode());
695 } finally {
696 doCompress = oldCompress;
697 }
698 }
699 }
700 }
701 } finally {
702 if (zf != null) {
703 zf.close();
704 }
705 }
706 }
707
708 /**
709 * method for subclasses to override
710 */
711 protected void initZipOutputStream(ZipOutputStream zOut)
712 throws IOException, BuildException {
713 }
714
715 /**
716 * method for subclasses to override
717 */
718 protected void finalizeZipOutputStream(ZipOutputStream zOut)
719 throws IOException, BuildException {
720 }
721
722 /**
723 * Create an empty zip file
724 *
725 * @return true for historic reasons
726 */
727 protected boolean createEmptyZip(File zipFile) throws BuildException {
728 // In this case using java.util.zip will not work
729 // because it does not permit a zero-entry archive.
730 // Must create it manually.
731 log("Note: creating empty " + archiveType + " archive " + zipFile,
732 Project.MSG_INFO);
733 OutputStream os = null;
734 try {
735 os = new FileOutputStream(zipFile);
736 // Cf. PKZIP specification.
737 byte[] empty = new byte[22];
738 empty[0] = 80; // P
739 empty[1] = 75; // K
740 empty[2] = 5;
741 empty[3] = 6;
742 // remainder zeros
743 os.write(empty);
744 } catch (IOException ioe) {
745 throw new BuildException("Could not create empty ZIP archive "
746 + "(" + ioe.getMessage() + ")", ioe,
747 getLocation());
748 } finally {
749 if (os != null) {
750 try {
751 os.close();
752 } catch (IOException e) {
753 //ignore
754 }
755 }
756 }
757 return true;
758 }
759
760 /**
761 * @since Ant 1.5.2
762 */
763 private synchronized ZipScanner getZipScanner() {
764 if (zs == null) {
765 zs = new ZipScanner();
766 zs.setEncoding(encoding);
767 zs.setSrc(zipFile);
768 }
769 return zs;
770 }
771
772 /**
773 * Collect the resources that are newer than the corresponding
774 * entries (or missing) in the original archive.
775 *
776 * <p>If we are going to recreate the archive instead of updating
777 * it, all resources should be considered as new, if a single one
778 * is. Because of this, subclasses overriding this method must
779 * call <code>super.getResourcesToAdd</code> and indicate with the
780 * third arg if they already know that the archive is
781 * out-of-date.</p>
782 *
783 * @param filesets The filesets to grab resources from
784 * @param zipFile intended archive file (may or may not exist)
785 * @param needsUpdate whether we already know that the archive is
786 * out-of-date. Subclasses overriding this method are supposed to
787 * set this value correctly in their call to
788 * super.getResourcesToAdd.
789 * @return an array of resources to add for each fileset passed in as well
790 * as a flag that indicates whether the archive is uptodate.
791 *
792 * @exception BuildException if it likes
793 */
794 protected ArchiveState getResourcesToAdd(FileSet[] filesets,
795 File zipFile,
796 boolean needsUpdate)
797 throws BuildException {
798
799 Resource[][] initialResources = grabResources(filesets);
800 if (isEmpty(initialResources)) {
801 if (needsUpdate && doUpdate) {
802 /*
803 * This is a rather hairy case.
804 *
805 * One of our subclasses knows that we need to update the
806 * archive, but at the same time, there are no resources
807 * known to us that would need to be added. Only the
808 * subclass seems to know what's going on.
809 *
810 * This happens if <jar> detects that the manifest has changed,
811 * for example. The manifest is not part of any resources
812 * because of our support for inline <manifest>s.
813 *
814 * If we invoke createEmptyZip like Ant 1.5.2 did,
815 * we'll loose all stuff that has been in the original
816 * archive (bugzilla report 17780).
817 */
818 return new ArchiveState(true, initialResources);
819 }
820
821 if (emptyBehavior.equals("skip")) {
822 if (doUpdate) {
823 log(archiveType + " archive " + zipFile
824 + " not updated because no new files were included.",
825 Project.MSG_VERBOSE);
826 } else {
827 log("Warning: skipping " + archiveType + " archive "
828 + zipFile + " because no files were included.",
829 Project.MSG_WARN);
830 }
831 } else if (emptyBehavior.equals("fail")) {
832 throw new BuildException("Cannot create " + archiveType
833 + " archive " + zipFile
834 + ": no files were included.",
835 getLocation());
836 } else {
837 // Create.
838 if (!zipFile.exists()) {
839 needsUpdate = true;
840 }
841 }
842 return new ArchiveState(needsUpdate, initialResources);
843 }
844
845 // initialResources is not empty
846
847 if (!zipFile.exists()) {
848 return new ArchiveState(true, initialResources);
849 }
850
851 if (needsUpdate && !doUpdate) {
852 // we are recreating the archive, need all resources
853 return new ArchiveState(true, initialResources);
854 }
855
856 Resource[][] newerResources = new Resource[filesets.length][];
857
858 for (int i = 0; i < filesets.length; i++) {
859 if (!(fileset instanceof ZipFileSet)
860 || ((ZipFileSet) fileset).getSrc(getProject()) == null) {
861 File base = filesets[i].getDir(getProject());
862
863 for (int j = 0; j < initialResources[i].length; j++) {
864 File resourceAsFile =
865 fileUtils.resolveFile(base,
866 initialResources[i][j].getName());
867 if (resourceAsFile.equals(zipFile)) {
868 throw new BuildException("A zip file cannot include "
869 + "itself", getLocation());
870 }
871 }
872 }
873 }
874
875 for (int i = 0; i < filesets.length; i++) {
876 if (initialResources[i].length == 0) {
877 newerResources[i] = new Resource[] {};
878 continue;
879 }
880
881 FileNameMapper myMapper = new IdentityMapper();
882 if (filesets[i] instanceof ZipFileSet) {
883 ZipFileSet zfs = (ZipFileSet) filesets[i];
884 if (zfs.getFullpath(getProject()) != null
885 && !zfs.getFullpath(getProject()).equals("")) {
886 // in this case all files from origin map to
887 // the fullPath attribute of the zipfileset at
888 // destination
889 MergingMapper fm = new MergingMapper();
890 fm.setTo(zfs.getFullpath(getProject()));
891 myMapper = fm;
892
893 } else if (zfs.getPrefix(getProject()) != null
894 && !zfs.getPrefix(getProject()).equals("")) {
895 GlobPatternMapper gm = new GlobPatternMapper();
896 gm.setFrom("*");
897 String prefix = zfs.getPrefix(getProject());
898 if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
899 prefix += "/";
900 }
901 gm.setTo(prefix + "*");
902 myMapper = gm;
903 }
904 }
905
906 Resource[] resources = initialResources[i];
907 if (doFilesonly) {
908 resources = selectFileResources(resources);
909 }
910
911 newerResources[i] =
912 ResourceUtils.selectOutOfDateSources(this,
913 resources,
914 myMapper,
915 getZipScanner());
916 needsUpdate = needsUpdate || (newerResources[i].length > 0);
917
918 if (needsUpdate && !doUpdate) {
919 // we will return initialResources anyway, no reason
920 // to scan further.
921 break;
922 }
923 }
924
925 if (needsUpdate && !doUpdate) {
926 // we are recreating the archive, need all resources
927 return new ArchiveState(true, initialResources);
928 }
929
930 return new ArchiveState(needsUpdate, newerResources);
931 }
932
933 /**
934 * Fetch all included and not excluded resources from the sets.
935 *
936 * <p>Included directories will precede included files.</p>
937 *
938 * @since Ant 1.5.2
939 */
940 protected Resource[][] grabResources(FileSet[] filesets) {
941 Resource[][] result = new Resource[filesets.length][];
942 for (int i = 0; i < filesets.length; i++) {
943 boolean skipEmptyNames = true;
944 if (filesets[i] instanceof ZipFileSet) {
945 ZipFileSet zfs = (ZipFileSet) filesets[i];
946 skipEmptyNames = zfs.getPrefix(getProject()).equals("")
947 && zfs.getFullpath(getProject()).equals("");
948 }
949 DirectoryScanner rs =
950 filesets[i].getDirectoryScanner(getProject());
951 if (rs instanceof ZipScanner) {
952 ((ZipScanner) rs).setEncoding(encoding);
953 }
954 Vector resources = new Vector();
955 String[] directories = rs.getIncludedDirectories();
956 for (int j = 0; j < directories.length; j++) {
957 if (!"".equals(directories[j]) || !skipEmptyNames) {
958 resources.addElement(rs.getResource(directories[j]));
959 }
960 }
961 String[] files = rs.getIncludedFiles();
962 for (int j = 0; j < files.length; j++) {
963 if (!"".equals(files[j]) || !skipEmptyNames) {
964 resources.addElement(rs.getResource(files[j]));
965 }
966 }
967
968 result[i] = new Resource[resources.size()];
969 resources.copyInto(result[i]);
970 }
971 return result;
972 }
973
974 /**
975 * @since Ant 1.5.2
976 */
977 protected void zipDir(File dir, ZipOutputStream zOut, String vPath,
978 int mode)
979 throws IOException {
980 zipDir(dir, zOut, vPath, mode, null);
981 }
982
983 /**
984 * Add a directory to the zip stream.
985 * @param dir the directort to add to the archive
986 * @param zOut the stream to write to
987 * @param vPath the name this entry shall have in the archive
988 * @param mode the Unix permissions to set.
989 * @param extra ZipExtraFields to add
990 * @throws IOException on error
991 * @since Ant 1.6.3
992 */
993 protected void zipDir(File dir, ZipOutputStream zOut, String vPath,
994 int mode, ZipExtraField[] extra)
995 throws IOException {
996 if (addedDirs.get(vPath) != null) {
997 // don't add directories we've already added.
998 // no warning if we try, it is harmless in and of itself
999 return;
1000 }
1001
1002 log("adding directory " + vPath, Project.MSG_VERBOSE);
1003 addedDirs.put(vPath, vPath);
1004
1005 if (!skipWriting) {
1006 ZipEntry ze = new ZipEntry (vPath);
1007 if (dir != null && dir.exists()) {
1008 // ZIPs store time with a granularity of 2 seconds, round up
1009 ze.setTime(dir.lastModified() + (roundUp ? 1999 : 0));
1010 } else {
1011 // ZIPs store time with a granularity of 2 seconds, round up
1012 ze.setTime(System.currentTimeMillis() + (roundUp ? 1999 : 0));
1013 }
1014 ze.setSize (0);
1015 ze.setMethod (ZipEntry.STORED);
1016 // This is faintly ridiculous:
1017 ze.setCrc (EMPTY_CRC);
1018 ze.setUnixMode(mode);
1019
1020 if (extra != null) {
1021 ze.setExtraFields(extra);
1022 }
1023
1024 zOut.putNextEntry(ze);
1025 }
1026 }
1027
1028 /**
1029 * Adds a new entry to the archive, takes care of duplicates as well.
1030 *
1031 * @param in the stream to read data for the entry from.
1032 * @param zOut the stream to write to.
1033 * @param vPath the name this entry shall have in the archive.
1034 * @param lastModified last modification time for the entry.
1035 * @param fromArchive the original archive we are copying this
1036 * entry from, will be null if we are not copying from an archive.
1037 * @param mode the Unix permissions to set.
1038 *
1039 * @since Ant 1.5.2
1040 */
1041 protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,
1042 long lastModified, File fromArchive, int mode)
1043 throws IOException {
1044 if (entries.contains(vPath)) {
1045
1046 if (duplicate.equals("preserve")) {
1047 log(vPath + " already added, skipping", Project.MSG_INFO);
1048 return;
1049 } else if (duplicate.equals("fail")) {
1050 throw new BuildException("Duplicate file " + vPath
1051 + " was found and the duplicate "
1052 + "attribute is 'fail'.");
1053 } else {
1054 // duplicate equal to add, so we continue
1055 log("duplicate file " + vPath
1056 + " found, adding.", Project.MSG_VERBOSE);
1057 }
1058 } else {
1059 log("adding entry " + vPath, Project.MSG_VERBOSE);
1060 }
1061
1062 entries.put(vPath, vPath);
1063
1064 if (!skipWriting) {
1065 ZipEntry ze = new ZipEntry(vPath);
1066 ze.setTime(lastModified);
1067 ze.setMethod(doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED);
1068
1069 /*
1070 * ZipOutputStream.putNextEntry expects the ZipEntry to
1071 * know its size and the CRC sum before you start writing
1072 * the data when using STORED mode - unless it is seekable.
1073 *
1074 * This forces us to process the data twice.
1075 */
1076 if (!zOut.isSeekable() && !doCompress) {
1077 long size = 0;
1078 CRC32 cal = new CRC32();
1079 if (!in.markSupported()) {
1080 // Store data into a byte[]
1081 ByteArrayOutputStream bos = new ByteArrayOutputStream();
1082
1083 byte[] buffer = new byte[8 * 1024];
1084 int count = 0;
1085 do {
1086 size += count;
1087 cal.update(buffer, 0, count);
1088 bos.write(buffer, 0, count);
1089 count = in.read(buffer, 0, buffer.length);
1090 } while (count != -1);
1091 in = new ByteArrayInputStream(bos.toByteArray());
1092
1093 } else {
1094 in.mark(Integer.MAX_VALUE);
1095 byte[] buffer = new byte[8 * 1024];
1096 int count = 0;
1097 do {
1098 size += count;
1099 cal.update(buffer, 0, count);
1100 count = in.read(buffer, 0, buffer.length);
1101 } while (count != -1);
1102 in.reset();
1103 }
1104 ze.setSize(size);
1105 ze.setCrc(cal.getValue());
1106 }
1107
1108 ze.setUnixMode(mode);
1109 zOut.putNextEntry(ze);
1110
1111 byte[] buffer = new byte[8 * 1024];
1112 int count = 0;
1113 do {
1114 if (count != 0) {
1115 zOut.write(buffer, 0, count);
1116 }
1117 count = in.read(buffer, 0, buffer.length);
1118 } while (count != -1);
1119 }
1120 addedFiles.addElement(vPath);
1121 }
1122
1123 /**
1124 * Method that gets called when adding from java.io.File instances.
1125 *
1126 * <p>This implementation delegates to the six-arg version.</p>
1127 *
1128 * @param file the file to add to the archive
1129 * @param zOut the stream to write to
1130 * @param vPath the name this entry shall have in the archive
1131 * @param mode the Unix permissions to set.
1132 *
1133 * @since Ant 1.5.2
1134 */
1135 protected void zipFile(File file, ZipOutputStream zOut, String vPath,
1136 int mode)
1137 throws IOException {
1138 if (file.equals(zipFile)) {
1139 throw new BuildException("A zip file cannot include itself",
1140 getLocation());
1141 }
1142
1143 FileInputStream fIn = new FileInputStream(file);
1144 try {
1145 // ZIPs store time with a granularity of 2 seconds, round up
1146 zipFile(fIn, zOut, vPath,
1147 file.lastModified() + (roundUp ? 1999 : 0),
1148 null, mode);
1149 } finally {
1150 fIn.close();
1151 }
1152 }
1153
1154 /**
1155 * Ensure all parent dirs of a given entry have been added.
1156 *
1157 * @since Ant 1.5.2
1158 */
1159 protected final void addParentDirs(File baseDir, String entry,
1160 ZipOutputStream zOut, String prefix,
1161 int dirMode)
1162 throws IOException {
1163 if (!doFilesonly) {
1164 Stack directories = new Stack();
1165 int slashPos = entry.length();
1166
1167 while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) {
1168 String dir = entry.substring(0, slashPos + 1);
1169 if (addedDirs.get(prefix + dir) != null) {
1170 break;
1171 }
1172 directories.push(dir);
1173 }
1174
1175 while (!directories.isEmpty()) {
1176 String dir = (String) directories.pop();
1177 File f = null;
1178 if (baseDir != null) {
1179 f = new File(baseDir, dir);
1180 } else {
1181 f = new File(dir);
1182 }
1183 zipDir(f, zOut, prefix + dir, dirMode);
1184 }
1185 }
1186 }
1187
1188 /**
1189 * Do any clean up necessary to allow this instance to be used again.
1190 *
1191 * <p>When we get here, the Zip file has been closed and all we
1192 * need to do is to reset some globals.</p>
1193 *
1194 * <p>This method will only reset globals that have been changed
1195 * during execute(), it will not alter the attributes or nested
1196 * child elements. If you want to reset the instance so that you
1197 * can later zip a completely different set of files, you must use
1198 * the reset method.</p>
1199 *
1200 * @see #reset
1201 */
1202 protected void cleanUp() {
1203 addedDirs.clear();
1204 addedFiles.removeAllElements();
1205 entries.clear();
1206 addingNewFiles = false;
1207 doUpdate = savedDoUpdate;
1208 Enumeration e = filesetsFromGroupfilesets.elements();
1209 while (e.hasMoreElements()) {
1210 ZipFileSet zf = (ZipFileSet) e.nextElement();
1211 filesets.removeElement(zf);
1212 }
1213 filesetsFromGroupfilesets.removeAllElements();
1214 }
1215
1216 /**
1217 * Makes this instance reset all attributes to their default
1218 * values and forget all children.
1219 *
1220 * @since Ant 1.5
1221 *
1222 * @see #cleanUp
1223 */
1224 public void reset() {
1225 filesets.removeAllElements();
1226 zipFile = null;
1227 baseDir = null;
1228 groupfilesets.removeAllElements();
1229 duplicate = "add";
1230 archiveType = "zip";
1231 doCompress = true;
1232 emptyBehavior = "skip";
1233 doUpdate = false;
1234 doFilesonly = false;
1235 encoding = null;
1236 }
1237
1238 /**
1239 * @return true if all individual arrays are empty
1240 *
1241 * @since Ant 1.5.2
1242 */
1243 protected static final boolean isEmpty(Resource[][] r) {
1244 for (int i = 0; i < r.length; i++) {
1245 if (r[i].length > 0) {
1246 return false;
1247 }
1248 }
1249 return true;
1250 }
1251
1252 /**
1253 * Drops all non-file resources from the given array.
1254 *
1255 * @since Ant 1.6
1256 */
1257 protected Resource[] selectFileResources(Resource[] orig) {
1258 if (orig.length == 0) {
1259 return orig;
1260 }
1261
1262 Vector v = new Vector(orig.length);
1263 for (int i = 0; i < orig.length; i++) {
1264 if (!orig[i].isDirectory()) {
1265 v.addElement(orig[i]);
1266 } else {
1267 log("Ignoring directory " + orig[i].getName()
1268 + " as only files will be added.", Project.MSG_VERBOSE);
1269 }
1270 }
1271
1272 if (v.size() != orig.length) {
1273 Resource[] r = new Resource[v.size()];
1274 v.copyInto(r);
1275 return r;
1276 }
1277 return orig;
1278 }
1279
1280 /**
1281 * Possible behaviors when a duplicate file is added:
1282 * "add", "preserve" or "fail"
1283 */
1284 public static class Duplicate extends EnumeratedAttribute {
1285 public String[] getValues() {
1286 return new String[] {"add", "preserve", "fail"};
1287 }
1288 }
1289
1290 /**
1291 * Holds the up-to-date status and the out-of-date resources of
1292 * the original archive.
1293 *
1294 * @since Ant 1.5.3
1295 */
1296 public static class ArchiveState {
1297 private boolean outOfDate;
1298 private Resource[][] resourcesToAdd;
1299
1300 ArchiveState(boolean state, Resource[][] r) {
1301 outOfDate = state;
1302 resourcesToAdd = r;
1303 }
1304
1305 public boolean isOutOfDate() {
1306 return outOfDate;
1307 }
1308
1309 public Resource[][] getResourcesToAdd() {
1310 return resourcesToAdd;
1311 }
1312 /**
1313 * find out if there are absolutely no resources to add
1314 * @since Ant 1.6.3
1315 * @return true if there are no resources to add
1316 */
1317 public boolean isWithoutAnyResources() {
1318 if (resourcesToAdd == null) {
1319 return true;
1320 }
1321 for (int counter = 0; counter < resourcesToAdd.length; counter++) {
1322 if (resourcesToAdd[counter] != null) {
1323 if (resourcesToAdd[counter].length > 0) {
1324 return false;
1325 }
1326 }
1327 }
1328 return true;
1329 }
1330 }
1331}
Note: See TracBrowser for help on using the repository browser.