/* * 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.*; import org.apache.tools.ant.*; import org.apache.tools.ant.types.*; import org.apache.tools.ant.util.*; /** * 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; 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(); /** * 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; } /** * Adds a set of files (nested fileset attribute). */ public void addFileset(PrefixedFileSet set) { filesets.addElement(set); } /** * Adds a set of files (nested fileset attribute). */ public void addPrefixedfileset(PrefixedFileSet set) { addFileset(set); } /** * FileSet with an additional prefix attribute to specify the * location we want to move the files to (inside the archive). * Or, if this FileSet represents only a single file, then the * fullpath attribute can be set, which specifies the full path * that the file should have when it is placed in the archive. */ public static class PrefixedFileSet extends FileSet { private String prefix = ""; private String fullpath = ""; public void setPrefix(String loc) { prefix = loc; } public String getPrefix() {return prefix;} public void setFullpath(String loc) { fullpath = loc; } public String getFullpath() {return fullpath;} } /** * 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(String we) throws BuildException { we = we.toLowerCase(); // XXX could instead be using EnumeratedAttribute, but this works if (!"fail".equals(we) && !"skip".equals(we) && !"create".equals(we)) throw new BuildException("Unrecognized whenempty attribute: " + we); emptyBehavior = we; } 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 or prefixedfileset must be given!" ); } if (zipFile == null) { throw new BuildException("You must specify the " + archiveType + " file to create!"); } // 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++) { 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 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 { 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 += "/"; } String fullpath = fs.getFullpath(); // 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, ""); } // 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() {} }