aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Zip.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Zip.java')
-rw-r--r--framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Zip.java2274
1 files changed, 2274 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Zip.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Zip.java
new file mode 100644
index 00000000..ddc9bd49
--- /dev/null
+++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Zip.java
@@ -0,0 +1,2274 @@
+/*
+ * 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 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.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Stack;
+import java.util.Vector;
+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.PatternSet;
+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.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.task category="packaging"
+ */
+public class Zip extends MatchingTask {
+ private static final int BUFFER_SIZE = 8 * 1024;
+ private static final int ROUNDUP_MILLIS = 1999; // 2 seconds - 1
+ // CheckStyle:VisibilityModifier OFF - bc
+
+ protected File zipFile;
+ // use to scan own archive
+ private ZipScanner zs;
+ private File baseDir;
+ protected Hashtable<String, String> entries = new Hashtable<String, String>();
+ private final Vector<FileSet> groupfilesets = new Vector<FileSet>();
+ private final Vector<ZipFileSet> filesetsFromGroupfilesets = new Vector<ZipFileSet>();
+ 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 final Vector<ResourceCollection> resources = new Vector<ResourceCollection>();
+ protected Hashtable<String, String> addedDirs = new Hashtable<String, String>();
+ private final Vector<String> addedFiles = new Vector<String>();
+
+ private static final ResourceSelector MISSING_SELECTOR =
+ new ResourceSelector() {
+ public boolean isSelected(final Resource target) {
+ return !target.isExists();
+ }
+ };
+
+ private static final ResourceUtils.ResourceSelectorProvider
+ MISSING_DIR_PROVIDER = new ResourceUtils.ResourceSelectorProvider() {
+ public ResourceSelector
+ getTargetSelectorForSource(final Resource sr) {
+ return MISSING_SELECTOR;
+ }
+ };
+
+ /**
+ * 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.
+ *
+ * <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.
+ *
+ * @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;
+ }
+
+ private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
+
+ // 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
+ */
+ 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;
+
+ /**
+ * 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
+ */
+ private boolean useLanguageEncodingFlag = true;
+
+ /**
+ * 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
+ */
+ private boolean fallBackToUTF8 = false;
+
+ /**
+ * 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.
+ * @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.
+ * @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.
+ * @since Ant 1.5
+ * @param destFile The new destination File
+ */
+ public void setDestFile(final 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(final 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(final 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(final 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(final 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(final 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(final 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(final ResourceCollection a) {
+ resources.add(a);
+ }
+
+ /**
+ * Adds a group of zip files.
+ * @param set the group (a fileset) to add
+ */
+ public void addZipGroupFileset(final FileSet set) {
+ groupfilesets.addElement(set);
+ }
+
+ /**
+ * 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".
+ */
+ public static class WhenEmpty extends EnumeratedAttribute {
+ /**
+ * 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: <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.
+ *
+ * <p>For a list of possible values see <a
+ * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/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.
+ * @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(final boolean keep) {
+ keepCompression = keep;
+ }
+
+ /**
+ * 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
+ *
+ * @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(final 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.
+ *
+ * <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.
+ * @since Ant 1.8.0
+ */
+ public void setPreserve0Permissions(final boolean b) {
+ preserve0Permissions = b;
+ }
+
+ /**
+ * Assume 0 Unix mode is intentional.
+ * @since Ant 1.8.0
+ */
+ public boolean getPreserve0Permissions() {
+ return preserve0Permissions;
+ }
+
+ /**
+ * Whether to set the language encoding flag.
+ * @since Ant 1.8.0
+ */
+ public void setUseLanguageEncodingFlag(final boolean b) {
+ useLanguageEncodingFlag = b;
+ }
+
+ /**
+ * Whether the language encoding flag will be used.
+ * @since Ant 1.8.0
+ */
+ public boolean getUseLanguageEnodingFlag() {
+ return useLanguageEncodingFlag;
+ }
+
+ /**
+ * Whether Unicode extra fields will be created.
+ * @since Ant 1.8.0
+ */
+ public void setCreateUnicodeExtraFields(final UnicodeExtraField b) {
+ createUnicodeExtraFields = b;
+ }
+
+ /**
+ * Whether Unicode extra fields will be created.
+ * @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.
+ *
+ * <p>Defaults to false.</p>
+ *
+ * @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.
+ *
+ * @since Ant 1.8.0
+ */
+ public boolean getFallBackToUTF8() {
+ return fallBackToUTF8;
+ }
+
+ /**
+ * Whether Zip64 extensions should be used.
+ * @since Ant 1.9.1
+ */
+ public void setZip64Mode(final Zip64ModeAttribute b) {
+ zip64Mode = b;
+ }
+
+ /**
+ * Whether Zip64 extensions will be used.
+ * @since Ant 1.9.1
+ */
+ public Zip64ModeAttribute getZip64Mode() {
+ return zip64Mode;
+ }
+
+ /**
+ * 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.
+ * @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
+ */
+ 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 Vector<ResourceCollection> vfss = new Vector<ResourceCollection>();
+ if (baseDir != null) {
+ final FileSet fs = (FileSet) getImplicitFileSet().clone();
+ fs.setDir(baseDir);
+ vfss.addElement(fs);
+ }
+ final int size = resources.size();
+ for (int i = 0; i < size; i++) {
+ final ResourceCollection rc = resources.elementAt(i);
+ vfss.addElement(rc);
+ }
+
+ final ResourceCollection[] fss = new ResourceCollection[vfss.size()];
+ vfss.copyInto(fss);
+ 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 " + 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);
+
+ final int addSize = addedFiles.size();
+ for (int i = 0; i < addSize; i++) {
+ final PatternSet.NameEntry ne = oldFiles.createExclude();
+ ne.setName(addedFiles.elementAt(i));
+ }
+ final DirectoryScanner ds =
+ oldFiles.getDirectoryScanner(getProject());
+ ((ZipScanner) ds).setEncoding(encoding);
+
+ final 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) {
+ final String[] d = ds.getIncludedDirectories();
+ final Resource[] dr = new Resource[d.length];
+ for (int i = 0; i < d.length; i++) {
+ dr[i] = ds.getResource(d[i]);
+ }
+ final 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.
+ 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. */
+ private File renameFile() {
+ final File renamedFile = FILE_UTILS.createTempFile(
+ "zip", ".tmp", zipFile.getParentFile(), true, false);
+ try {
+ FILE_UTILS.rename(zipFile, renamedFile);
+ } catch (final SecurityException e) {
+ throw new BuildException(
+ "Not allowed to rename old file ("
+ + zipFile.getAbsolutePath()
+ + ") to temporary file");
+ } catch (final IOException e) {
+ throw new BuildException(
+ "Unable to rename old file ("
+ + zipFile.getAbsolutePath()
+ + ") to temporary file");
+ }
+ return renamedFile;
+ }
+
+ /** 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 */
+ private void checkAttributesAndElements() {
+ 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.");
+ }
+ }
+
+ /** 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 */
+ private void processGroupFilesets() {
+ // Add the files found in groupfileset to fileset
+ final int size = groupfilesets.size();
+ for (int i = 0; i < size; i++) {
+
+ logWhenWriting("Processing groupfileset ", Project.MSG_VERBOSE);
+ final FileSet fs = groupfilesets.elementAt(i);
+ final FileScanner scanner = fs.getDirectoryScanner(getProject());
+ final String[] files = scanner.getIncludedFiles();
+ final File basedir = scanner.getBasedir();
+ for (int j = 0; j < files.length; j++) {
+
+ logWhenWriting("Adding file " + files[j] + " to fileset",
+ Project.MSG_VERBOSE);
+ final ZipFileSet zf = new ZipFileSet();
+ zf.setProject(getProject());
+ zf.setSrc(new File(basedir, files[j]));
+ add(zf);
+ filesetsFromGroupfilesets.addElement(zf);
+ }
+ }
+ }
+
+ /**
+ * 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(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.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()) {
+ if (doFilesonly) {
+ continue;
+ }
+ final int thisDirMode = zfs != null && zfs.hasDirModeBeenSet()
+ ? dirMode : getUnixMode(resources[i], zf, dirMode);
+ addDirectoryResource(resources[i], name, prefix,
+ base, zOut,
+ dirMode, thisDirMode);
+
+ } else { // !isDirectory
+
+ addParentDirs(base, name, zOut, prefix, dirMode);
+
+ if (dealingWithFiles) {
+ final File f = FILE_UTILS.resolveFile(base,
+ resources[i].getName());
+ zipFile(f, zOut, prefix + name, fileMode);
+ } else {
+ final int thisFileMode =
+ zfs != null && zfs.hasFileModeBeenSet()
+ ? fileMode : getUnixMode(resources[i], zf,
+ fileMode);
+ addResource(resources[i], 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).
+ */
+ 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 = 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.
+ */
+ private int getUnixMode(final Resource r, final ZipFile zf, final int defaultMode)
+ throws IOException {
+
+ 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.
+ */
+ 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);
+ }
+ InputStream is = null;
+ try {
+ is = zf.getInputStream(ze);
+ zipFile(is, zOut, prefix + name, ze.getTime(),
+ fromArchive, mode, ze.getExtraFields(true));
+ } finally {
+ doCompress = oldCompress;
+ FileUtils.close(is);
+ }
+ }
+ } else {
+ InputStream is = null;
+ try {
+ is = r.getInputStream();
+ zipFile(is, zOut, prefix + name, r.getLastModified(),
+ fromArchive, mode, r instanceof ZipResource
+ ? ((ZipResource) r).getExtraFields() : null);
+ } finally {
+ FileUtils.close(is);
+ }
+ }
+ }
+
+ /**
+ * 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 (int i = 0; i < resources.length; i++) {
+ final Resource resource = resources[i];
+ String name = resource.getName();
+ if (name == null) {
+ continue;
+ }
+ name = name.replace(File.separatorChar, '/');
+
+ if ("".equals(name)) {
+ 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
+ * @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
+ * @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
+ * @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);
+ }
+ OutputStream os = null;
+ try {
+ os = new FileOutputStream(zipFile);
+ // 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());
+ } finally {
+ FileUtils.close(os);
+ }
+ 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.
+ *
+ * <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 ArrayList<ResourceCollection> filesets = new ArrayList<ResourceCollection>();
+ final ArrayList<ResourceCollection> rest = new ArrayList<ResourceCollection>();
+ for (int i = 0; i < rcs.length; i++) {
+ if (rcs[i] instanceof FileSet) {
+ filesets.add(rcs[i]);
+ } else {
+ rest.add(rcs[i]);
+ }
+ }
+ 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 = new ThreadLocal<Boolean>() {
+ @Override
+ protected Boolean initialValue() {
+ return Boolean.FALSE;
+ }
+ };
+
+ /**
+ * 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 (emptyBehavior.equals("skip")) {
+ 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 (emptyBehavior.equals("fail")) {
+ 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()).equals("")) {
+ // 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()).equals("")) {
+ 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.
+ *
+ * <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(Boolean.valueOf(!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());
+ 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.size() > 0) {
+ final ArrayList<Resource> newer = new ArrayList<Resource>();
+ 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.
+ *
+ * <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()).equals("")
+ && zfs.getFullpath(getProject()).equals("");
+ }
+ final DirectoryScanner rs =
+ filesets[i].getDirectoryScanner(getProject());
+ if (rs instanceof ZipScanner) {
+ ((ZipScanner) rs).setEncoding(encoding);
+ }
+ final Vector<Resource> resources = new Vector<Resource>();
+ if (!doFilesonly) {
+ final String[] directories = rs.getIncludedDirectories();
+ for (int j = 0; j < directories.length; j++) {
+ if (!"".equals(directories[j]) || !skipEmptyNames) {
+ resources.addElement(rs.getResource(directories[j]));
+ }
+ }
+ }
+ final 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.
+ *
+ * <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 ArrayList<Resource> dirs = new ArrayList<Resource>();
+ final ArrayList<Resource> files = new ArrayList<Resource>();
+ for (final Resource r : rcs[i]) {
+ if (r.isExists()) {
+ if (r.isDirectory()) {
+ dirs.add(r);
+ } else {
+ files.add(r);
+ }
+ }
+ }
+ // make sure directories are in alpha-order - this also
+ // ensures parents come before their children
+ Collections.sort(dirs, new Comparator<Resource>() {
+ public int compare(final Resource r1, final Resource r2) {
+ return r1.getName().compareTo(r2.getName());
+ }
+ });
+ final ArrayList<Resource> rs = new ArrayList<Resource>(dirs);
+ rs.addAll(files);
+ result[i] = 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(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.
+ * @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 ? (Resource) null : new FileResource(dir),
+ zOut, vPath, mode, extra);
+ }
+
+ /**
+ * 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 (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<ZipExtraField[]>();
+
+ /**
+ * Provides the extra fields for the zip entry currently being
+ * added to the archive - if any.
+ * @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.
+ * @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.
+ *
+ * @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(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 (duplicate.equals("preserve")) {
+ logWhenWriting(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
+ 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(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;
+ final CRC32 cal = new CRC32();
+ if (!in.markSupported()) {
+ // Store data into a byte[]
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ final byte[] buffer = new byte[BUFFER_SIZE];
+ 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);
+ final byte[] buffer = new byte[BUFFER_SIZE];
+ 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);
+ 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 = in.read(buffer, 0, buffer.length);
+ } while (count != -1);
+ }
+ addedFiles.addElement(vPath);
+ }
+
+ /**
+ * 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 <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());
+ }
+
+ final FileInputStream fIn = new FileInputStream(file);
+ try {
+ // ZIPs store time with a granularity of 2 seconds, round up
+ zipFile(fIn, zOut, vPath,
+ file.lastModified() + (roundUp ? ROUNDUP_MILLIS : 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(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<String>();
+ 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 = 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.
+ *
+ * <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.removeAllElements();
+ entries.clear();
+ addingNewFiles = false;
+ doUpdate = savedDoUpdate;
+ final Enumeration<ZipFileSet> e = filesetsFromGroupfilesets.elements();
+ while (e.hasMoreElements()) {
+ final ZipFileSet zf = e.nextElement();
+ resources.removeElement(zf);
+ }
+ filesetsFromGroupfilesets.removeAllElements();
+ HAVE_NON_FILE_SET_RESOURCES_TO_ADD.set(Boolean.FALSE);
+ }
+
+ /**
+ * 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(final 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(final Resource[] orig) {
+ return selectResources(orig,
+ new ResourceSelector() {
+ public boolean isSelected(final Resource r) {
+ if (!r.isDirectory()) {
+ return true;
+ } else 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.
+ * @param orig the resources to filter
+ * @return the filters resources
+ * @since Ant 1.8.0
+ */
+ protected Resource[] selectDirectoryResources(final Resource[] orig) {
+ return selectResources(orig,
+ new ResourceSelector() {
+ public boolean isSelected(final Resource r) {
+ return r.isDirectory();
+ }
+ });
+ }
+
+ /**
+ * Drops all resources from the given array that are not selected
+ * @param orig the resources to filter
+ * @return the filters resources
+ * @since Ant 1.8.0
+ */
+ protected Resource[] selectResources(final Resource[] orig,
+ final ResourceSelector selector) {
+ if (orig.length == 0) {
+ return orig;
+ }
+
+ final ArrayList<Resource> v = new ArrayList<Resource>(orig.length);
+ for (int i = 0; i < orig.length; i++) {
+ if (selector.isSelected(orig[i])) {
+ v.add(orig[i]);
+ }
+ }
+
+ if (v.size() != orig.length) {
+ return v.toArray(new Resource[v.size()]);
+ }
+ return orig;
+ }
+
+ /**
+ * Logs a message at the given output level, but only if this is
+ * the pass that will actually create the archive.
+ *
+ * @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"
+ */
+ public static class Duplicate extends EnumeratedAttribute {
+ /**
+ * @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
+ */
+ 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.
+ * @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;
+ }
+ }
+
+ /**
+ * Policiy 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<String, UnicodeExtraFieldPolicy>();
+ 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.
+ *
+ * <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 uset 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<String, Zip64Mode>();
+
+ 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());
+ }
+
+ }
+ }