A couple of weeks ago I sent out a patch for <zip> and <jar> tasks which
had several enhancements and bugfixes, but did not get any firm response
and it was not applied. There was some disagreement as to handling of
empty archives; so I have created a new patch, which I think resolves
this disagreement by grandfathering the current behavior but making it
easy to switch; and included several more improvements. Attached is a
fresh patch against current CVS sources; noticeable changes from CVS
version in no particular order are:

1. Behavior of Zip and Jar when no files match is more carefully
regulated. (With current CVS sources, it will silently skip making the
archive--not intentionally but due to an improperly swallowed
exception.) There is now an optional attribute "whenempty" which is used
to control what happens when you try to make an archive with no files.
"create" causes it to be created as you asked, though a warning is
printed. "skip" causes it to skip trying to make the archive (and a
warning is printed). "fail" causes the build to halt with an exception.
The default for Zip is "skip" to match current behavior (though
personally I would prefer "fail" or "create" as defaults; it would be a
one-line change to modify the default behavior). The default for Jar is
"create" (since there is always the manifest, so the archive is not
exactly empty). In the case of empty ZIP files, java.util.zip.* cannot
handle this, so an empty ZIP archive (22 bytes, but zero file entries)
is created when "create" is given.

2. Error-handling of both tasks is made more robust; some exceptions
were being swallowed before, or some streams might not have been closed
reliably, etc.

3. Entries for containing directories are added to the archive whenever
files are added, since some tools might fail if these were not present,
and for consistency with existing ZIP and JAR creation tools.

4. The default manifest for JARs says they were created by Ant and gives
the Ant version number used.

5. JARs are recreated if their manifest is specified and is changed on
disk (and of course if any contained files are changed or added too, as
for ZIPs).

6. Documentation update for both tasks covering user-visible changes.

7. Both <zip> and <jar> tasks may take embedded filesets. Thus, for
compatibility with current scripts and for simplicity of syntax, there
is still an implicit fileset used when you specify basedir (which is now
optional if there are embedded filesets). But you may add <fileset> or
<filesetref> elements inside the <zip> or <jar> and these will also be
included in the archive. If multiple filesets are employed, it works
more or less like the JAR tool's -C option: each fileset has its own
basedir, and any files acquired from that fileset are stored with a
relative archive path according to that basedir. This makes it possible
to efficiently and in one task include files from multiple disparate
directories, while controlling the archive path (critical for e.g.
resource paths/packages in JARs).

8. For Jar task, if you have a file META-INF/MANIFEST.MF present in one
of your filesets, this was always ignored in deference to the (automatic
or supplied) manifest. Now a warning is printed if this happens.

Only tested on Linux with Sun JDK 1.3; I tried to not use any > 1.1 APIs
but someone should check of course. Including ZIP of informal test set;
tries to make ZIPs and JARs in various ways with different options. Just
run the build.xml file, target "all" to create; "touchreal" to change
timestamp of a content file, then try "all" again; "touchmani" for a
manifest file; "clean" to delete archives; three other targets mentioned
in it which are expected to fail.

I think that's it. Let me know if anything is missing.

Cheers,
-Jesse

-- 
Jesse Glick   <mailto:[EMAIL PROTECTED]>
NetBeans, Open APIs  <http://www.netbeans.org/>
tel (+4202) 3300-9161 Sun Micro x49161 Praha CR
Index: build.xml
===================================================================
RCS file: /home/cvspublic/jakarta-ant/build.xml,v
retrieving revision 1.62
diff -d -u -r1.62 build.xml
--- build.xml   2000/08/21 15:05:52     1.62
+++ build.xml   2000/08/30 13:41:10
@@ -90,7 +90,6 @@
     </javac>
  
     <copydir src="${src.dir}" dest="${build.classes}">
-      <include name="**/defaultManifest.mf" />
       <include name="**/*.properties" />
     </copydir>
 
@@ -102,6 +101,7 @@
              forceoverwrite="true"
              filtering="on">
       <include name="**/version.txt" />
+      <include name="**/defaultManifest.mf" />
     </copydir>
   </target>
 
