/* * ZipManager.java * * Created on 23. Oktober 2004, 20:41 */ package de.schlichtherle.nio; /* ... */ import net.sf.jazzlib.*; /** * This class manages ZIP files as a rewritable resource, i.e. it * maintains a directory structure, provides input and output streams, and * finally updates ZIP or JAR files. It also knows how to handle nested ZIP * files. * ... */ class ZipManager extends Configuration implements ZipConstants { /* ... */ /** * For use by address@hidden #determineCharSet(File)} only. */ private static String zipEncoding; /** * @return The character set encoding to use for entry names in the * target ZIP or JAR file. Currently, this is "UTF-8" * for JAR files and "CP437" for ZIP files. * More intelligent heuristics may be added in future. */ public final static String determineCharSet(final File target) { if (File.isJarFileOnly(target)) return "UTF-8"; else { if (zipEncoding == null) { try { zipEncoding = "CP437"; new String("").getBytes(zipEncoding); } catch (UnsupportedEncodingException failure) { // Most probably international character sets have not been // installed on the JVM. zipEncoding = "UTF-8"; } } return zipEncoding; } } /* ... */ /** * A plain ZipFile object used for input from the device file. * Also used to mount the file system. */ protected LocalisedZipFile zipIn; /** * This is an amended ZipOutputStream which keeps track of * all entries which have been newly created or updated. */ protected RememberingZipOutputStream tmpOut; /** The character set to use for entry names. */ protected final String charSet; /** * This constructor cooperates with address@hidden #getInstance(File)} to prepare * for unnesting all ZIP files in the path of target. This is a * fail-fast implementation: If any ZIP file in the path of target * does not have the required I/O permissions, the constructor will fail * with a FileNotFoundException. */ protected ZipManager(final File target) throws IOException { /* ... */ charSet = determineCharSet(target); } /* ... */ /** * This method ensures that the virtual ZIP or JAR file system is mounted * and initialises fileSystem with it. * *

If the ZIP or JAR file does not exist or its entry list can't be * read for some reason an IOException will be thrown. This behaviour * will repeat on every call until the filesystem could either get mounted * (i.e. if the ZIP or JAR file has been modified externally) or * successfully automatically created (if autoCreate is on). * *

Note: This method requires external synchronisation on this * object! * * @param autoCreate If the ZIP file is not readable for whatever reason * and this is true, a new file system with only a root * directory is created with its last modification time set to the * system's current time. * @throws IOException if the file system can't be mounted from the * ZIP or JAR file for any reason. */ private void mountFileSystem(boolean autoCreate) throws IOException { /* ... */ zipIn = new LocalisedZipFile(deviceFile); /* ... */ } /** * A factory method returning an input stream which is positioned * at the beginning of the specified zippedPath entry in this * ZIP file. * * @param entry An entry in the virtual ZIP file system. null is * not allowed. * @return A valid InputStream object - null is never returned. * @throws FileNotFoundException if the ZIP file does not exist or the * requested entry is a directory or does not exist in the ZIP * file. * @throws IOException if the entry can't be opened for reading from the * ZIP file (this usually means that the ZIP file is corrupted). * @throws NullPointerException if called by a thread while another thread * is still reading from a stream returned by this method before. * This is a bug in the package java.util.zip.* at least in * J2SE 1.4.2_05 on Windows platforms. * As a workaround (and for a clean design anyway), * always close your input streams when you're done! */ public InputStream getInputStream(final ZipFileSystem.Entry entry) throws IOException { if (zipIn == null) { /* ... */ zipIn = new LocalisedZipFile(deviceFile); /* ... */ } /* ... */ } /** * A factory method returning an OutputStream allowing to * (re)write entries in the ZIP file managed by this object. * *

Note:If you have called this method on the same ZIP manager * object before, you must have finished all file operations on the * returned stream before calling this method again! The current * implementation does not support concurrent use of output streams * which are working on the same ZIP file. * *

If entry is null, output to the ZIP file is set up, * but no entry header is actually written and null is returned. * Use this to test if writing to the ZIP file is possible and to create * the ZIP file when necessary. * * Note: This method will not create a file system and thus * should not be called with a null argument from outside this * class! * * @param entry the entry to be written to the ZIP file. * If null, all data structures will be set up, * but no entry header will be written. You cannot write any * data in this case, but this is still useful in order to check * output conditions. * @return null if entry was null. A valid * output stream object for writing the requested entry otherwise. * @throws IOException if the ZIP file actually exists and the entry has * already been written or if the (possibly temporary) output file * cannot be opened for writing for any reason. */ public OutputStream getOutputStream(ZipFileSystem.Entry entry) throws IOException { if (tmpOut == null) { /* ... */ tmpOut = new RememberingZipOutputStream( new java.io.FileOutputStream(tmpFile)); } /* ... */ } /** * This class uses address@hidden java.util.zip.CRC32} to implement * address@hidden CRC32If}. * This allows us to use native CRC32 code from Sun's JDK for maximum * performance. */ protected static class JDKCRC32 extends java.util.zip.CRC32 implements CRC32If { } /** * This class uses address@hidden java.util.zip.Deflater} to implement * address@hidden DeflaterIf}. * This allows us to use native Deflater code from Sun's JDK for maximum * performance. */ protected static class JDKDeflater extends java.util.zip.Deflater implements DeflaterIf { public JDKDeflater(int level, boolean nowrap) { super(level, nowrap); } } /** * This class uses address@hidden java.util.zip.Inflater} to implement * address@hidden InflaterIf}. * This allows us to use native Inflater code from Sun's JDK for maximum * performance. */ protected static class JDKInflater extends java.util.zip.Inflater implements InflaterIf { public JDKInflater(boolean nowrap) { super(nowrap); } } /** * This class implements a localised ZipFile. */ protected class LocalisedZipFile extends ZipFile { public LocalisedZipFile(java.io.File file) throws net.sf.jazzlib.ZipException, IOException, UnsupportedEncodingException { super(file, charSet); } public final InputStream getInputStream(net.sf.jazzlib.ZipEntry entry) throws IOException { InflaterIf inf = useJazzlibCompression ? (InflaterIf) new Inflater(true) : (InflaterIf) new JDKInflater(true); return super.getInputStream(entry, inf); } } /** * This class implements a localised ZIPOutputStream which * remembers the entries it has written. */ protected class RememberingZipOutputStream extends ZipOutputStream { /* ... */ RememberingZipOutputStream(OutputStream out) throws UnsupportedEncodingException { super(out, charSet, useJazzlibCompression ? (DeflaterIf) new Deflater(net.sf.jazzlib.Deflater.BEST_COMPRESSION, true) : (DeflaterIf) new JDKDeflater(java.util.zip.Deflater.BEST_COMPRESSION, true), useJazzlibCompression ? (CRC32If) new net.sf.jazzlib.CRC32() : new JDKCRC32()); } /* ... */ } }