blob: cf1a9076115190856555c018de2f4ae11635537b [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import java.util.zip.CRC32;
import java.util.zip.ZipInputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.FileScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.PatternSet;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.types.ZipScanner;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.SourceFileScanner;
import org.apache.tools.ant.util.MergingMapper;
import org.apache.tools.zip.ZipOutputStream;
import org.apache.tools.zip.ZipEntry;
/**
* Create a Zip file.
*
* @author James Davidson <a href="mailto:duncan@x180.com">duncan@x180.com</a>
* @author Jon S. Stevens <a href="mailto:jon@clearink.com">jon@clearink.com</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
*
* @since Ant 1.1
*
* @ant.task category="packaging"
*/
public class Zip extends MatchingTask {
protected File zipFile;
private File baseDir;
protected Hashtable entries = new Hashtable();
private Vector groupfilesets = new Vector();
private Vector filesetsFromGroupfilesets = new Vector();
protected String duplicate = "add";
private boolean doCompress = true;
private boolean doUpdate = false;
// shadow of the above if the value is altered in execute
private boolean savedDoUpdate = false;
private boolean doFilesonly = false;
protected String archiveType = "zip";
// For directories:
private static final long EMPTY_CRC = new CRC32 ().getValue ();
protected String emptyBehavior = "skip";
private Vector filesets = new Vector ();
protected Hashtable addedDirs = new Hashtable();
private Vector addedFiles = new Vector();
/**
* true when we are adding new files into the Zip file, as opposed
* to adding back the unchanged files
*/
private boolean addingNewFiles = false;
/**
* Encoding to use for filenames, defaults to the platform's
* default encoding.
*/
private String encoding;
/**
* This is the name/location of where to
* create the .zip file.
*
* @deprecated Use setDestFile(File) instead.
* @ant.attribute ignore="true"
*/
public void setZipfile(File zipFile) {
setDestFile(zipFile);
}
/**
* This is the name/location of where to
* create the file.
* @since Ant 1.5
* @deprecated Use setDestFile(File) instead
* @ant.attribute ignore="true"
*/
public void setFile(File file) {
setDestFile(file);
}
/**
* The file to create; required.
* @since Ant 1.5
* @param destFile The new destination File
*/
public void setDestFile(File destFile) {
this.zipFile = destFile;
}
/**
* Directory from which to archive files; optional.
*/
public void setBasedir(File baseDir) {
this.baseDir = baseDir;
}
/**
* Whether we want to compress the files or only store them;
* optional, default=true;
*/
public void setCompress(boolean c) {
doCompress = c;
}
/**
* If true, emulate Sun's jar utility by not adding parent directories;
* optional, defaults to false.
*/
public void setFilesonly(boolean f) {
doFilesonly = f;
}
/**
* If true, updates an existing file, otherwise overwrite
* any existing one; optional defaults to false.
*/
public void setUpdate(boolean c) {
doUpdate = c;
savedDoUpdate = c;
}
/**
* Are we updating an existing archive?
*/
public boolean isInUpdateMode() {
return doUpdate;
}
/**
* Adds a set of files.
*/
public void addFileset(FileSet set) {
filesets.addElement(set);
}
/**
* Adds a set of files that can be
* read from an archive and be given a prefix/fullpath.
*/
public void addZipfileset(ZipFileSet set) {
filesets.addElement(set);
}
/**
* Adds a group of zip files.
*/
public void addZipGroupFileset(FileSet set) {
groupfilesets.addElement(set);
}
/**
* Sets behavior for when a duplicate file is about to be added -
* one of <code>keep</code>, <code>skip</code> or <code>overwrite</code>.
* Possible values are: <code>keep</code> (keep both
* of the files); <code>skip</code> (keep the first version
* of the file found); <code>overwrite</code> overwrite the file
* with the new file
* Default for zip tasks is <code>keep</code>
*/
public void setDuplicate(Duplicate df) {
duplicate = df.getValue();
}
/**
* Possible behaviors when there are no matching files for the task:
* "fail", "skip", or "create".
*/
public static class WhenEmpty extends EnumeratedAttribute {
public String[] getValues() {
return new String[] {"fail", "skip", "create"};
}
}
/**
* Sets behavior of the task when no files match.
* Possible values are: <code>fail</code> (throw an exception
* and halt the build); <code>skip</code> (do not create
* any archive, but issue a warning); <code>create</code>
* (make an archive with no entries).
* Default for zip tasks is <code>skip</code>;
* for jar tasks, <code>create</code>.
*/
public void setWhenempty(WhenEmpty we) {
emptyBehavior = we.getValue();
}
/**
* Encoding to use for filenames, defaults to the platform's
* default encoding.
*
* <p>For a list of possible values see <a
* 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>
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* validate and build
*/
public void execute() throws BuildException {
if (baseDir == null && filesets.size() == 0
&& groupfilesets.size() == 0 && "zip".equals(archiveType)) {
throw new BuildException("basedir attribute must be set, "
+ "or at least "
+ "one fileset must be given!");
}
if (zipFile == null) {
throw new BuildException("You must specify the "
+ archiveType + " file to create!");
}
// Renamed version of original file, if it exists
File renamedFile = null;
// Whether or not an actual update is required -
// we don't need to update if the original file doesn't exist
addingNewFiles = true;
doUpdate = doUpdate && zipFile.exists();
// Add the files found in groupfileset to fileset
for (int i = 0; i < groupfilesets.size(); i++) {
log("Processing groupfileset ", Project.MSG_VERBOSE);
FileSet fs = (FileSet) groupfilesets.elementAt(i);
FileScanner scanner = fs.getDirectoryScanner(project);
String[] files = scanner.getIncludedFiles();
File basedir = scanner.getBasedir();
for (int j = 0; j < files.length; j++) {
log("Adding file " + files[j] + " to fileset",
Project.MSG_VERBOSE);
ZipFileSet zf = new ZipFileSet();
zf.setSrc(new File(basedir, files[j]));
filesets.addElement(zf);
filesetsFromGroupfilesets.addElement(zf);
}
}
// Create the scanners to pass to isUpToDate().
Vector dss = new Vector();
if (baseDir != null) {
dss.addElement(getDirectoryScanner(baseDir));
}
for (int i = 0; i < filesets.size(); i++) {
FileSet fs = (FileSet) filesets.elementAt(i);
dss.addElement (fs.getDirectoryScanner(project));
}
int dssSize = dss.size();
FileScanner[] scanners = new FileScanner[dssSize];
dss.copyInto(scanners);
boolean success = false;
try {
// quick exit if the target is up to date
// can also handle empty archives
if (isUpToDate(scanners, zipFile)) {
return;
}
if (doUpdate) {
FileUtils fileUtils = FileUtils.newFileUtils();
renamedFile =
fileUtils.createTempFile("zip", ".tmp",
fileUtils.getParentFile(zipFile));
try {
if (!zipFile.renameTo(renamedFile)) {
throw new BuildException("Unable to rename old file "
+ "to temporary file");
}
} catch (SecurityException e) {
throw new BuildException("Not allowed to rename old file "
+ "to temporary file");
}
}
String action = doUpdate ? "Updating " : "Building ";
log(action + archiveType + ": " + zipFile.getAbsolutePath());
ZipOutputStream zOut =
new ZipOutputStream(new FileOutputStream(zipFile));
zOut.setEncoding(encoding);
try {
if (doCompress) {
zOut.setMethod(ZipOutputStream.DEFLATED);
} else {
zOut.setMethod(ZipOutputStream.STORED);
}
initZipOutputStream(zOut);
// Add the implicit fileset to the archive.
if (baseDir != null) {
addFiles(getDirectoryScanner(baseDir), zOut, "", "");
}
// Add the explicit filesets to the archive.
addFiles(filesets, zOut);
if (doUpdate) {
addingNewFiles = false;
ZipFileSet oldFiles = new ZipFileSet();
oldFiles.setSrc(renamedFile);
for (int i = 0; i < addedFiles.size(); i++) {
PatternSet.NameEntry ne = oldFiles.createExclude();
ne.setName((String) addedFiles.elementAt(i));
}
Vector tmp = new Vector(1);
tmp.addElement(oldFiles);
addFiles(tmp, zOut);
}
finalizeZipOutputStream(zOut);
// If we've been successful on an update, delete the
// temporary file
if (doUpdate) {
if (!renamedFile.delete()) {
log ("Warning: unable to delete temporary file " +
renamedFile.getName(), Project.MSG_WARN);
}
}
success = true;
} finally {
// Close the output stream.
try {
if (zOut != null) {
zOut.close();
}
} catch (IOException ex) {
// If we're in this finally clause because of an
// exception, we don't really care if there's an
// exception when closing the stream. E.g. if it
// throws "ZIP file must have at least one entry",
// because an exception happened before we added
// any files, then we must swallow this
// exception. Otherwise, the error that's reported
// will be the close() error, which is not the
// real cause of the problem.
if (success) {
throw ex;
}
}
}
} catch (IOException ioe) {
String msg = "Problem creating " + archiveType + ": "
+ ioe.getMessage();
// delete a bogus ZIP file (but only if it's not the original one)
if ((!doUpdate || renamedFile != null) && !zipFile.delete()) {
msg += " (and the archive is probably corrupt but I could not "
+ "delete it)";
}
if (doUpdate && renamedFile != null) {
if (!renamedFile.renameTo(zipFile)) {
msg += " (and I couldn't rename the temporary file " +
renamedFile.getName() + " back)";
}
}
throw new BuildException(msg, ioe, location);
} finally {
cleanUp();
}
}
/**
* Indicates if the task is adding new files into the archive as opposed to
* copying back unchanged files from the backup copy
*/
protected boolean isAddingNewFiles() {
return addingNewFiles;
}
/**
* Add all files of the given FileScanner to the ZipOutputStream
* prependig the given prefix to each filename.
*
* <p>Ensure parent directories have been added as well.
*/
protected void addFiles(FileScanner scanner, ZipOutputStream zOut,
String prefix, String fullpath)
throws IOException {
if (prefix.length() > 0 && fullpath.length() > 0) {
throw new BuildException("Both prefix and fullpath attributes must"
+ " not be set on the same fileset.");
}
File thisBaseDir = scanner.getBasedir();
// directories that matched include patterns
String[] dirs = scanner.getIncludedDirectories();
if (dirs.length > 0 && fullpath.length() > 0) {
throw new BuildException("fullpath attribute may only be specified"
+ " for filesets that specify a single"
+ " file.");
}
for (int i = 0; i < dirs.length; i++) {
if ("".equals(dirs[i])) {
continue;
}
String name = dirs[i].replace(File.separatorChar, '/');
if (!name.endsWith("/")) {
name += "/";
}
addParentDirs(thisBaseDir, name, zOut, prefix);
}
// files that matched include patterns
String[] files = scanner.getIncludedFiles();
if (files.length > 1 && fullpath.length() > 0) {
throw new BuildException("fullpath attribute may only be specified"
+ " for filesets that specify a single"
+ "file.");
}
for (int i = 0; i < files.length; i++) {
File f = new File(thisBaseDir, files[i]);
if (fullpath.length() > 0) {
// Add this file at the specified location.
addParentDirs(null, fullpath, zOut, "");
zipFile(f, zOut, fullpath);
} else {
// Add this file with the specified prefix.
String name = files[i].replace(File.separatorChar, '/');
addParentDirs(thisBaseDir, name, zOut, prefix);
zipFile(f, zOut, prefix + name);
}
}
}
protected void addZipEntries(ZipFileSet fs, DirectoryScanner ds,
ZipOutputStream zOut, String prefix,
String fullpath)
throws IOException {
log("adding zip entries: " + fullpath, Project.MSG_VERBOSE);
if (prefix.length() > 0 && fullpath.length() > 0) {
throw new BuildException("Both prefix and fullpath attributes must"
+ " not be set on the same fileset.");
}
ZipScanner zipScanner = (ZipScanner) ds;
File zipSrc = fs.getSrc();
ZipEntry entry;
java.util.zip.ZipEntry origEntry;
ZipInputStream in = null;
try {
in = new ZipInputStream(new FileInputStream(zipSrc));
while ((origEntry = in.getNextEntry()) != null) {
entry = new ZipEntry(origEntry);
String vPath = entry.getName();
if (zipScanner.match(vPath)) {
if (fullpath.length() > 0) {
addParentDirs(null, fullpath, zOut, "");
zipFile(in, zOut, fullpath, entry.getTime(), zipSrc);
} else {
addParentDirs(null, vPath, zOut, prefix);
if (!entry.isDirectory()) {
zipFile(in, zOut, prefix + vPath, entry.getTime(),
zipSrc);
}
}
}
}
} finally {
if (in != null) {
in.close();
}
}
}
/**
* method for subclasses to override
*/
protected void initZipOutputStream(ZipOutputStream zOut)
throws IOException, BuildException {
}
/**
* method for subclasses to override
*/
protected void finalizeZipOutputStream(ZipOutputStream zOut)
throws IOException, BuildException {
}
/**
* Create an empty zip file
*
* @return true if the file is then considered up to date.
*/
protected boolean createEmptyZip(File zipFile) {
// In this case using java.util.zip will not work
// because it does not permit a zero-entry archive.
// Must create it manually.
log("Note: creating empty " + archiveType + " archive " + zipFile,
Project.MSG_INFO);
OutputStream os = null;
try {
os = new FileOutputStream(zipFile);
// Cf. PKZIP specification.
byte[] empty = new byte[22];
empty[0] = 80; // P
empty[1] = 75; // K
empty[2] = 5;
empty[3] = 6;
// remainder zeros
os.write(empty);
} catch (IOException ioe) {
throw new BuildException("Could not create empty ZIP archive "
+ "(" + ioe.getMessage() + ")", ioe,
location);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
}
}
}
return true;
}
/**
* Check whether the archive is up-to-date; and handle behavior
* for empty archives.
* @param scanners list of prepared scanners containing files to archive
* @param zipFile intended archive file (may or may not exist)
* @return true if nothing need be done (may have done something
* already); false if archive creation should proceed
* @exception BuildException if it likes
*/
protected boolean isUpToDate(FileScanner[] scanners, File zipFile)
throws BuildException {
String[][] fileNames = grabFileNames(scanners);
File[] files = grabFiles(scanners, fileNames);
if (files.length == 0) {
if (emptyBehavior.equals("skip")) {
log("Warning: skipping " + archiveType + " archive " + zipFile +
" because no files were included.", Project.MSG_WARN);
return true;
} else if (emptyBehavior.equals("fail")) {
throw new BuildException("Cannot create " + archiveType
+ " archive " + zipFile +
": no files were included.", location);
} else {
// Create.
return createEmptyZip(zipFile);
}
} else {
for (int i = 0; i < files.length; ++i) {
if (files[i].equals(zipFile)) {
throw new BuildException("A zip file cannot include "
+ "itself", location);
}
}
if (!zipFile.exists()) {
return false;
}
SourceFileScanner sfs = new SourceFileScanner(this);
MergingMapper mm = new MergingMapper();
mm.setTo(zipFile.getAbsolutePath());
for (int i = 0; i < scanners.length; i++) {
if (sfs.restrict(fileNames[i], scanners[i].getBasedir(), null,
mm).length > 0) {
return false;
}
}
return true;
}
}
protected static File[] grabFiles(FileScanner[] scanners) {
return grabFiles(scanners, grabFileNames(scanners));
}
protected static File[] grabFiles(FileScanner[] scanners,
String[][] fileNames) {
Vector files = new Vector();
for (int i = 0; i < fileNames.length; i++) {
File thisBaseDir = scanners[i].getBasedir();
for (int j = 0; j < fileNames[i].length; j++) {
files.addElement(new File(thisBaseDir, fileNames[i][j]));
}
}
File[] toret = new File[files.size()];
files.copyInto(toret);
return toret;
}
protected static String[][] grabFileNames(FileScanner[] scanners) {
String[][] result = new String[scanners.length][];
for (int i = 0; i < scanners.length; i++) {
String[] files = scanners[i].getIncludedFiles();
String[] dirs = scanners[i].getIncludedDirectories();
result[i] = new String[files.length + dirs.length];
System.arraycopy(files, 0, result[i], 0, files.length);
System.arraycopy(dirs, 0, result[i], files.length, dirs.length);
}
return result;
}
protected void zipDir(File dir, ZipOutputStream zOut, String vPath)
throws IOException {
if (addedDirs.get(vPath) != null) {
// don't add directories we've already added.
// no warning if we try, it is harmless in and of itself
return;
}
log("adding directory " + vPath, Project.MSG_VERBOSE);
addedDirs.put(vPath, vPath);
ZipEntry ze = new ZipEntry (vPath);
if (dir != null && dir.exists()) {
ze.setTime(dir.lastModified());
} else {
ze.setTime(System.currentTimeMillis());
}
ze.setSize (0);
ze.setMethod (ZipEntry.STORED);
// This is faintly ridiculous:
ze.setCrc (EMPTY_CRC);
// this is 040775 | MS-DOS directory flag in reverse byte order
ze.setExternalAttributes(0x41FD0010L);
zOut.putNextEntry (ze);
}
protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,
long lastModified, File file)
throws IOException {
if (entries.contains(vPath)) {
if (duplicate.equals("preserve")) {
log(vPath + " already added, skipping", Project.MSG_INFO);
return;
} else if (duplicate.equals("fail")) {
throw new BuildException("Duplicate file " + vPath
+ " was found and the duplicate "
+ "attribute is 'fail'.");
} else {
// duplicate equal to add, so we continue
log("duplicate file " + vPath
+ " found, adding.", Project.MSG_VERBOSE);
}
} else {
log("adding entry " + vPath, Project.MSG_VERBOSE);
}
entries.put(vPath, vPath);
ZipEntry ze = new ZipEntry(vPath);
ze.setTime(lastModified);
/*
* XXX ZipOutputStream.putEntry expects the ZipEntry to know its
* size and the CRC sum before you start writing the data when using
* STORED mode.
*
* This forces us to process the data twice.
*
* I couldn't find any documentation on this, just found out by try
* and error.
*/
if (!doCompress) {
long size = 0;
CRC32 cal = new CRC32();
if (!in.markSupported()) {
// Store data into a byte[]
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[8 * 1024];
int count = 0;
do {
size += count;
cal.update(buffer, 0, count);
bos.write(buffer, 0, count);
count = in.read(buffer, 0, buffer.length);
} while (count != -1);
in = new ByteArrayInputStream(bos.toByteArray());
} else {
in.mark(Integer.MAX_VALUE);
byte[] buffer = new byte[8 * 1024];
int count = 0;
do {
size += count;
cal.update(buffer, 0, count);
count = in.read(buffer, 0, buffer.length);
} while (count != -1);
in.reset();
}
ze.setSize(size);
ze.setCrc(cal.getValue());
}
zOut.putNextEntry(ze);
byte[] buffer = new byte[8 * 1024];
int count = 0;
do {
if (count != 0) {
zOut.write(buffer, 0, count);
}
count = in.read(buffer, 0, buffer.length);
} while (count != -1);
addedFiles.addElement(vPath);
}
protected void zipFile(File file, ZipOutputStream zOut, String vPath)
throws IOException {
if (file.equals(zipFile)) {
throw new BuildException("A zip file cannot include itself",
location);
}
FileInputStream fIn = new FileInputStream(file);
try {
zipFile(fIn, zOut, vPath, file.lastModified(), null);
} finally {
fIn.close();
}
}
/**
* Ensure all parent dirs of a given entry have been added.
*/
protected void addParentDirs(File baseDir, String entry,
ZipOutputStream zOut, String prefix)
throws IOException {
if (!doFilesonly) {
Stack directories = new Stack();
int slashPos = entry.length();
while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) {
String dir = entry.substring(0, slashPos + 1);
if (addedDirs.get(prefix + dir) != null) {
break;
}
directories.push(dir);
}
while (!directories.isEmpty()) {
String dir = (String) directories.pop();
File f = null;
if (baseDir != null) {
f = new File(baseDir, dir);
} else {
f = new File(dir);
}
zipDir(f, zOut, prefix + dir);
}
}
}
/**
* Iterate over the given Vector of (zip)filesets and add
* all files to the ZipOutputStream using the given prefix
* or fullpath.
*/
protected void addFiles(Vector filesets, ZipOutputStream zOut)
throws IOException {
// Add each fileset in the Vector.
for (int i = 0; i < filesets.size(); i++) {
FileSet fs = (FileSet) filesets.elementAt(i);
DirectoryScanner ds = fs.getDirectoryScanner(project);
String prefix = "";
String fullpath = "";
if (fs instanceof ZipFileSet) {
ZipFileSet zfs = (ZipFileSet) fs;
prefix = zfs.getPrefix();
fullpath = zfs.getFullpath();
}
if (prefix.length() > 0
&& !prefix.endsWith("/")
&& !prefix.endsWith("\\")) {
prefix += "/";
}
// Need to manually add either fullpath's parent directory, or
// the prefix directory, to the archive.
if (prefix.length() > 0) {
addParentDirs(null, prefix, zOut, "");
zipDir(null, zOut, prefix);
} else if (fullpath.length() > 0) {
addParentDirs(null, fullpath, zOut, "");
}
if (fs instanceof ZipFileSet
&& ((ZipFileSet) fs).getSrc() != null) {
addZipEntries((ZipFileSet) fs, ds, zOut, prefix, fullpath);
} else {
// Add the fileset.
addFiles(ds, zOut, prefix, fullpath);
}
}
}
/**
* Do any clean up necessary to allow this instance to be used again.
*
* <p>When we get here, the Zip file has been closed and all we
* need to do is to reset some globals.</p>
*
* <p>This method will only reset globals that have been changed
* during execute(), it will not alter the attributes or nested
* child elements. If you want to reset the instance so that you
* can later zip a completely different set of files, you must use
* the reset method.</p>
*
* @see #reset
*/
protected void cleanUp() {
addedDirs.clear();
addedFiles.removeAllElements();
entries.clear();
addingNewFiles = false;
doUpdate = savedDoUpdate;
Enumeration enum = filesetsFromGroupfilesets.elements();
while (enum.hasMoreElements()) {
ZipFileSet zf = (ZipFileSet) enum.nextElement();
filesets.removeElement(zf);
}
filesetsFromGroupfilesets.removeAllElements();
}
/**
* Makes this instance reset all attributes to their default
* values and forget all children.
*
* @since Ant 1.5
*
* @see #cleanUp
*/
public void reset() {
filesets.removeAllElements();
zipFile = null;
baseDir = null;
groupfilesets.removeAllElements();
duplicate = "add";
archiveType = "zip";
doCompress = true;
emptyBehavior = "skip";
doUpdate = false;
doFilesonly = false;
encoding = null;
}
/**
* Possible behaviors when a duplicate file is added:
* "add", "preserve" or "fail"
*/
public static class Duplicate extends EnumeratedAttribute {
public String[] getValues() {
return new String[] {"add", "preserve", "fail"};
}
}
}