Index: docs/index.html
===================================================================
RCS file: /home/cvspublic/jakarta-ant/docs/index.html,v
retrieving revision 1.82
diff -d -u -r1.82 index.html
--- docs/index.html     2000/08/21 14:41:11     1.82
+++ docs/index.html     2000/08/30 13:41:12
@@ -2022,6 +2022,15 @@
 <code>&lt;include&gt;</code>, <code>&lt;exclude&gt;</code>,
 <code>&lt;patternset&gt;</code> and <code>&lt;patternsetref&gt;</code>
 elements.</p>
+<p>You can also use nested file sets for more flexibility, and specify
+multiple ones to merge together different trees of files into one JAR.
+See the <a href="#zip">Zip</a> task for more details and examples.</p>
+<p>If the manifest is omitted, a simple one will be supplied by Ant.
+You should not include <samp>META-INF/MANIFEST.MF</samp> in your set of files.
+<p>The <code>whenempty</code> parameter controls what happens when no files 
match.
+If <code>create</code> (the default), the JAR is created anyway with only a 
manifest.
+If <code>skip</code>, the JAR is not created and a warning is issued.
+If <code>fail</code>, the JAR is not created and the build is halted with an 
error.
 <h3>Parameters</h3>
 <table border="1" cellpadding="2" cellspacing="0">
   <tr>
@@ -2037,7 +2046,7 @@
   <tr>
     <td valign="top">basedir</td>
     <td valign="top">the directory from which to jar the files.</td>
-    <td valign="top" align="center">Yes</td>
+    <td valign="top" align="center">No</td>
   </tr>
   <tr>
     <td valign="top">compress</td>
@@ -2079,6 +2088,11 @@
     <td valign="top">the manifest file to use.</td>
     <td valign="top" align="center">No</td>
   </tr>
+  <tr>
+    <td valign="top">whenempty</td>
+    <td valign="top">Behavior to use if no files match.</td>
+    <td valign="top" align="center">No</td>
+  </tr>
 </table>
 <h3>Examples</h3>
 <pre>  &lt;jar jarfile=&quot;${dist}/lib/app.jar&quot; 
basedir=&quot;${build}/classes&quot; /&gt;</pre>
@@ -2100,6 +2114,21 @@
 called <code>app.jar</code> in the <code>${dist}/lib</code> directory. Only
 files under the directory <code>mypackage/test</code> are used, and files with
 the name <code>Test.class</code> are excluded.</p>
+<pre>  &lt;jar jarfile=&quot;${dist}/lib/app.jar&quot;&gt;
+    &lt;fileset dir=&quot;${build}/classes&quot;
+             excludes=&quot;**/Test.class&quot;
+    /&gt;
+    &lt;fileset dir=&quot;${src}/resources&quot;/&gt;
+  &lt;/jar&gt;</pre>
+<p>jars all files in the <code>${build}/classes</code> directory and also
+in the <code>${src}/resources</code> directory together in a file
+called <code>app.jar</code> in the <code>${dist}/lib</code> directory.
+Files with the name <code>Test.class</code> are excluded.
+If there are files such as 
<code>${build}/classes/mypackage/MyClass.class</code>
+and <code>${src}/resources/mypackage/image.gif</code>, they will appear
+in the same directory in the JAR (and thus be considered in the same package
+by Java).</p>
+
 <hr>
 <h2><a name="java">Java</a></h2>
 <h3>Description</h3>
@@ -3655,6 +3684,19 @@
 <code>&lt;include&gt;</code>, <code>&lt;exclude&gt;</code>,
 <code>&lt;patternset&gt;</code> and <code>&lt;patternsetref&gt;</code>
 elements.</p>
+<p>Or, you may place within it nested file sets, or references to file sets.
+In this case <code>basedir</code> is optional; the implicit file set is 
<em>only used</em>
+if <code>basedir</code> is set. You may use any mixture of the implicit file 
set
+(with <code>basedir</code> set, and optional attributes like 
<code>includes</code>
+and optional subelements like <code>&lt;include&gt;</code>); explicit nested
+<code>&lt;fileset&gt;</code> elements; and nested 
<code>&lt;filesetref&gt;</code>
+elements; so long as at least one fileset total is specified. The ZIP file will
+only reflect the relative paths of files <em>within</em> each fileset.</p>
+<p>The <code>whenempty</code> parameter controls what happens when no files 
match.
+If <code>skip</code> (the default), the ZIP is not created and a warning is 
issued.
+If <code>fail</code>, the ZIP is not created and the build is halted with an 
error.
+If <code>create</code>, an empty ZIP file (explicitly zero entries) is created,
+which should be recognized as such by compliant ZIP manipulation tools.</p>
 <h3>Parameters</h3>
 <table border="1" cellpadding="2" cellspacing="0">
   <tr>
