| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 1999 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, if |
| * any, must include the following acknowlegement: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowlegement may appear in the software itself, |
| * if and wherever such third-party acknowlegements normally appear. |
| * |
| * 4. The names "The Jakarta Project", "Ant", and "Apache Software |
| * Foundation" must not be used to endorse or promote products derived |
| * from this software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache" |
| * nor may "Apache" appear in their names without prior written |
| * permission of the Apache Group. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| package org.apache.tools.ant.taskdefs; |
| |
| import java.io.*; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Stack; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| import java.util.zip.CRC32; |
| import java.util.zip.ZipInputStream; |
| import org.apache.tools.ant.*; |
| import org.apache.tools.ant.types.*; |
| import org.apache.tools.ant.util.*; |
| import org.apache.tools.zip.*; |
| |
| /** |
| * Create a ZIP archive. |
| * |
| * @author James Davidson <a href="mailto:duncan@x180.com">duncan@x180.com</a> |
| * @author Jon S. Stevens <a href="mailto:jon@clearink.com">jon@clearink.com</a> |
| * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> |
| */ |
| public class Zip extends MatchingTask { |
| |
| private File zipFile; |
| private File baseDir; |
| private boolean doCompress = true; |
| private boolean doUpdate = false; |
| private boolean doFilesonly = false; |
| protected String archiveType = "zip"; |
| // For directories: |
| private static long emptyCrc = new CRC32 ().getValue (); |
| protected String emptyBehavior = "skip"; |
| private Vector filesets = new Vector (); |
| private Hashtable addedDirs = new Hashtable(); |
| private Vector addedFiles = new Vector(); |
| |
| /** true when we are adding new files into the Zip file, as opposed to |
| adding back the unchanged files */ |
| private boolean addingNewFiles; |
| |
| |
| /** |
| * Encoding to use for filenames, defaults to the platform's |
| * default encoding. |
| */ |
| private String encoding = null; |
| |
| /** |
| * This is the name/location of where to |
| * create the .zip file. |
| */ |
| public void setZipfile(File zipFile) { |
| this.zipFile = zipFile; |
| } |
| |
| /** |
| * This is the base directory to look in for |
| * things to zip. |
| */ |
| public void setBasedir(File baseDir) { |
| this.baseDir = baseDir; |
| } |
| |
| /** |
| * Sets whether we want to compress the files or only store them. |
| */ |
| public void setCompress(boolean c) { |
| doCompress = c; |
| } |
| |
| /** |
| * Emulate Sun's jar utility by not adding parent dirs |
| */ |
| public void setFilesonly(boolean f) { |
| doFilesonly = f; |
| } |
| |
| /** |
| * Sets whether we want to update the file (if it exists) |
| * or create a new one. |
| */ |
| public void setUpdate(boolean c) { |
| doUpdate = c; |
| } |
| |
| /** |
| * Adds a set of files (nested fileset attribute). |
| */ |
| public void addFileset(FileSet set) { |
| filesets.addElement(set); |
| } |
| |
| /** |
| * Adds a set of files (nested zipfileset attribute) that can be |
| * read from an archive and be given a prefix/fullpath. |
| */ |
| public void addZipfileset(ZipFileSet set) { |
| filesets.addElement(set); |
| } |
| |
| |
| /** Possible behaviors when there are no matching files for the task. */ |
| public static class WhenEmpty extends EnumeratedAttribute { |
| public String[] getValues() { |
| return new String[] {"fail", "skip", "create"}; |
| } |
| } |
| |
| /** |
| * Sets behavior of the task when no files match. |
| * Possible values are: <code>fail</code> (throw an exception |
| * and halt the build); <code>skip</code> (do not create |
| * any archive, but issue a warning); <code>create</code> |
| * (make an archive with no entries). |
| * Default for zip tasks is <code>skip</code>; |
| * for jar tasks, <code>create</code>. |
| */ |
| public void setWhenempty(WhenEmpty we) { |
| emptyBehavior = we.getValue(); |
| } |
| |
| /** |
| * Encoding to use for filenames, defaults to the platform's |
| * default encoding. |
| * |
| * <p>For a list of possible values see <a |
| * href="http://java.sun.com/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> |
| */ |
| public void setEncoding(String encoding) { |
| this.encoding = encoding; |
| } |
| |
| public void execute() throws BuildException { |
| if (baseDir == null && filesets.size() == 0 && "zip".equals(archiveType)) { |
| throw new BuildException( "basedir attribute must be set, or at least " + |
| "one fileset must be given!" ); |
| } |
| |
| if (zipFile == null) { |
| throw new BuildException("You must specify the " + archiveType + " file to create!"); |
| } |
| |
| // Renamed version of original file, if it exists |
| File renamedFile = null; |
| // Whether or not an actual update is required - |
| // we don't need to update if the original file doesn't exist |
| |
| addingNewFiles = true; |
| boolean reallyDoUpdate = false; |
| if (doUpdate && zipFile.exists()) |
| { |
| reallyDoUpdate = true; |
| |
| int i; |
| for (i=0; i < 1000; i++) |
| { |
| renamedFile = new File(zipFile.getParent(), "tmp."+i); |
| |
| if (!renamedFile.exists()) { |
| break; |
| } |
| } |
| if (i == 1000) { |
| throw new BuildException("Can't find available temporary filename to which to rename old file."); |
| } |
| |
| try |
| { |
| if (!zipFile.renameTo(renamedFile)) { |
| throw new BuildException("Unable to rename old file to temporary file"); |
| } |
| } |
| catch (SecurityException e) |
| { |
| throw new BuildException("Not allowed to rename old file to temporary file"); |
| } |
| } |
| |
| // Create the scanners to pass to isUpToDate(). |
| Vector dss = new Vector (); |
| if (baseDir != null) { |
| dss.addElement(getDirectoryScanner(baseDir)); |
| } |
| for (int i=0; i<filesets.size(); i++) { |
| FileSet fs = (FileSet) filesets.elementAt(i); |
| dss.addElement (fs.getDirectoryScanner(project)); |
| } |
| int dssSize = dss.size(); |
| FileScanner[] scanners = new FileScanner[dssSize]; |
| dss.copyInto(scanners); |
| |
| // quick exit if the target is up to date |
| // can also handle empty archives |
| if (isUpToDate(scanners, zipFile)) { |
| return; |
| } |
| |
| String action = reallyDoUpdate ? "Updating " : "Building "; |
| |
| log(action + archiveType +": "+ zipFile.getAbsolutePath()); |
| |
| boolean success = false; |
| try { |
| ZipOutputStream zOut = |
| new ZipOutputStream(new FileOutputStream(zipFile)); |
| zOut.setEncoding(encoding); |
| try { |
| if (doCompress) { |
| zOut.setMethod(ZipOutputStream.DEFLATED); |
| } else { |
| zOut.setMethod(ZipOutputStream.STORED); |
| } |
| initZipOutputStream(zOut); |
| |
| // Add the implicit fileset to the archive. |
| if (baseDir != null) { |
| addFiles(getDirectoryScanner(baseDir), zOut, "", ""); |
| } |
| // Add the explicit filesets to the archive. |
| addFiles(filesets, zOut); |
| if (reallyDoUpdate) { |
| addingNewFiles = false; |
| ZipFileSet oldFiles = new ZipFileSet(); |
| oldFiles.setSrc(renamedFile); |
| |
| StringBuffer exclusionPattern = new StringBuffer(); |
| for (int i=0; i < addedFiles.size(); i++) |
| { |
| if (i != 0) { |
| exclusionPattern.append(","); |
| } |
| exclusionPattern.append((String)addedFiles.elementAt(i)); |
| } |
| oldFiles.setExcludes(exclusionPattern.toString()); |
| Vector tmp = new Vector(); |
| tmp.addElement(oldFiles); |
| addFiles(tmp, zOut); |
| } |
| finalizeZipOutputStream(zOut); |
| success = true; |
| } finally { |
| // Close the output stream. |
| try { |
| if (zOut != null) { |
| zOut.close(); |
| } |
| } catch(IOException ex) { |
| // If we're in this finally clause because of an exception, we don't |
| // really care if there's an exception when closing the stream. E.g. if it |
| // throws "ZIP file must have at least one entry", because an exception happened |
| // before we added any files, then we must swallow this exception. Otherwise, |
| // the error that's reported will be the close() error, which is not the real |
| // cause of the problem. |
| if (success) |
| throw ex; |
| } |
| } |
| } catch (IOException ioe) { |
| String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); |
| |
| // delete a bogus ZIP file |
| if (!zipFile.delete()) { |
| msg += " (and the archive is probably corrupt but I could not delete it)"; |
| } |
| |
| if (reallyDoUpdate) { |
| if (!renamedFile.renameTo(zipFile)) { |
| msg+=" (and I couldn't rename the temporary file "+ |
| renamedFile.getName()+" back)"; |
| } |
| } |
| |
| throw new BuildException(msg, ioe, location); |
| } finally { |
| cleanUp(); |
| } |
| |
| // If we've been successful on an update, delete the temporary file |
| if (success && reallyDoUpdate) { |
| if (!renamedFile.delete()) { |
| log ("Warning: unable to delete temporary file " + |
| renamedFile.getName(), Project.MSG_WARN); |
| } |
| } |
| } |
| |
| /** |
| * Indicates if the task is adding new files into the archive as opposed to |
| * copying back unchanged files from the backup copy |
| */ |
| protected boolean isAddingNewFiles() { |
| return addingNewFiles; |
| } |
| |
| /** |
| * Add all files of the given FileScanner to the ZipOutputStream |
| * prependig the given prefix to each filename. |
| * |
| * <p>Ensure parent directories have been added as well. |
| */ |
| protected void addFiles(FileScanner scanner, ZipOutputStream zOut, |
| String prefix, String fullpath) throws IOException { |
| if (prefix.length() > 0 && fullpath.length() > 0) |
| throw new BuildException("Both prefix and fullpath attributes may not be set on the same fileset."); |
| |
| File thisBaseDir = scanner.getBasedir(); |
| |
| // directories that matched include patterns |
| String[] dirs = scanner.getIncludedDirectories(); |
| if (dirs.length > 0 && fullpath.length() > 0) |
| throw new BuildException("fullpath attribute may only be specified for filesets that specify a single file."); |
| for (int i = 0; i < dirs.length; i++) { |
| if ("".equals(dirs[i])) { |
| continue; |
| } |
| String name = dirs[i].replace(File.separatorChar,'/'); |
| if (!name.endsWith("/")) { |
| name += "/"; |
| } |
| addParentDirs(thisBaseDir, name, zOut, prefix); |
| } |
| |
| // files that matched include patterns |
| String[] files = scanner.getIncludedFiles(); |
| if (files.length > 1 && fullpath.length() > 0) |
| throw new BuildException("fullpath attribute may only be specified for filesets that specify a single file."); |
| for (int i = 0; i < files.length; i++) { |
| File f = new File(thisBaseDir, files[i]); |
| if (fullpath.length() > 0) |
| { |
| // Add this file at the specified location. |
| addParentDirs(null, fullpath, zOut, ""); |
| zipFile(f, zOut, fullpath); |
| } |
| else |
| { |
| // Add this file with the specified prefix. |
| String name = files[i].replace(File.separatorChar,'/'); |
| addParentDirs(thisBaseDir, name, zOut, prefix); |
| zipFile(f, zOut, prefix+name); |
| } |
| } |
| } |
| |
| protected void addZipEntries(ZipFileSet fs, DirectoryScanner ds, |
| ZipOutputStream zOut, String prefix) |
| throws IOException |
| { |
| ZipScanner zipScanner = (ZipScanner) ds; |
| File zipSrc = fs.getSrc(); |
| |
| ZipEntry entry; |
| java.util.zip.ZipEntry origEntry; |
| ZipInputStream in = null; |
| try { |
| in = new ZipInputStream(new FileInputStream(zipSrc)); |
| |
| while ((origEntry = in.getNextEntry()) != null) { |
| entry = new ZipEntry(origEntry); |
| String vPath = entry.getName(); |
| if (zipScanner.match(vPath)) { |
| addParentDirs(null, vPath, zOut, prefix); |
| if (! entry.isDirectory()) { |
| zipFile(in, zOut, prefix+vPath, entry.getTime()); |
| } |
| } |
| } |
| } finally { |
| if (in != null) { |
| in.close(); |
| } |
| } |
| } |
| |
| protected void initZipOutputStream(ZipOutputStream zOut) |
| throws IOException, BuildException |
| { |
| } |
| |
| protected void finalizeZipOutputStream(ZipOutputStream zOut) |
| throws IOException, BuildException |
| { |
| } |
| |
| /** |
| * Create an empty zip file |
| * |
| * @return true if the file is then considered up to date. |
| */ |
| protected boolean createEmptyZip(File zipFile) { |
| // 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 "+archiveType+" 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; |
| } |
| |
| |
| /** |
| * 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 |
| { |
| String[][] fileNames = grabFileNames(scanners); |
| File[] files = grabFiles(scanners, fileNames); |
| if (files.length == 0) { |
| if (emptyBehavior.equals("skip")) { |
| log("Warning: skipping "+archiveType+" archive " + zipFile + |
| " because no files were included.", Project.MSG_WARN); |
| return true; |
| } else if (emptyBehavior.equals("fail")) { |
| throw new BuildException("Cannot create "+archiveType+" archive " + zipFile + |
| ": no files were included.", location); |
| } else { |
| // Create. |
| return createEmptyZip(zipFile); |
| } |
| } else { |
| for (int i = 0; i < files.length; ++i) { |
| if (files[i].equals(zipFile)) { |
| throw new BuildException("A zip file cannot include itself", location); |
| } |
| } |
| |
| if (!zipFile.exists()) return false; |
| |
| SourceFileScanner sfs = new SourceFileScanner(this); |
| MergingMapper mm = new MergingMapper(); |
| mm.setTo(zipFile.getAbsolutePath()); |
| for (int i=0; i<scanners.length; i++) { |
| if (sfs.restrict(fileNames[i], scanners[i].getBasedir(), null, |
| mm).length > 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| protected static File[] grabFiles(FileScanner[] scanners) { |
| return grabFiles(scanners, grabFileNames(scanners)); |
| } |
| |
| protected static File[] grabFiles(FileScanner[] scanners, |
| String[][] fileNames) { |
| Vector files = new Vector(); |
| for (int i = 0; i < fileNames.length; i++) { |
| File thisBaseDir = scanners[i].getBasedir(); |
| for (int j = 0; j < fileNames[i].length; j++) |
| files.addElement(new File(thisBaseDir, fileNames[i][j])); |
| } |
| File[] toret = new File[files.size()]; |
| files.copyInto(toret); |
| return toret; |
| } |
| |
| protected static String[][] grabFileNames(FileScanner[] scanners) { |
| String[][] result = new String[scanners.length][]; |
| for (int i=0; i<scanners.length; i++) { |
| String[] files = scanners[i].getIncludedFiles(); |
| String[] dirs = scanners[i].getIncludedDirectories(); |
| result[i] = new String[files.length + dirs.length]; |
| System.arraycopy(files, 0, result[i], 0, files.length); |
| System.arraycopy(dirs, 0, result[i], files.length, dirs.length); |
| } |
| return result; |
| } |
| |
| protected void zipDir(File dir, ZipOutputStream zOut, String vPath) |
| throws IOException |
| { |
| if (addedDirs.get(vPath) != null) { |
| // don't add directories we've already added. |
| // no warning if we try, it is harmless in and of itself |
| return; |
| } |
| addedDirs.put(vPath, vPath); |
| |
| ZipEntry ze = new ZipEntry (vPath); |
| if (dir != null && dir.exists()) { |
| ze.setTime(dir.lastModified()); |
| } else { |
| ze.setTime(System.currentTimeMillis()); |
| } |
| ze.setSize (0); |
| ze.setMethod (ZipEntry.STORED); |
| // This is faintly ridiculous: |
| ze.setCrc (emptyCrc); |
| |
| // this is 040775 | MS-DOS directory flag in reverse byte order |
| ze.setExternalAttributes(0x41FD0010L); |
| |
| zOut.putNextEntry (ze); |
| } |
| |
| protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath, |
| long lastModified) |
| throws IOException |
| { |
| ZipEntry ze = new ZipEntry(vPath); |
| ze.setTime(lastModified); |
| |
| /* |
| * XXX ZipOutputStream.putEntry expects the ZipEntry to know its |
| * size and the CRC sum before you start writing the data when using |
| * STORED mode. |
| * |
| * This forces us to process the data twice. |
| * |
| * I couldn't find any documentation on this, just found out by try |
| * and error. |
| */ |
| if (!doCompress) { |
| long size = 0; |
| CRC32 cal = new CRC32(); |
| if (!in.markSupported()) { |
| // Store data into a byte[] |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| |
| byte[] buffer = new byte[8 * 1024]; |
| int count = 0; |
| do { |
| size += count; |
| cal.update(buffer, 0, count); |
| bos.write(buffer, 0, count); |
| count = in.read(buffer, 0, buffer.length); |
| } while (count != -1); |
| in = new ByteArrayInputStream(bos.toByteArray()); |
| |
| } else { |
| in.mark(Integer.MAX_VALUE); |
| byte[] buffer = new byte[8 * 1024]; |
| int count = 0; |
| do { |
| size += count; |
| cal.update(buffer, 0, count); |
| count = in.read(buffer, 0, buffer.length); |
| } while (count != -1); |
| in.reset(); |
| } |
| ze.setSize(size); |
| ze.setCrc(cal.getValue()); |
| } |
| |
| zOut.putNextEntry(ze); |
| |
| byte[] buffer = new byte[8 * 1024]; |
| int count = 0; |
| do { |
| if (count != 0) { |
| zOut.write(buffer, 0, count); |
| } |
| count = in.read(buffer, 0, buffer.length); |
| } while (count != -1); |
| addedFiles.addElement(vPath); |
| } |
| |
| protected void zipFile(File file, ZipOutputStream zOut, String vPath) |
| throws IOException |
| { |
| if (file.equals(zipFile)) { |
| throw new BuildException("A zip file cannot include itself", location); |
| } |
| |
| FileInputStream fIn = new FileInputStream(file); |
| try { |
| zipFile(fIn, zOut, vPath, file.lastModified()); |
| } finally { |
| fIn.close(); |
| } |
| } |
| |
| /** |
| * Ensure all parent dirs of a given entry have been added. |
| */ |
| protected void addParentDirs(File baseDir, String entry, |
| ZipOutputStream zOut, String prefix) |
| throws IOException { |
| if( !doFilesonly ) { |
| Stack directories = new Stack(); |
| int slashPos = entry.length(); |
| |
| while ((slashPos = entry.lastIndexOf((int)'/', slashPos-1)) != -1) { |
| String dir = entry.substring(0, slashPos+1); |
| if (addedDirs.get(prefix+dir) != null) { |
| break; |
| } |
| directories.push(dir); |
| } |
| |
| while (!directories.isEmpty()) { |
| String dir = (String) directories.pop(); |
| File f = null; |
| if (baseDir != null) { |
| f = new File(baseDir, dir); |
| } else { |
| f = new File(dir); |
| } |
| zipDir(f, zOut, prefix+dir); |
| } |
| } |
| } |
| |
| /** |
| * Iterate over the given Vector of (zip)filesets and add |
| * all files to the ZipOutputStream using the given prefix. |
| */ |
| protected void addFiles(Vector filesets, ZipOutputStream zOut) |
| throws IOException { |
| // Add each fileset in the Vector. |
| for (int i = 0; i<filesets.size(); i++) { |
| FileSet fs = (FileSet) filesets.elementAt(i); |
| DirectoryScanner ds = fs.getDirectoryScanner(project); |
| |
| String prefix = ""; |
| String fullpath = ""; |
| if (fs instanceof ZipFileSet) { |
| ZipFileSet zfs = (ZipFileSet) fs; |
| prefix = zfs.getPrefix(); |
| fullpath = zfs.getFullpath(); |
| } |
| |
| if (prefix.length() > 0 |
| && !prefix.endsWith("/") |
| && !prefix.endsWith("\\")) { |
| prefix += "/"; |
| } |
| |
| // Need to manually add either fullpath's parent directory, or |
| // the prefix directory, to the archive. |
| if (prefix.length() > 0) { |
| addParentDirs(null, prefix, zOut, ""); |
| zipDir(null, zOut, prefix); |
| } else if (fullpath.length() > 0) { |
| addParentDirs(null, fullpath, zOut, ""); |
| } |
| |
| if (fs instanceof ZipFileSet |
| && ((ZipFileSet) fs).getSrc() != null) { |
| addZipEntries((ZipFileSet) fs, ds, zOut, prefix); |
| } else { |
| // Add the fileset. |
| addFiles(ds, zOut, prefix, fullpath); |
| } |
| } |
| } |
| |
| /** |
| * Do any clean up necessary to allow this instance to be used again. |
| * |
| * <p>When we get here, the Zip file has been closed and all we |
| * need to do is to reset some globals.</p> |
| */ |
| protected void cleanUp() { |
| addedDirs = new Hashtable(); |
| addedFiles = new Vector(); |
| } |
| } |