0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018: package org.apache.tools.ant.taskdefs;
0019:
0020: import java.io.ByteArrayInputStream;
0021: import java.io.ByteArrayOutputStream;
0022: import java.io.File;
0023: import java.io.FileInputStream;
0024: import java.io.FileOutputStream;
0025: import java.io.IOException;
0026: import java.io.InputStream;
0027: import java.io.OutputStream;
0028: import java.util.ArrayList;
0029: import java.util.Enumeration;
0030: import java.util.Hashtable;
0031: import java.util.Iterator;
0032: import java.util.Stack;
0033: import java.util.Vector;
0034: import java.util.zip.CRC32;
0035:
0036: import org.apache.tools.ant.BuildException;
0037: import org.apache.tools.ant.DirectoryScanner;
0038: import org.apache.tools.ant.FileScanner;
0039: import org.apache.tools.ant.Project;
0040: import org.apache.tools.ant.types.ArchiveFileSet;
0041: import org.apache.tools.ant.types.EnumeratedAttribute;
0042: import org.apache.tools.ant.types.FileSet;
0043: import org.apache.tools.ant.types.PatternSet;
0044: import org.apache.tools.ant.types.Resource;
0045: import org.apache.tools.ant.types.ResourceCollection;
0046: import org.apache.tools.ant.types.ZipFileSet;
0047: import org.apache.tools.ant.types.ZipScanner;
0048: import org.apache.tools.ant.types.resources.ArchiveResource;
0049: import org.apache.tools.ant.types.resources.FileResource;
0050: import org.apache.tools.ant.util.FileNameMapper;
0051: import org.apache.tools.ant.util.FileUtils;
0052: import org.apache.tools.ant.util.GlobPatternMapper;
0053: import org.apache.tools.ant.util.IdentityMapper;
0054: import org.apache.tools.ant.util.MergingMapper;
0055: import org.apache.tools.ant.util.ResourceUtils;
0056: import org.apache.tools.zip.ZipEntry;
0057: import org.apache.tools.zip.ZipExtraField;
0058: import org.apache.tools.zip.ZipFile;
0059: import org.apache.tools.zip.ZipOutputStream;
0060:
0061: /**
0062: * Create a Zip file.
0063: *
0064: * @since Ant 1.1
0065: *
0066: * @ant.task category="packaging"
0067: */
0068: public class Zip extends MatchingTask {
0069: // CheckStyle:VisibilityModifier OFF - bc
0070:
0071: protected File zipFile;
0072: // use to scan own archive
0073: private ZipScanner zs;
0074: private File baseDir;
0075: protected Hashtable entries = new Hashtable();
0076: private Vector groupfilesets = new Vector();
0077: private Vector filesetsFromGroupfilesets = new Vector();
0078: protected String duplicate = "add";
0079: private boolean doCompress = true;
0080: private boolean doUpdate = false;
0081: // shadow of the above if the value is altered in execute
0082: private boolean savedDoUpdate = false;
0083: private boolean doFilesonly = false;
0084: protected String archiveType = "zip";
0085:
0086: // For directories:
0087: private static final long EMPTY_CRC = new CRC32().getValue();
0088: protected String emptyBehavior = "skip";
0089: private Vector resources = new Vector();
0090: protected Hashtable addedDirs = new Hashtable();
0091: private Vector addedFiles = new Vector();
0092:
0093: protected boolean doubleFilePass = false;
0094: protected boolean skipWriting = false;
0095:
0096: private static final FileUtils FILE_UTILS = FileUtils
0097: .getFileUtils();
0098:
0099: // CheckStyle:VisibilityModifier ON
0100:
0101: /**
0102: * true when we are adding new files into the Zip file, as opposed
0103: * to adding back the unchanged files
0104: */
0105: private boolean addingNewFiles = false;
0106:
0107: /**
0108: * Encoding to use for filenames, defaults to the platform's
0109: * default encoding.
0110: */
0111: private String encoding;
0112:
0113: /**
0114: * Whether the original compression of entries coming from a ZIP
0115: * archive should be kept (for example when updating an archive).
0116: *
0117: * @since Ant 1.6
0118: */
0119: private boolean keepCompression = false;
0120:
0121: /**
0122: * Whether the file modification times will be rounded up to the
0123: * next even number of seconds.
0124: *
0125: * @since Ant 1.6.2
0126: */
0127: private boolean roundUp = true;
0128:
0129: /**
0130: * Comment for the archive.
0131: * @since Ant 1.6.3
0132: */
0133: private String comment = "";
0134:
0135: private int level = ZipOutputStream.DEFAULT_COMPRESSION;
0136:
0137: /**
0138: * This is the name/location of where to
0139: * create the .zip file.
0140: * @param zipFile the path of the zipFile
0141: * @deprecated since 1.5.x.
0142: * Use setDestFile(File) instead.
0143: * @ant.attribute ignore="true"
0144: */
0145: public void setZipfile(File zipFile) {
0146: setDestFile(zipFile);
0147: }
0148:
0149: /**
0150: * This is the name/location of where to
0151: * create the file.
0152: * @param file the path of the zipFile
0153: * @since Ant 1.5
0154: * @deprecated since 1.5.x.
0155: * Use setDestFile(File) instead.
0156: * @ant.attribute ignore="true"
0157: */
0158: public void setFile(File file) {
0159: setDestFile(file);
0160: }
0161:
0162: /**
0163: * The file to create; required.
0164: * @since Ant 1.5
0165: * @param destFile The new destination File
0166: */
0167: public void setDestFile(File destFile) {
0168: this .zipFile = destFile;
0169: }
0170:
0171: /**
0172: * The file to create.
0173: * @return the destination file
0174: * @since Ant 1.5.2
0175: */
0176: public File getDestFile() {
0177: return zipFile;
0178: }
0179:
0180: /**
0181: * Directory from which to archive files; optional.
0182: * @param baseDir the base directory
0183: */
0184: public void setBasedir(File baseDir) {
0185: this .baseDir = baseDir;
0186: }
0187:
0188: /**
0189: * Whether we want to compress the files or only store them;
0190: * optional, default=true;
0191: * @param c if true, compress the files
0192: */
0193: public void setCompress(boolean c) {
0194: doCompress = c;
0195: }
0196:
0197: /**
0198: * Whether we want to compress the files or only store them;
0199: * @return true if the files are to be compressed
0200: * @since Ant 1.5.2
0201: */
0202: public boolean isCompress() {
0203: return doCompress;
0204: }
0205:
0206: /**
0207: * If true, emulate Sun's jar utility by not adding parent directories;
0208: * optional, defaults to false.
0209: * @param f if true, emulate sun's jar by not adding parent directories
0210: */
0211: public void setFilesonly(boolean f) {
0212: doFilesonly = f;
0213: }
0214:
0215: /**
0216: * If true, updates an existing file, otherwise overwrite
0217: * any existing one; optional defaults to false.
0218: * @param c if true, updates an existing zip file
0219: */
0220: public void setUpdate(boolean c) {
0221: doUpdate = c;
0222: savedDoUpdate = c;
0223: }
0224:
0225: /**
0226: * Are we updating an existing archive?
0227: * @return true if updating an existing archive
0228: */
0229: public boolean isInUpdateMode() {
0230: return doUpdate;
0231: }
0232:
0233: /**
0234: * Adds a set of files.
0235: * @param set the fileset to add
0236: */
0237: public void addFileset(FileSet set) {
0238: add(set);
0239: }
0240:
0241: /**
0242: * Adds a set of files that can be
0243: * read from an archive and be given a prefix/fullpath.
0244: * @param set the zipfileset to add
0245: */
0246: public void addZipfileset(ZipFileSet set) {
0247: add(set);
0248: }
0249:
0250: /**
0251: * Add a collection of resources to be archived.
0252: * @param a the resources to archive
0253: * @since Ant 1.7
0254: */
0255: public void add(ResourceCollection a) {
0256: resources.add(a);
0257: }
0258:
0259: /**
0260: * Adds a group of zip files.
0261: * @param set the group (a fileset) to add
0262: */
0263: public void addZipGroupFileset(FileSet set) {
0264: groupfilesets.addElement(set);
0265: }
0266:
0267: /**
0268: * Sets behavior for when a duplicate file is about to be added -
0269: * one of <code>add</code>, <code>preserve</code> or <code>fail</code>.
0270: * Possible values are: <code>add</code> (keep both
0271: * of the files); <code>preserve</code> (keep the first version
0272: * of the file found); <code>fail</code> halt a problem
0273: * Default for zip tasks is <code>add</code>
0274: * @param df a <code>Duplicate</code> enumerated value
0275: */
0276: public void setDuplicate(Duplicate df) {
0277: duplicate = df.getValue();
0278: }
0279:
0280: /**
0281: * Possible behaviors when there are no matching files for the task:
0282: * "fail", "skip", or "create".
0283: */
0284: public static class WhenEmpty extends EnumeratedAttribute {
0285: /**
0286: * The string values for the enumerated value
0287: * @return the values
0288: */
0289: public String[] getValues() {
0290: return new String[] { "fail", "skip", "create" };
0291: }
0292: }
0293:
0294: /**
0295: * Sets behavior of the task when no files match.
0296: * Possible values are: <code>fail</code> (throw an exception
0297: * and halt the build); <code>skip</code> (do not create
0298: * any archive, but issue a warning); <code>create</code>
0299: * (make an archive with no entries).
0300: * Default for zip tasks is <code>skip</code>;
0301: * for jar tasks, <code>create</code>.
0302: * @param we a <code>WhenEmpty</code> enumerated value
0303: */
0304: public void setWhenempty(WhenEmpty we) {
0305: emptyBehavior = we.getValue();
0306: }
0307:
0308: /**
0309: * Encoding to use for filenames, defaults to the platform's
0310: * default encoding.
0311: *
0312: * <p>For a list of possible values see <a
0313: * 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>
0314: * @param encoding the encoding name
0315: */
0316: public void setEncoding(String encoding) {
0317: this .encoding = encoding;
0318: }
0319:
0320: /**
0321: * Encoding to use for filenames.
0322: * @return the name of the encoding to use
0323: * @since Ant 1.5.2
0324: */
0325: public String getEncoding() {
0326: return encoding;
0327: }
0328:
0329: /**
0330: * Whether the original compression of entries coming from a ZIP
0331: * archive should be kept (for example when updating an archive).
0332: * Default is false.
0333: * @param keep if true, keep the original compression
0334: * @since Ant 1.6
0335: */
0336: public void setKeepCompression(boolean keep) {
0337: keepCompression = keep;
0338: }
0339:
0340: /**
0341: * Comment to use for archive.
0342: *
0343: * @param comment The content of the comment.
0344: * @since Ant 1.6.3
0345: */
0346: public void setComment(String comment) {
0347: this .comment = comment;
0348: }
0349:
0350: /**
0351: * Comment of the archive
0352: *
0353: * @return Comment of the archive.
0354: * @since Ant 1.6.3
0355: */
0356: public String getComment() {
0357: return comment;
0358: }
0359:
0360: /**
0361: * Set the compression level to use. Default is
0362: * ZipOutputStream.DEFAULT_COMPRESSION.
0363: * @param level compression level.
0364: * @since Ant 1.7
0365: */
0366: public void setLevel(int level) {
0367: this .level = level;
0368: }
0369:
0370: /**
0371: * Get the compression level.
0372: * @return compression level.
0373: * @since Ant 1.7
0374: */
0375: public int getLevel() {
0376: return level;
0377: }
0378:
0379: /**
0380: * Whether the file modification times will be rounded up to the
0381: * next even number of seconds.
0382: *
0383: * <p>Zip archives store file modification times with a
0384: * granularity of two seconds, so the times will either be rounded
0385: * up or down. If you round down, the archive will always seem
0386: * out-of-date when you rerun the task, so the default is to round
0387: * up. Rounding up may lead to a different type of problems like
0388: * JSPs inside a web archive that seem to be slightly more recent
0389: * than precompiled pages, rendering precompilation useless.</p>
0390: * @param r a <code>boolean</code> value
0391: * @since Ant 1.6.2
0392: */
0393: public void setRoundUp(boolean r) {
0394: roundUp = r;
0395: }
0396:
0397: /**
0398: * validate and build
0399: * @throws BuildException on error
0400: */
0401: public void execute() throws BuildException {
0402:
0403: if (doubleFilePass) {
0404: skipWriting = true;
0405: executeMain();
0406: skipWriting = false;
0407: executeMain();
0408: } else {
0409: executeMain();
0410: }
0411: }
0412:
0413: /**
0414: * Build the zip file.
0415: * This is called twice if doubleFilePass is true.
0416: * @throws BuildException on error
0417: */
0418: public void executeMain() throws BuildException {
0419:
0420: if (baseDir == null && resources.size() == 0
0421: && groupfilesets.size() == 0
0422: && "zip".equals(archiveType)) {
0423: throw new BuildException("basedir attribute must be set, "
0424: + "or at least one "
0425: + "resource collection must be given!");
0426: }
0427:
0428: if (zipFile == null) {
0429: throw new BuildException("You must specify the "
0430: + archiveType + " file to create!");
0431: }
0432:
0433: if (zipFile.exists() && !zipFile.isFile()) {
0434: throw new BuildException(zipFile + " is not a file.");
0435: }
0436:
0437: if (zipFile.exists() && !zipFile.canWrite()) {
0438: throw new BuildException(zipFile + " is read-only.");
0439: }
0440:
0441: // Renamed version of original file, if it exists
0442: File renamedFile = null;
0443: addingNewFiles = true;
0444:
0445: // Whether or not an actual update is required -
0446: // we don't need to update if the original file doesn't exist
0447: if (doUpdate && !zipFile.exists()) {
0448: doUpdate = false;
0449: log("ignoring update attribute as " + archiveType
0450: + " doesn't exist.", Project.MSG_DEBUG);
0451: }
0452:
0453: // Add the files found in groupfileset to fileset
0454: for (int i = 0; i < groupfilesets.size(); i++) {
0455:
0456: log("Processing groupfileset ", Project.MSG_VERBOSE);
0457: FileSet fs = (FileSet) groupfilesets.elementAt(i);
0458: FileScanner scanner = fs.getDirectoryScanner(getProject());
0459: String[] files = scanner.getIncludedFiles();
0460: File basedir = scanner.getBasedir();
0461: for (int j = 0; j < files.length; j++) {
0462:
0463: log("Adding file " + files[j] + " to fileset",
0464: Project.MSG_VERBOSE);
0465: ZipFileSet zf = new ZipFileSet();
0466: zf.setProject(getProject());
0467: zf.setSrc(new File(basedir, files[j]));
0468: add(zf);
0469: filesetsFromGroupfilesets.addElement(zf);
0470: }
0471: }
0472:
0473: // collect filesets to pass them to getResourcesToAdd
0474: Vector vfss = new Vector();
0475: if (baseDir != null) {
0476: FileSet fs = (FileSet) getImplicitFileSet().clone();
0477: fs.setDir(baseDir);
0478: vfss.addElement(fs);
0479: }
0480: for (int i = 0; i < resources.size(); i++) {
0481: ResourceCollection rc = (ResourceCollection) resources
0482: .elementAt(i);
0483: vfss.addElement(rc);
0484: }
0485:
0486: ResourceCollection[] fss = new ResourceCollection[vfss.size()];
0487: vfss.copyInto(fss);
0488: boolean success = false;
0489: try {
0490: // can also handle empty archives
0491: ArchiveState state = getResourcesToAdd(fss, zipFile, false);
0492:
0493: // quick exit if the target is up to date
0494: if (!state.isOutOfDate()) {
0495: return;
0496: }
0497:
0498: if (!zipFile.exists() && state.isWithoutAnyResources()) {
0499: createEmptyZip(zipFile);
0500: return;
0501: }
0502: Resource[][] addThem = state.getResourcesToAdd();
0503:
0504: if (doUpdate) {
0505: renamedFile = FILE_UTILS.createTempFile("zip", ".tmp",
0506: zipFile.getParentFile());
0507: renamedFile.deleteOnExit();
0508:
0509: try {
0510: FILE_UTILS.rename(zipFile, renamedFile);
0511: } catch (SecurityException e) {
0512: throw new BuildException(
0513: "Not allowed to rename old file ("
0514: + zipFile.getAbsolutePath()
0515: + ") to temporary file");
0516: } catch (IOException e) {
0517: throw new BuildException(
0518: "Unable to rename old file ("
0519: + zipFile.getAbsolutePath()
0520: + ") to temporary file");
0521: }
0522: }
0523:
0524: String action = doUpdate ? "Updating " : "Building ";
0525:
0526: log(action + archiveType + ": " + zipFile.getAbsolutePath());
0527:
0528: ZipOutputStream zOut = null;
0529: try {
0530: if (!skipWriting) {
0531: zOut = new ZipOutputStream(zipFile);
0532:
0533: zOut.setEncoding(encoding);
0534: zOut
0535: .setMethod(doCompress ? ZipOutputStream.DEFLATED
0536: : ZipOutputStream.STORED);
0537: zOut.setLevel(level);
0538: }
0539: initZipOutputStream(zOut);
0540:
0541: // Add the explicit resource collections to the archive.
0542: for (int i = 0; i < fss.length; i++) {
0543: if (addThem[i].length != 0) {
0544: addResources(fss[i], addThem[i], zOut);
0545: }
0546: }
0547:
0548: if (doUpdate) {
0549: addingNewFiles = false;
0550: ZipFileSet oldFiles = new ZipFileSet();
0551: oldFiles.setProject(getProject());
0552: oldFiles.setSrc(renamedFile);
0553: oldFiles.setDefaultexcludes(false);
0554:
0555: for (int i = 0; i < addedFiles.size(); i++) {
0556: PatternSet.NameEntry ne = oldFiles
0557: .createExclude();
0558: ne.setName((String) addedFiles.elementAt(i));
0559: }
0560: DirectoryScanner ds = oldFiles
0561: .getDirectoryScanner(getProject());
0562: ((ZipScanner) ds).setEncoding(encoding);
0563:
0564: String[] f = ds.getIncludedFiles();
0565: Resource[] r = new Resource[f.length];
0566: for (int i = 0; i < f.length; i++) {
0567: r[i] = ds.getResource(f[i]);
0568: }
0569:
0570: if (!doFilesonly) {
0571: String[] d = ds.getIncludedDirectories();
0572: Resource[] dr = new Resource[d.length];
0573: for (int i = 0; i < d.length; i++) {
0574: dr[i] = ds.getResource(d[i]);
0575: }
0576: Resource[] tmp = r;
0577: r = new Resource[tmp.length + dr.length];
0578: System.arraycopy(dr, 0, r, 0, dr.length);
0579: System.arraycopy(tmp, 0, r, dr.length,
0580: tmp.length);
0581: }
0582: addResources(oldFiles, r, zOut);
0583: }
0584: if (zOut != null) {
0585: zOut.setComment(comment);
0586: }
0587: finalizeZipOutputStream(zOut);
0588:
0589: // If we've been successful on an update, delete the
0590: // temporary file
0591: if (doUpdate) {
0592: if (!renamedFile.delete()) {
0593: log("Warning: unable to delete temporary file "
0594: + renamedFile.getName(),
0595: Project.MSG_WARN);
0596: }
0597: }
0598: success = true;
0599: } finally {
0600: // Close the output stream.
0601: try {
0602: if (zOut != null) {
0603: zOut.close();
0604: }
0605: } catch (IOException ex) {
0606: // If we're in this finally clause because of an
0607: // exception, we don't really care if there's an
0608: // exception when closing the stream. E.g. if it
0609: // throws "ZIP file must have at least one entry",
0610: // because an exception happened before we added
0611: // any files, then we must swallow this
0612: // exception. Otherwise, the error that's reported
0613: // will be the close() error, which is not the
0614: // real cause of the problem.
0615: if (success) {
0616: throw ex;
0617: }
0618: }
0619: }
0620: } catch (IOException ioe) {
0621: String msg = "Problem creating " + archiveType + ": "
0622: + ioe.getMessage();
0623:
0624: // delete a bogus ZIP file (but only if it's not the original one)
0625: if ((!doUpdate || renamedFile != null) && !zipFile.delete()) {
0626: msg += " (and the archive is probably corrupt but I could not "
0627: + "delete it)";
0628: }
0629:
0630: if (doUpdate && renamedFile != null) {
0631: try {
0632: FILE_UTILS.rename(renamedFile, zipFile);
0633: } catch (IOException e) {
0634: msg += " (and I couldn't rename the temporary file "
0635: + renamedFile.getName() + " back)";
0636: }
0637: }
0638:
0639: throw new BuildException(msg, ioe, getLocation());
0640: } finally {
0641: cleanUp();
0642: }
0643: }
0644:
0645: /**
0646: * Indicates if the task is adding new files into the archive as opposed to
0647: * copying back unchanged files from the backup copy
0648: * @return true if adding new files
0649: */
0650: protected final boolean isAddingNewFiles() {
0651: return addingNewFiles;
0652: }
0653:
0654: /**
0655: * Add the given resources.
0656: *
0657: * @param fileset may give additional information like fullpath or
0658: * permissions.
0659: * @param resources the resources to add
0660: * @param zOut the stream to write to
0661: * @throws IOException on error
0662: *
0663: * @since Ant 1.5.2
0664: */
0665: protected final void addResources(FileSet fileset,
0666: Resource[] resources, ZipOutputStream zOut)
0667: throws IOException {
0668:
0669: String prefix = "";
0670: String fullpath = "";
0671: int dirMode = ArchiveFileSet.DEFAULT_DIR_MODE;
0672: int fileMode = ArchiveFileSet.DEFAULT_FILE_MODE;
0673:
0674: ArchiveFileSet zfs = null;
0675: if (fileset instanceof ArchiveFileSet) {
0676: zfs = (ArchiveFileSet) fileset;
0677: prefix = zfs.getPrefix(getProject());
0678: fullpath = zfs.getFullpath(getProject());
0679: dirMode = zfs.getDirMode(getProject());
0680: fileMode = zfs.getFileMode(getProject());
0681: }
0682:
0683: if (prefix.length() > 0 && fullpath.length() > 0) {
0684: throw new BuildException(
0685: "Both prefix and fullpath attributes must"
0686: + " not be set on the same fileset.");
0687: }
0688:
0689: if (resources.length != 1 && fullpath.length() > 0) {
0690: throw new BuildException(
0691: "fullpath attribute may only be specified"
0692: + " for filesets that specify a single"
0693: + " file.");
0694: }
0695:
0696: if (prefix.length() > 0) {
0697: if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
0698: prefix += "/";
0699: }
0700: addParentDirs(null, prefix, zOut, "", dirMode);
0701: }
0702:
0703: ZipFile zf = null;
0704: try {
0705: boolean dealingWithFiles = false;
0706: File base = null;
0707:
0708: if (zfs == null || zfs.getSrc(getProject()) == null) {
0709: dealingWithFiles = true;
0710: base = fileset.getDir(getProject());
0711: } else if (zfs instanceof ZipFileSet) {
0712: zf = new ZipFile(zfs.getSrc(getProject()), encoding);
0713: }
0714:
0715: for (int i = 0; i < resources.length; i++) {
0716: String name = null;
0717: if (fullpath.length() > 0) {
0718: name = fullpath;
0719: } else {
0720: name = resources[i].getName();
0721: }
0722: name = name.replace(File.separatorChar, '/');
0723:
0724: if ("".equals(name)) {
0725: continue;
0726: }
0727: if (resources[i].isDirectory() && !name.endsWith("/")) {
0728: name = name + "/";
0729: }
0730:
0731: if (!doFilesonly && !dealingWithFiles
0732: && resources[i].isDirectory()
0733: && !zfs.hasDirModeBeenSet()) {
0734: int nextToLastSlash = name.lastIndexOf("/", name
0735: .length() - 2);
0736: if (nextToLastSlash != -1) {
0737: addParentDirs(base, name.substring(0,
0738: nextToLastSlash + 1), zOut, prefix,
0739: dirMode);
0740: }
0741: if (zf != null) {
0742: ZipEntry ze = zf.getEntry(resources[i]
0743: .getName());
0744: addParentDirs(base, name, zOut, prefix, ze
0745: .getUnixMode());
0746: } else {
0747: ArchiveResource tr = (ArchiveResource) resources[i];
0748: addParentDirs(base, name, zOut, prefix, tr
0749: .getMode());
0750: }
0751:
0752: } else {
0753: addParentDirs(base, name, zOut, prefix, dirMode);
0754: }
0755:
0756: if (!resources[i].isDirectory() && dealingWithFiles) {
0757: File f = FILE_UTILS.resolveFile(base, resources[i]
0758: .getName());
0759: zipFile(f, zOut, prefix + name, fileMode);
0760: } else if (!resources[i].isDirectory()) {
0761: if (zf != null) {
0762: ZipEntry ze = zf.getEntry(resources[i]
0763: .getName());
0764:
0765: if (ze != null) {
0766: boolean oldCompress = doCompress;
0767: if (keepCompression) {
0768: doCompress = (ze.getMethod() == ZipEntry.DEFLATED);
0769: }
0770: try {
0771: zipFile(
0772: zf.getInputStream(ze),
0773: zOut,
0774: prefix + name,
0775: ze.getTime(),
0776: zfs.getSrc(getProject()),
0777: zfs.hasFileModeBeenSet() ? fileMode
0778: : ze.getUnixMode());
0779: } finally {
0780: doCompress = oldCompress;
0781: }
0782: }
0783: } else {
0784: ArchiveResource tr = (ArchiveResource) resources[i];
0785: InputStream is = null;
0786: try {
0787: is = tr.getInputStream();
0788: zipFile(is, zOut, prefix + name,
0789: resources[i].getLastModified(), zfs
0790: .getSrc(getProject()),
0791: zfs.hasFileModeBeenSet() ? fileMode
0792: : tr.getMode());
0793: } finally {
0794: FileUtils.close(is);
0795: }
0796: }
0797: }
0798: }
0799: } finally {
0800: if (zf != null) {
0801: zf.close();
0802: }
0803: }
0804: }
0805:
0806: /**
0807: * Add the given resources.
0808: *
0809: * @param rc may give additional information like fullpath or
0810: * permissions.
0811: * @param resources the resources to add
0812: * @param zOut the stream to write to
0813: * @throws IOException on error
0814: *
0815: * @since Ant 1.7
0816: */
0817: protected final void addResources(ResourceCollection rc,
0818: Resource[] resources, ZipOutputStream zOut)
0819: throws IOException {
0820: if (rc instanceof FileSet) {
0821: addResources((FileSet) rc, resources, zOut);
0822: return;
0823: }
0824: for (int i = 0; i < resources.length; i++) {
0825: String name = resources[i].getName().replace(
0826: File.separatorChar, '/');
0827: if ("".equals(name)) {
0828: continue;
0829: }
0830: if (resources[i].isDirectory() && doFilesonly) {
0831: continue;
0832: }
0833: File base = null;
0834: if (resources[i] instanceof FileResource) {
0835: base = ((FileResource) resources[i]).getBaseDir();
0836: }
0837: if (resources[i].isDirectory()) {
0838: if (!name.endsWith("/")) {
0839: name = name + "/";
0840: }
0841: }
0842:
0843: addParentDirs(base, name, zOut, "",
0844: ArchiveFileSet.DEFAULT_DIR_MODE);
0845:
0846: if (!resources[i].isDirectory()) {
0847: if (resources[i] instanceof FileResource) {
0848: File f = ((FileResource) resources[i]).getFile();
0849: zipFile(f, zOut, name,
0850: ArchiveFileSet.DEFAULT_FILE_MODE);
0851: } else {
0852: InputStream is = null;
0853: try {
0854: is = resources[i].getInputStream();
0855: zipFile(is, zOut, name, resources[i]
0856: .getLastModified(), null,
0857: ArchiveFileSet.DEFAULT_FILE_MODE);
0858: } finally {
0859: FileUtils.close(is);
0860: }
0861: }
0862: }
0863: }
0864: }
0865:
0866: /**
0867: * method for subclasses to override
0868: * @param zOut the zip output stream
0869: * @throws IOException on output error
0870: * @throws BuildException on other errors
0871: */
0872: protected void initZipOutputStream(ZipOutputStream zOut)
0873: throws IOException, BuildException {
0874: }
0875:
0876: /**
0877: * method for subclasses to override
0878: * @param zOut the zip output stream
0879: * @throws IOException on output error
0880: * @throws BuildException on other errors
0881: */
0882: protected void finalizeZipOutputStream(ZipOutputStream zOut)
0883: throws IOException, BuildException {
0884: }
0885:
0886: /**
0887: * Create an empty zip file
0888: * @param zipFile the zip file
0889: * @return true for historic reasons
0890: * @throws BuildException on error
0891: */
0892: protected boolean createEmptyZip(File zipFile)
0893: throws BuildException {
0894: // In this case using java.util.zip will not work
0895: // because it does not permit a zero-entry archive.
0896: // Must create it manually.
0897: log("Note: creating empty " + archiveType + " archive "
0898: + zipFile, Project.MSG_INFO);
0899: OutputStream os = null;
0900: try {
0901: os = new FileOutputStream(zipFile);
0902: // Cf. PKZIP specification.
0903: byte[] empty = new byte[22];
0904: empty[0] = 80; // P
0905: empty[1] = 75; // K
0906: empty[2] = 5;
0907: empty[3] = 6;
0908: // remainder zeros
0909: os.write(empty);
0910: } catch (IOException ioe) {
0911: throw new BuildException(
0912: "Could not create empty ZIP archive " + "("
0913: + ioe.getMessage() + ")", ioe,
0914: getLocation());
0915: } finally {
0916: if (os != null) {
0917: try {
0918: os.close();
0919: } catch (IOException e) {
0920: //ignore
0921: }
0922: }
0923: }
0924: return true;
0925: }
0926:
0927: /**
0928: * @since Ant 1.5.2
0929: */
0930: private synchronized ZipScanner getZipScanner() {
0931: if (zs == null) {
0932: zs = new ZipScanner();
0933: zs.setEncoding(encoding);
0934: zs.setSrc(zipFile);
0935: }
0936: return zs;
0937: }
0938:
0939: /**
0940: * Collect the resources that are newer than the corresponding
0941: * entries (or missing) in the original archive.
0942: *
0943: * <p>If we are going to recreate the archive instead of updating
0944: * it, all resources should be considered as new, if a single one
0945: * is. Because of this, subclasses overriding this method must
0946: * call <code>super.getResourcesToAdd</code> and indicate with the
0947: * third arg if they already know that the archive is
0948: * out-of-date.</p>
0949: *
0950: * <p>This method first delegates to getNonFileSetResourceToAdd
0951: * and then invokes the FileSet-arg version. All this to keep
0952: * backwards compatibility for subclasses that don't know how to
0953: * deal with non-FileSet ResourceCollections.</p>
0954: *
0955: * @param rcs The resource collections to grab resources from
0956: * @param zipFile intended archive file (may or may not exist)
0957: * @param needsUpdate whether we already know that the archive is
0958: * out-of-date. Subclasses overriding this method are supposed to
0959: * set this value correctly in their call to
0960: * <code>super.getResourcesToAdd</code>.
0961: * @return an array of resources to add for each fileset passed in as well
0962: * as a flag that indicates whether the archive is uptodate.
0963: *
0964: * @exception BuildException if it likes
0965: * @since Ant 1.7
0966: */
0967: protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs,
0968: File zipFile, boolean needsUpdate) throws BuildException {
0969: ArrayList filesets = new ArrayList();
0970: ArrayList rest = new ArrayList();
0971: for (int i = 0; i < rcs.length; i++) {
0972: if (rcs[i] instanceof FileSet) {
0973: filesets.add(rcs[i]);
0974: } else {
0975: rest.add(rcs[i]);
0976: }
0977: }
0978: ResourceCollection[] rc = (ResourceCollection[]) rest
0979: .toArray(new ResourceCollection[rest.size()]);
0980: ArchiveState as = getNonFileSetResourcesToAdd(rc, zipFile,
0981: needsUpdate);
0982:
0983: FileSet[] fs = (FileSet[]) filesets
0984: .toArray(new FileSet[filesets.size()]);
0985: ArchiveState as2 = getResourcesToAdd(fs, zipFile, as
0986: .isOutOfDate());
0987: if (!as.isOutOfDate() && as2.isOutOfDate()) {
0988: /*
0989: * Bad luck.
0990: *
0991: * There are resources in the filesets that make the
0992: * archive out of date, but not in the non-fileset
0993: * resources. We need to rescan the non-FileSets to grab
0994: * all of them now.
0995: */
0996: as = getNonFileSetResourcesToAdd(rc, zipFile, true);
0997: }
0998:
0999: Resource[][] toAdd = new Resource[rcs.length][];
1000: int fsIndex = 0;
1001: int restIndex = 0;
1002: for (int i = 0; i < rcs.length; i++) {
1003: if (rcs[i] instanceof FileSet) {
1004: toAdd[i] = as2.getResourcesToAdd()[fsIndex++];
1005: } else {
1006: toAdd[i] = as.getResourcesToAdd()[restIndex++];
1007: }
1008: }
1009: return new ArchiveState(as2.isOutOfDate(), toAdd);
1010: }
1011:
1012: /**
1013: * Collect the resources that are newer than the corresponding
1014: * entries (or missing) in the original archive.
1015: *
1016: * <p>If we are going to recreate the archive instead of updating
1017: * it, all resources should be considered as new, if a single one
1018: * is. Because of this, subclasses overriding this method must
1019: * call <code>super.getResourcesToAdd</code> and indicate with the
1020: * third arg if they already know that the archive is
1021: * out-of-date.</p>
1022: *
1023: * @param filesets The filesets to grab resources from
1024: * @param zipFile intended archive file (may or may not exist)
1025: * @param needsUpdate whether we already know that the archive is
1026: * out-of-date. Subclasses overriding this method are supposed to
1027: * set this value correctly in their call to
1028: * <code>super.getResourcesToAdd</code>.
1029: * @return an array of resources to add for each fileset passed in as well
1030: * as a flag that indicates whether the archive is uptodate.
1031: *
1032: * @exception BuildException if it likes
1033: */
1034: protected ArchiveState getResourcesToAdd(FileSet[] filesets,
1035: File zipFile, boolean needsUpdate) throws BuildException {
1036:
1037: Resource[][] initialResources = grabResources(filesets);
1038: if (isEmpty(initialResources)) {
1039: if (needsUpdate && doUpdate) {
1040: /*
1041: * This is a rather hairy case.
1042: *
1043: * One of our subclasses knows that we need to update the
1044: * archive, but at the same time, there are no resources
1045: * known to us that would need to be added. Only the
1046: * subclass seems to know what's going on.
1047: *
1048: * This happens if <jar> detects that the manifest has changed,
1049: * for example. The manifest is not part of any resources
1050: * because of our support for inline <manifest>s.
1051: *
1052: * If we invoke createEmptyZip like Ant 1.5.2 did,
1053: * we'll loose all stuff that has been in the original
1054: * archive (bugzilla report 17780).
1055: */
1056: return new ArchiveState(true, initialResources);
1057: }
1058:
1059: if (emptyBehavior.equals("skip")) {
1060: if (doUpdate) {
1061: log(
1062: archiveType
1063: + " archive "
1064: + zipFile
1065: + " not updated because no new files were included.",
1066: Project.MSG_VERBOSE);
1067: } else {
1068: log("Warning: skipping " + archiveType
1069: + " archive " + zipFile
1070: + " because no files were included.",
1071: Project.MSG_WARN);
1072: }
1073: } else if (emptyBehavior.equals("fail")) {
1074: throw new BuildException("Cannot create " + archiveType
1075: + " archive " + zipFile
1076: + ": no files were included.", getLocation());
1077: } else {
1078: // Create.
1079: if (!zipFile.exists()) {
1080: needsUpdate = true;
1081: }
1082: }
1083: return new ArchiveState(needsUpdate, initialResources);
1084: }
1085:
1086: // initialResources is not empty
1087:
1088: if (!zipFile.exists()) {
1089: return new ArchiveState(true, initialResources);
1090: }
1091:
1092: if (needsUpdate && !doUpdate) {
1093: // we are recreating the archive, need all resources
1094: return new ArchiveState(true, initialResources);
1095: }
1096:
1097: Resource[][] newerResources = new Resource[filesets.length][];
1098:
1099: for (int i = 0; i < filesets.length; i++) {
1100: if (!(fileset instanceof ZipFileSet)
1101: || ((ZipFileSet) fileset).getSrc(getProject()) == null) {
1102: File base = filesets[i].getDir(getProject());
1103:
1104: for (int j = 0; j < initialResources[i].length; j++) {
1105: File resourceAsFile = FILE_UTILS.resolveFile(base,
1106: initialResources[i][j].getName());
1107: if (resourceAsFile.equals(zipFile)) {
1108: throw new BuildException(
1109: "A zip file cannot include " + "itself",
1110: getLocation());
1111: }
1112: }
1113: }
1114: }
1115:
1116: for (int i = 0; i < filesets.length; i++) {
1117: if (initialResources[i].length == 0) {
1118: newerResources[i] = new Resource[] {};
1119: continue;
1120: }
1121:
1122: FileNameMapper myMapper = new IdentityMapper();
1123: if (filesets[i] instanceof ZipFileSet) {
1124: ZipFileSet zfs = (ZipFileSet) filesets[i];
1125: if (zfs.getFullpath(getProject()) != null
1126: && !zfs.getFullpath(getProject()).equals("")) {
1127: // in this case all files from origin map to
1128: // the fullPath attribute of the zipfileset at
1129: // destination
1130: MergingMapper fm = new MergingMapper();
1131: fm.setTo(zfs.getFullpath(getProject()));
1132: myMapper = fm;
1133:
1134: } else if (zfs.getPrefix(getProject()) != null
1135: && !zfs.getPrefix(getProject()).equals("")) {
1136: GlobPatternMapper gm = new GlobPatternMapper();
1137: gm.setFrom("*");
1138: String prefix = zfs.getPrefix(getProject());
1139: if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
1140: prefix += "/";
1141: }
1142: gm.setTo(prefix + "*");
1143: myMapper = gm;
1144: }
1145: }
1146:
1147: Resource[] resources = initialResources[i];
1148: if (doFilesonly) {
1149: resources = selectFileResources(resources);
1150: }
1151:
1152: newerResources[i] = ResourceUtils.selectOutOfDateSources(
1153: this , resources, myMapper, getZipScanner());
1154: needsUpdate = needsUpdate || (newerResources[i].length > 0);
1155:
1156: if (needsUpdate && !doUpdate) {
1157: // we will return initialResources anyway, no reason
1158: // to scan further.
1159: break;
1160: }
1161: }
1162:
1163: if (needsUpdate && !doUpdate) {
1164: // we are recreating the archive, need all resources
1165: return new ArchiveState(true, initialResources);
1166: }
1167:
1168: return new ArchiveState(needsUpdate, newerResources);
1169: }
1170:
1171: /**
1172: * Collect the resources that are newer than the corresponding
1173: * entries (or missing) in the original archive.
1174: *
1175: * <p>If we are going to recreate the archive instead of updating
1176: * it, all resources should be considered as new, if a single one
1177: * is. Because of this, subclasses overriding this method must
1178: * call <code>super.getResourcesToAdd</code> and indicate with the
1179: * third arg if they already know that the archive is
1180: * out-of-date.</p>
1181: *
1182: * @param rcs The filesets to grab resources from
1183: * @param zipFile intended archive file (may or may not exist)
1184: * @param needsUpdate whether we already know that the archive is
1185: * out-of-date. Subclasses overriding this method are supposed to
1186: * set this value correctly in their call to
1187: * <code>super.getResourcesToAdd</code>.
1188: * @return an array of resources to add for each fileset passed in as well
1189: * as a flag that indicates whether the archive is uptodate.
1190: *
1191: * @exception BuildException if it likes
1192: */
1193: protected ArchiveState getNonFileSetResourcesToAdd(
1194: ResourceCollection[] rcs, File zipFile, boolean needsUpdate)
1195: throws BuildException {
1196: /*
1197: * Backwards compatibility forces us to repeat the logic of
1198: * getResourcesToAdd(FileSet[], ...) here once again.
1199: */
1200:
1201: Resource[][] initialResources = grabNonFileSetResources(rcs);
1202: if (isEmpty(initialResources)) {
1203: // no emptyBehavior handling since the FileSet version
1204: // will take care of it.
1205: return new ArchiveState(needsUpdate, initialResources);
1206: }
1207:
1208: // initialResources is not empty
1209:
1210: if (!zipFile.exists()) {
1211: return new ArchiveState(true, initialResources);
1212: }
1213:
1214: if (needsUpdate && !doUpdate) {
1215: // we are recreating the archive, need all resources
1216: return new ArchiveState(true, initialResources);
1217: }
1218:
1219: Resource[][] newerResources = new Resource[rcs.length][];
1220:
1221: for (int i = 0; i < rcs.length; i++) {
1222: if (initialResources[i].length == 0) {
1223: newerResources[i] = new Resource[] {};
1224: continue;
1225: }
1226:
1227: for (int j = 0; j < initialResources[i].length; j++) {
1228: if (initialResources[i][j] instanceof FileResource
1229: && zipFile
1230: .equals(((FileResource) initialResources[i][j])
1231: .getFile())) {
1232: throw new BuildException(
1233: "A zip file cannot include " + "itself",
1234: getLocation());
1235: }
1236: }
1237:
1238: Resource[] rs = initialResources[i];
1239: if (doFilesonly) {
1240: rs = selectFileResources(rs);
1241: }
1242:
1243: newerResources[i] = ResourceUtils.selectOutOfDateSources(
1244: this , rs, new IdentityMapper(), getZipScanner());
1245: needsUpdate = needsUpdate || (newerResources[i].length > 0);
1246:
1247: if (needsUpdate && !doUpdate) {
1248: // we will return initialResources anyway, no reason
1249: // to scan further.
1250: break;
1251: }
1252: }
1253:
1254: if (needsUpdate && !doUpdate) {
1255: // we are recreating the archive, need all resources
1256: return new ArchiveState(true, initialResources);
1257: }
1258:
1259: return new ArchiveState(needsUpdate, newerResources);
1260: }
1261:
1262: /**
1263: * Fetch all included and not excluded resources from the sets.
1264: *
1265: * <p>Included directories will precede included files.</p>
1266: * @param filesets an array of filesets
1267: * @return the resources included
1268: * @since Ant 1.5.2
1269: */
1270: protected Resource[][] grabResources(FileSet[] filesets) {
1271: Resource[][] result = new Resource[filesets.length][];
1272: for (int i = 0; i < filesets.length; i++) {
1273: boolean skipEmptyNames = true;
1274: if (filesets[i] instanceof ZipFileSet) {
1275: ZipFileSet zfs = (ZipFileSet) filesets[i];
1276: skipEmptyNames = zfs.getPrefix(getProject()).equals("")
1277: && zfs.getFullpath(getProject()).equals("");
1278: }
1279: DirectoryScanner rs = filesets[i]
1280: .getDirectoryScanner(getProject());
1281: if (rs instanceof ZipScanner) {
1282: ((ZipScanner) rs).setEncoding(encoding);
1283: }
1284: Vector resources = new Vector();
1285: if (!doFilesonly) {
1286: String[] directories = rs.getIncludedDirectories();
1287: for (int j = 0; j < directories.length; j++) {
1288: if (!"".equals(directories[j]) || !skipEmptyNames) {
1289: resources.addElement(rs
1290: .getResource(directories[j]));
1291: }
1292: }
1293: }
1294: String[] files = rs.getIncludedFiles();
1295: for (int j = 0; j < files.length; j++) {
1296: if (!"".equals(files[j]) || !skipEmptyNames) {
1297: resources.addElement(rs.getResource(files[j]));
1298: }
1299: }
1300:
1301: result[i] = new Resource[resources.size()];
1302: resources.copyInto(result[i]);
1303: }
1304: return result;
1305: }
1306:
1307: /**
1308: * Fetch all included and not excluded resources from the collections.
1309: *
1310: * <p>Included directories will precede included files.</p>
1311: * @param rcs an array of resource collections
1312: * @return the resources included
1313: * @since Ant 1.7
1314: */
1315: protected Resource[][] grabNonFileSetResources(
1316: ResourceCollection[] rcs) {
1317: Resource[][] result = new Resource[rcs.length][];
1318: for (int i = 0; i < rcs.length; i++) {
1319: Iterator iter = rcs[i].iterator();
1320: ArrayList rs = new ArrayList();
1321: int lastDir = 0;
1322: while (iter.hasNext()) {
1323: Resource r = (Resource) iter.next();
1324: if (r.isExists()) {
1325: if (r.isDirectory()) {
1326: rs.add(lastDir++, r);
1327: } else {
1328: rs.add(r);
1329: }
1330: }
1331: }
1332: result[i] = (Resource[]) rs
1333: .toArray(new Resource[rs.size()]);
1334: }
1335: return result;
1336: }
1337:
1338: /**
1339: * Add a directory to the zip stream.
1340: * @param dir the directort to add to the archive
1341: * @param zOut the stream to write to
1342: * @param vPath the name this entry shall have in the archive
1343: * @param mode the Unix permissions to set.
1344: * @throws IOException on error
1345: * @since Ant 1.5.2
1346: */
1347: protected void zipDir(File dir, ZipOutputStream zOut, String vPath,
1348: int mode) throws IOException {
1349: zipDir(dir, zOut, vPath, mode, null);
1350: }
1351:
1352: /**
1353: * Add a directory to the zip stream.
1354: * @param dir the directort to add to the archive
1355: * @param zOut the stream to write to
1356: * @param vPath the name this entry shall have in the archive
1357: * @param mode the Unix permissions to set.
1358: * @param extra ZipExtraFields to add
1359: * @throws IOException on error
1360: * @since Ant 1.6.3
1361: */
1362: protected void zipDir(File dir, ZipOutputStream zOut, String vPath,
1363: int mode, ZipExtraField[] extra) throws IOException {
1364: if (doFilesonly) {
1365: log("skipping directory " + vPath
1366: + " for file-only archive", Project.MSG_VERBOSE);
1367: return;
1368: }
1369: if (addedDirs.get(vPath) != null) {
1370: // don't add directories we've already added.
1371: // no warning if we try, it is harmless in and of itself
1372: return;
1373: }
1374:
1375: log("adding directory " + vPath, Project.MSG_VERBOSE);
1376: addedDirs.put(vPath, vPath);
1377:
1378: if (!skipWriting) {
1379: ZipEntry ze = new ZipEntry(vPath);
1380: if (dir != null && dir.exists()) {
1381: // ZIPs store time with a granularity of 2 seconds, round up
1382: ze.setTime(dir.lastModified() + (roundUp ? 1999 : 0));
1383: } else {
1384: // ZIPs store time with a granularity of 2 seconds, round up
1385: ze.setTime(System.currentTimeMillis()
1386: + (roundUp ? 1999 : 0));
1387: }
1388: ze.setSize(0);
1389: ze.setMethod(ZipEntry.STORED);
1390: // This is faintly ridiculous:
1391: ze.setCrc(EMPTY_CRC);
1392: ze.setUnixMode(mode);
1393:
1394: if (extra != null) {
1395: ze.setExtraFields(extra);
1396: }
1397:
1398: zOut.putNextEntry(ze);
1399: }
1400: }
1401:
1402: /**
1403: * Adds a new entry to the archive, takes care of duplicates as well.
1404: *
1405: * @param in the stream to read data for the entry from.
1406: * @param zOut the stream to write to.
1407: * @param vPath the name this entry shall have in the archive.
1408: * @param lastModified last modification time for the entry.
1409: * @param fromArchive the original archive we are copying this
1410: * entry from, will be null if we are not copying from an archive.
1411: * @param mode the Unix permissions to set.
1412: *
1413: * @since Ant 1.5.2
1414: * @throws IOException on error
1415: */
1416: protected void zipFile(InputStream in, ZipOutputStream zOut,
1417: String vPath, long lastModified, File fromArchive, int mode)
1418: throws IOException {
1419: if (entries.contains(vPath)) {
1420:
1421: if (duplicate.equals("preserve")) {
1422: log(vPath + " already added, skipping",
1423: Project.MSG_INFO);
1424: return;
1425: } else if (duplicate.equals("fail")) {
1426: throw new BuildException("Duplicate file " + vPath
1427: + " was found and the duplicate "
1428: + "attribute is 'fail'.");
1429: } else {
1430: // duplicate equal to add, so we continue
1431: log("duplicate file " + vPath + " found, adding.",
1432: Project.MSG_VERBOSE);
1433: }
1434: } else {
1435: log("adding entry " + vPath, Project.MSG_VERBOSE);
1436: }
1437:
1438: entries.put(vPath, vPath);
1439:
1440: if (!skipWriting) {
1441: ZipEntry ze = new ZipEntry(vPath);
1442: ze.setTime(lastModified);
1443: ze.setMethod(doCompress ? ZipEntry.DEFLATED
1444: : ZipEntry.STORED);
1445:
1446: /*
1447: * ZipOutputStream.putNextEntry expects the ZipEntry to
1448: * know its size and the CRC sum before you start writing
1449: * the data when using STORED mode - unless it is seekable.
1450: *
1451: * This forces us to process the data twice.
1452: */
1453: if (!zOut.isSeekable() && !doCompress) {
1454: long size = 0;
1455: CRC32 cal = new CRC32();
1456: if (!in.markSupported()) {
1457: // Store data into a byte[]
1458: ByteArrayOutputStream bos = new ByteArrayOutputStream();
1459:
1460: byte[] buffer = new byte[8 * 1024];
1461: int count = 0;
1462: do {
1463: size += count;
1464: cal.update(buffer, 0, count);
1465: bos.write(buffer, 0, count);
1466: count = in.read(buffer, 0, buffer.length);
1467: } while (count != -1);
1468: in = new ByteArrayInputStream(bos.toByteArray());
1469:
1470: } else {
1471: in.mark(Integer.MAX_VALUE);
1472: byte[] buffer = new byte[8 * 1024];
1473: int count = 0;
1474: do {
1475: size += count;
1476: cal.update(buffer, 0, count);
1477: count = in.read(buffer, 0, buffer.length);
1478: } while (count != -1);
1479: in.reset();
1480: }
1481: ze.setSize(size);
1482: ze.setCrc(cal.getValue());
1483: }
1484:
1485: ze.setUnixMode(mode);
1486: zOut.putNextEntry(ze);
1487:
1488: byte[] buffer = new byte[8 * 1024];
1489: int count = 0;
1490: do {
1491: if (count != 0) {
1492: zOut.write(buffer, 0, count);
1493: }
1494: count = in.read(buffer, 0, buffer.length);
1495: } while (count != -1);
1496: }
1497: addedFiles.addElement(vPath);
1498: }
1499:
1500: /**
1501: * Method that gets called when adding from <code>java.io.File</code> instances.
1502: *
1503: * <p>This implementation delegates to the six-arg version.</p>
1504: *
1505: * @param file the file to add to the archive
1506: * @param zOut the stream to write to
1507: * @param vPath the name this entry shall have in the archive
1508: * @param mode the Unix permissions to set.
1509: * @throws IOException on error
1510: *
1511: * @since Ant 1.5.2
1512: */
1513: protected void zipFile(File file, ZipOutputStream zOut,
1514: String vPath, int mode) throws IOException {
1515: if (file.equals(zipFile)) {
1516: throw new BuildException(
1517: "A zip file cannot include itself", getLocation());
1518: }
1519:
1520: FileInputStream fIn = new FileInputStream(file);
1521: try {
1522: // ZIPs store time with a granularity of 2 seconds, round up
1523: zipFile(fIn, zOut, vPath, file.lastModified()
1524: + (roundUp ? 1999 : 0), null, mode);
1525: } finally {
1526: fIn.close();
1527: }
1528: }
1529:
1530: /**
1531: * Ensure all parent dirs of a given entry have been added.
1532: * @param baseDir the base directory to use (may be null)
1533: * @param entry the entry name to create directories from
1534: * @param zOut the stream to write to
1535: * @param prefix a prefix to place on the created entries
1536: * @param dirMode the directory mode
1537: * @throws IOException on error
1538: * @since Ant 1.5.2
1539: */
1540: protected final void addParentDirs(File baseDir, String entry,
1541: ZipOutputStream zOut, String prefix, int dirMode)
1542: throws IOException {
1543: if (!doFilesonly) {
1544: Stack directories = new Stack();
1545: int slashPos = entry.length();
1546:
1547: while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) {
1548: String dir = entry.substring(0, slashPos + 1);
1549: if (addedDirs.get(prefix + dir) != null) {
1550: break;
1551: }
1552: directories.push(dir);
1553: }
1554:
1555: while (!directories.isEmpty()) {
1556: String dir = (String) directories.pop();
1557: File f = null;
1558: if (baseDir != null) {
1559: f = new File(baseDir, dir);
1560: } else {
1561: f = new File(dir);
1562: }
1563: zipDir(f, zOut, prefix + dir, dirMode);
1564: }
1565: }
1566: }
1567:
1568: /**
1569: * Do any clean up necessary to allow this instance to be used again.
1570: *
1571: * <p>When we get here, the Zip file has been closed and all we
1572: * need to do is to reset some globals.</p>
1573: *
1574: * <p>This method will only reset globals that have been changed
1575: * during execute(), it will not alter the attributes or nested
1576: * child elements. If you want to reset the instance so that you
1577: * can later zip a completely different set of files, you must use
1578: * the reset method.</p>
1579: *
1580: * @see #reset
1581: */
1582: protected void cleanUp() {
1583: addedDirs.clear();
1584: addedFiles.removeAllElements();
1585: entries.clear();
1586: addingNewFiles = false;
1587: doUpdate = savedDoUpdate;
1588: Enumeration e = filesetsFromGroupfilesets.elements();
1589: while (e.hasMoreElements()) {
1590: ZipFileSet zf = (ZipFileSet) e.nextElement();
1591: resources.removeElement(zf);
1592: }
1593: filesetsFromGroupfilesets.removeAllElements();
1594: }
1595:
1596: /**
1597: * Makes this instance reset all attributes to their default
1598: * values and forget all children.
1599: *
1600: * @since Ant 1.5
1601: *
1602: * @see #cleanUp
1603: */
1604: public void reset() {
1605: resources.removeAllElements();
1606: zipFile = null;
1607: baseDir = null;
1608: groupfilesets.removeAllElements();
1609: duplicate = "add";
1610: archiveType = "zip";
1611: doCompress = true;
1612: emptyBehavior = "skip";
1613: doUpdate = false;
1614: doFilesonly = false;
1615: encoding = null;
1616: }
1617:
1618: /**
1619: * Check is the resource arrays are empty.
1620: * @param r the arrays to check
1621: * @return true if all individual arrays are empty
1622: *
1623: * @since Ant 1.5.2
1624: */
1625: protected static final boolean isEmpty(Resource[][] r) {
1626: for (int i = 0; i < r.length; i++) {
1627: if (r[i].length > 0) {
1628: return false;
1629: }
1630: }
1631: return true;
1632: }
1633:
1634: /**
1635: * Drops all non-file resources from the given array.
1636: * @param orig the resources to filter
1637: * @return the filters resources
1638: * @since Ant 1.6
1639: */
1640: protected Resource[] selectFileResources(Resource[] orig) {
1641: if (orig.length == 0) {
1642: return orig;
1643: }
1644:
1645: Vector v = new Vector(orig.length);
1646: for (int i = 0; i < orig.length; i++) {
1647: if (!orig[i].isDirectory()) {
1648: v.addElement(orig[i]);
1649: } else {
1650: log("Ignoring directory " + orig[i].getName()
1651: + " as only files will be added.",
1652: Project.MSG_VERBOSE);
1653: }
1654: }
1655:
1656: if (v.size() != orig.length) {
1657: Resource[] r = new Resource[v.size()];
1658: v.copyInto(r);
1659: return r;
1660: }
1661: return orig;
1662: }
1663:
1664: /**
1665: * Possible behaviors when a duplicate file is added:
1666: * "add", "preserve" or "fail"
1667: */
1668: public static class Duplicate extends EnumeratedAttribute {
1669: /**
1670: * @see EnumeratedAttribute#getValues()
1671: */
1672: /** {@inheritDoc} */
1673: public String[] getValues() {
1674: return new String[] { "add", "preserve", "fail" };
1675: }
1676: }
1677:
1678: /**
1679: * Holds the up-to-date status and the out-of-date resources of
1680: * the original archive.
1681: *
1682: * @since Ant 1.5.3
1683: */
1684: public static class ArchiveState {
1685: private boolean outOfDate;
1686: private Resource[][] resourcesToAdd;
1687:
1688: ArchiveState(boolean state, Resource[][] r) {
1689: outOfDate = state;
1690: resourcesToAdd = r;
1691: }
1692:
1693: /**
1694: * Return the outofdate status.
1695: * @return the outofdate status
1696: */
1697: public boolean isOutOfDate() {
1698: return outOfDate;
1699: }
1700:
1701: /**
1702: * Get the resources to add.
1703: * @return the resources to add
1704: */
1705: public Resource[][] getResourcesToAdd() {
1706: return resourcesToAdd;
1707: }
1708:
1709: /**
1710: * find out if there are absolutely no resources to add
1711: * @since Ant 1.6.3
1712: * @return true if there are no resources to add
1713: */
1714: public boolean isWithoutAnyResources() {
1715: if (resourcesToAdd == null) {
1716: return true;
1717: }
1718: for (int counter = 0; counter < resourcesToAdd.length; counter++) {
1719: if (resourcesToAdd[counter] != null) {
1720: if (resourcesToAdd[counter].length > 0) {
1721: return false;
1722: }
1723: }
1724: }
1725: return true;
1726: }
1727: }
1728: }
|