KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > groboutils > codecoverage > v2 > ant > zip > ZipFile


1 /*
2  * Copyright 2003-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */

17
18 package net.sourceforge.groboutils.codecoverage.v2.ant.zip;
19
20 import java.io.File JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.InputStream JavaDoc;
23 import java.io.RandomAccessFile JavaDoc;
24 import java.io.UnsupportedEncodingException JavaDoc;
25 import java.util.Calendar JavaDoc;
26 import java.util.Date JavaDoc;
27 import java.util.Enumeration JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import java.util.zip.Inflater JavaDoc;
30 import java.util.zip.InflaterInputStream JavaDoc;
31 import java.util.zip.ZipException JavaDoc;
32
33 /**
34  * Replacement for <code>java.util.ZipFile</code>.
35  *
36  * <p>This class adds support for file name encodings other than UTF-8
37  * (which is required to work on ZIP files created by native zip tools
38  * and is able to skip a preamble like the one found in self
39  * extracting archives. Furthermore it returns instances of
40  * <code>org.apache.tools.zip.ZipEntry</code> instead of
41  * <code>java.util.zip.ZipEntry</code>.</p>
42  *
43  * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would
44  * have to reimplement all methods anyway. Like
45  * <code>java.util.ZipFile</code>, it uses RandomAccessFile under the
46  * covers and supports compressed and uncompressed entries.</p>
47  *
48  * <p>The method signatures mimic the ones of
49  * <code>java.util.zip.ZipFile</code>, with a couple of exceptions:
50  *
51  * <ul>
52  * <li>There is no getName method.</li>
53  * <li>entries has been renamed to getEntries.</li>
54  * <li>getEntries and getEntry return
55  * <code>org.apache.tools.zip.ZipEntry</code> instances.</li>
56  * <li>close is allowed to throw IOException.</li>
57  * </ul>
58  *
59  * @author Stefan Bodewig
60  * @version $Revision: 1.1 $
61  */

