/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999 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
* .
*/
package org.apache.tools.ant.taskdefs;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.CRC32;
import java.util.zip.ZipInputStream;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.util.*;
import org.apache.tools.zip.*;
/**
* Create a ZIP archive.
*
* @author James Davidson duncan@x180.com
* @author Jon S. Stevens jon@clearink.com
* @author Stefan Bodewig
*/
public class Zip extends MatchingTask {
private File zipFile;
private File baseDir;
private boolean doCompress = true;
private boolean doUpdate = true;
private boolean doFilesonly = false;
protected String archiveType = "zip";
// For directories:
private static long emptyCrc = new CRC32 ().getValue ();
protected String emptyBehavior = "skip";
private Vector filesets = new Vector ();
private Hashtable addedDirs = new Hashtable();
private Vector addedFiles = new Vector();
/**
* Encoding to use for filenames, defaults to the platform's
* default encoding.
*/
private String encoding = null;
/**
* This is the name/location of where to
* create the .zip file.
*/
public void setZipfile(File zipFile) {
this.zipFile = zipFile;
}
/**
* This is the base directory to look in for
* things to zip.
*/
public void setBasedir(File baseDir) {
this.baseDir = baseDir;
}
/**
* Sets whether we want to compress the files or only store them.
*/
public void setCompress(boolean c) {
doCompress = c;
}
/**
* Emulate Sun's jar utility by not adding parent dirs
*/
public void setFilesonly(boolean f) {
doFilesonly = f;
}
/**
* Sets whether we want to update the file (if it exists)
* or create a new one.
*/
public void setUpdate(boolean c) {
doUpdate = c;
}
/**
* Adds a set of files (nested fileset attribute).
*/
public void addFileset(FileSet set) {
filesets.addElement(set);
}
/**
* Adds a set of files (nested zipfileset attribute) that can be
* read from an archive and be given a prefix/fullpath.
*/
public void addZipfileset(ZipFileSet set) {
filesets.addElement(set);
}
/** Possible behaviors when there are no matching files for the task. */
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: fail (throw an exception
* and halt the build); skip (do not create
* any archive, but issue a warning); create
* (make an archive with no entries).
* Default for zip tasks is skip;
* for jar tasks, create.
*/
public void setWhenempty(WhenEmpty we) {
emptyBehavior = we.getValue();
}
/**
* Encoding to use for filenames, defaults to the platform's
* default encoding.
*
*
For a list of possible values see http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html.
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void execute() throws BuildException {
if (baseDir == null && filesets.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
boolean reallyDoUpdate=false;
if (doUpdate && zipFile.exists())
{
reallyDoUpdate=true;
int i;
for (i=0; i < 1000; i++)
{
renamedFile = new File (zipFile.getParent(), "tmp."+i);
if (!renamedFile.exists())
break;
}
if (i==1000)
throw new BuildException
("Can't find temporary filename to rename old file to.");
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");
}
}
// Create the scanners to pass to isUpToDate().
Vector dss = new Vector ();
if (baseDir != null)
dss.addElement(getDirectoryScanner(baseDir));
for (int i=0; iEnsure 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 may 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)
throws IOException
{
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)) {
addParentDirs(null, vPath, zOut, prefix);
if (! entry.isDirectory()) {
zipFile(in, zOut, prefix+vPath, entry.getTime());
}
}
}
} finally {
if (in != null) {
in.close();
}
}
}
protected void initZipOutputStream(ZipOutputStream zOut)
throws IOException, BuildException
{
}
/**
* 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.
if (zipFile.exists()) return true;
// 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);
try {
OutputStream os = new FileOutputStream(zipFile);
try {
// 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);
} finally {
os.close();
}
} catch (IOException ioe) {
throw new BuildException("Could not create empty ZIP archive", ioe, location);
}
return true;
}
} 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 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 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);
} else {
// Add the fileset.
addFiles(ds, zOut, prefix, fullpath);
}
}
}
/**
* Do any clean up necessary to allow this instance to be used again.
*
* When we get here, the Zip file has been closed and all we
* need to do is to reset some globals.
*/
protected void cleanUp() {
addedDirs = new Hashtable();
addedFiles = new Vector();
}
}