@@ -3670,7 +3712,7 @@
   <tr>
     <td valign="top">basedir</td>
     <td valign="top">the directory from which to zip the files.</td>
-    <td align="center" valign="top">Yes</td>
+    <td align="center" valign="top">No</td>
   </tr>
   <tr>
     <td valign="top">compress</td>
@@ -3707,6 +3749,11 @@
       (&quot;yes&quot;/&quot;no&quot;). Default excludes are used when 
omitted.</td>
     <td valign="top" align="center">No</td>
   </tr>
+  <tr>
+    <td valign="top">whenempty</td>
+    <td valign="top">Behavior when no files match.</td>
+    <td valign="top" align="center">No</td>
+  </tr>
 </table>
 <h3>Examples</h3>
 <pre>  &lt;zip zipfile=&quot;${dist}/manual.zip&quot;
@@ -3729,6 +3776,14 @@
 <p>zips all files in the <code>htdocs/manual</code> directory in a file called 
<code>manual.zip</code>
 in the <code>${dist}</code> directory. Only html files under the directory 
<code>api</code>
 are zipped, and files with the name <code>todo.html</code> are excluded.</p>
+<pre>  &lt;zip zipfile=&quot;${dist}/manual.zip&quot;&gt;
+    &lt;fileset dir=&quot;htdocs/manual&quot;/&gt;
+    &lt;fileset dir=&quot;.&quot; includes=&quot;ChangeLog.txt&quot;/&gt;
+  &lt;/zip&gt;</pre>
+<p>zips all files in the <code>htdocs/manual</code> directory in a file called 
<code>manual.zip</code>
+in the <code>${dist}</code> directory, and also adds the file 
<code>ChangeLog.txt</code> in the
+current directory. <code>ChangeLog.txt</code> will be added to the top of the 
ZIP file, just as if
+it had been located at <code>htdocs/manual/ChangeLog.txt</code>.</p>
 
 <hr>
 <h2><a name="optionaltasks">Optional tasks</a></h2>
Index: src/main/org/apache/tools/ant/defaultManifest.mf
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/defaultManifest.mf,v
retrieving revision 1.1
diff -d -u -r1.1 defaultManifest.mf
--- src/main/org/apache/tools/ant/defaultManifest.mf    2000/01/13 10:41:40     
1.1
+++ src/main/org/apache/tools/ant/defaultManifest.mf    2000/08/30 13:41:32
@@ -1 +1,3 @@
 Manifest-Version: 1.0
+Created-By: Ant @VERSION@
+
Index: src/main/org/apache/tools/ant/taskdefs/Jar.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Jar.java,v
retrieving revision 1.5
diff -d -u -r1.5 Jar.java
--- src/main/org/apache/tools/ant/taskdefs/Jar.java     2000/06/27 11:12:11     
1.5
+++ src/main/org/apache/tools/ant/taskdefs/Jar.java     2000/08/30 13:41:33
@@ -86,38 +86,67 @@
             super.zipDir(new File(manifest.getParent()), zOut, "META-INF/");
            super.zipFile(manifest, zOut, "META-INF/MANIFEST.MF");
        } else {
-            /*
-             * We don't store directories at all and this one will cause a lot
-             * of problems with STORED Zip-Mode.
-             *
-             * That's why i've removed it -- Stefan Bodewig
-             */
-            //            ZipEntry ze = new ZipEntry("META-INF/");
-            //            zOut.putNextEntry(ze);
            String s = "/org/apache/tools/ant/defaultManifest.mf";
            InputStream in = this.getClass().getResourceAsStream(s);
             if ( in == null )
                throw new BuildException ( "Could not find: " + s );
+           super.zipDir(null, zOut, "META-INF/");
            zipFile(in, zOut, "META-INF/MANIFEST.MF", 
System.currentTimeMillis());
        }
      }
 
