Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 1,154   Methods: 39
NCLOC: 669   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
Zip.java 72.4% 73.6% 82.1% 73.8%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 package org.apache.tools.ant.taskdefs;
 55   
 
 56   
 import java.io.ByteArrayInputStream;
 57   
 import java.io.ByteArrayOutputStream;
 58   
 import java.io.File;
 59   
 import java.io.FileInputStream;
 60   
 import java.io.FileOutputStream;
 61   
 import java.io.IOException;
 62   
 import java.io.InputStream;
 63   
 import java.io.OutputStream;
 64   
 import java.util.Enumeration;
 65   
 import java.util.Hashtable;
 66   
 import java.util.Stack;
 67   
 import java.util.Vector;
 68   
 import java.util.zip.CRC32;
 69   
 import java.util.zip.ZipFile;
 70   
 import java.util.zip.ZipInputStream;
 71   
 
 72   
 import org.apache.tools.ant.BuildException;
 73   
 import org.apache.tools.ant.DirectoryScanner;
 74   
 import org.apache.tools.ant.FileScanner;
 75   
 import org.apache.tools.ant.Project;
 76   
 import org.apache.tools.ant.types.EnumeratedAttribute;
 77   
 import org.apache.tools.ant.types.FileSet;
 78   
 import org.apache.tools.ant.types.PatternSet;
 79   
 import org.apache.tools.ant.types.Resource;
 80   
 import org.apache.tools.ant.types.ZipFileSet;
 81   
 import org.apache.tools.ant.types.ZipScanner;
 82   
 import org.apache.tools.ant.util.FileNameMapper;
 83   
 import org.apache.tools.ant.util.FileUtils;
 84   
 import org.apache.tools.ant.util.GlobPatternMapper;
 85   
 import org.apache.tools.ant.util.IdentityMapper;
 86   
 import org.apache.tools.ant.util.MergingMapper;
 87   
 import org.apache.tools.ant.util.ResourceUtils;
 88   
 import org.apache.tools.zip.ZipEntry;
 89   
 import org.apache.tools.zip.ZipOutputStream;
 90   
 
 91   
 /**
 92   
  * Create a Zip file.
 93   
  *
 94   
  * @author James Davidson <a href="mailto:duncan@x180.com">duncan@x180.com</a>
 95   
  * @author Jon S. Stevens <a href="mailto:jon@clearink.com">jon@clearink.com</a>
 96   
  * @author Stefan Bodewig
 97   
  * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
 98   
  *
 99   
  * @since Ant 1.1
 100   
  *
 101   
  * @ant.task category="packaging"
 102   
  */
 103   
 public class Zip extends MatchingTask {
 104   
 
 105   
     protected File zipFile;
 106   
     // use to scan own archive
 107   
     private ZipScanner zs;
 108   
     private File baseDir;
 109   
     protected Hashtable entries = new Hashtable();
 110   
     private Vector groupfilesets = new Vector();
 111   
     private Vector filesetsFromGroupfilesets = new Vector();
 112   
     protected String duplicate = "add";
 113   
     private boolean doCompress = true;
 114   
     private boolean doUpdate = false;
 115   
     // shadow of the above if the value is altered in execute
 116   
     private boolean savedDoUpdate = false;
 117   
     private boolean doFilesonly = false;
 118   
     protected String archiveType = "zip";
 119   
 
 120   
     // For directories:
 121   
     private static final long EMPTY_CRC = new CRC32 ().getValue ();
 122   
     protected String emptyBehavior = "skip";
 123   
     private Vector filesets = new Vector ();
 124   
     protected Hashtable addedDirs = new Hashtable();
 125   
     private Vector addedFiles = new Vector();
 126   
 
 127   
     protected boolean doubleFilePass = false;
 128   
     protected boolean skipWriting = false;
 129   
 
 130   
     private static FileUtils fileUtils = FileUtils.newFileUtils();
 131   
 
 132   
     /**
 133   
      * true when we are adding new files into the Zip file, as opposed
 134   
      * to adding back the unchanged files
 135   
      */
 136   
     private boolean addingNewFiles = false;
 137   
 
 138   
     /**
 139   
      * Encoding to use for filenames, defaults to the platform's
 140   
      * default encoding.
 141   
      */
 142   
     private String encoding;
 143   
 
 144   
     /**
 145   
      * This is the name/location of where to
 146   
      * create the .zip file.
 147   
      *
 148   
      * @deprecated Use setDestFile(File) instead.
 149   
      * @ant.attribute ignore="true"
 150   
      */
 151  3
     public void setZipfile(File zipFile) {
 152  3
         setDestFile(zipFile);
 153   
     }
 154   
 
 155   
     /**
 156   
      * This is the name/location of where to
 157   
      * create the file.
 158   
      * @since Ant 1.5
 159   
      * @deprecated Use setDestFile(File) instead
 160   
      * @ant.attribute ignore="true"
 161   
      */
 162  17
     public void setFile(File file) {
 163  17
         setDestFile(file);
 164   
     }
 165   
 
 166   
 
 167   
     /**
 168   
      * The file to create; required.
 169   
      * @since Ant 1.5
 170   
      * @param destFile The new destination File
 171   
      */
 172  85
     public void setDestFile(File destFile) {
 173  85
        this.zipFile = destFile;
 174   
     }
 175   
 
 176   
     /**
 177   
      * The file to create.
 178   
      * @since Ant 1.5.2
 179   
      */
 180  24
     public File getDestFile() {
 181  24
         return zipFile;
 182   
     }
 183   
 
 184   
 
 185   
     /**
 186   
      * Directory from which to archive files; optional.
 187   
      */
 188  54
     public void setBasedir(File baseDir) {
 189  54
         this.baseDir = baseDir;
 190   
     }
 191   
 
 192   
     /**
 193   
      * Whether we want to compress the files or only store them;
 194   
      * optional, default=true;
 195   
      */
 196  0
     public void setCompress(boolean c) {
 197  0
         doCompress = c;
 198   
     }
 199   
 
 200   
     /**
 201   
      * Whether we want to compress the files or only store them;
 202   
      *
 203   
      * @since Ant 1.5.2
 204   
      */
 205  12
     public boolean isCompress() {
 206  12
         return doCompress;
 207   
     }
 208   
 
 209   
     /**
 210   
      * If true, emulate Sun's jar utility by not adding parent directories;
 211   
      * optional, defaults to false.
 212   
      */
 213  1
     public void setFilesonly(boolean f) {
 214  1
         doFilesonly = f;
 215   
     }
 216   
 
 217   
     /**
 218   
      * If true, updates an existing file, otherwise overwrite
 219   
      * any existing one; optional defaults to false.
 220   
      */
 221  11
     public void setUpdate(boolean c) {
 222  11
         doUpdate = c;
 223  11
         savedDoUpdate = c;
 224   
     }
 225   
 
 226   
     /**
 227   
      * Are we updating an existing archive?
 228   
      */
 229  73
     public boolean isInUpdateMode() {
 230  73
         return doUpdate;
 231   
     }
 232   
 
 233   
     /**
 234   
      * Adds a set of files.
 235   
      */
 236  5
     public void addFileset(FileSet set) {
 237  5
         filesets.addElement(set);
 238   
     }
 239   
 
 240   
     /**
 241   
      * Adds a set of files that can be
 242   
      * read from an archive and be given a prefix/fullpath.
 243   
      */
 244  8
     public void addZipfileset(ZipFileSet set) {
 245  8
         filesets.addElement(set);
 246   
     }
 247   
 
 248   
     /**
 249   
      * Adds a group of zip files.
 250   
      */
 251  1
     public void addZipGroupFileset(FileSet set) {
 252  1
         groupfilesets.addElement(set);
 253   
     }
 254   
 
 255   
     /**
 256   
      * Sets behavior for when a duplicate file is about to be added -
 257   
      * one of <code>keep</code>, <code>skip</code> or <code>overwrite</code>.
 258   
      * Possible values are: <code>keep</code> (keep both
 259   
      * of the files); <code>skip</code> (keep the first version
 260   
      * of the file found); <code>overwrite</code> overwrite the file
 261   
      * with the new file
 262   
      * Default for zip tasks is <code>keep</code>
 263   
      */
 264  0
     public void setDuplicate(Duplicate df) {
 265  0
         duplicate = df.getValue();
 266   
     }
 267   
 
 268   
     /**
 269   
      * Possible behaviors when there are no matching files for the task:
 270   
      * "fail", "skip", or "create".
 271   
      */
 272   
     public static class WhenEmpty extends EnumeratedAttribute {
 273  1
         public String[] getValues() {
 274  1
             return new String[] {"fail", "skip", "create"};
 275   
         }
 276   
     }
 277   
 
 278   
     /**
 279   
      * Sets behavior of the task when no files match.
 280   
      * Possible values are: <code>fail</code> (throw an exception
 281   
      * and halt the build); <code>skip</code> (do not create
 282   
      * any archive, but issue a warning); <code>create</code>
 283   
      * (make an archive with no entries).
 284   
      * Default for zip tasks is <code>skip</code>;
 285   
      * for jar tasks, <code>create</code>.
 286   
      */
 287  0
     public void setWhenempty(WhenEmpty we) {
 288  0
         emptyBehavior = we.getValue();
 289   
     }
 290   
 
 291   
     /**
 292   
      * Encoding to use for filenames, defaults to the platform's
 293   
      * default encoding.
 294   
      *
 295   
      * <p>For a list of possible values see <a
 296   
      * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.</p>
 297   
      */
 298  63
     public void setEncoding(String encoding) {
 299  63
         this.encoding = encoding;
 300   
     }
 301   
 
 302   
     /**
 303   
      * Encoding to use for filenames.
 304   
      *
 305   
      * @since Ant 1.5.2
 306   
      */
 307  12
     public String getEncoding() {
 308  12
         return encoding;
 309   
     }
 310   
 
 311   
     /**
 312   
      * validate and build
 313   
      */
 314  82
     public void execute() throws BuildException {
 315   
 
 316  82
         if (doubleFilePass) {
 317  0
             skipWriting = true;
 318  0
             executeMain();
 319  0
             skipWriting = false;
 320  0
             executeMain();
 321   
         }
 322   
         else {
 323  82
             executeMain();
 324   
         }
 325   
     }
 326   
 
 327  82
     public void executeMain() throws BuildException {
 328   
 
 329  82
         if (baseDir == null && filesets.size() == 0
 330   
             && groupfilesets.size() == 0 && "zip".equals(archiveType)) {
 331  2
             throw new BuildException("basedir attribute must be set, "
 332   
                                      + "or at least "
 333   
                                      + "one fileset must be given!");
 334   
         }
 335   
 
 336  80
         if (zipFile == null) {
 337  1
             throw new BuildException("You must specify the "
 338   
                                      + archiveType + " file to create!");
 339   
         }
 340   
 
 341   
         // Renamed version of original file, if it exists
 342  79
         File renamedFile = null;
 343   
         // Whether or not an actual update is required -
 344   
         // we don't need to update if the original file doesn't exist
 345   
 
 346  79
         addingNewFiles = true;
 347  79
         if (doUpdate && !zipFile.exists()) {
 348  0
             doUpdate = false;
 349  0
             log("ignoring update attribute as " + archiveType
 350   
                 + " doesn't exist.", Project.MSG_DEBUG);
 351   
         }
 352   
 
 353   
         // Add the files found in groupfileset to fileset
 354  79
         for (int i = 0; i < groupfilesets.size(); i++) {
 355   
 
 356  1
             log("Processing groupfileset ", Project.MSG_VERBOSE);
 357  1
             FileSet fs = (FileSet) groupfilesets.elementAt(i);
 358  1
             FileScanner scanner = fs.getDirectoryScanner(getProject());
 359  1
             String[] files = scanner.getIncludedFiles();
 360  1
             File basedir = scanner.getBasedir();
 361  1
             for (int j = 0; j < files.length; j++) {
 362   
 
 363  2
                 log("Adding file " + files[j] + " to fileset",
 364   
                     Project.MSG_VERBOSE);
 365  2
                 ZipFileSet zf = new ZipFileSet();
 366  2
                 zf.setSrc(new File(basedir, files[j]));
 367  2
                 filesets.addElement(zf);
 368  2
                 filesetsFromGroupfilesets.addElement(zf);
 369   
             }
 370   
         }
 371   
 
 372   
         // collect filesets to pass them to getResourcesToAdd
 373  79
         Vector vfss = new Vector();
 374  79
         if (baseDir != null) {
 375  55
             FileSet fs = (FileSet) getImplicitFileSet().clone();
 376  55
             fs.setDir(baseDir);
 377  55
             vfss.addElement(fs);
 378   
         }
 379  79
         for (int i = 0; i < filesets.size(); i++) {
 380  17
             FileSet fs = (FileSet) filesets.elementAt(i);
 381  17
             vfss.addElement(fs);
 382   
         }
 383   
 
 384  79
         FileSet[] fss = new FileSet[vfss.size()];
 385  79
         vfss.copyInto(fss);
 386  79
         boolean success = false;
 387  79
         try {
 388   
             // can also handle empty archives
 389  79
             ArchiveState state = getResourcesToAdd(fss, zipFile, false);
 390   
 
 391   
             // quick exit if the target is up to date
 392  75
             if (!state.isOutOfDate()) {
 393  8
                 return;
 394   
             }
 395   
 
 396  67
             Resource[][] addThem = state.getResourcesToAdd();
 397   
 
 398  67
             if (doUpdate) {
 399  7
                 renamedFile =
 400   
                     fileUtils.createTempFile("zip", ".tmp",
 401   
                                              fileUtils.getParentFile(zipFile));
 402   
 
 403  7
                 try {
 404  7
                     if (!zipFile.renameTo(renamedFile)) {
 405  0
                         throw new BuildException("Unable to rename old file "
 406   
                                                  + "to temporary file");
 407   
                     }
 408   
                 } catch (SecurityException e) {
 409  0
                     throw new BuildException("Not allowed to rename old file "
 410   
                                              + "to temporary file");
 411   
                 }
 412   
             }
 413   
 
 414  67
             String action = doUpdate ? "Updating " : "Building ";
 415   
 
 416  67
             log(action + archiveType + ": " + zipFile.getAbsolutePath());
 417   
 
 418  67
             ZipOutputStream zOut = null;
 419  67
             try {
 420   
 
 421  67
                 if (! skipWriting) {
 422  67
                     zOut = new ZipOutputStream(new FileOutputStream(zipFile));
 423   
 
 424  67
                     zOut.setEncoding(encoding);
 425  67
                     if (doCompress) {
 426  67
                         zOut.setMethod(ZipOutputStream.DEFLATED);
 427   
                     } else {
 428  0
                         zOut.setMethod(ZipOutputStream.STORED);
 429   
                     }
 430   
                 }
 431  67
                 initZipOutputStream(zOut);
 432   
 
 433   
                 // Add the explicit filesets to the archive.
 434  67
                 for (int i = 0; i < fss.length; i++) {
 435  62
                     if (addThem[i].length != 0) {
 436  60
                         addResources(fss[i], addThem[i], zOut);
 437   
                     }
 438   
                 }
 439   
                 
 440  67
                 if (doUpdate) {
 441  7
                     addingNewFiles = false;
 442  7
                     ZipFileSet oldFiles = new ZipFileSet();
 443  7
                     oldFiles.setSrc(renamedFile);
 444   
 
 445  7
                     for (int i = 0; i < addedFiles.size(); i++) {
 446  61
                         PatternSet.NameEntry ne = oldFiles.createExclude();
 447  61
                         ne.setName((String) addedFiles.elementAt(i));
 448   
                     }
 449  7
                     DirectoryScanner ds = 
 450   
                         oldFiles.getDirectoryScanner(getProject());
 451  7
                     String[] f = ds.getIncludedFiles();
 452  7
                     Resource[] r = new Resource[f.length];
 453  7
                     for (int i = 0; i < f.length; i++) {
 454  4
                         r[i] = ds.getResource(f[i]);
 455   
                     }
 456   
                     
 457  7
                     addResources(oldFiles, r, zOut);
 458   
                 }
 459  67
                 finalizeZipOutputStream(zOut);
 460   
 
 461   
                 // If we've been successful on an update, delete the
 462   
                 // temporary file
 463  67
                 if (doUpdate) {
 464  7
                     if (!renamedFile.delete()) {
 465  0
                         log ("Warning: unable to delete temporary file " +
 466   
                              renamedFile.getName(), Project.MSG_WARN);
 467   
                     }
 468   
                 }
 469  67
                 success = true;
 470   
             } finally {
 471   
                 // Close the output stream.
 472  67
                 try {
 473  67
                     if (zOut != null) {
 474  67
                         zOut.close();
 475   
                     }
 476   
                 } catch (IOException ex) {
 477   
                     // If we're in this finally clause because of an
 478   
                     // exception, we don't really care if there's an
 479   
                     // exception when closing the stream. E.g. if it
 480   
                     // throws "ZIP file must have at least one entry",
 481   
                     // because an exception happened before we added
 482   
                     // any files, then we must swallow this
 483   
                     // exception. Otherwise, the error that's reported
 484   
                     // will be the close() error, which is not the
 485   
                     // real cause of the problem.
 486  0
                     if (success) {
 487  0
                         throw ex;
 488   
                     }
 489   
                 }
 490   
             }
 491   
         } catch (IOException ioe) {
 492  0
             String msg = "Problem creating " + archiveType + ": "
 493   
                 + ioe.getMessage();
 494   
 
 495   
             // delete a bogus ZIP file (but only if it's not the original one)
 496  0
             if ((!doUpdate || renamedFile != null) && !zipFile.delete()) {
 497  0
                 msg += " (and the archive is probably corrupt but I could not "
 498   
                     + "delete it)";
 499   
             }
 500   
 
 501  0
             if (doUpdate && renamedFile != null) {
 502  0
                 if (!renamedFile.renameTo(zipFile)) {
 503  0
                     msg += " (and I couldn't rename the temporary file " +
 504   
                         renamedFile.getName() + " back)";
 505   
                 }
 506   
             }
 507   
 
 508  0
             throw new BuildException(msg, ioe, getLocation());
 509   
         } finally {
 510  79
             cleanUp();
 511   
         }
 512   
     }
 513   
 
 514   
     /**
 515   
      * Indicates if the task is adding new files into the archive as opposed to
 516   
      * copying back unchanged files from the backup copy
 517   
      */
 518  0
     protected final boolean isAddingNewFiles() {
 519  0
         return addingNewFiles;
 520   
     }
 521   
 
 522   
     /**
 523   
      * Add the given resources.
 524   
      *
 525   
      * @param fileset may give additional information like fullpath or
 526   
      * permissions.
 527   
      * @param resources the resources to add
 528   
      * @param zOut the stream to write to
 529   
      *
 530   
      * @since Ant 1.5.2
 531   
      */
 532  67
     protected final void addResources(FileSet fileset, Resource[] resources,
 533   
                                       ZipOutputStream zOut)
 534   
         throws IOException {
 535   
 
 536  67
         String prefix = "";
 537  67
         String fullpath = "";
 538  67
         int dirMode = ZipFileSet.DEFAULT_DIR_MODE;
 539  67
         int fileMode = ZipFileSet.DEFAULT_FILE_MODE;
 540   
 
 541  67
         ZipFileSet zfs = null;
 542  67
         if (fileset instanceof ZipFileSet) {
 543  16
             zfs = (ZipFileSet) fileset;
 544  16
             prefix = zfs.getPrefix();
 545  16
             fullpath = zfs.getFullpath();
 546  16
             dirMode = zfs.getDirMode();
 547  16
             fileMode = zfs.getFileMode();
 548   
         }
 549   
 
 550  67
         if (prefix.length() > 0 && fullpath.length() > 0) {
 551  0
             throw new BuildException("Both prefix and fullpath attributes must"
 552   
                                      + " not be set on the same fileset.");
 553   
         }
 554   
 
 555  67
         if (resources.length != 1 && fullpath.length() > 0) {
 556  0
             throw new BuildException("fullpath attribute may only be specified"
 557   
                                      + " for filesets that specify a single"
 558   
                                      + " file.");
 559   
         }
 560   
 
 561  67
         if (prefix.length() > 0) {
 562  1
             if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
 563  1
                 prefix += "/";
 564   
             }
 565  1
             addParentDirs(null, prefix, zOut, "", dirMode);
 566   
         }
 567   
 
 568  67
         ZipFile zf = null;
 569  67
         try {
 570  67
             boolean dealingWithFiles = false;
 571  67
             File base = null;
 572   
 
 573  67
             if (zfs == null || zfs.getSrc() == null) {
 574  53
                 dealingWithFiles = true;
 575  53
                 base = fileset.getDir(getProject());
 576   
             } else {
 577  14
                 zf = new ZipFile(zfs.getSrc());
 578   
             }
 579   
             
 580  67
             for (int i = 0; i < resources.length; i++) {
 581  1413
                 String name = null;
 582  1413
                 if (fullpath.length() > 0) {
 583  1
                     name = fullpath;
 584   
                 } else {
 585  1412
                     name = resources[i].getName();
 586   
                 }
 587  1413
                 name = name.replace(File.separatorChar, '/');
 588   
                 
 589  1413
                 if ("".equals(name)) {
 590  9
                     continue;
 591   
                 }
 592  1404
                 if (resources[i].isDirectory() && ! name.endsWith("/")) {
 593  190
                     name = name + "/";
 594   
                 }
 595   
                 
 596  1404
                 addParentDirs(base, name, zOut, prefix, dirMode);
 597   
                 
 598  1404
                 if (!resources[i].isDirectory() && dealingWithFiles) {
 599  905
                     File f = fileUtils.resolveFile(base, 
 600   
                                                    resources[i].getName());
 601  905
                     zipFile(f, zOut, prefix + name, fileMode);
 602  499
                 } else if (!resources[i].isDirectory()) {
 603  242
                     java.util.zip.ZipEntry ze = 
 604   
                         zf.getEntry(resources[i].getName());
 605  242
                     if (ze != null) {
 606  242
                         zipFile(zf.getInputStream(ze), zOut, prefix + name, 
 607   
                                 ze.getTime(), zfs.getSrc(), fileMode);
 608   
                     }
 609   
                 }
 610   
             }
 611   
         } finally {
 612  67
             if (zf != null) {
 613  14
                 zf.close();
 614   
             }
 615   
         }
 616   
     }
 617   
 
 618   
     /**
 619   
      * method for subclasses to override
 620   
      */
 621  76
     protected void initZipOutputStream(ZipOutputStream zOut)
 622   
         throws IOException, BuildException {
 623   
     }
 624   
 
 625   
     /**
 626   
      * method for subclasses to override
 627   
      */
 628  20
     protected void finalizeZipOutputStream(ZipOutputStream zOut)
 629   
         throws IOException, BuildException {
 630   
     }
 631   
 
 632   
     /**
 633   
      * Create an empty zip file
 634   
      *
 635   
      * @return true for historic reasons
 636   
      */
 637  0
     protected boolean createEmptyZip(File zipFile) throws BuildException {
 638   
         // In this case using java.util.zip will not work
 639   
         // because it does not permit a zero-entry archive.
 640   
         // Must create it manually.
 641  0
         log("Note: creating empty " + archiveType + " archive " + zipFile,
 642   
             Project.MSG_INFO);
 643  0
         OutputStream os = null;
 644  0
         try {
 645  0
             os = new FileOutputStream(zipFile);
 646   
             // Cf. PKZIP specification.
 647  0
             byte[] empty = new byte[22];
 648  0
             empty[0] = 80; // P
 649  0
             empty[1] = 75; // K
 650  0
             empty[2] = 5;
 651  0
             empty[3] = 6;
 652   
             // remainder zeros
 653  0
             os.write(empty);
 654   
         } catch (IOException ioe) {
 655  0
             throw new BuildException("Could not create empty ZIP archive "
 656   
                                      + "(" + ioe.getMessage() + ")", ioe,
 657   
                                      getLocation());
 658   
         } finally {
 659  0
             if (os != null) {
 660  0
                 try {
 661  0
                     os.close();
 662   
                 } catch (IOException e) {
 663   
                 }
 664   
             }
 665   
         }
 666  0
         return true;
 667   
     }
 668   
 
 669   
     /**
 670   
      * @since Ant 1.5.2
 671   
      */
 672  18
     private synchronized ZipScanner getZipScanner() {
 673  18
         if (zs == null) {
 674  18
             zs = new ZipScanner();
 675  18
             zs.setSrc(zipFile);
 676   
         }
 677  18
         return zs;
 678   
     }
 679   
 
 680   
     /**
 681   
      * Collect the resources that are newer than the corresponding
 682   
      * entries (or missing) in the original archive.
 683   
      *
 684   
      * <p>If we are going to recreate the archive instead of updating
 685   
      * it, all resources should be considered as new, if a single one
 686   
      * is.  Because of this, subclasses overriding this method must
 687   
      * call <code>super.getResourcesToAdd</code> and indicate with the
 688   
      * third arg if they already know that the archive is
 689   
      * out-of-date.</p>
 690   
      *
 691   
      * @param filesets The filesets to grab resources from
 692   
      * @param zipFile intended archive file (may or may not exist)
 693   
      * @param needsUpdate whether we already know that the archive is
 694   
      * out-of-date.  Subclasses overriding this method are supposed to
 695   
      * set this value correctly in their call to
 696   
      * super.getResourcesToAdd.
 697   
      * @return an array of resources to add for each fileset passed in as well
 698   
      *         as a flag that indicates whether the archive is uptodate.
 699   
      *
 700   
      * @exception BuildException if it likes
 701   
      */
 702  79
     protected ArchiveState getResourcesToAdd(FileSet[] filesets,
 703   
                                              File zipFile,
 704   
                                              boolean needsUpdate)
 705   
         throws BuildException {
 706   
 
 707  79
         Resource[][] initialResources = grabResources(filesets);
 708  79
         if (isEmpty(initialResources)) {
 709  13
             if (needsUpdate && doUpdate) {
 710   
                 /*
 711   
                  * This is a rather hairy case.
 712   
                  *
 713   
                  * One of our subclasses knows that we need to update the
 714   
                  * archive, but at the same time, there are no resources
 715   
                  * known to us that would need to be added.  Only the
 716   
                  * subclass seems to know what's going on.
 717   
                  *
 718   
                  * This happens if <jar> detects that the manifest has changed,
 719   
                  * for example.  The manifest is not part of any resources
 720   
                  * because of our support for inline <manifest>s.
 721   
                  *
 722   
                  * If we invoke createEmptyZip like Ant 1.5.2 did,
 723   
                  * we'll loose all stuff that has been in the original
 724   
                  * archive (bugzilla report 17780).
 725   
                  */
 726  1
                 return new ArchiveState(true, initialResources);
 727   
             }
 728   
 
 729  12
             if (emptyBehavior.equals("skip")) {
 730  0
                 if (doUpdate) {
 731  0
                     log(archiveType + " archive " + zipFile 
 732   
                         + " not updated because no new files were included.", 
 733   
                         Project.MSG_VERBOSE);
 734   
                 } else {
 735  0
                     log("Warning: skipping " + archiveType + " archive " 
 736   
                         + zipFile + " because no files were included.", 
 737   
                         Project.MSG_WARN);
 738   
                 }
 739  12
             } else if (emptyBehavior.equals("fail")) {
 740  0
                 throw new BuildException("Cannot create " + archiveType
 741   
                                          + " archive " + zipFile +
 742   
                                          ": no files were included.", 
 743   
                                          getLocation());
 744   
             } else {
 745   
                 // Create.
 746  12
                 createEmptyZip(zipFile);
 747   
             }
 748  9
             return new ArchiveState(needsUpdate, initialResources);
 749   
         }
 750   
 
 751   
         // initialResources is not empty
 752   
 
 753  66
         if (!zipFile.exists()) {
 754  47
             return new ArchiveState(true, initialResources);
 755   
         }
 756   
 
 757  19
         if (needsUpdate && !doUpdate) {
 758   
             // we are recreating the archive, need all resources
 759  0
             return new ArchiveState(true, initialResources);
 760   
         }
 761   
 
 762  19
         Resource[][] newerResources = new Resource[filesets.length][];
 763   
 
 764  19
         for (int i = 0; i < filesets.length; i++) {
 765  20
             if (!(fileset instanceof ZipFileSet) 
 766   
                 || ((ZipFileSet) fileset).getSrc() == null) {
 767  20
                 File base = filesets[i].getDir(getProject());
 768   
             
 769  20
                 for (int j = 0; j < initialResources[i].length; j++) {
 770  395
                     File resourceAsFile = 
 771   
                         fileUtils.resolveFile(base, 
 772   
                                               initialResources[i][j].getName());
 773  395
                     if (resourceAsFile.equals(zipFile)) {
 774  1
                         throw new BuildException("A zip file cannot include "
 775   
                                                  + "itself", getLocation());
 776   
                     }
 777   
                 }
 778   
             }
 779   
         }
 780   
 
 781  18
         for (int i = 0; i < filesets.length; i++) {
 782  19
             if (initialResources[i].length == 0) {
 783  1
                 newerResources[i] = new Resource[] {};
 784  1
                 continue;
 785   
             }
 786   
             
 787  18
             FileNameMapper myMapper = new IdentityMapper();
 788  18
             if (filesets[i] instanceof ZipFileSet) {
 789  6
                 ZipFileSet zfs = (ZipFileSet) filesets[i];
 790  6
                 if (zfs.getFullpath() != null
 791   
                     && !zfs.getFullpath().equals("") ) {
 792   
                     // in this case all files from origin map to
 793   
                     // the fullPath attribute of the zipfileset at
 794   
                     // destination
 795  0
                     MergingMapper fm = new MergingMapper();
 796  0
                     fm.setTo(zfs.getFullpath());
 797  0
                     myMapper = fm;
 798   
 
 799  6
                 } else if (zfs.getPrefix() != null 
 800   
                            && !zfs.getPrefix().equals("")) {
 801  0
                     GlobPatternMapper gm=new GlobPatternMapper();
 802  0
                     gm.setFrom("*");
 803  0
                     String prefix = zfs.getPrefix();
 804  0
                     if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
 805  0
                         prefix += "/";
 806   
                     }
 807  0
                     gm.setTo(prefix + "*");
 808  0
                     myMapper = gm;
 809   
                 }
 810   
             }
 811  18
             newerResources[i] = 
 812   
                 ResourceUtils.selectOutOfDateSources(this,
 813   
                                                      initialResources[i],
 814   
                                                      myMapper,
 815   
                                                      getZipScanner());
 816  18
             needsUpdate = needsUpdate || (newerResources[i].length > 0);
 817   
 
 818  18
             if (needsUpdate && !doUpdate) {
 819   
                 // we will return initialResources anyway, no reason
 820   
                 // to scan further.
 821  4
                 break;
 822   
             }
 823   
         }
 824   
 
 825  18
         if (needsUpdate && !doUpdate) {
 826   
             // we are recreating the archive, need all resources
 827  4
             return new ArchiveState(true, initialResources);
 828   
         }
 829   
         
 830  14
         return new ArchiveState(needsUpdate, newerResources);
 831   
     }
 832   
 
 833   
     /**
 834   
      * Fetch all included and not excluded resources from the sets.
 835   
      *
 836   
      * <p>Included directories will preceede included files.</p> 
 837   
      *
 838   
      * @since Ant 1.5.2
 839   
      */
 840  79
     protected Resource[][] grabResources(FileSet[] filesets) {
 841  79
         Resource[][] result = new Resource[filesets.length][];
 842  79
         for (int i = 0; i < filesets.length; i++) {
 843  72
             DirectoryScanner rs = 
 844   
                 filesets[i].getDirectoryScanner(getProject());
 845  72
             Vector resources = new Vector();
 846  72
             String[] directories = rs.getIncludedDirectories();
 847  72
             for (int j = 0; j < directories.length; j++) {
 848  312
                 resources.addElement(rs.getResource(directories[j]));
 849   
             }
 850  72
             String[] files = rs.getIncludedFiles();
 851  72
             for (int j = 0; j < files.length; j++) {
 852  1384
                 resources.addElement(rs.getResource(files[j]));
 853   
             }
 854   
             
 855  72
             result[i] = new Resource[resources.size()];
 856  72
             resources.copyInto(result[i]);
 857   
         }
 858  79
         return result;
 859   
     }
 860   
 
 861   
     /**
 862   
      * @since Ant 1.5.2
 863   
      */
 864  279
     protected void zipDir(File dir, ZipOutputStream zOut, String vPath,
 865   
                           int mode)
 866   
         throws IOException {
 867  279
         if (addedDirs.get(vPath) != null) {
 868   
             // don't add directories we've already added.
 869   
             // no warning if we try, it is harmless in and of itself
 870  9
             return;
 871   
         }
 872   
 
 873  270
         log("adding directory " + vPath, Project.MSG_VERBOSE);
 874  270
         addedDirs.put(vPath, vPath);
 875   
 
 876  270
         if (! skipWriting) {
 877  270
             ZipEntry ze = new ZipEntry (vPath);
 878  270
             if (dir != null && dir.exists()) {
 879   
                 // ZIPs store time with a granularity of 2 seconds, round up
 880  210
                 ze.setTime(dir.lastModified() + 1999);
 881   
             } else {
 882   
                 // ZIPs store time with a granularity of 2 seconds, round up
 883  60
                 ze.setTime(System.currentTimeMillis() + 1999);
 884   
             }
 885  270
             ze.setSize (0);
 886  270
             ze.setMethod (ZipEntry.STORED);
 887   
             // This is faintly ridiculous:
 888  270
             ze.setCrc (EMPTY_CRC);
 889  270
             ze.setUnixMode(mode);
 890   
 
 891  270
             zOut.putNextEntry (ze);
 892   
         }
 893   
     }
 894   
 
 895   
     /**
 896   
      * Adds a new entry to the archive, takes care of duplicates as well.
 897   
      *
 898   
      * @param in the stream to read data for the entry from.
 899   
      * @param zOut the stream to write to.
 900   
      * @param vPath the name this entry shall have in the archive.
 901   
      * @param lastModified last modification time for the entry.
 902   
      * @param fromArchive the original archive we are copying this
 903   
      * entry from, will be null if we are not copying from an archive.
 904   
      * @param mode the Unix permissions to set.
 905   
      *
 906   
      * @since Ant 1.5.2
 907   
      */
 908  1203
     protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,
 909   
                            long lastModified, File fromArchive, int mode)
 910   
         throws IOException {
 911  1203
         if (entries.contains(vPath)) {
 912   
 
 913  17
             if (duplicate.equals("preserve")) {
 914  0
                 log(vPath + " already added, skipping", Project.MSG_INFO);
 915  0
                 return;
 916  17
             } else if (duplicate.equals("fail")) {
 917  0
                 throw new BuildException("Duplicate file " + vPath
 918   
                                          + " was found and the duplicate "
 919   
                                          + "attribute is 'fail'.");
 920   
             } else {
 921   
                 // duplicate equal to add, so we continue
 922  17
                 log("duplicate file " + vPath
 923   
                     + " found, adding.", Project.MSG_VERBOSE);
 924   
             }
 925   
         } else {
 926  1186
             log("adding entry " + vPath, Project.MSG_VERBOSE);
 927   
         }
 928   
 
 929  1203
         entries.put(vPath, vPath);
 930   
 
 931  1203
         if (! skipWriting) {
 932  1203
             ZipEntry ze = new ZipEntry(vPath);
 933  1203
             ze.setTime(lastModified);
 934   
 
 935   
             /*
 936   
             * ZipOutputStream.putNextEntry expects the ZipEntry to
 937   
             * know its size and the CRC sum before you start writing
 938   
             * the data when using STORED mode.
 939   
             *
 940   
             * This forces us to process the data twice.
 941   
             *
 942   
             * In DEFLATED mode, it will take advantage of a Zip
 943   
             * Version 2 feature where size can be stored after the
 944   
             * data (as the data itself signals end of data).
 945   
             */
 946  1203
             if (!doCompress) {
 947  0
                 long size = 0;
 948  0
                 CRC32 cal = new CRC32();
 949  0
                 if (!in.markSupported()) {
 950   
                     // Store data into a byte[]
 951  0
                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
 952   
 
 953  0
                     byte[] buffer = new byte[8 * 1024];
 954  0
                     int count = 0;
 955  0
                     do {
 956  0
                         size += count;
 957  0
                         cal.update(buffer, 0, count);
 958  0
                         bos.write(buffer, 0, count);
 959  0
                         count = in.read(buffer, 0, buffer.length);
 960  0
                     } while (count != -1);
 961  0
                     in = new ByteArrayInputStream(bos.toByteArray());
 962   
 
 963   
                 } else {
 964  0
                     in.mark(Integer.MAX_VALUE);
 965  0
                     byte[] buffer = new byte[8 * 1024];
 966  0
                     int count = 0;
 967  0
                     do {
 968  0
                         size += count;
 969  0
                         cal.update(buffer, 0, count);
 970  0
                         count = in.read(buffer, 0, buffer.length);
 971  0
                     } while (count != -1);
 972  0
                     in.reset();
 973   
                 }
 974  0
                 ze.setSize(size);
 975  0
                 ze.setCrc(cal.getValue());
 976   
             }
 977   
 
 978  1203
             ze.setUnixMode(mode);
 979  1203
             zOut.putNextEntry(ze);
 980   
 
 981  1203
             byte[] buffer = new byte[8 * 1024];
 982  1203
             int count = 0;
 983  1203
             do {
 984  5810
                 if (count != 0) {
 985  4607
                     zOut.write(buffer, 0, count);
 986   
                 }
 987  5810
                 count = in.read(buffer, 0, buffer.length);
 988  5810
             } while (count != -1);
 989   
         }
 990  1203
         addedFiles.addElement(vPath);
 991   
     }
 992   
 
 993   
     /**
 994   
      * Method that gets called when adding from java.io.File instances.
 995   
      *
 996   
      * <p>This implementation delegates to the six-arg version.</p>
 997   
      *
 998   
      * @param file the file to add to the archive
 999   
      * @param zOut the stream to write to
 1000   
      * @param vPath the name this entry shall have in the archive
 1001   
      * @param mode the Unix permissions to set.
 1002   
      *
 1003   
      * @since Ant 1.5.2
 1004   
      */
 1005  905
     protected void zipFile(File file, ZipOutputStream zOut, String vPath, 
 1006   
                            int mode)
 1007   
         throws IOException {
 1008  905
         if (file.equals(zipFile)) {
 1009  0
             throw new BuildException("A zip file cannot include itself",
 1010   
                                      getLocation());
 1011   
         }
 1012   
 
 1013  905
         FileInputStream fIn = new FileInputStream(file);
 1014  905
         try {
 1015   
             // ZIPs store time with a granularity of 2 seconds, round up
 1016  905
             zipFile(fIn, zOut, vPath, file.lastModified() + 1999, null, mode);
 1017   
         } finally {
 1018  905
             fIn.close();
 1019   
         }
 1020   
     }
 1021   
 
 1022   
     /**
 1023   
      * Ensure all parent dirs of a given entry have been added.
 1024   
      *
 1025   
      * @since Ant 1.5.2
 1026   
      */
 1027  1405
     protected final void addParentDirs(File baseDir, String entry,
 1028   
                                        ZipOutputStream zOut, String prefix,
 1029   
                                        int dirMode)
 1030   
         throws IOException {
 1031  1405
         if (!doFilesonly) {
 1032  1405
             Stack directories = new Stack();
 1033  1405
             int slashPos = entry.length();
 1034   
 
 1035  ?
             while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) {
 1036  1115
                 String dir = entry.substring(0, slashPos + 1);
 1037  1115
                 if (addedDirs.get(prefix + dir) != null) {
 1038  892
                     break;
 1039   
                 }
 1040  223
                 directories.push(dir);
 1041   
             }
 1042   
 
 1043  1405
             while (!directories.isEmpty()) {
 1044  223
                 String dir = (String) directories.pop();
 1045  223
                 File f = null;
 1046  223
                 if (baseDir != null) {
 1047  211
                     f = new File(baseDir, dir);
 1048   
                 } else {
 1049  12
                     f = new File(dir);
 1050   
                 }
 1051  223
                 zipDir(f, zOut, prefix + dir, dirMode);
 1052   
             }
 1053   
         }
 1054   
     }
 1055   
 
 1056   
     /**
 1057   
      * Do any clean up necessary to allow this instance to be used again.
 1058   
      *
 1059   
      * <p>When we get here, the Zip file has been closed and all we
 1060   
      * need to do is to reset some globals.</p>
 1061   
      *
 1062   
      * <p>This method will only reset globals that have been changed
 1063   
      * during execute(), it will not alter the attributes or nested
 1064   
      * child elements.  If you want to reset the instance so that you
 1065   
      * can later zip a completely different set of files, you must use
 1066   
      * the reset method.</p>
 1067   
      *
 1068   
      * @see #reset
 1069   
      */
 1070  79
     protected void cleanUp() {
 1071  79
         addedDirs.clear();
 1072  79
         addedFiles.removeAllElements();
 1073  79
         entries.clear();
 1074  79
         addingNewFiles = false;
 1075  79
         doUpdate = savedDoUpdate;
 1076  79
         Enumeration enum = filesetsFromGroupfilesets.elements();
 1077  79
         while (enum.hasMoreElements()) {
 1078  2
             ZipFileSet zf = (ZipFileSet) enum.nextElement();
 1079  2
             filesets.removeElement(zf);
 1080   
         }
 1081  79
         filesetsFromGroupfilesets.removeAllElements();
 1082   
     }
 1083   
 
 1084   
     /**
 1085   
      * Makes this instance reset all attributes to their default
 1086   
      * values and forget all children.
 1087   
      *
 1088   
      * @since Ant 1.5
 1089   
      *
 1090   
      * @see #cleanUp
 1091   
      */
 1092  0
     public void reset() {
 1093  0
         filesets.removeAllElements();
 1094  0
         zipFile = null;
 1095  0
         baseDir = null;
 1096  0
         groupfilesets.removeAllElements();
 1097  0
         duplicate = "add";
 1098  0
         archiveType = "zip";
 1099  0
         doCompress = true;
 1100  0
         emptyBehavior = "skip";
 1101  0
         doUpdate = false;
 1102  0
         doFilesonly = false;
 1103  0
         encoding = null;
 1104   
     }
 1105   
 
 1106   
     /**
 1107   
      * @return true if all individual arrays are empty
 1108   
      * 
 1109   
      * @since Ant 1.5.2
 1110   
      */
 1111  79
     protected final static boolean isEmpty(Resource[][] r) {
 1112  79
         for (int i = 0; i < r.length; i++) {
 1113  69
             if (r[i].length > 0) {
 1114  66
                 return false;
 1115   
             }
 1116   
         }
 1117  13
         return true;
 1118   
     }
 1119   
 
 1120   
     /**
 1121   
      * Possible behaviors when a duplicate file is added:
 1122   
      * "add", "preserve" or "fail"
 1123   
      */
 1124   
     public static class Duplicate extends EnumeratedAttribute {
 1125  0
         public String[] getValues() {
 1126  0
             return new String[] {"add", "preserve", "fail"};
 1127   
         }
 1128   
     }
 1129   
 
 1130   
     /**
 1131   
      * Holds the up-to-date status and the out-of-date resources of
 1132   
      * the original archive.
 1133   
      *
 1134   
      * @since Ant 1.5.3
 1135   
      */
 1136   
     public static class ArchiveState {
 1137   
         private boolean outOfDate;
 1138   
         private Resource[][] resourcesToAdd;
 1139   
 
 1140  75
         ArchiveState(boolean state, Resource[][] r) {
 1141  75
             outOfDate = state;
 1142  75
             resourcesToAdd = r;
 1143   
         }
 1144   
 
 1145  75
         public boolean isOutOfDate() {
 1146  75
             return outOfDate;
 1147   
         }
 1148   
 
 1149  67
         public Resource[][] getResourcesToAdd() {
 1150  67
             return resourcesToAdd;
 1151   
         }
 1152   
     }
 1153   
 }
 1154