62 public class ZipFile {
63
64     /**
65      * Maps ZipEntrys to Longs, recording the offsets of the local
66      * file headers.
67      */

68     private Hashtable JavaDoc entries = new Hashtable JavaDoc();
69
70     /**
71      * Maps String to ZipEntrys, name -> actual entry.
72      */

73     private Hashtable JavaDoc nameMap = new Hashtable JavaDoc();
74
75     /**
76      * Maps ZipEntrys to Longs, recording the offsets of the actual file data.
77      */

78     private Hashtable JavaDoc dataOffsets = new Hashtable JavaDoc();
79
80     /**
81      * The encoding to use for filenames and the file comment.
82      *
83      * <p>For a list of possible values see <a
84      * 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>.
85      * Defaults to the platform's default character encoding.</p>
86      */

87     private String JavaDoc encoding = null;
88
89     /**
90      * The actual data source.
91      */

92     private RandomAccessFile JavaDoc archive;
93
94     /**
95      * Opens the given file for reading, assuming the platform's
96      * native encoding for file names.
97      *
98      * @param f the archive.
99      *
100      * @throws IOException if an error occurs while reading the file.
101      */

102     public ZipFile(File JavaDoc f) throws IOException JavaDoc {
103         this(f, null);
104     }
105
106     /**
107      * Opens the given file for reading, assuming the platform's
108      * native encoding for file names.
109      *
110      * @param name name of the archive.
111      *
112      * @throws IOException if an error occurs while reading the file.
113      */

114     public ZipFile(String JavaDoc name) throws IOException JavaDoc {
115         this(new File JavaDoc(name), null);
116     }
117
118     /**
119      * Opens the given file for reading, assuming the specified
120      * encoding for file names.
121      *
122      * @param name name of the archive.
123      * @param encoding the encoding to use for file names
124      *
125      * @throws IOException if an error occurs while reading the file.
126      */

127     public ZipFile(String JavaDoc name, String JavaDoc encoding) throws IOException JavaDoc {
128         this(new File JavaDoc(name), encoding);
129     }
130
131     /**
132      * Opens the given file for reading, assuming the specified
133      * encoding for file names.
134      *
135      * @param f the archive.
136      * @param encoding the encoding to use for file names
137      *
138      * @throws IOException if an error occurs while reading the file.
139      */

140     public ZipFile(File JavaDoc f, String JavaDoc encoding) throws IOException JavaDoc {
141         this.encoding = encoding;
142         archive = new RandomAccessFile JavaDoc(f, "r");
143         populateFromCentralDirectory();
144         resolveLocalFileHeaderData();
145     }
146
147     /**
148      * The encoding to use for filenames and the file comment.
149      *
150      * @return null if using the platform's default character encoding.
151      */

152     public String JavaDoc getEncoding() {
153         return encoding;
154     }
155
156     /**
157      * Closes the archive.
158      * @throws IOException if an error occurs closing the archive.
159      */

160     public void close() throws IOException JavaDoc {
161         archive.close();
162     }
163
164     /**
165      * Returns all entries as {@link org.apache.tools.ant.ZipEntry
166      * ZipEntry} instances.
167      * @return all entries as ZipEntry instances.
168      */

169     public Enumeration JavaDoc getEntries() {
170         return entries.keys();
171     }
172
173     /**
174      * Returns a named entry - or <code>null</code> if no entry by
175      * that name exists.
176      * @param name name of the entry.
177      * @return the ZipEntry corresponding to the given name - or
178      * <code>null</code> if not present.
179      */

180     public ZipEntry getEntry(String JavaDoc name) {
181         return (ZipEntry) nameMap.get(name);
182     }
183
184     /**
185      * Returns an InputStream for reading the contents of the given entry.
186      * @param ze the entry to get the stream for.
187      * @return a stream to read the entry from.
188      */

189     public InputStream JavaDoc getInputStream(ZipEntry ze)
190         throws IOException JavaDoc, ZipException JavaDoc {
191         Long JavaDoc start = (Long JavaDoc) dataOffsets.get(ze);
192         if (start == null) {
193             return null;
194         }
195         BoundedInputStream bis =
196             new BoundedInputStream(start.longValue(), ze.getCompressedSize());
197         switch (ze.getMethod()) {
198             case ZipEntry.STORED:
199                 return bis;
200             case ZipEntry.DEFLATED:
201                 bis.addDummy();
202                 return new InflaterInputStream JavaDoc(bis, new Inflater JavaDoc(true));
203             default:
204                 throw new ZipException JavaDoc("Found unsupported compression method "
205                                        + ze.getMethod());
206         }
207     }
208
209     private static final int CFH_LEN =
210         /* version made by */ 2 +
211         /* version needed to extract */ 2 +
212         /* general purpose bit flag */ 2 +
213         /* compression method */ 2 +
214         /* last mod file time */ 2 +
215         /* last mod file date */ 2 +
216         /* crc-32 */ 4 +
217         /* compressed size */ 4 +
218         /* uncompressed size */ 4 +
219         /* filename length */ 2 +
220         /* extra field length */ 2 +
221         /* file comment length */ 2 +
222         /* disk number start */ 2 +
223         /* internal file attributes */ 2 +
224         /* external file attributes */ 4 +
225         /* relative offset of local header */ 4;
226
227     /**
228      * Reads the central directory of the given archive and populates
229      * the internal tables with ZipEntry instances.
230      *
231      * <p>The ZipEntrys will know all data that can be obtained from
232      * the central directory alone, but not the data that requires the
233      * local file header or additional data to be read.</p>
234      */

235     private void populateFromCentralDirectory()
236         throws IOException JavaDoc {
237         positionAtCentralDirectory();
238
239         byte[] cfh = new byte[CFH_LEN];
240
241         byte[] signatureBytes = new byte[4];
242         archive.readFully(signatureBytes);
243         ZipLong sig = new ZipLong(signatureBytes);
244         while (sig.equals(ZipOutputStream.CFH_SIG)) {
245             archive.readFully(cfh);
246             int off = 0;
247             ZipEntry ze = new ZipEntry();
248
249             ZipShort versionMadeBy = new ZipShort(cfh, off);
250             off += 2;
251             ze.setPlatform((versionMadeBy.getValue() >> 8) & 0x0F);
252
253             off += 4; // skip version info and general purpose byte
254

255             ze.setMethod((new ZipShort(cfh, off)).getValue());
256             off += 2;
257
258             ze.setTime(fromDosTime(new ZipLong(cfh, off)).getTime());
259             off += 4;
260
261             ze.setCrc((new ZipLong(cfh, off)).getValue());
262             off += 4;
263
264             ze.setCompressedSize((new ZipLong(cfh, off)).getValue());
265             off += 4;
266
267             ze.setSize((new ZipLong(cfh, off)).getValue());
268             off += 4;
269
270             int fileNameLen = (new ZipShort(cfh, off)).getValue();
271             off += 2;
272
273             int extraLen = (new ZipShort(cfh, off)).getValue();
274             off += 2;
275
276             int commentLen = (new ZipShort(cfh, off)).getValue();
277             off += 2;
278
279             off += 2; // disk number
280

281             ze.setInternalAttributes((new ZipShort(cfh, off)).getValue());
282             off += 2;
283
284             ze.setExternalAttributes((new ZipLong(cfh, off)).getValue());
285             off += 4;
286
287             // LFH offset
288
entries.put(ze, new Long JavaDoc((new ZipLong(cfh, off)).getValue()));
289
290             byte[] fileName = new byte[fileNameLen];
291             archive.readFully(fileName);
292             ze.setName(getString(fileName));
293
294             nameMap.put(ze.getName(), ze);
295
296             archive.skipBytes(extraLen);
297
298             byte[] comment = new byte[commentLen];
299             archive.readFully(comment);
300             ze.setComment(getString(comment));
301
302             archive.readFully(signatureBytes);
303             sig = new ZipLong(signatureBytes);
304         }
305     }
306
307     private static final int MIN_EOCD_SIZE =
308         /* end of central dir signature */ 4 +
309         /* number of this disk */ 2 +
310         /* number of the disk with the */ +
311         /* start of the central directory */ 2 +
312         /* total number of entries in */ +
313         /* the central dir on this disk */ 2 +
314         /* total number of entries in */ +
315         /* the central dir */ 2 +
316         /* size of the central directory */ 4 +
317         /* offset of start of central */ +
318         /* directory with respect to */ +
319         /* the starting disk number */ 4 +
320         /* zipfile comment length */ 2;
321
322     private static final int CFD_LOCATOR_OFFSET =
323         /* end of central dir signature */ 4 +
324         /* number of this disk */ 2 +
325         /* number of the disk with the */ +
326         /* start of the central directory */ 2 +
327         /* total number of entries in */ +
328         /* the central dir on this disk */ 2 +
329         /* total number of entries in */ +
330         /* the central dir */ 2 +
331         /* size of the central directory */ 4;
332
333     /**
334      * Searches for the &quot;End of central dir record&quot;, parses
335      * it and positions the stream at the first central directory
336      * record.
337      */

338     private void positionAtCentralDirectory()
339         throws IOException JavaDoc {
340         long off = archive.length() - MIN_EOCD_SIZE;
341         archive.seek(off);
342         byte[] sig = ZipOutputStream.EOCD_SIG.getBytes();
343         int curr = archive.read();
344         boolean found = false;
345         while (curr != -1) {
346             if (curr == sig[0]) {
347                 curr = archive.read();
348                 if (curr == sig[1]) {
349                     curr = archive.read();
350                     if (curr == sig[2]) {
351                         curr = archive.read();
352                         if (curr == sig[3]) {
353                             found = true;
354                             break;
355                         }
356                     }
357                 }
358             }
359             archive.seek(--off);
360             curr = archive.read();
361         }
362         if (!found) {
363             throw new ZipException JavaDoc("archive is not a ZIP archive");
364         }
365         archive.seek(off + CFD_LOCATOR_OFFSET);
366         byte[] cfdOffset = new byte[4];
367         archive.readFully(cfdOffset);
368         archive.seek((new ZipLong(cfdOffset)).getValue());
369     }
370
371     /**
372      * Number of bytes in local file header up to the &quot;length of
373      * filename&quot; entry.
374      */

375     private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
376         /* local file header signature */ 4 +
377         /* version needed to extract */ 2 +
378         /* general purpose bit flag */ 2 +
379         /* compression method */ 2 +
380         /* last mod file time */ 2 +
381         /* last mod file date */ 2 +
382         /* crc-32 */ 4 +
383         /* compressed size */ 4 +
384         /* uncompressed size */ 4;
385
386     /**
387      * Walks through all recorded entries and adds the data available
388      * from the local file header.
389      *
390      * <p>Also records the offsets for the data to read from the
391      * entries.</p>
392      */

393     private void resolveLocalFileHeaderData()
394         throws IOException JavaDoc {
395         Enumeration JavaDoc e = getEntries();
396         while (e.hasMoreElements()) {
397             ZipEntry ze = (ZipEntry) e.nextElement();
398             long offset = ((Long JavaDoc) entries.get(ze)).longValue();
399             archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
400             byte[] b = new byte[2];
401             archive.readFully(b);
402             int fileNameLen = (new ZipShort(b)).getValue();
403             archive.readFully(b);
404             int extraFieldLen = (new ZipShort(b)).getValue();
405             archive.skipBytes(fileNameLen);
406             byte[] localExtraData = new byte[extraFieldLen];
407             archive.readFully(localExtraData);
408             ze.setExtra(localExtraData);
409             dataOffsets.put(ze,
410                             new Long JavaDoc(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
411                                      + 2 + 2 + fileNameLen + extraFieldLen));
412         }
413     }
414
415     /**
416      * Convert a DOS date/time field to a Date object.
417      *
418      * @param l contains the stored DOS time.
419      * @return a Date instance corresponding to the given time.
420      */

421     protected static Date JavaDoc fromDosTime(ZipLong l) {
422         long dosTime = l.getValue();
423         Calendar JavaDoc cal = Calendar.getInstance();
424         cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
425         cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
426         cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
427         cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
428         cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
429         cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
430         return cal.getTime();
431     }
432
433     /**
434      * Retrieve a String from the given bytes using the encoding set
435      * for this ZipFile.
436      *
437      * @param bytes the byte array to transform
438      * @return String obtained by using the given encoding
439      * @throws ZipException if the encoding cannot be recognized.
440      */

441     protected String JavaDoc getString(byte[] bytes) throws ZipException JavaDoc {
442         if (encoding == null) {
443             return new String JavaDoc(bytes);
444         } else {
445             try {
446                 return new String JavaDoc(bytes, encoding);
447             } catch (UnsupportedEncodingException JavaDoc uee) {
448                 throw new ZipException JavaDoc(uee.getMessage());
449             }
450         }
451     }
452
453     /**
454      * InputStream that delegates requests to the underlying
455      * RandomAccessFile, making sure that only bytes from a certain
456      * range can be read.
457      */

458     private class BoundedInputStream extends InputStream JavaDoc {
459         private long remaining;
460         private long loc;
461         private boolean addDummyByte = false;
462
463         BoundedInputStream(long start, long remaining) {
464             this.remaining = remaining;
465             loc = start;
466         }
467
468         public int read() throws IOException JavaDoc {
469             if (remaining-- <= 0) {
470                 if (addDummyByte) {
471                     addDummyByte = false;
472                     return 0;
473                 }
474                 return -1;
475             }
476             synchronized (archive) {
477                 archive.seek(loc++);
478                 return archive.read();
479             }
480         }
481
482         public int read(byte[] b, int off, int len) throws IOException JavaDoc {
483             if (remaining <= 0) {
484                 if (addDummyByte) {
485                     addDummyByte = false;
486                     b[off] = 0;
487                     return 1;
488                 }
489                 return -1;
490             }
491
492             if (len <= 0) {
493                 return 0;
494             }
495
496             if (len > remaining) {
497                 len = (int) remaining;
498             }
499             int ret = -1;
500             synchronized (archive) {
501                 archive.seek(loc);
502                 ret = archive.read(b, off, len);
503             }
504             if (ret > 0) {
505                 loc += ret;
506                 remaining -= ret;
507             }
508             return ret;
509         }
510
511         /**
512          * Inflater needs an extra dummy byte for nowrap - see
513          * Inflater's javadocs.
514          */

515         void addDummy() {
516             addDummyByte = true;
517         }
518     }
519
520 }
521
Popular Tags