/*
 *  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
 *
 *      https://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 org.apache.tools.ant.taskdefs;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.stream.Stream;
import java.util.zip.CRC32;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.FileScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.ArchiveFileSet;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.types.ZipScanner;
import org.apache.tools.ant.types.resources.ArchiveResource;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.types.resources.Union;
import org.apache.tools.ant.types.resources.ZipResource;
import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
import org.apache.tools.ant.util.DateUtils;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.ant.util.IdentityMapper;
import org.apache.tools.ant.util.MergingMapper;
import org.apache.tools.ant.util.ResourceUtils;
import org.apache.tools.zip.UnixStat;
import org.apache.tools.zip.Zip64Mode;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipExtraField;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.apache.tools.zip.ZipOutputStream.UnicodeExtraFieldPolicy;

Create a Zip file.
Since:Ant 1.1
@ant.taskcategory="packaging"
/** * Create a Zip file. * * @since Ant 1.1 * * @ant.task category="packaging" */
public class Zip extends MatchingTask { private static final int BUFFER_SIZE = 8 * 1024;
The granularity of timestamps inside a ZIP archive.
/** * The granularity of timestamps inside a ZIP archive. */
private static final int ZIP_FILE_TIMESTAMP_GRANULARITY = 2000; private static final int ROUNDUP_MILLIS = ZIP_FILE_TIMESTAMP_GRANULARITY - 1; // CheckStyle:VisibilityModifier OFF - bc private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); // For directories: private static final long EMPTY_CRC = new CRC32().getValue(); private static final ResourceSelector MISSING_SELECTOR = target -> !target.isExists(); private static final ResourceUtils.ResourceSelectorProvider MISSING_DIR_PROVIDER = sr -> MISSING_SELECTOR; protected File zipFile; // use to scan own archive private ZipScanner zs; private File baseDir; protected Hashtable<String, String> entries = new Hashtable<>(); private final List<FileSet> groupfilesets = new Vector<>(); private final List<ZipFileSet> 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"; protected String emptyBehavior = "skip"; private final List<ResourceCollection> resources = new Vector<>(); protected Hashtable<String, String> addedDirs = new Hashtable<>(); private final List<String> addedFiles = new Vector<>(); private String fixedModTime = null; // User-provided. private long modTimeMillis = 0; // Calculated.
If this flag is true, execute() will run most operations twice, the first time with skipWriting set to true and the second time with setting it to false.

The only situation in Ant's current code base where this is ever going to be true is if the jar task has been configured with a filesetmanifest other than "skip".

/** * If this flag is true, execute() will run most operations twice, * the first time with {@link #skipWriting skipWriting} set to * true and the second time with setting it to false. * * <p>The only situation in Ant's current code base where this is * ever going to be true is if the jar task has been configured * with a filesetmanifest other than "skip".</p> */
protected boolean doubleFilePass = false;
whether the methods should just perform some sort of dry-run.

Will only ever be true in the first pass if the task performs two passes because doubleFilePass is true.

/** * whether the methods should just perform some sort of dry-run. * * <p>Will only ever be true in the first pass if the task * performs two passes because {@link #doubleFilePass * doubleFilePass} is true.</p> */
protected boolean skipWriting = false;
Whether this is the first time the archive building methods are invoked.
Returns:true if either doubleFilePass is false or skipWriting is true.
Since:Ant 1.8.0
/** * Whether this is the first time the archive building methods are invoked. * * @return true if either {@link #doubleFilePass doubleFilePass} * is false or {@link #skipWriting skipWriting} is true. * * @since Ant 1.8.0 */
protected final boolean isFirstPass() { return !doubleFilePass || skipWriting; } // CheckStyle:VisibilityModifier ON // This boolean is set if the task detects that the // target is outofdate and has written to the target file. private boolean updatedFile = false;
true when we are adding new files into the Zip file, as opposed to adding back the unchanged files
/** * 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.
/** * 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
/** * 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
/** * 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
/** * Comment for the archive. * @since Ant 1.6.3 */
private String comment = ""; private int level = ZipOutputStream.DEFAULT_COMPRESSION;
Assume 0 Unix mode is intentional.
Since:Ant 1.8.0
/** * Assume 0 Unix mode is intentional. * @since Ant 1.8.0 */
private boolean preserve0Permissions = false;
Whether to set the language encoding flag when creating the archive.
Since:Ant 1.8.0
/** * Whether to set the language encoding flag when creating the archive. * * @since Ant 1.8.0 */
private boolean useLanguageEncodingFlag = true;
Whether to add unicode extra fields.
Since:Ant 1.8.0
/** * Whether to add unicode extra fields. * * @since Ant 1.8.0 */
private UnicodeExtraField createUnicodeExtraFields = UnicodeExtraField.NEVER;
Whether to fall back to UTF-8 if a name cannot be encoded using the specified encoding.
Since:Ant 1.8.0
/** * Whether to fall back to UTF-8 if a name cannot be encoded using * the specified encoding. * * @since Ant 1.8.0 */
private boolean fallBackToUTF8 = false;
Whether to enable Zip64 extensions.
Since:Ant 1.9.1
/** * Whether to enable Zip64 extensions. * * @since Ant 1.9.1 */
private Zip64ModeAttribute zip64Mode = Zip64ModeAttribute.AS_NEEDED;
This is the name/location of where to create the .zip file.
Params:
  • zipFile – the path of the zipFile
Deprecated:since 1.5.x. Use setDestFile(File) instead.
@ant.attributeignore="true"
/** * 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" */
@Deprecated public void setZipfile(final File zipFile) { setDestFile(zipFile); }
This is the name/location of where to create the file.
Params:
  • file – the path of the zipFile
Since:Ant 1.5
Deprecated:since 1.5.x. Use setDestFile(File) instead.
@ant.attributeignore="true"
/** * 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" */
@Deprecated public void setFile(final File file) { setDestFile(file); }
The file to create; required.
Params:
  • destFile – The new destination File
Since:Ant 1.5
/** * The file to create; required. * @since Ant 1.5 * @param destFile The new destination File */
public void setDestFile(final File destFile) { this.zipFile = destFile; }
The file to create.
Returns:the destination file
Since:Ant 1.5.2
/** * 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.
Params:
  • baseDir – the base directory
/** * Directory from which to archive files; optional. * @param baseDir the base directory */
public void setBasedir(final File baseDir) { this.baseDir = baseDir; }
Whether we want to compress the files or only store them; optional, default=true;
Params:
  • c – if true, compress the files
/** * Whether we want to compress the files or only store them; * optional, default=true; * @param c if true, compress the files */
public void setCompress(final boolean c) { doCompress = c; }
Whether we want to compress the files or only store them;
Returns:true if the files are to be compressed
Since:Ant 1.5.2
/** * 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.
Params:
  • f – if true, emulate sun's jar by not adding parent directories
/** * 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(final boolean f) { doFilesonly = f; }
If true, updates an existing file, otherwise overwrite any existing one; optional defaults to false.
Params:
  • c – if true, updates an existing zip file
/** * 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(final boolean c) { doUpdate = c; savedDoUpdate = c; }
Are we updating an existing archive?
Returns:true if updating an existing archive
/** * Are we updating an existing archive? * @return true if updating an existing archive */
public boolean isInUpdateMode() { return doUpdate; }
Adds a set of files.
Params:
  • set – the fileset to add
/** * Adds a set of files. * @param set the fileset to add */
public void addFileset(final FileSet set) { add(set); }
Adds a set of files that can be read from an archive and be given a prefix/fullpath.
Params:
  • set – the zipfileset to add
/** * 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(final ZipFileSet set) { add(set); }
Add a collection of resources to be archived.
Params:
  • a – the resources to archive
Since:Ant 1.7
/** * Add a collection of resources to be archived. * @param a the resources to archive * @since Ant 1.7 */
public void add(final ResourceCollection a) { resources.add(a); }
Adds a group of zip files.
Params:
  • set – the group (a fileset) to add
/** * Adds a group of zip files. * @param set the group (a fileset) to add */
public void addZipGroupFileset(final FileSet set) { groupfilesets.add(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
Params:
  • df – a Duplicate enumerated value
/** * Sets behavior for when a duplicate file is about to be added - * one of <code>add</code>, <code>preserve</code> or <code>fail</code>. * Possible values are: <code>add</code> (keep both * of the files); <code>preserve</code> (keep the first version * of the file found); <code>fail</code> halt a problem * Default for zip tasks is <code>add</code> * @param df a <code>Duplicate</code> enumerated value */
public void setDuplicate(final Duplicate df) { duplicate = df.getValue(); }
Possible behaviors when there are no matching files for the task: "fail", "skip", or "create".
/** * 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
Returns:the values
/** * The string values for the enumerated value * @return the values */
@Override 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.
Params:
  • we – a WhenEmpty enumerated value
/** * 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>. * @param we a <code>WhenEmpty</code> enumerated value */
public void setWhenempty(final WhenEmpty we) { emptyBehavior = we.getValue(); }
Encoding to use for filenames, defaults to the platform's default encoding.

For a list of possible values see https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html.

Params:
  • encoding – the encoding name
/** * Encoding to use for filenames, defaults to the platform's * default encoding. * * <p>For a list of possible values see <a * href="https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html">https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html</a>.</p> * @param encoding the encoding name */
public void setEncoding(final String encoding) { this.encoding = encoding; }
Encoding to use for filenames.
Returns:the name of the encoding to use
Since:Ant 1.5.2
/** * 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.
Params:
  • keep – if true, keep the original compression
Since:Ant 1.6
/** * 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(final boolean keep) { keepCompression = keep; }
Comment to use for archive.
Params:
  • comment – The content of the comment.
Since:Ant 1.6.3
/** * Comment to use for archive. * * @param comment The content of the comment. * @since Ant 1.6.3 */
public void setComment(final String comment) { this.comment = comment; }
Comment of the archive
Returns:Comment of the archive.
Since:Ant 1.6.3
/** * 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.
Params:
  • level – compression level.
Since:Ant 1.7
/** * Set the compression level to use. Default is * ZipOutputStream.DEFAULT_COMPRESSION. * @param level compression level. * @since Ant 1.7 */
public void setLevel(final int level) { this.level = level; }
Get the compression level.
Returns:compression level.
Since:Ant 1.7
/** * 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.

Params:
  • r – a boolean value
Since:Ant 1.6.2
/** * Whether the file modification times will be rounded up to the * next even number of seconds. * * <p>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.</p> * @param r a <code>boolean</code> value * @since Ant 1.6.2 */
public void setRoundUp(final boolean r) { roundUp = r; }
Assume 0 Unix mode is intentional.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Assume 0 Unix mode is intentional. * @param b boolean * @since Ant 1.8.0 */
public void setPreserve0Permissions(final boolean b) { preserve0Permissions = b; }
Assume 0 Unix mode is intentional.
Returns:boolean
Since:Ant 1.8.0
/** * Assume 0 Unix mode is intentional. * @return boolean * @since Ant 1.8.0 */
public boolean getPreserve0Permissions() { return preserve0Permissions; }
Whether to set the language encoding flag.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether to set the language encoding flag. * @param b boolean * @since Ant 1.8.0 */
public void setUseLanguageEncodingFlag(final boolean b) { useLanguageEncodingFlag = b; }
Whether the language encoding flag will be used.
Returns:boolean
Since:Ant 1.8.0
/** * Whether the language encoding flag will be used. * @return boolean * @since Ant 1.8.0 */
public boolean getUseLanguageEnodingFlag() { return useLanguageEncodingFlag; }
Whether Unicode extra fields will be created.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether Unicode extra fields will be created. * @param b boolean * @since Ant 1.8.0 */
public void setCreateUnicodeExtraFields(final UnicodeExtraField b) { createUnicodeExtraFields = b; }
Whether Unicode extra fields will be created.
Returns:boolean
Since:Ant 1.8.0
/** * Whether Unicode extra fields will be created. * @return boolean * @since Ant 1.8.0 */
public UnicodeExtraField getCreateUnicodeExtraFields() { return createUnicodeExtraFields; }
Whether to fall back to UTF-8 if a name cannot be encoded using the specified encoding.

Defaults to false.

Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether to fall back to UTF-8 if a name cannot be encoded using * the specified encoding. * * <p>Defaults to false.</p> * * @param b boolean * @since Ant 1.8.0 */
public void setFallBackToUTF8(final boolean b) { fallBackToUTF8 = b; }
Whether to fall back to UTF-8 if a name cannot be encoded using the specified encoding.
Returns:boolean
Since:Ant 1.8.0
/** * Whether to fall back to UTF-8 if a name cannot be encoded using * the specified encoding. * * @return boolean * @since Ant 1.8.0 */
public boolean getFallBackToUTF8() { return fallBackToUTF8; }
Whether Zip64 extensions should be used.
Params:
  • b – boolean
Since:Ant 1.9.1
/** * Whether Zip64 extensions should be used. * @param b boolean * @since Ant 1.9.1 */
public void setZip64Mode(final Zip64ModeAttribute b) { zip64Mode = b; }
Whether Zip64 extensions will be used.
Returns:boolean
Since:Ant 1.9.1
/** * Whether Zip64 extensions will be used. * @return boolean * @since Ant 1.9.1 */
public Zip64ModeAttribute getZip64Mode() { return zip64Mode; }
Set all stored file modification times to time.
Params:
  • time – Milliseconds since 1970-01-01 00:00, or YYYY-MM-DD{T/ }HH:MM[:SS[.SSS]][ ][±ZZ[[:]ZZ]], or MM/DD/YYYY HH:MM[:SS] {AM/PM}, where {a/b} indicates that you must choose one of a or b, and [c] indicates that you may use or omit c. ±ZZZZ is the timezone offset, and may be literally "Z" to mean GMT.
Since:Ant 1.10.2
/** * Set all stored file modification times to {@code time}. * @param time Milliseconds since 1970-01-01 00:00, or * <code>YYYY-MM-DD{T/ }HH:MM[:SS[.SSS]][ ][&plusmn;ZZ[[:]ZZ]]</code>, or * <code>MM/DD/YYYY HH:MM[:SS] {AM/PM}</code>, where {a/b} indicates * that you must choose one of a or b, and [c] indicates that you * may use or omit c. &plusmn;ZZZZ is the timezone offset, and may be * literally "Z" to mean GMT. * @since Ant 1.10.2 */
public void setModificationtime(String time) { fixedModTime = time; }
The file modification time previously provided to setModificationtime(String) or null if unset.
Returns:String
Since:Ant 1.10.2
/** * The file modification time previously provided to * {@link #setModificationtime(String)} or {@code null} if unset. * @return String * @since Ant 1.10.2 */
public String getModificationtime() { return fixedModTime; }
validate and build
Throws:
  • BuildException – on error
/** * validate and build * @throws BuildException on error */
@Override public void execute() throws BuildException { if (doubleFilePass) { skipWriting = true; executeMain(); skipWriting = false; executeMain(); } else { executeMain(); } }
Get the value of the updatedFile attribute. This should only be called after executeMain has been called.
Returns:true if executeMain has written to the zip file.
/** * Get the value of the updatedFile attribute. * This should only be called after executeMain has been * called. * @return true if executeMain has written to the zip file. */
protected boolean hasUpdatedFile() { return updatedFile; }
Build the zip file. This is called twice if doubleFilePass is true.
Throws:
  • BuildException – on error
/** * Build the zip file. * This is called twice if doubleFilePass is true. * @throws BuildException on error */
public void executeMain() throws BuildException { checkAttributesAndElements(); // Renamed version of original file, if it exists File renamedFile = null; addingNewFiles = true; processDoUpdate(); processGroupFilesets(); // collect filesets to pass them to getResourcesToAdd final List<ResourceCollection> vfss = new ArrayList<>(); if (baseDir != null) { final FileSet fs = (FileSet) getImplicitFileSet().clone(); fs.setDir(baseDir); vfss.add(fs); } vfss.addAll(resources); final ResourceCollection[] fss = vfss.toArray(new ResourceCollection[vfss.size()]); boolean success = false; try { // can also handle empty archives final ArchiveState state = getResourcesToAdd(fss, zipFile, false); // quick exit if the target is up to date if (!state.isOutOfDate()) { return; } final File parent = zipFile.getParentFile(); if (parent != null && !parent.isDirectory() && !(parent.mkdirs() || parent.isDirectory())) { throw new BuildException( "Failed to create missing parent directory for %s", zipFile); } updatedFile = true; if (!zipFile.exists() && state.isWithoutAnyResources()) { createEmptyZip(zipFile); return; } final Resource[][] addThem = state.getResourcesToAdd(); if (doUpdate) { renamedFile = renameFile(); } final String action = doUpdate ? "Updating " : "Building "; if (!skipWriting) { log(action + archiveType + ": " + zipFile.getAbsolutePath()); } ZipOutputStream zOut = null; try { if (!skipWriting) { zOut = new ZipOutputStream(zipFile); zOut.setEncoding(encoding); zOut.setUseLanguageEncodingFlag(useLanguageEncodingFlag); zOut.setCreateUnicodeExtraFields(createUnicodeExtraFields. getPolicy()); zOut.setFallbackToUTF8(fallBackToUTF8); zOut.setMethod(doCompress ? ZipOutputStream.DEFLATED : ZipOutputStream.STORED); zOut.setLevel(level); zOut.setUseZip64(zip64Mode.getMode()); } 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; final ZipFileSet oldFiles = new ZipFileSet(); oldFiles.setProject(getProject()); oldFiles.setSrc(renamedFile); oldFiles.setDefaultexcludes(false); for (String addedFile : addedFiles) { oldFiles.createExclude().setName(addedFile); } final DirectoryScanner ds = oldFiles.getDirectoryScanner(getProject()); ((ZipScanner) ds).setEncoding(encoding); Stream<String> includedResourceNames = Stream.of(ds.getIncludedFiles()); if (!doFilesonly) { includedResourceNames = Stream.concat(includedResourceNames, Stream.of(ds.getIncludedDirectories())); } Resource[] r = includedResourceNames.map(ds::getResource) .toArray(Resource[]::new); 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. closeZout(zOut, success); } } catch (final 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 (final IOException e) { msg += " (and I couldn't rename the temporary file " + renamedFile.getName() + " back)"; } } throw new BuildException(msg, ioe, getLocation()); } finally { cleanUp(); } }
rename the zip file.
/** rename the zip file. */
private File renameFile() { final File renamedFile = FILE_UTILS.createTempFile( "zip", ".tmp", zipFile.getParentFile(), true, false); try { FILE_UTILS.rename(zipFile, renamedFile); } catch (final SecurityException | IOException e) { throw new BuildException( "Unable to rename old file (%s) to temporary file", zipFile.getAbsolutePath()); } return renamedFile; }
Close zout
/** Close zout */
private void closeZout(final ZipOutputStream zOut, final boolean success) throws IOException { if (zOut == null) { return; } try { zOut.close(); } catch (final 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; } } }
Check the attributes and elements
/** Check the attributes and elements */
private void checkAttributesAndElements() { if (baseDir == null && resources.isEmpty() && groupfilesets.isEmpty() && "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 %s file to create!", archiveType); } if (fixedModTime != null) { try { modTimeMillis = DateUtils.parseLenientDateTime(fixedModTime).getTime(); } catch (ParseException pe) { throw new BuildException("Failed to parse date string %s.", fixedModTime); } if (roundUp) { modTimeMillis += ROUNDUP_MILLIS; } } if (zipFile.exists() && !zipFile.isFile()) { throw new BuildException("%s is not a file.", zipFile); } if (zipFile.exists() && !zipFile.canWrite()) { throw new BuildException("%s is read-only.", zipFile); } }
Process doupdate
/** Process doupdate */
private void processDoUpdate() { // 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; logWhenWriting("ignoring update attribute as " + archiveType + " doesn't exist.", Project.MSG_DEBUG); } }
Process groupfilesets
/** Process groupfilesets */
private void processGroupFilesets() { // Add the files found in groupfileset to fileset for (FileSet fs : groupfilesets) { logWhenWriting("Processing groupfileset ", Project.MSG_VERBOSE); final FileScanner scanner = fs.getDirectoryScanner(getProject()); final File basedir = scanner.getBasedir(); for (String file : scanner.getIncludedFiles()) { logWhenWriting("Adding file " + file + " to fileset", Project.MSG_VERBOSE); final ZipFileSet zf = new ZipFileSet(); zf.setProject(getProject()); zf.setSrc(new File(basedir, file)); add(zf); filesetsFromGroupfilesets.add(zf); } } }
Indicates if the task is adding new files into the archive as opposed to copying back unchanged files from the backup copy
Returns:true if adding new files
/** * 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.
Params:
  • fileset – may give additional information like fullpath or permissions.
  • resources – the resources to add
  • zOut – the stream to write to
Throws:
Since:Ant 1.5.2
/** * 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(final FileSet fileset, final Resource[] resources, final 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.isEmpty() && !fullpath.isEmpty()) { throw new BuildException( "Both prefix and fullpath attributes must not be set on the same fileset."); } if (resources.length != 1 && !fullpath.isEmpty()) { throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file."); } if (!prefix.isEmpty()) { 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 (Resource resource : resources) { String name; if (fullpath.isEmpty()) { name = resource.getName(); } else { name = fullpath; } name = name.replace(File.separatorChar, '/'); if (name.isEmpty()) { continue; } if (resource.isDirectory()) { if (doFilesonly) { continue; } final int thisDirMode = zfs != null && zfs.hasDirModeBeenSet() ? dirMode : getUnixMode(resource, zf, dirMode); addDirectoryResource(resource, name, prefix, base, zOut, dirMode, thisDirMode); } else { // !isDirectory addParentDirs(base, name, zOut, prefix, dirMode); if (dealingWithFiles) { final File f = FILE_UTILS.resolveFile(base, resource.getName()); zipFile(f, zOut, prefix + name, fileMode); } else { final int thisFileMode = zfs != null && zfs.hasFileModeBeenSet() ? fileMode : getUnixMode(resource, zf, fileMode); addResource(resource, name, prefix, zOut, thisFileMode, zf, zfs == null ? null : zfs.getSrc(getProject())); } } } } finally { if (zf != null) { zf.close(); } } }
Add a directory entry to the archive using a specified Unix-mode and the default mode for its parent directories (if necessary).
/** * Add a directory entry to the archive using a specified * Unix-mode and the default mode for its parent directories (if * necessary). */
private void addDirectoryResource(final Resource r, String name, final String prefix, final File base, final ZipOutputStream zOut, final int defaultDirMode, final int thisDirMode) throws IOException { if (!name.endsWith("/")) { name += "/"; } final int nextToLastSlash = name.lastIndexOf('/', name.length() - 2); if (nextToLastSlash != -1) { addParentDirs(base, name.substring(0, nextToLastSlash + 1), zOut, prefix, defaultDirMode); } zipDir(r, zOut, prefix + name, thisDirMode, r instanceof ZipResource ? ((ZipResource) r).getExtraFields() : null); }
Determine a Resource's Unix mode or return the given default value if not available.
/** * Determine a Resource's Unix mode or return the given default * value if not available. */
private int getUnixMode(final Resource r, final ZipFile zf, final int defaultMode) { int unixMode = defaultMode; if (zf != null) { final ZipEntry ze = zf.getEntry(r.getName()); unixMode = ze.getUnixMode(); if ((unixMode == 0 || unixMode == UnixStat.DIR_FLAG) && !preserve0Permissions) { unixMode = defaultMode; } } else if (r instanceof ArchiveResource) { unixMode = ((ArchiveResource) r).getMode(); } return unixMode; }
Add a file entry.
/** * Add a file entry. */
private void addResource(final Resource r, final String name, final String prefix, final ZipOutputStream zOut, final int mode, final ZipFile zf, final File fromArchive) throws IOException { if (zf != null) { final ZipEntry ze = zf.getEntry(r.getName()); if (ze != null) { final boolean oldCompress = doCompress; if (keepCompression) { doCompress = (ze.getMethod() == ZipEntry.DEFLATED); } try (final BufferedInputStream is = new BufferedInputStream(zf.getInputStream(ze))) { zipFile(is, zOut, prefix + name, ze.getTime(), fromArchive, mode, ze.getExtraFields(true)); } finally { doCompress = oldCompress; } } } else { try (final BufferedInputStream is = new BufferedInputStream(r.getInputStream())) { zipFile(is, zOut, prefix + name, r.getLastModified(), fromArchive, mode, r instanceof ZipResource ? ((ZipResource) r).getExtraFields() : null); } } }
Add the given resources.
Params:
  • rc – may give additional information like fullpath or permissions.
  • resources – the resources to add
  • zOut – the stream to write to
Throws:
Since:Ant 1.7
/** * 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(final ResourceCollection rc, final Resource[] resources, final ZipOutputStream zOut) throws IOException { if (rc instanceof FileSet) { addResources((FileSet) rc, resources, zOut); return; } for (final Resource resource : resources) { String name = resource.getName(); if (name == null) { continue; } name = name.replace(File.separatorChar, '/'); if (name.isEmpty()) { continue; } if (resource.isDirectory() && doFilesonly) { continue; } File base = null; final FileProvider fp = resource.as(FileProvider.class); if (fp != null) { base = ResourceUtils.asFileResource(fp).getBaseDir(); } if (resource.isDirectory()) { addDirectoryResource(resource, name, "", base, zOut, ArchiveFileSet.DEFAULT_DIR_MODE, ArchiveFileSet.DEFAULT_DIR_MODE); } else { addParentDirs(base, name, zOut, "", ArchiveFileSet.DEFAULT_DIR_MODE); if (fp != null) { final File f = (fp).getFile(); zipFile(f, zOut, name, ArchiveFileSet.DEFAULT_FILE_MODE); } else { addResource(resource, name, "", zOut, ArchiveFileSet.DEFAULT_FILE_MODE, null, null); } } } }
method for subclasses to override
Params:
  • zOut – the zip output stream
Throws:
/** * method for subclasses to override * @param zOut the zip output stream * @throws IOException on output error * @throws BuildException on other errors */
protected void initZipOutputStream(final ZipOutputStream zOut) throws IOException, BuildException { }
method for subclasses to override
Params:
  • zOut – the zip output stream
Throws:
/** * method for subclasses to override * @param zOut the zip output stream * @throws IOException on output error * @throws BuildException on other errors */
protected void finalizeZipOutputStream(final ZipOutputStream zOut) throws IOException, BuildException { }
Create an empty zip file
Params:
  • zipFile – the zip file
Throws:
Returns:true for historic reasons
/** * Create an empty zip file * @param zipFile the zip file * @return true for historic reasons * @throws BuildException on error */
protected boolean createEmptyZip(final 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. if (!skipWriting) { log("Note: creating empty " + archiveType + " archive " + zipFile, Project.MSG_INFO); } try (OutputStream os = Files.newOutputStream(zipFile.toPath())) { // CheckStyle:MagicNumber OFF // Cf. PKZIP specification. final byte[] empty = new byte[22]; empty[0] = 80; // P empty[1] = 75; // K empty[2] = 5; empty[3] = 6; // remainder zeros // CheckStyle:MagicNumber ON os.write(empty); } catch (final IOException ioe) { throw new BuildException("Could not create empty ZIP archive " + "(" + ioe.getMessage() + ")", ioe, getLocation()); } return true; }
Since:Ant 1.5.2
/** * @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 getNonFileSetResourcesToAdd 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.

Params:
  • rcs – The resource collections to grab resources from
  • zipFile – intended archive file (may or may not exist)
  • 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.
Throws:
Returns:an array of resources to add for each fileset passed in as well as a flag that indicates whether the archive is uptodate.
Since:Ant 1.7
/** * Collect the resources that are newer than the corresponding * entries (or missing) in the original archive. * * <p>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 <code>super.getResourcesToAdd</code> and indicate with the * third arg if they already know that the archive is * out-of-date.</p> * * <p>This method first delegates to getNonFileSetResourcesToAdd * 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.</p> * * @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 * <code>super.getResourcesToAdd</code>. * @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(final ResourceCollection[] rcs, final File zipFile, final boolean needsUpdate) throws BuildException { final List<FileSet> filesets = new ArrayList<>(); final List<ResourceCollection> rest = new ArrayList<>(); for (ResourceCollection rc : rcs) { if (rc instanceof FileSet) { filesets.add((FileSet) rc); } else { rest.add(rc); } } final ResourceCollection[] rc = rest.toArray(new ResourceCollection[rest.size()]); ArchiveState as = getNonFileSetResourcesToAdd(rc, zipFile, needsUpdate); final FileSet[] fs = filesets.toArray(new FileSet[filesets.size()]); final 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); } final 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); } /* * This is yet another hacky construct to extend the FileSet[] * getResourcesToAdd method so we can pass the information whether * non-fileset resources have been available to it without having * to move the withEmpty behavior checks (since either would break * subclasses in several ways). */ private static final ThreadLocal<Boolean> HAVE_NON_FILE_SET_RESOURCES_TO_ADD = ThreadLocal.withInitial(() -> Boolean.FALSE);
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.

Params:
  • filesets – The filesets to grab resources from
  • zipFile – intended archive file (may or may not exist)
  • 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.
Throws:
Returns:an array of resources to add for each fileset passed in as well as a flag that indicates whether the archive is uptodate.
/** * Collect the resources that are newer than the corresponding * entries (or missing) in the original archive. * * <p>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 <code>super.getResourcesToAdd</code> and indicate with the * third arg if they already know that the archive is * out-of-date.</p> * * @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 * <code>super.getResourcesToAdd</code>. * @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(final FileSet[] filesets, final File zipFile, boolean needsUpdate) throws BuildException { final Resource[][] initialResources = grabResources(filesets); if (isEmpty(initialResources)) { if (Boolean.FALSE.equals(HAVE_NON_FILE_SET_RESOURCES_TO_ADD.get())) { 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 <jar> detects that the manifest * has changed, for example. The manifest is not * part of any resources because of our support * for inline <manifest>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 ("skip".equals(emptyBehavior)) { if (doUpdate) { logWhenWriting(archiveType + " archive " + zipFile + " not updated because no new files were" + " included.", Project.MSG_VERBOSE); } else { logWhenWriting("Warning: skipping " + archiveType + " archive " + zipFile + " because no files were included.", Project.MSG_WARN); } } else if ("fail".equals(emptyBehavior)) { throw new BuildException("Cannot create " + archiveType + " archive " + zipFile + ": no files were included.", getLocation()); } else { // Create. if (!zipFile.exists()) { needsUpdate = true; } } } // either there are non-fileset resources or we // (re-)create the archive anyway 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); } final Resource[][] newerResources = new Resource[filesets.length][]; for (int i = 0; i < filesets.length; i++) { if (!(fileset instanceof ZipFileSet) || ((ZipFileSet) fileset).getSrc(getProject()) == null) { final File base = filesets[i].getDir(getProject()); for (int j = 0; j < initialResources[i].length; j++) { final 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) { final ZipFileSet zfs = (ZipFileSet) filesets[i]; if (zfs.getFullpath(getProject()) != null && !zfs.getFullpath(getProject()).isEmpty()) { // in this case all files from origin map to // the fullPath attribute of the zipfileset at // destination final MergingMapper fm = new MergingMapper(); fm.setTo(zfs.getFullpath(getProject())); myMapper = fm; } else if (zfs.getPrefix(getProject()) != null && !zfs.getPrefix(getProject()).isEmpty()) { final GlobPatternMapper gm = new GlobPatternMapper(); gm.setFrom("*"); String prefix = zfs.getPrefix(getProject()); if (!prefix.endsWith("/") && !prefix.endsWith("\\")) { prefix += "/"; } gm.setTo(prefix + "*"); myMapper = gm; } } newerResources[i] = selectOutOfDateResources(initialResources[i], myMapper); 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.

Params:
  • rcs – The filesets to grab resources from
  • zipFile – intended archive file (may or may not exist)
  • 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.
Throws:
Returns:an array of resources to add for each fileset passed in as well as a flag that indicates whether the archive is uptodate.
/** * Collect the resources that are newer than the corresponding * entries (or missing) in the original archive. * * <p>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 <code>super.getResourcesToAdd</code> and indicate with the * third arg if they already know that the archive is * out-of-date.</p> * * @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 * <code>super.getResourcesToAdd</code>. * @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(final ResourceCollection[] rcs, final File zipFile, boolean needsUpdate) throws BuildException { /* * Backwards compatibility forces us to repeat the logic of * getResourcesToAdd(FileSet[], ...) here once again. */ final Resource[][] initialResources = grabNonFileSetResources(rcs); final boolean empty = isEmpty(initialResources); HAVE_NON_FILE_SET_RESOURCES_TO_ADD.set(!empty); if (empty) { // 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); } final 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++) { final FileProvider fp = initialResources[i][j].as(FileProvider.class); if (fp != null && zipFile.equals(fp.getFile())) { throw new BuildException("A zip file cannot include itself", getLocation()); } } newerResources[i] = selectOutOfDateResources(initialResources[i], new IdentityMapper()); 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); } private Resource[] selectOutOfDateResources(final Resource[] initial, final FileNameMapper mapper) { final Resource[] rs = selectFileResources(initial); Resource[] result = ResourceUtils.selectOutOfDateSources(this, rs, mapper, getZipScanner(), ZIP_FILE_TIMESTAMP_GRANULARITY); if (!doFilesonly) { final Union u = new Union(); u.addAll(Arrays.asList(selectDirectoryResources(initial))); final ResourceCollection rc = ResourceUtils.selectSources(this, u, mapper, getZipScanner(), MISSING_DIR_PROVIDER); if (!rc.isEmpty()) { final List<Resource> newer = new ArrayList<>(); newer.addAll(Arrays.asList(((Union) rc).listResources())); newer.addAll(Arrays.asList(result)); result = newer.toArray(result); } } return result; }
Fetch all included and not excluded resources from the sets.

Included directories will precede included files.

Params:
  • filesets – an array of filesets
Returns:the resources included
Since:Ant 1.5.2
/** * Fetch all included and not excluded resources from the sets. * * <p>Included directories will precede included files.</p> * @param filesets an array of filesets * @return the resources included * @since Ant 1.5.2 */
protected Resource[][] grabResources(final FileSet[] filesets) { final Resource[][] result = new Resource[filesets.length][]; for (int i = 0; i < filesets.length; i++) { boolean skipEmptyNames = true; if (filesets[i] instanceof ZipFileSet) { final ZipFileSet zfs = (ZipFileSet) filesets[i]; skipEmptyNames = zfs.getPrefix(getProject()).isEmpty() && zfs.getFullpath(getProject()).isEmpty(); } final DirectoryScanner rs = filesets[i].getDirectoryScanner(getProject()); if (rs instanceof ZipScanner) { ((ZipScanner) rs).setEncoding(encoding); } final List<Resource> resources = new Vector<>(); if (!doFilesonly) { for (String d : rs.getIncludedDirectories()) { if (!d.isEmpty() || !skipEmptyNames) { resources.add(rs.getResource(d)); } } } for (String f : rs.getIncludedFiles()) { if (!f.isEmpty() || !skipEmptyNames) { resources.add(rs.getResource(f)); } } result[i] = resources.toArray(new Resource[resources.size()]); } return result; }
Fetch all included and not excluded resources from the collections.

Included directories will precede included files.

Params:
  • rcs – an array of resource collections
Returns:the resources included
Since:Ant 1.7
/** * Fetch all included and not excluded resources from the collections. * * <p>Included directories will precede included files.</p> * @param rcs an array of resource collections * @return the resources included * @since Ant 1.7 */
protected Resource[][] grabNonFileSetResources(final ResourceCollection[] rcs) { final Resource[][] result = new Resource[rcs.length][]; for (int i = 0; i < rcs.length; i++) { final List<Resource> dirs = new ArrayList<>(); final List<Resource> files = new ArrayList<>(); for (final Resource r : rcs[i]) { if (r.isDirectory()) { dirs.add(r); } else if (r.isExists()) { files.add(r); } } // make sure directories are in alpha-order - this also // ensures parents come before their children dirs.sort(Comparator.comparing(Resource::getName)); final List<Resource> rs = new ArrayList<>(dirs); rs.addAll(files); result[i] = rs.toArray(new Resource[rs.size()]); } return result; }
Add a directory to the zip stream.
Params:
  • dir – the directory to add to the archive
  • zOut – the stream to write to
  • vPath – the name this entry shall have in the archive
  • mode – the Unix permissions to set.
Throws:
Since:Ant 1.5.2
/** * Add a directory to the zip stream. * @param dir the directory 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(final File dir, final ZipOutputStream zOut, final String vPath, final int mode) throws IOException { zipDir(dir, zOut, vPath, mode, null); }
Add a directory to the zip stream.
Params:
  • dir – the directory to add to the archive
  • zOut – the stream to write to
  • vPath – the name this entry shall have in the archive
  • mode – the Unix permissions to set.
  • extra – ZipExtraFields to add
Throws:
Since:Ant 1.6.3
/** * Add a directory to the zip stream. * @param dir the directory 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(final File dir, final ZipOutputStream zOut, final String vPath, final int mode, final ZipExtraField[] extra) throws IOException { zipDir(dir == null ? null : new FileResource(dir), zOut, vPath, mode, extra); }
Add a directory to the zip stream.
Params:
  • dir – the directory to add to the archive
  • zOut – the stream to write to
  • vPath – the name this entry shall have in the archive
  • mode – the Unix permissions to set.
  • extra – ZipExtraFields to add
Throws:
Since:Ant 1.8.0
/** * Add a directory to the zip stream. * @param dir the directory 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.8.0 */
protected void zipDir(final Resource dir, final ZipOutputStream zOut, final String vPath, final int mode, final ZipExtraField[] extra) throws IOException { if (doFilesonly) { logWhenWriting("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; } logWhenWriting("adding directory " + vPath, Project.MSG_VERBOSE); addedDirs.put(vPath, vPath); if (!skipWriting) { final ZipEntry ze = new ZipEntry(vPath); // ZIPs store time with a granularity of 2 seconds, round up final int millisToAdd = roundUp ? ROUNDUP_MILLIS : 0; if (fixedModTime != null) { ze.setTime(modTimeMillis); } else if (dir != null && dir.isExists()) { ze.setTime(dir.getLastModified() + millisToAdd); } else { ze.setTime(System.currentTimeMillis() + millisToAdd); } 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); } } /* * This is a hacky construct to extend the zipFile method to * support a new parameter (extra fields to preserve) without * breaking subclasses that override the old method signature. */ private static final ThreadLocal<ZipExtraField[]> CURRENT_ZIP_EXTRA = new ThreadLocal<>();
Provides the extra fields for the zip entry currently being added to the archive - if any.
Returns:ZipExtraField[]
Since:Ant 1.8.0
/** * Provides the extra fields for the zip entry currently being * added to the archive - if any. * @return ZipExtraField[] * @since Ant 1.8.0 */
protected final ZipExtraField[] getCurrentExtraFields() { return CURRENT_ZIP_EXTRA.get(); }
Sets the extra fields for the zip entry currently being added to the archive - if any.
Params:
  • extra – ZipExtraField[]
Since:Ant 1.8.0
/** * Sets the extra fields for the zip entry currently being * added to the archive - if any. * @param extra ZipExtraField[] * @since Ant 1.8.0 */
protected final void setCurrentExtraFields(final ZipExtraField[] extra) { CURRENT_ZIP_EXTRA.set(extra); }
Adds a new entry to the archive, takes care of duplicates as well.
Params:
  • in – the stream to read data for the entry from. The caller of the method is responsible for closing the stream.
  • zOut – the stream to write to.
  • vPath – the name this entry shall have in the archive.
  • lastModified – last modification time for the entry.
  • fromArchive – the original archive we are copying this entry from, will be null if we are not copying from an archive.
  • mode – the Unix permissions to set.
Throws:
Since:Ant 1.5.2
/** * Adds a new entry to the archive, takes care of duplicates as well. * * @param in the stream to read data for the entry from. The * caller of the method is responsible for closing the stream. * @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(final InputStream in, final ZipOutputStream zOut, final String vPath, final long lastModified, final File fromArchive, final int mode) throws IOException { // fromArchive is used in subclasses overriding this method if (entries.containsKey(vPath)) { if ("preserve".equals(duplicate)) { logWhenWriting(vPath + " already added, skipping", Project.MSG_INFO); return; } if ("fail".equals(duplicate)) { throw new BuildException( "Duplicate file %s was found and the duplicate attribute is 'fail'.", vPath); } // duplicate equal to add, so we continue logWhenWriting("duplicate file " + vPath + " found, adding.", Project.MSG_VERBOSE); } else { logWhenWriting("adding entry " + vPath, Project.MSG_VERBOSE); } entries.put(vPath, vPath); if (!skipWriting) { final ZipEntry ze = new ZipEntry(vPath); ze.setTime(fixedModTime != null ? modTimeMillis : lastModified); ze.setMethod(doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED); // if the input stream doesn't support mark/reset ability, we wrap it in a // stream that adds that support. // Note: We do *not* close this newly created wrapping input stream, since // we don't "own" the underlying input stream that's passed to us and closing // that is the responsibility of the caller. final InputStream markableInputStream = in.markSupported() ? in : new BufferedInputStream(in); /* * 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; final CRC32 cal = new CRC32(); markableInputStream.mark(Integer.MAX_VALUE); final byte[] buffer = new byte[BUFFER_SIZE]; int count = 0; do { size += count; cal.update(buffer, 0, count); count = markableInputStream.read(buffer, 0, buffer.length); } while (count != -1); markableInputStream.reset(); ze.setSize(size); ze.setCrc(cal.getValue()); } ze.setUnixMode(mode); final ZipExtraField[] extra = getCurrentExtraFields(); if (extra != null) { ze.setExtraFields(extra); } zOut.putNextEntry(ze); final byte[] buffer = new byte[BUFFER_SIZE]; int count = 0; do { if (count != 0) { zOut.write(buffer, 0, count); } count = markableInputStream.read(buffer, 0, buffer.length); } while (count != -1); } addedFiles.add(vPath); }
Adds a new entry to the archive, takes care of duplicates as well.
Params:
  • in – the stream to read data for the entry from. The caller of the method is responsible for closing the stream.
  • zOut – the stream to write to.
  • vPath – the name this entry shall have in the archive.
  • lastModified – last modification time for the entry.
  • fromArchive – the original archive we are copying this entry from, will be null if we are not copying from an archive.
  • mode – the Unix permissions to set.
  • extra – ZipExtraFields to add
Throws:
Since:Ant 1.8.0
/** * Adds a new entry to the archive, takes care of duplicates as well. * * @param in the stream to read data for the entry from. The * caller of the method is responsible for closing the stream. * @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. * @param extra ZipExtraFields to add * * @since Ant 1.8.0 * @throws IOException on error */
protected final void zipFile(final InputStream in, final ZipOutputStream zOut, final String vPath, final long lastModified, final File fromArchive, final int mode, final ZipExtraField[] extra) throws IOException { try { setCurrentExtraFields(extra); zipFile(in, zOut, vPath, lastModified, fromArchive, mode); } finally { setCurrentExtraFields(null); } }
Method that gets called when adding from java.io.File instances.

This implementation delegates to the six-arg version.

Params:
  • file – the file to add to the archive
  • zOut – the stream to write to
  • vPath – the name this entry shall have in the archive
  • mode – the Unix permissions to set.
Throws:
Since:Ant 1.5.2
/** * Method that gets called when adding from <code>java.io.File</code> instances. * * <p>This implementation delegates to the six-arg version.</p> * * @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(final File file, final ZipOutputStream zOut, final String vPath, final int mode) throws IOException { if (file.equals(zipFile)) { throw new BuildException("A zip file cannot include itself", getLocation()); } try (final BufferedInputStream bIn = new BufferedInputStream(Files.newInputStream(file.toPath()))) { // ZIPs store time with a granularity of 2 seconds, round up zipFile(bIn, zOut, vPath, file.lastModified() + (roundUp ? ROUNDUP_MILLIS : 0), null, mode); } }
Ensure all parent dirs of a given entry have been added.
Params:
  • baseDir – the base directory to use (may be null)
  • entry – the entry name to create directories from
  • zOut – the stream to write to
  • prefix – a prefix to place on the created entries
  • dirMode – the directory mode
Throws:
Since:Ant 1.5.2
/** * 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(final File baseDir, final String entry, final ZipOutputStream zOut, final String prefix, final int dirMode) throws IOException { if (!doFilesonly) { final Stack<String> directories = new Stack<>(); int slashPos = entry.length(); while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) { final String dir = entry.substring(0, slashPos + 1); if (addedDirs.get(prefix + dir) != null) { break; } directories.push(dir); } while (!directories.isEmpty()) { final String dir = directories.pop(); File f; 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 Also:
  • reset
/** * 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.clear(); entries.clear(); addingNewFiles = false; doUpdate = savedDoUpdate; resources.removeAll(filesetsFromGroupfilesets); filesetsFromGroupfilesets.clear(); HAVE_NON_FILE_SET_RESOURCES_TO_ADD.set(Boolean.FALSE); }
Makes this instance reset all attributes to their default values and forget all children.
See Also:
Since:Ant 1.5
/** * Makes this instance reset all attributes to their default * values and forget all children. * * @since Ant 1.5 * * @see #cleanUp */
public void reset() { resources.clear(); zipFile = null; baseDir = null; groupfilesets.clear(); duplicate = "add"; archiveType = "zip"; doCompress = true; emptyBehavior = "skip"; doUpdate = false; doFilesonly = false; encoding = null; }
Check is the resource arrays are empty.
Params:
  • r – the arrays to check
Returns:true if all individual arrays are empty
Since:Ant 1.5.2
/** * 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(final Resource[][] r) { for (Resource[] element : r) { if (element.length > 0) { return false; } } return true; }
Drops all non-file resources from the given array.
Params:
  • orig – the resources to filter
Returns:the filters resources
Since:Ant 1.6
/** * 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(final Resource[] orig) { return selectResources(orig, r -> { if (!r.isDirectory()) { return true; } if (doFilesonly) { logWhenWriting("Ignoring directory " + r.getName() + " as only files will" + " be added.", Project.MSG_VERBOSE); } return false; }); }
Drops all non-directory resources from the given array.
Params:
  • orig – the resources to filter
Returns:the filters resources
Since:Ant 1.8.0
/** * Drops all non-directory resources from the given array. * @param orig the resources to filter * @return the filters resources * @since Ant 1.8.0 */
protected Resource[] selectDirectoryResources(final Resource[] orig) { return selectResources(orig, Resource::isDirectory); }
Drops all resources from the given array that are not selected
Params:
  • orig – the resources to filter
  • selector – ResourceSelector
Returns:the filters resources
Since:Ant 1.8.0
/** * Drops all resources from the given array that are not selected * @param orig the resources to filter * @param selector ResourceSelector * @return the filters resources * @since Ant 1.8.0 */
protected Resource[] selectResources(final Resource[] orig, final ResourceSelector selector) { if (orig.length == 0) { return orig; } Resource[] result = Stream.of(orig).filter(selector::isSelected) .toArray(Resource[]::new); return result.length == orig.length ? orig : result; }
Logs a message at the given output level, but only if this is the pass that will actually create the archive.
Params:
  • msg – String
  • level – int
Since:Ant 1.8.0
/** * Logs a message at the given output level, but only if this is * the pass that will actually create the archive. * * @param msg String * @param level int * @since Ant 1.8.0 */
protected void logWhenWriting(final String msg, final int level) { if (!skipWriting) { log(msg, level); } }
Possible behaviors when a duplicate file is added: "add", "preserve" or "fail"
/** * Possible behaviors when a duplicate file is added: * "add", "preserve" or "fail" */
public static class Duplicate extends EnumeratedAttribute {
See Also:
  • {@inheritDoc}
/** * @see EnumeratedAttribute#getValues() * {@inheritDoc} */
@Override 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
/** * 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 final boolean outOfDate; private final Resource[][] resourcesToAdd; ArchiveState(final boolean state, final Resource[][] r) { outOfDate = state; resourcesToAdd = r; }
Return the outofdate status.
Returns:the outofdate status
/** * Return the outofdate status. * @return the outofdate status */
public boolean isOutOfDate() { return outOfDate; }
Get the resources to add.
Returns:the resources to add
/** * 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
Returns:true if there are no resources to add
/** * 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 (Resource[] element : resourcesToAdd) { if (element != null && element.length > 0) { return false; } } return true; } }
Policy for creation of Unicode extra fields: never, always or not-encodeable.
Since:Ant 1.8.0
/** * Policy for creation of Unicode extra fields: never, always or * not-encodeable. * * @since Ant 1.8.0 */
public static final class UnicodeExtraField extends EnumeratedAttribute { private static final Map<String, UnicodeExtraFieldPolicy> POLICIES = new HashMap<>(); private static final String NEVER_KEY = "never"; private static final String ALWAYS_KEY = "always"; private static final String N_E_KEY = "not-encodeable"; static { POLICIES.put(NEVER_KEY, ZipOutputStream.UnicodeExtraFieldPolicy.NEVER); POLICIES.put(ALWAYS_KEY, ZipOutputStream.UnicodeExtraFieldPolicy.ALWAYS); POLICIES.put(N_E_KEY, ZipOutputStream.UnicodeExtraFieldPolicy .NOT_ENCODEABLE); } @Override public String[] getValues() { return new String[] {NEVER_KEY, ALWAYS_KEY, N_E_KEY}; } public static final UnicodeExtraField NEVER = new UnicodeExtraField(NEVER_KEY); private UnicodeExtraField(final String name) { setValue(name); } public UnicodeExtraField() { } public ZipOutputStream.UnicodeExtraFieldPolicy getPolicy() { return POLICIES.get(getValue()); } }
The choices for Zip64 extensions.

never: never add any Zip64 extensions. This will cause the task to fail if you try to add entries bigger than 4GB or create an archive bigger than 4GB or holding more that 65535 entries.

as-needed: create Zip64 extensions only when the entry's size is bigger than 4GB or one of the archive limits is hit. This mode also adds partial Zip64 extensions for all deflated entries written by Ant.

always: create Zip64 extensions for all entries.

Note some ZIP implementations don't handle Zip64 extensions well and others may fail if the Zip64 extra field data is only present inside the local file header but not the central directory - which is what as-needed may result in. Java5 and Microsoft Visual Studio's Extension loader are known to fconsider the archive broken in such cases. If you are targeting such an archiver use the value never unless you know you need Zip64 extensions.

Since:Ant 1.9.1
/** * The choices for Zip64 extensions. * * <p><b>never</b>: never add any Zip64 extensions. This will * cause the task to fail if you try to add entries bigger than * 4GB or create an archive bigger than 4GB or holding more that * 65535 entries.</p> * * <p><b>as-needed</b>: create Zip64 extensions only when the * entry's size is bigger than 4GB or one of the archive limits is * hit. This mode also adds partial Zip64 extensions for all * deflated entries written by Ant.</p> * * <p><b>always</b>: create Zip64 extensions for all entries.</p> * * <p><b>Note</b> some ZIP implementations don't handle Zip64 * extensions well and others may fail if the Zip64 extra field * data is only present inside the local file header but not the * central directory - which is what <em>as-needed</em> may result * in. Java5 and Microsoft Visual Studio's Extension loader are * known to fconsider the archive broken in such cases. If you * are targeting such an archiver use the value <em>never</em> * unless you know you need Zip64 extensions.</p> * * @since Ant 1.9.1 */
public static final class Zip64ModeAttribute extends EnumeratedAttribute { private static final Map<String, Zip64Mode> MODES = new HashMap<>(); private static final String NEVER_KEY = "never"; private static final String ALWAYS_KEY = "always"; private static final String A_N_KEY = "as-needed"; static { MODES.put(NEVER_KEY, Zip64Mode.Never); MODES.put(ALWAYS_KEY, Zip64Mode.Always); MODES.put(A_N_KEY, Zip64Mode.AsNeeded); } @Override public String[] getValues() { return new String[] {NEVER_KEY, ALWAYS_KEY, A_N_KEY}; } public static final Zip64ModeAttribute NEVER = new Zip64ModeAttribute(NEVER_KEY); public static final Zip64ModeAttribute AS_NEEDED = new Zip64ModeAttribute(A_N_KEY); private Zip64ModeAttribute(final String name) { setValue(name); } public Zip64ModeAttribute() { } public Zip64Mode getMode() { return MODES.get(getValue()); } } }