+    protected boolean isUpToDate(FileScanner[] scanners, File zipFile)
+    {
+        File[] files = grabFiles(scanners);
+        if (emptyBehavior == null) emptyBehavior = "create";
+        if (files.length == 0) {
+            if (emptyBehavior.equals("skip")) {
+                log("Warning: skipping JAR archive " + zipFile +
+                    " because no files were included.", Project.MSG_WARN);
+                return true;
+            } else if (emptyBehavior.equals("fail")) {
+                throw new BuildException("Cannot create JAR archive " + 
zipFile +
+                                         ": no files were included.", 
location);
+            } else {
+                // create
+                if (!zipFile.exists() ||
+                    (manifest != null &&
+                     manifest.lastModified() > zipFile.lastModified()))
+                    log("Note: creating empty JAR archive " + zipFile, 
Project.MSG_INFO);
+                // and continue below...
+            }
+        }
+        if (!zipFile.exists()) return false;
+       if (manifest != null && manifest.lastModified() > 
zipFile.lastModified())
+           return false;
+        for (int i=0; i<files.length; i++) {
+            if (files[i].lastModified() > zipFile.lastModified()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     protected void zipDir(File dir, ZipOutputStream zOut, String vPath)
         throws IOException
     {
         // First add directory to zip entry
-        if(!vPath.equals("META-INF/")) {
+        if(!vPath.equalsIgnoreCase("META-INF/")) {
             // we already added a META-INF
             super.zipDir(dir, zOut, vPath);
         }
+        // no warning if not, it is harmless in and of itself
     }
 
     protected void zipFile(File file, ZipOutputStream zOut, String vPath)
         throws IOException
     {
         // We already added a META-INF/MANIFEST.MF
-        if (!vPath.equals("META-INF/MANIFEST.MF")) {
+        if (!vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) {
             super.zipFile(file, zOut, vPath);
+        } else {
+            log("Warning: selected JAR files include a META-INF/MANIFEST.MF 
which will be ignored " +
+                "(please use manifest attribute to jar task)", 
Project.MSG_WARN);
         }
     }
 }
Index: src/main/org/apache/tools/ant/taskdefs/Zip.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java,v
retrieving revision 1.10
diff -d -u -r1.10 Zip.java
--- src/main/org/apache/tools/ant/taskdefs/Zip.java     2000/08/03 09:13:18     
1.10
+++ src/main/org/apache/tools/ant/taskdefs/Zip.java     2000/08/30 13:41:33
@@ -55,9 +55,11 @@
 package org.apache.tools.ant.taskdefs;
 
 import org.apache.tools.ant.*;
+import org.apache.tools.ant.types.*;
 
 import java.io.*;
 import java.util.Enumeration;
+import java.util.Hashtable;
 import java.util.StringTokenizer;
 import java.util.Vector;
 import java.util.zip.*;
@@ -75,6 +77,10 @@
     private File baseDir;
     private boolean doCompress = true;
     protected String archiveType = "zip";
+    // For directories:
+    private static long emptyCrc = new CRC32 ().getValue ();
+    protected String emptyBehavior = null;
+    private Vector filesets = new Vector ();
     
     /**
      * This is the name/location of where to 
@@ -99,73 +105,113 @@
         doCompress = Project.toBoolean(compress);
     }
 
-    public void execute() throws BuildException {
-        if (baseDir == null) {
-            throw new BuildException("basedir attribute must be set!");
-        }
-        if (!baseDir.exists()) {
-            throw new BuildException("basedir does not exist!");
-        }
+    /**
+     * Adds a set of files (nested fileset attribute).
+     */
+    public void addFileset(FileSet set) {
+        filesets.addElement(set);
+    }
 
-        DirectoryScanner ds = super.getDirectoryScanner(baseDir);
+    /**
+     * Adds a reference to a set of files (nested filesetref element).
+     */
+    public void addFilesetref(Reference ref) {
+        filesets.addElement(ref);
+    }
 
-        String[] files = ds.getIncludedFiles();
-        String[] dirs  = ds.getIncludedDirectories();
+    /**
+     * Sets behavior of the task when no files match.
+     * Possible values are: <code>fail</code> (throw an exception
+     * and halt the build); <code>skip</code> (do not create
+     * any archive, but issue a warning); <code>create</code>
+     * (make an archive with no entries).
+     * Default for zip tasks is <code>skip</code>;
+     * for jar tasks, <code>create</code>.
+     */
+    public void setWhenempty(String we) throws BuildException {
+        we = we.toLowerCase();
+        // XXX could instead be using EnumeratedAttribute, but this works
+        if (!"fail".equals(we) && !"skip".equals(we) && !"create".equals(we))
+            throw new BuildException("Unrecognized whenempty attribute: " + 
we);
+        emptyBehavior = we;
+    }
+
+    public void execute() throws BuildException {
+        if (baseDir == null && filesets.size() == 0)
+            throw new BuildException("basedir attribute must be set, or at 
least one fileset must be given!");
+
+        Vector dss = new Vector ();
+        if (baseDir != null)
+            dss.addElement(getDirectoryScanner(baseDir));
+        for (int i=0; i<filesets.size(); i++) {
+            Object o = filesets.elementAt(i);
+            FileSet fs;
+            if (o instanceof FileSet) {
+                fs = (FileSet) o;
+            } else {
+                Reference r = (Reference) o;
+                o = r.getReferencedObject(project);
+                if (o instanceof FileSet) {
+                    fs = (FileSet) o;
+                } else {
+                    throw new BuildException(r.getRefId() + " does not denote 
a fileset", location);
+                }
+            }
+            dss.addElement (fs.getDirectoryScanner(project));
+        }
+        FileScanner[] scanners = new FileScanner[dss.size()];
+        dss.copyInto(scanners);
 
         // quick exit if the target is up to date
-        boolean upToDate = true;
-        for (int i=0; i<files.length && upToDate; i++)
-            if (new File(baseDir,files[i]).lastModified() > 
-                zipFile.lastModified())
-                upToDate = false;
-        if (upToDate) return;
+        // can also handle empty archives
+        if (isUpToDate(scanners, zipFile)) return;
 
         log("Building "+ archiveType +": "+ zipFile.getAbsolutePath());
 
-        ZipOutputStream zOut = null;
-        try {
-            zOut = new ZipOutputStream(new FileOutputStream(zipFile));
-            if (doCompress) {
-                zOut.setMethod(ZipOutputStream.DEFLATED);
-            } else {
-                zOut.setMethod(ZipOutputStream.STORED);
-            }
-            initZipOutputStream(zOut);
+       try {
+           ZipOutputStream zOut = new ZipOutputStream(new 
FileOutputStream(zipFile));
+           try {
+               if (doCompress) {
+                   zOut.setMethod(ZipOutputStream.DEFLATED);
+               } else {
+                   zOut.setMethod(ZipOutputStream.STORED);
+               }
+               initZipOutputStream(zOut);
 
-            for (int i = 0; i < dirs.length; i++) {
-                File f = new File(baseDir,dirs[i]);
-                String name = dirs[i].replace(File.separatorChar,'/')+"/";
-                zipDir(f, zOut, name);
-            }
+                // XXX ideally would also enter includedDirectories to the 
archive
+               Hashtable parentDirs = new Hashtable();
 
-            for (int i = 0; i < files.length; i++) {
-                File f = new File(baseDir,files[i]);
-                String name = files[i].replace(File.separatorChar,'/');
-                zipFile(f, zOut, name);
-            }
-        } catch (IOException ioe) {
-            String msg = "Problem creating " + archiveType + " " + 
ioe.getMessage();
+                for (int j = 0; j < scanners.length; j++) {
+                    String[] files = scanners[j].getIncludedFiles();
+                    File thisBaseDir = scanners[j].getBasedir();
+                    for (int i = 0; i < files.length; i++) {
+                        File f = new File(thisBaseDir,files[i]);
+                        String name = files[i].replace(File.separatorChar,'/');
+                        // Look for & create parent dirs as needed.
+                        int slashPos = -1;
+                        while ((slashPos = name.indexOf((int)'/', slashPos + 
1)) != -1) {
+                            String dir = name.substring(0, slashPos);
+                            if (!parentDirs.contains(dir)) {
+                                parentDirs.put(dir, dir);
+                                zipDir(new File(thisBaseDir, dir.replace('/', 
File.separatorChar)),
+                                       zOut, dir + '/');
+                            }
+                        }
+                        zipFile(f, zOut, name);
+                    }
+                }
+           } finally {
+               zOut.close ();
+           }
+       } catch (IOException ioe) {
+           String msg = "Problem creating " + archiveType + ": " + 
ioe.getMessage();
 
             // delete a bogus ZIP file
-           if (zOut != null) {
-               try {
-                   zOut.close();
-                    zOut = null;
-               } catch (IOException e) {}
-                if (!zipFile.delete()) {
-                    msg = zipFile + " is probably corrupt but I could not 
delete it";
-                }
-            }
+           if (!zipFile.delete()) {
+               msg += " (and the archive is probably corrupt but I could not 
delete it)";
+           }
 
             throw new BuildException(msg, ioe, location);
-       } finally {
-           if (zOut != null) {
-               try {
-                    // close up
-                   zOut.close();
-               }
-               catch (IOException e) {}
-           }
         }
     }
 
@@ -174,9 +220,87 @@
     {
     }
 
+    /**
+     * Check whether the archive is up-to-date; and handle behavior for empty 
archives.
+     * @param scanners list of prepared scanners containing files to archive
+     * @param zipFile intended archive file (may or may not exist)
+     * @return true if nothing need be done (may have done something already); 
false if
+     *         archive creation should proceed
+     * @exception BuildException if it likes
+     */
+    protected boolean isUpToDate(FileScanner[] scanners, File zipFile) throws 
BuildException
+    {
+        if (emptyBehavior == null) emptyBehavior = "skip";
+        File[] files = grabFiles(scanners);
+        if (files.length == 0) {
+            if (emptyBehavior.equals("skip")) {
+                log("Warning: skipping ZIP archive " + zipFile +
+                    " because no files were included.", Project.MSG_WARN);
+                return true;
+            } else if (emptyBehavior.equals("fail")) {
+                throw new BuildException("Cannot create ZIP archive " + 
zipFile +
+                                         ": no files were included.", 
location);
+            } else {
+                // Create.
+                if (zipFile.exists()) return true;
+                // In this case using java.util.zip will not work
+                // because it does not permit a zero-entry archive.
+                // Must create it manually.
+                log("Note: creating empty ZIP archive " + zipFile, 
Project.MSG_INFO);
+                try {
+                    OutputStream os = new FileOutputStream(zipFile);
+                    try {
+                        // Cf. PKZIP specification.
+                        byte[] empty = new byte[22];
+                        empty[0] = 80; // P
+                        empty[1] = 75; // K
+                        empty[2] = 5;
+                        empty[3] = 6;
+                        // remainder zeros
+                        os.write(empty);
+                    } finally {
+                        os.close();
+                    }
+                } catch (IOException ioe) {
+                    throw new BuildException("Could not create empty ZIP 
archive", ioe, location);
+                }
+                return true;
+            }
+        } else {
+            // Probably unnecessary but just for clarity:
+            if (!zipFile.exists()) return false;
+            for (int i=0; i<files.length; i++) {
+                if (files[i].lastModified() > zipFile.lastModified()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    protected static File[] grabFiles(FileScanner[] scanners) {
+        Vector files = new Vector ();
+        for (int i = 0; i < scanners.length; i++) {
+            File thisBaseDir = scanners[i].getBasedir();
+            String[] ifiles = scanners[i].getIncludedFiles();
+            for (int j = 0; j < ifiles.length; j++)
+                files.add(new File(thisBaseDir, ifiles[j]));
+        }
+        File[] toret = new File[files.size()];
+        files.copyInto(toret);
+        return toret;
+    }
+
     protected void zipDir(File dir, ZipOutputStream zOut, String vPath)
         throws IOException
     {
+       ZipEntry ze = new ZipEntry (vPath);
+       if (dir != null) ze.setTime (dir.lastModified ());
+       ze.setSize (0);
+       ze.setMethod (ZipEntry.STORED);
+       // This is faintly ridiculous:
+       ze.setCrc (emptyCrc);
+       zOut.putNextEntry (ze);
     }
 
     protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,

<<attachment: zip-jar-test.zip>>

Reply via email to