/* * 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(); } }