All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.greenpepper.shaded.org.apache.tools.ant.taskdefs.Zip Maven / Gradle / Ivy

There is a newer version: 4.2.4
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.greenpepper.shaded.org.apache.tools.ant.taskdefs;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;
import java.util.zip.CRC32;

import com.greenpepper.shaded.org.apache.tools.ant.BuildException;
import com.greenpepper.shaded.org.apache.tools.ant.DirectoryScanner;
import com.greenpepper.shaded.org.apache.tools.ant.FileScanner;
import com.greenpepper.shaded.org.apache.tools.ant.Project;
import com.greenpepper.shaded.org.apache.tools.ant.types.ArchiveFileSet;
import com.greenpepper.shaded.org.apache.tools.ant.types.EnumeratedAttribute;
import com.greenpepper.shaded.org.apache.tools.ant.types.FileSet;
import com.greenpepper.shaded.org.apache.tools.ant.types.PatternSet;
import com.greenpepper.shaded.org.apache.tools.ant.types.Resource;
import com.greenpepper.shaded.org.apache.tools.ant.types.ResourceCollection;
import com.greenpepper.shaded.org.apache.tools.ant.types.ZipFileSet;
import com.greenpepper.shaded.org.apache.tools.ant.types.ZipScanner;
import com.greenpepper.shaded.org.apache.tools.ant.types.resources.ArchiveResource;
import com.greenpepper.shaded.org.apache.tools.ant.types.resources.FileResource;
import com.greenpepper.shaded.org.apache.tools.ant.util.FileNameMapper;
import com.greenpepper.shaded.org.apache.tools.ant.util.FileUtils;
import com.greenpepper.shaded.org.apache.tools.ant.util.GlobPatternMapper;
import com.greenpepper.shaded.org.apache.tools.ant.util.IdentityMapper;
import com.greenpepper.shaded.org.apache.tools.ant.util.MergingMapper;
import com.greenpepper.shaded.org.apache.tools.ant.util.ResourceUtils;
import com.greenpepper.shaded.org.apache.tools.zip.ZipEntry;
import com.greenpepper.shaded.org.apache.tools.zip.ZipExtraField;
import com.greenpepper.shaded.org.apache.tools.zip.ZipFile;
import com.greenpepper.shaded.org.apache.tools.zip.ZipOutputStream;

/**
 * Create a Zip file.
 *
 * @since Ant 1.1
 *
 * @ant.task category="packaging"
 */
public class Zip extends MatchingTask {
    // CheckStyle:VisibilityModifier OFF - bc

    protected File zipFile;
    // use to scan own archive
    private ZipScanner zs;
    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 resources = new Vector();
    protected Hashtable addedDirs = new Hashtable();
    private Vector addedFiles = new Vector();

    protected boolean doubleFilePass = false;
    protected boolean skipWriting = false;

    private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();

    // CheckStyle:VisibilityModifier ON

    /**
     * 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;

    /**
     * Whether the original compression of entries coming from a ZIP
     * archive should be kept (for example when updating an archive).
     *
     * @since Ant 1.6
     */
    private boolean keepCompression = false;

    /**
     * Whether the file modification times will be rounded up to the
     * next even number of seconds.
     *
     * @since Ant 1.6.2
     */
    private boolean roundUp = true;

    /**
     * Comment for the archive.
     * @since Ant 1.6.3
     */
    private String comment = "";

    private int level = ZipOutputStream.DEFAULT_COMPRESSION;

    /**
     * This is the name/location of where to
     * create the .zip file.
     * @param zipFile the path of the zipFile
     * @deprecated since 1.5.x.
     *             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.
     * @param file the path of the zipFile
     * @since Ant 1.5
     * @deprecated since 1.5.x.
     *             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;
    }

    /**
     * The file to create.
     * @return the destination file
     * @since Ant 1.5.2
     */
    public File getDestFile() {
        return zipFile;
    }


    /**
     * Directory from which to archive files; optional.
     * @param baseDir the base directory
     */
    public void setBasedir(File baseDir) {
        this.baseDir = baseDir;
    }

    /**
     * Whether we want to compress the files or only store them;
     * optional, default=true;
     * @param c if true, compress the files
     */
    public void setCompress(boolean c) {
        doCompress = c;
    }

    /**
     * Whether we want to compress the files or only store them;
     * @return true if the files are to be compressed
     * @since Ant 1.5.2
     */
    public boolean isCompress() {
        return doCompress;
    }

    /**
     * If true, emulate Sun's jar utility by not adding parent directories;
     * optional, defaults to false.
     * @param f if true, emulate sun's jar by not adding parent directories
     */
    public void setFilesonly(boolean f) {
        doFilesonly = f;
    }

    /**
     * If true, updates an existing file, otherwise overwrite
     * any existing one; optional defaults to false.
     * @param c if true, updates an existing zip file
     */
    public void setUpdate(boolean c) {
        doUpdate = c;
        savedDoUpdate = c;
    }

    /**
     * Are we updating an existing archive?
     * @return true if updating an existing archive
     */
    public boolean isInUpdateMode() {
        return doUpdate;
    }

    /**
     * Adds a set of files.
     * @param set the fileset to add
     */
    public void addFileset(FileSet set) {
        add(set);
    }

    /**
     * Adds a set of files that can be
     * read from an archive and be given a prefix/fullpath.
     * @param set the zipfileset to add
     */
    public void addZipfileset(ZipFileSet set) {
        add(set);
    }

    /**
     * Add a collection of resources to be archived.
     * @param a the resources to archive
     * @since Ant 1.7
     */
    public void add(ResourceCollection a) {
        resources.add(a);
    }

    /**
     * Adds a group of zip files.
     * @param set the group (a fileset) to add
     */
    public void addZipGroupFileset(FileSet set) {
        groupfilesets.addElement(set);
    }

    /**
     * Sets behavior for when a duplicate file is about to be added -
     * one of add, preserve or fail.
     * Possible values are: add (keep both
     * of the files); preserve (keep the first version
     * of the file found); fail halt a problem
     * Default for zip tasks is add
     * @param df a Duplicate enumerated value
     */
    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 {
        /**
         * The string values for the enumerated value
         * @return the values
         */
        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.
     * @param we a WhenEmpty enumerated value
     */
    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/j2se/1.5.0/docs/guide/intl/encoding.doc.html.

* @param encoding the encoding name */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Encoding to use for filenames. * @return the name of the encoding to use * @since Ant 1.5.2 */ public String getEncoding() { return encoding; } /** * Whether the original compression of entries coming from a ZIP * archive should be kept (for example when updating an archive). * Default is false. * @param keep if true, keep the original compression * @since Ant 1.6 */ public void setKeepCompression(boolean keep) { keepCompression = keep; } /** * Comment to use for archive. * * @param comment The content of the comment. * @since Ant 1.6.3 */ public void setComment(String comment) { this.comment = comment; } /** * Comment of the archive * * @return Comment of the archive. * @since Ant 1.6.3 */ public String getComment() { return comment; } /** * Set the compression level to use. Default is * ZipOutputStream.DEFAULT_COMPRESSION. * @param level compression level. * @since Ant 1.7 */ public void setLevel(int level) { this.level = level; } /** * Get the compression level. * @return compression level. * @since Ant 1.7 */ public int getLevel() { return level; } /** * Whether the file modification times will be rounded up to the * next even number of seconds. * *

Zip archives store file modification times with a * granularity of two seconds, so the times will either be rounded * up or down. If you round down, the archive will always seem * out-of-date when you rerun the task, so the default is to round * up. Rounding up may lead to a different type of problems like * JSPs inside a web archive that seem to be slightly more recent * than precompiled pages, rendering precompilation useless.

* @param r a boolean value * @since Ant 1.6.2 */ public void setRoundUp(boolean r) { roundUp = r; } /** * validate and build * @throws BuildException on error */ public void execute() throws BuildException { if (doubleFilePass) { skipWriting = true; executeMain(); skipWriting = false; executeMain(); } else { executeMain(); } } /** * Build the zip file. * This is called twice if doubleFilePass is true. * @throws BuildException on error */ public void executeMain() throws BuildException { if (baseDir == null && resources.size() == 0 && groupfilesets.size() == 0 && "zip".equals(archiveType)) { throw new BuildException("basedir attribute must be set, " + "or at least one " + "resource collection must be given!"); } if (zipFile == null) { throw new BuildException("You must specify the " + archiveType + " file to create!"); } if (zipFile.exists() && !zipFile.isFile()) { throw new BuildException(zipFile + " is not a file."); } if (zipFile.exists() && !zipFile.canWrite()) { throw new BuildException(zipFile + " is read-only."); } // Renamed version of original file, if it exists File renamedFile = null; addingNewFiles = true; // Whether or not an actual update is required - // we don't need to update if the original file doesn't exist if (doUpdate && !zipFile.exists()) { doUpdate = false; log("ignoring update attribute as " + archiveType + " doesn't exist.", Project.MSG_DEBUG); } // 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(getProject()); 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.setProject(getProject()); zf.setSrc(new File(basedir, files[j])); add(zf); filesetsFromGroupfilesets.addElement(zf); } } // collect filesets to pass them to getResourcesToAdd Vector vfss = new Vector(); if (baseDir != null) { FileSet fs = (FileSet) getImplicitFileSet().clone(); fs.setDir(baseDir); vfss.addElement(fs); } for (int i = 0; i < resources.size(); i++) { ResourceCollection rc = (ResourceCollection) resources.elementAt(i); vfss.addElement(rc); } ResourceCollection[] fss = new ResourceCollection[vfss.size()]; vfss.copyInto(fss); boolean success = false; try { // can also handle empty archives ArchiveState state = getResourcesToAdd(fss, zipFile, false); // quick exit if the target is up to date if (!state.isOutOfDate()) { return; } if (!zipFile.exists() && state.isWithoutAnyResources()) { createEmptyZip(zipFile); return; } Resource[][] addThem = state.getResourcesToAdd(); if (doUpdate) { renamedFile = FILE_UTILS.createTempFile("zip", ".tmp", zipFile.getParentFile()); renamedFile.deleteOnExit(); try { FILE_UTILS.rename(zipFile, renamedFile); } catch (SecurityException e) { throw new BuildException( "Not allowed to rename old file (" + zipFile.getAbsolutePath() + ") to temporary file"); } catch (IOException e) { throw new BuildException( "Unable to rename old file (" + zipFile.getAbsolutePath() + ") to temporary file"); } } String action = doUpdate ? "Updating " : "Building "; log(action + archiveType + ": " + zipFile.getAbsolutePath()); ZipOutputStream zOut = null; try { if (!skipWriting) { zOut = new ZipOutputStream(zipFile); zOut.setEncoding(encoding); zOut.setMethod(doCompress ? ZipOutputStream.DEFLATED : ZipOutputStream.STORED); zOut.setLevel(level); } initZipOutputStream(zOut); // Add the explicit resource collections to the archive. for (int i = 0; i < fss.length; i++) { if (addThem[i].length != 0) { addResources(fss[i], addThem[i], zOut); } } if (doUpdate) { addingNewFiles = false; ZipFileSet oldFiles = new ZipFileSet(); oldFiles.setProject(getProject()); oldFiles.setSrc(renamedFile); oldFiles.setDefaultexcludes(false); for (int i = 0; i < addedFiles.size(); i++) { PatternSet.NameEntry ne = oldFiles.createExclude(); ne.setName((String) addedFiles.elementAt(i)); } DirectoryScanner ds = oldFiles.getDirectoryScanner(getProject()); ((ZipScanner) ds).setEncoding(encoding); String[] f = ds.getIncludedFiles(); Resource[] r = new Resource[f.length]; for (int i = 0; i < f.length; i++) { r[i] = ds.getResource(f[i]); } if (!doFilesonly) { String[] d = ds.getIncludedDirectories(); Resource[] dr = new Resource[d.length]; for (int i = 0; i < d.length; i++) { dr[i] = ds.getResource(d[i]); } Resource[] tmp = r; r = new Resource[tmp.length + dr.length]; System.arraycopy(dr, 0, r, 0, dr.length); System.arraycopy(tmp, 0, r, dr.length, tmp.length); } addResources(oldFiles, r, zOut); } if (zOut != null) { zOut.setComment(comment); } 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) { try { FILE_UTILS.rename(renamedFile, zipFile); } catch (IOException e) { msg += " (and I couldn't rename the temporary file " + renamedFile.getName() + " back)"; } } throw new BuildException(msg, ioe, getLocation()); } finally { cleanUp(); } } /** * Indicates if the task is adding new files into the archive as opposed to * copying back unchanged files from the backup copy * @return true if adding new files */ protected final boolean isAddingNewFiles() { return addingNewFiles; } /** * Add the given resources. * * @param fileset may give additional information like fullpath or * permissions. * @param resources the resources to add * @param zOut the stream to write to * @throws IOException on error * * @since Ant 1.5.2 */ protected final void addResources(FileSet fileset, Resource[] resources, ZipOutputStream zOut) throws IOException { String prefix = ""; String fullpath = ""; int dirMode = ArchiveFileSet.DEFAULT_DIR_MODE; int fileMode = ArchiveFileSet.DEFAULT_FILE_MODE; ArchiveFileSet zfs = null; if (fileset instanceof ArchiveFileSet) { zfs = (ArchiveFileSet) fileset; prefix = zfs.getPrefix(getProject()); fullpath = zfs.getFullpath(getProject()); dirMode = zfs.getDirMode(getProject()); fileMode = zfs.getFileMode(getProject()); } if (prefix.length() > 0 && fullpath.length() > 0) { throw new BuildException("Both prefix and fullpath attributes must" + " not be set on the same fileset."); } if (resources.length != 1 && fullpath.length() > 0) { throw new BuildException("fullpath attribute may only be specified" + " for filesets that specify a single" + " file."); } if (prefix.length() > 0) { if (!prefix.endsWith("/") && !prefix.endsWith("\\")) { prefix += "/"; } addParentDirs(null, prefix, zOut, "", dirMode); } ZipFile zf = null; try { boolean dealingWithFiles = false; File base = null; if (zfs == null || zfs.getSrc(getProject()) == null) { dealingWithFiles = true; base = fileset.getDir(getProject()); } else if (zfs instanceof ZipFileSet) { zf = new ZipFile(zfs.getSrc(getProject()), encoding); } for (int i = 0; i < resources.length; i++) { String name = null; if (fullpath.length() > 0) { name = fullpath; } else { name = resources[i].getName(); } name = name.replace(File.separatorChar, '/'); if ("".equals(name)) { continue; } if (resources[i].isDirectory() && !name.endsWith("/")) { name = name + "/"; } if (!doFilesonly && !dealingWithFiles && resources[i].isDirectory() && !zfs.hasDirModeBeenSet()) { int nextToLastSlash = name.lastIndexOf("/", name.length() - 2); if (nextToLastSlash != -1) { addParentDirs(base, name.substring(0, nextToLastSlash + 1), zOut, prefix, dirMode); } if (zf != null) { ZipEntry ze = zf.getEntry(resources[i].getName()); addParentDirs(base, name, zOut, prefix, ze.getUnixMode()); } else { ArchiveResource tr = (ArchiveResource) resources[i]; addParentDirs(base, name, zOut, prefix, tr.getMode()); } } else { addParentDirs(base, name, zOut, prefix, dirMode); } if (!resources[i].isDirectory() && dealingWithFiles) { File f = FILE_UTILS.resolveFile(base, resources[i].getName()); zipFile(f, zOut, prefix + name, fileMode); } else if (!resources[i].isDirectory()) { if (zf != null) { ZipEntry ze = zf.getEntry(resources[i].getName()); if (ze != null) { boolean oldCompress = doCompress; if (keepCompression) { doCompress = (ze.getMethod() == ZipEntry.DEFLATED); } try { zipFile(zf.getInputStream(ze), zOut, prefix + name, ze.getTime(), zfs.getSrc(getProject()), zfs.hasFileModeBeenSet() ? fileMode : ze.getUnixMode()); } finally { doCompress = oldCompress; } } } else { ArchiveResource tr = (ArchiveResource) resources[i]; InputStream is = null; try { is = tr.getInputStream(); zipFile(is, zOut, prefix + name, resources[i].getLastModified(), zfs.getSrc(getProject()), zfs.hasFileModeBeenSet() ? fileMode : tr.getMode()); } finally { FileUtils.close(is); } } } } } finally { if (zf != null) { zf.close(); } } } /** * Add the given resources. * * @param rc may give additional information like fullpath or * permissions. * @param resources the resources to add * @param zOut the stream to write to * @throws IOException on error * * @since Ant 1.7 */ protected final void addResources(ResourceCollection rc, Resource[] resources, ZipOutputStream zOut) throws IOException { if (rc instanceof FileSet) { addResources((FileSet) rc, resources, zOut); return; } for (int i = 0; i < resources.length; i++) { String name = resources[i].getName().replace(File.separatorChar, '/'); if ("".equals(name)) { continue; } if (resources[i].isDirectory() && doFilesonly) { continue; } File base = null; if (resources[i] instanceof FileResource) { base = ((FileResource) resources[i]).getBaseDir(); } if (resources[i].isDirectory()) { if (!name.endsWith("/")) { name = name + "/"; } } addParentDirs(base, name, zOut, "", ArchiveFileSet.DEFAULT_DIR_MODE); if (!resources[i].isDirectory()) { if (resources[i] instanceof FileResource) { File f = ((FileResource) resources[i]).getFile(); zipFile(f, zOut, name, ArchiveFileSet.DEFAULT_FILE_MODE); } else { InputStream is = null; try { is = resources[i].getInputStream(); zipFile(is, zOut, name, resources[i].getLastModified(), null, ArchiveFileSet.DEFAULT_FILE_MODE); } finally { FileUtils.close(is); } } } } } /** * method for subclasses to override * @param zOut the zip output stream * @throws IOException on output error * @throws BuildException on other errors */ protected void initZipOutputStream(ZipOutputStream zOut) throws IOException, BuildException { } /** * method for subclasses to override * @param zOut the zip output stream * @throws IOException on output error * @throws BuildException on other errors */ protected void finalizeZipOutputStream(ZipOutputStream zOut) throws IOException, BuildException { } /** * Create an empty zip file * @param zipFile the zip file * @return true for historic reasons * @throws BuildException on error */ protected boolean createEmptyZip(File zipFile) throws BuildException { // 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, getLocation()); } finally { if (os != null) { try { os.close(); } catch (IOException e) { //ignore } } } return true; } /** * @since Ant 1.5.2 */ private synchronized ZipScanner getZipScanner() { if (zs == null) { zs = new ZipScanner(); zs.setEncoding(encoding); zs.setSrc(zipFile); } return zs; } /** * Collect the resources that are newer than the corresponding * entries (or missing) in the original archive. * *

If we are going to recreate the archive instead of updating * it, all resources should be considered as new, if a single one * is. Because of this, subclasses overriding this method must * call super.getResourcesToAdd and indicate with the * third arg if they already know that the archive is * out-of-date.

* *

This method first delegates to getNonFileSetResourceToAdd * and then invokes the FileSet-arg version. All this to keep * backwards compatibility for subclasses that don't know how to * deal with non-FileSet ResourceCollections.

* * @param rcs The resource collections to grab resources from * @param zipFile intended archive file (may or may not exist) * @param needsUpdate whether we already know that the archive is * out-of-date. Subclasses overriding this method are supposed to * set this value correctly in their call to * super.getResourcesToAdd. * @return an array of resources to add for each fileset passed in as well * as a flag that indicates whether the archive is uptodate. * * @exception BuildException if it likes * @since Ant 1.7 */ protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs, File zipFile, boolean needsUpdate) throws BuildException { ArrayList filesets = new ArrayList(); ArrayList rest = new ArrayList(); for (int i = 0; i < rcs.length; i++) { if (rcs[i] instanceof FileSet) { filesets.add(rcs[i]); } else { rest.add(rcs[i]); } } ResourceCollection[] rc = (ResourceCollection[]) rest.toArray(new ResourceCollection[rest.size()]); ArchiveState as = getNonFileSetResourcesToAdd(rc, zipFile, needsUpdate); FileSet[] fs = (FileSet[]) filesets.toArray(new FileSet[filesets .size()]); ArchiveState as2 = getResourcesToAdd(fs, zipFile, as.isOutOfDate()); if (!as.isOutOfDate() && as2.isOutOfDate()) { /* * Bad luck. * * There are resources in the filesets that make the * archive out of date, but not in the non-fileset * resources. We need to rescan the non-FileSets to grab * all of them now. */ as = getNonFileSetResourcesToAdd(rc, zipFile, true); } Resource[][] toAdd = new Resource[rcs.length][]; int fsIndex = 0; int restIndex = 0; for (int i = 0; i < rcs.length; i++) { if (rcs[i] instanceof FileSet) { toAdd[i] = as2.getResourcesToAdd()[fsIndex++]; } else { toAdd[i] = as.getResourcesToAdd()[restIndex++]; } } return new ArchiveState(as2.isOutOfDate(), toAdd); } /** * Collect the resources that are newer than the corresponding * entries (or missing) in the original archive. * *

If we are going to recreate the archive instead of updating * it, all resources should be considered as new, if a single one * is. Because of this, subclasses overriding this method must * call super.getResourcesToAdd and indicate with the * third arg if they already know that the archive is * out-of-date.

* * @param filesets The filesets to grab resources from * @param zipFile intended archive file (may or may not exist) * @param needsUpdate whether we already know that the archive is * out-of-date. Subclasses overriding this method are supposed to * set this value correctly in their call to * super.getResourcesToAdd. * @return an array of resources to add for each fileset passed in as well * as a flag that indicates whether the archive is uptodate. * * @exception BuildException if it likes */ protected ArchiveState getResourcesToAdd(FileSet[] filesets, File zipFile, boolean needsUpdate) throws BuildException { Resource[][] initialResources = grabResources(filesets); if (isEmpty(initialResources)) { if (needsUpdate && doUpdate) { /* * This is a rather hairy case. * * One of our subclasses knows that we need to update the * archive, but at the same time, there are no resources * known to us that would need to be added. Only the * subclass seems to know what's going on. * * This happens if detects that the manifest has changed, * for example. The manifest is not part of any resources * because of our support for inline s. * * If we invoke createEmptyZip like Ant 1.5.2 did, * we'll loose all stuff that has been in the original * archive (bugzilla report 17780). */ return new ArchiveState(true, initialResources); } if (emptyBehavior.equals("skip")) { if (doUpdate) { log(archiveType + " archive " + zipFile + " not updated because no new files were included.", Project.MSG_VERBOSE); } else { log("Warning: skipping " + archiveType + " archive " + zipFile + " because no files were included.", Project.MSG_WARN); } } else if (emptyBehavior.equals("fail")) { throw new BuildException("Cannot create " + archiveType + " archive " + zipFile + ": no files were included.", getLocation()); } else { // Create. if (!zipFile.exists()) { needsUpdate = true; } } return new ArchiveState(needsUpdate, initialResources); } // initialResources is not empty if (!zipFile.exists()) { return new ArchiveState(true, initialResources); } if (needsUpdate && !doUpdate) { // we are recreating the archive, need all resources return new ArchiveState(true, initialResources); } Resource[][] newerResources = new Resource[filesets.length][]; for (int i = 0; i < filesets.length; i++) { if (!(fileset instanceof ZipFileSet) || ((ZipFileSet) fileset).getSrc(getProject()) == null) { File base = filesets[i].getDir(getProject()); for (int j = 0; j < initialResources[i].length; j++) { File resourceAsFile = FILE_UTILS.resolveFile(base, initialResources[i][j].getName()); if (resourceAsFile.equals(zipFile)) { throw new BuildException("A zip file cannot include " + "itself", getLocation()); } } } } for (int i = 0; i < filesets.length; i++) { if (initialResources[i].length == 0) { newerResources[i] = new Resource[] {}; continue; } FileNameMapper myMapper = new IdentityMapper(); if (filesets[i] instanceof ZipFileSet) { ZipFileSet zfs = (ZipFileSet) filesets[i]; if (zfs.getFullpath(getProject()) != null && !zfs.getFullpath(getProject()).equals("")) { // in this case all files from origin map to // the fullPath attribute of the zipfileset at // destination MergingMapper fm = new MergingMapper(); fm.setTo(zfs.getFullpath(getProject())); myMapper = fm; } else if (zfs.getPrefix(getProject()) != null && !zfs.getPrefix(getProject()).equals("")) { GlobPatternMapper gm = new GlobPatternMapper(); gm.setFrom("*"); String prefix = zfs.getPrefix(getProject()); if (!prefix.endsWith("/") && !prefix.endsWith("\\")) { prefix += "/"; } gm.setTo(prefix + "*"); myMapper = gm; } } Resource[] resources = initialResources[i]; if (doFilesonly) { resources = selectFileResources(resources); } newerResources[i] = ResourceUtils.selectOutOfDateSources(this, resources, myMapper, getZipScanner()); needsUpdate = needsUpdate || (newerResources[i].length > 0); if (needsUpdate && !doUpdate) { // we will return initialResources anyway, no reason // to scan further. break; } } if (needsUpdate && !doUpdate) { // we are recreating the archive, need all resources return new ArchiveState(true, initialResources); } return new ArchiveState(needsUpdate, newerResources); } /** * Collect the resources that are newer than the corresponding * entries (or missing) in the original archive. * *

If we are going to recreate the archive instead of updating * it, all resources should be considered as new, if a single one * is. Because of this, subclasses overriding this method must * call super.getResourcesToAdd and indicate with the * third arg if they already know that the archive is * out-of-date.

* * @param rcs The filesets to grab resources from * @param zipFile intended archive file (may or may not exist) * @param needsUpdate whether we already know that the archive is * out-of-date. Subclasses overriding this method are supposed to * set this value correctly in their call to * super.getResourcesToAdd. * @return an array of resources to add for each fileset passed in as well * as a flag that indicates whether the archive is uptodate. * * @exception BuildException if it likes */ protected ArchiveState getNonFileSetResourcesToAdd(ResourceCollection[] rcs, File zipFile, boolean needsUpdate) throws BuildException { /* * Backwards compatibility forces us to repeat the logic of * getResourcesToAdd(FileSet[], ...) here once again. */ Resource[][] initialResources = grabNonFileSetResources(rcs); if (isEmpty(initialResources)) { // no emptyBehavior handling since the FileSet version // will take care of it. return new ArchiveState(needsUpdate, initialResources); } // initialResources is not empty if (!zipFile.exists()) { return new ArchiveState(true, initialResources); } if (needsUpdate && !doUpdate) { // we are recreating the archive, need all resources return new ArchiveState(true, initialResources); } Resource[][] newerResources = new Resource[rcs.length][]; for (int i = 0; i < rcs.length; i++) { if (initialResources[i].length == 0) { newerResources[i] = new Resource[] {}; continue; } for (int j = 0; j < initialResources[i].length; j++) { if (initialResources[i][j] instanceof FileResource && zipFile.equals(((FileResource) initialResources[i][j]).getFile())) { throw new BuildException("A zip file cannot include " + "itself", getLocation()); } } Resource[] rs = initialResources[i]; if (doFilesonly) { rs = selectFileResources(rs); } newerResources[i] = ResourceUtils.selectOutOfDateSources(this, rs, new IdentityMapper(), getZipScanner()); needsUpdate = needsUpdate || (newerResources[i].length > 0); if (needsUpdate && !doUpdate) { // we will return initialResources anyway, no reason // to scan further. break; } } if (needsUpdate && !doUpdate) { // we are recreating the archive, need all resources return new ArchiveState(true, initialResources); } return new ArchiveState(needsUpdate, newerResources); } /** * Fetch all included and not excluded resources from the sets. * *

Included directories will precede included files.

* @param filesets an array of filesets * @return the resources included * @since Ant 1.5.2 */ protected Resource[][] grabResources(FileSet[] filesets) { Resource[][] result = new Resource[filesets.length][]; for (int i = 0; i < filesets.length; i++) { boolean skipEmptyNames = true; if (filesets[i] instanceof ZipFileSet) { ZipFileSet zfs = (ZipFileSet) filesets[i]; skipEmptyNames = zfs.getPrefix(getProject()).equals("") && zfs.getFullpath(getProject()).equals(""); } DirectoryScanner rs = filesets[i].getDirectoryScanner(getProject()); if (rs instanceof ZipScanner) { ((ZipScanner) rs).setEncoding(encoding); } Vector resources = new Vector(); if (!doFilesonly) { String[] directories = rs.getIncludedDirectories(); for (int j = 0; j < directories.length; j++) { if (!"".equals(directories[j]) || !skipEmptyNames) { resources.addElement(rs.getResource(directories[j])); } } } String[] files = rs.getIncludedFiles(); for (int j = 0; j < files.length; j++) { if (!"".equals(files[j]) || !skipEmptyNames) { resources.addElement(rs.getResource(files[j])); } } result[i] = new Resource[resources.size()]; resources.copyInto(result[i]); } return result; } /** * Fetch all included and not excluded resources from the collections. * *

Included directories will precede included files.

* @param rcs an array of resource collections * @return the resources included * @since Ant 1.7 */ protected Resource[][] grabNonFileSetResources(ResourceCollection[] rcs) { Resource[][] result = new Resource[rcs.length][]; for (int i = 0; i < rcs.length; i++) { Iterator iter = rcs[i].iterator(); ArrayList rs = new ArrayList(); int lastDir = 0; while (iter.hasNext()) { Resource r = (Resource) iter.next(); if (r.isExists()) { if (r.isDirectory()) { rs.add(lastDir++, r); } else { rs.add(r); } } } result[i] = (Resource[]) rs.toArray(new Resource[rs.size()]); } return result; } /** * Add a directory to the zip stream. * @param dir the directort to add to the archive * @param zOut the stream to write to * @param vPath the name this entry shall have in the archive * @param mode the Unix permissions to set. * @throws IOException on error * @since Ant 1.5.2 */ protected void zipDir(File dir, ZipOutputStream zOut, String vPath, int mode) throws IOException { zipDir(dir, zOut, vPath, mode, null); } /** * Add a directory to the zip stream. * @param dir the directort to add to the archive * @param zOut the stream to write to * @param vPath the name this entry shall have in the archive * @param mode the Unix permissions to set. * @param extra ZipExtraFields to add * @throws IOException on error * @since Ant 1.6.3 */ protected void zipDir(File dir, ZipOutputStream zOut, String vPath, int mode, ZipExtraField[] extra) throws IOException { if (doFilesonly) { log("skipping directory " + vPath + " for file-only archive", Project.MSG_VERBOSE); return; } 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); if (!skipWriting) { ZipEntry ze = new ZipEntry (vPath); if (dir != null && dir.exists()) { // ZIPs store time with a granularity of 2 seconds, round up ze.setTime(dir.lastModified() + (roundUp ? 1999 : 0)); } else { // ZIPs store time with a granularity of 2 seconds, round up ze.setTime(System.currentTimeMillis() + (roundUp ? 1999 : 0)); } ze.setSize (0); ze.setMethod (ZipEntry.STORED); // This is faintly ridiculous: ze.setCrc (EMPTY_CRC); ze.setUnixMode(mode); if (extra != null) { ze.setExtraFields(extra); } zOut.putNextEntry(ze); } } /** * Adds a new entry to the archive, takes care of duplicates as well. * * @param in the stream to read data for the entry from. * @param zOut the stream to write to. * @param vPath the name this entry shall have in the archive. * @param lastModified last modification time for the entry. * @param fromArchive the original archive we are copying this * entry from, will be null if we are not copying from an archive. * @param mode the Unix permissions to set. * * @since Ant 1.5.2 * @throws IOException on error */ protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath, long lastModified, File fromArchive, int mode) 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); if (!skipWriting) { ZipEntry ze = new ZipEntry(vPath); ze.setTime(lastModified); ze.setMethod(doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED); /* * ZipOutputStream.putNextEntry expects the ZipEntry to * know its size and the CRC sum before you start writing * the data when using STORED mode - unless it is seekable. * * This forces us to process the data twice. */ if (!zOut.isSeekable() && !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()); } ze.setUnixMode(mode); 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); } /** * Method that gets called when adding from java.io.File instances. * *

This implementation delegates to the six-arg version.

* * @param file the file to add to the archive * @param zOut the stream to write to * @param vPath the name this entry shall have in the archive * @param mode the Unix permissions to set. * @throws IOException on error * * @since Ant 1.5.2 */ protected void zipFile(File file, ZipOutputStream zOut, String vPath, int mode) throws IOException { if (file.equals(zipFile)) { throw new BuildException("A zip file cannot include itself", getLocation()); } FileInputStream fIn = new FileInputStream(file); try { // ZIPs store time with a granularity of 2 seconds, round up zipFile(fIn, zOut, vPath, file.lastModified() + (roundUp ? 1999 : 0), null, mode); } finally { fIn.close(); } } /** * Ensure all parent dirs of a given entry have been added. * @param baseDir the base directory to use (may be null) * @param entry the entry name to create directories from * @param zOut the stream to write to * @param prefix a prefix to place on the created entries * @param dirMode the directory mode * @throws IOException on error * @since Ant 1.5.2 */ protected final void addParentDirs(File baseDir, String entry, ZipOutputStream zOut, String prefix, int dirMode) 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, dirMode); } } } /** * 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.

* *

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.

* * @see #reset */ protected void cleanUp() { addedDirs.clear(); addedFiles.removeAllElements(); entries.clear(); addingNewFiles = false; doUpdate = savedDoUpdate; Enumeration e = filesetsFromGroupfilesets.elements(); while (e.hasMoreElements()) { ZipFileSet zf = (ZipFileSet) e.nextElement(); resources.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() { resources.removeAllElements(); zipFile = null; baseDir = null; groupfilesets.removeAllElements(); duplicate = "add"; archiveType = "zip"; doCompress = true; emptyBehavior = "skip"; doUpdate = false; doFilesonly = false; encoding = null; } /** * Check is the resource arrays are empty. * @param r the arrays to check * @return true if all individual arrays are empty * * @since Ant 1.5.2 */ protected static final boolean isEmpty(Resource[][] r) { for (int i = 0; i < r.length; i++) { if (r[i].length > 0) { return false; } } return true; } /** * Drops all non-file resources from the given array. * @param orig the resources to filter * @return the filters resources * @since Ant 1.6 */ protected Resource[] selectFileResources(Resource[] orig) { if (orig.length == 0) { return orig; } Vector v = new Vector(orig.length); for (int i = 0; i < orig.length; i++) { if (!orig[i].isDirectory()) { v.addElement(orig[i]); } else { log("Ignoring directory " + orig[i].getName() + " as only files will be added.", Project.MSG_VERBOSE); } } if (v.size() != orig.length) { Resource[] r = new Resource[v.size()]; v.copyInto(r); return r; } return orig; } /** * Possible behaviors when a duplicate file is added: * "add", "preserve" or "fail" */ public static class Duplicate extends EnumeratedAttribute { /** * @see EnumeratedAttribute#getValues() */ /** {@inheritDoc} */ public String[] getValues() { return new String[] {"add", "preserve", "fail"}; } } /** * Holds the up-to-date status and the out-of-date resources of * the original archive. * * @since Ant 1.5.3 */ public static class ArchiveState { private boolean outOfDate; private Resource[][] resourcesToAdd; ArchiveState(boolean state, Resource[][] r) { outOfDate = state; resourcesToAdd = r; } /** * Return the outofdate status. * @return the outofdate status */ public boolean isOutOfDate() { return outOfDate; } /** * Get the resources to add. * @return the resources to add */ public Resource[][] getResourcesToAdd() { return resourcesToAdd; } /** * find out if there are absolutely no resources to add * @since Ant 1.6.3 * @return true if there are no resources to add */ public boolean isWithoutAnyResources() { if (resourcesToAdd == null) { return true; } for (int counter = 0; counter < resourcesToAdd.length; counter++) { if (resourcesToAdd[counter] != null) { if (resourcesToAdd[counter].length > 0) { return false; } } } return true; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy