001 /* ZipOutputStream.java --
002 Copyright (C) 2001, 2004, 2005, 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package java.util.zip;
040
041 import java.io.IOException;
042 import java.io.OutputStream;
043 import java.io.UnsupportedEncodingException;
044 import java.util.Enumeration;
045 import java.util.Vector;
046
047 /**
048 * This is a FilterOutputStream that writes the files into a zip
049 * archive one after another. It has a special method to start a new
050 * zip entry. The zip entries contains information about the file name
051 * size, compressed size, CRC, etc.
052 *
053 * It includes support for STORED and DEFLATED entries.
054 *
055 * This class is not thread safe.
056 *
057 * @author Jochen Hoenicke
058 */
059 public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants
060 {
061 private Vector entries = new Vector();
062 private CRC32 crc = new CRC32();
063 private ZipEntry curEntry = null;
064
065 private int curMethod;
066 private int size;
067 private int offset = 0;
068
069 private byte[] zipComment = new byte[0];
070 private int defaultMethod = DEFLATED;
071
072 /**
073 * Our Zip version is hard coded to 1.0 resp. 2.0
074 */
075 private static final int ZIP_STORED_VERSION = 10;
076 private static final int ZIP_DEFLATED_VERSION = 20;
077
078 /**
079 * Compression method. This method doesn't compress at all.
080 */
081 public static final int STORED = 0;
082
083 /**
084 * Compression method. This method uses the Deflater.
085 */
086 public static final int DEFLATED = 8;
087
088 /**
089 * Creates a new Zip output stream, writing a zip archive.
090 * @param out the output stream to which the zip archive is written.
091 */
092 public ZipOutputStream(OutputStream out)
093 {
094 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
095 }
096
097 /**
098 * Set the zip file comment.
099 * @param comment the comment.
100 * @exception IllegalArgumentException if encoding of comment is
101 * longer than 0xffff bytes.
102 */
103 public void setComment(String comment)
104 {
105 byte[] commentBytes;
106 try
107 {
108 commentBytes = comment.getBytes("UTF-8");
109 }
110 catch (UnsupportedEncodingException uee)
111 {
112 throw new AssertionError(uee);
113 }
114 if (commentBytes.length > 0xffff)
115 throw new IllegalArgumentException("Comment too long.");
116 zipComment = commentBytes;
117 }
118
119 /**
120 * Sets default compression method. If the Zip entry specifies
121 * another method its method takes precedence.
122 * @param method the method.
123 * @exception IllegalArgumentException if method is not supported.
124 * @see #STORED
125 * @see #DEFLATED
126 */
127 public void setMethod(int method)
128 {
129 if (method != STORED && method != DEFLATED)
130 throw new IllegalArgumentException("Method not supported.");
131 defaultMethod = method;
132 }
133
134 /**
135 * Sets default compression level. The new level will be activated
136 * immediately.
137 * @exception IllegalArgumentException if level is not supported.
138 * @see Deflater
139 */
140 public void setLevel(int level)
141 {
142 def.setLevel(level);
143 }
144
145 /**
146 * Write an unsigned short in little endian byte order.
147 */
148 private void writeLeShort(int value) throws IOException
149 {
150 out.write(value & 0xff);
151 out.write((value >> 8) & 0xff);
152 }
153
154 /**
155 * Write an int in little endian byte order.
156 */
157 private void writeLeInt(int value) throws IOException
158 {
159 writeLeShort(value);
160 writeLeShort(value >> 16);
161 }
162
163 /**
164 * Write a long value as an int. Some of the zip constants
165 * are declared as longs even though they fit perfectly well
166 * into integers.
167 */
168 private void writeLeInt(long value) throws IOException
169 {
170 writeLeInt((int) value);
171 }
172
173 /**
174 * Starts a new Zip entry. It automatically closes the previous
175 * entry if present. If the compression method is stored, the entry
176 * must have a valid size and crc, otherwise all elements (except
177 * name) are optional, but must be correct if present. If the time
178 * is not set in the entry, the current time is used.
179 * @param entry the entry.
180 * @exception IOException if an I/O error occured.
181 * @exception ZipException if stream was finished.
182 */
183 public void putNextEntry(ZipEntry entry) throws IOException
184 {
185 if (entries == null)
186 throw new ZipException("ZipOutputStream was finished");
187
188 int method = entry.getMethod();
189 int flags = 0;
190 if (method == -1)
191 method = defaultMethod;
192
193 if (method == STORED)
194 {
195 if (entry.getCompressedSize() >= 0)
196 {
197 if (entry.getSize() < 0)
198 entry.setSize(entry.getCompressedSize());
199 else if (entry.getSize() != entry.getCompressedSize())
200 throw new ZipException
201 ("Method STORED, but compressed size != size");
202 }
203 else
204 entry.setCompressedSize(entry.getSize());
205
206 if (entry.getSize() < 0)
207 throw new ZipException("Method STORED, but size not set");
208 if (entry.getCrc() < 0)
209 throw new ZipException("Method STORED, but crc not set");
210 }
211 else if (method == DEFLATED)
212 {
213 if (entry.getCompressedSize() < 0
214 || entry.getSize() < 0 || entry.getCrc() < 0)
215 flags |= 8;
216 }
217
218 if (curEntry != null)
219 closeEntry();
220
221 if (entry.getTime() < 0)
222 entry.setTime(System.currentTimeMillis());
223
224 entry.flags = flags;
225 entry.offset = offset;
226 entry.setMethod(method);
227 curMethod = method;
228 /* Write the local file header */
229 writeLeInt(LOCSIG);
230 writeLeShort(method == STORED
231 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
232 writeLeShort(flags);
233 writeLeShort(method);
234 writeLeInt(entry.getDOSTime());
235 if ((flags & 8) == 0)
236 {
237 writeLeInt((int)entry.getCrc());
238 writeLeInt((int)entry.getCompressedSize());
239 writeLeInt((int)entry.getSize());
240 }
241 else
242 {
243 writeLeInt(0);
244 writeLeInt(0);
245 writeLeInt(0);
246 }
247 byte[] name;
248 try
249 {
250 name = entry.getName().getBytes("UTF-8");
251 }
252 catch (UnsupportedEncodingException uee)
253 {
254 throw new AssertionError(uee);
255 }
256 if (name.length > 0xffff)
257 throw new ZipException("Name too long.");
258 byte[] extra = entry.getExtra();
259 if (extra == null)
260 extra = new byte[0];
261 writeLeShort(name.length);
262 writeLeShort(extra.length);
263 out.write(name);
264 out.write(extra);
265
266 offset += LOCHDR + name.length + extra.length;
267
268 /* Activate the entry. */
269
270 curEntry = entry;
271 crc.reset();
272 if (method == DEFLATED)
273 def.reset();
274 size = 0;
275 }
276
277 /**
278 * Closes the current entry.
279 * @exception IOException if an I/O error occured.
280 * @exception ZipException if no entry is active.
281 */
282 public void closeEntry() throws IOException
283 {
284 if (curEntry == null)
285 throw new ZipException("No open entry");
286
287 /* First finish the deflater, if appropriate */
288 if (curMethod == DEFLATED)
289 super.finish();
290
291 int csize = curMethod == DEFLATED ? def.getTotalOut() : size;
292
293 if (curEntry.getSize() < 0)
294 curEntry.setSize(size);
295 else if (curEntry.getSize() != size)
296 throw new ZipException("size was "+size
297 +", but I expected "+curEntry.getSize());
298
299 if (curEntry.getCompressedSize() < 0)
300 curEntry.setCompressedSize(csize);
301 else if (curEntry.getCompressedSize() != csize)
302 throw new ZipException("compressed size was "+csize
303 +", but I expected "+curEntry.getSize());
304
305 if (curEntry.getCrc() < 0)
306 curEntry.setCrc(crc.getValue());
307 else if (curEntry.getCrc() != crc.getValue())
308 throw new ZipException("crc was " + Long.toHexString(crc.getValue())
309 + ", but I expected "
310 + Long.toHexString(curEntry.getCrc()));
311
312 offset += csize;
313
314 /* Now write the data descriptor entry if needed. */
315 if (curMethod == DEFLATED && (curEntry.flags & 8) != 0)
316 {
317 writeLeInt(EXTSIG);
318 writeLeInt((int)curEntry.getCrc());
319 writeLeInt((int)curEntry.getCompressedSize());
320 writeLeInt((int)curEntry.getSize());
321 offset += EXTHDR;
322 }
323
324 entries.addElement(curEntry);
325 curEntry = null;
326 }
327
328 /**
329 * Writes the given buffer to the current entry.
330 * @exception IOException if an I/O error occured.
331 * @exception ZipException if no entry is active.
332 */
333 public void write(byte[] b, int off, int len) throws IOException
334 {
335 if (curEntry == null)
336 throw new ZipException("No open entry.");
337
338 switch (curMethod)
339 {
340 case DEFLATED:
341 super.write(b, off, len);
342 break;
343
344 case STORED:
345 out.write(b, off, len);
346 break;
347 }
348
349 crc.update(b, off, len);
350 size += len;
351 }
352
353 /**
354 * Finishes the stream. This will write the central directory at the
355 * end of the zip file and flush the stream.
356 * @exception IOException if an I/O error occured.
357 */
358 public void finish() throws IOException
359 {
360 if (entries == null)
361 return;
362 if (curEntry != null)
363 closeEntry();
364
365 int numEntries = 0;
366 int sizeEntries = 0;
367
368 Enumeration e = entries.elements();
369 while (e.hasMoreElements())
370 {
371 ZipEntry entry = (ZipEntry) e.nextElement();
372
373 int method = entry.getMethod();
374 writeLeInt(CENSIG);
375 writeLeShort(method == STORED
376 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
377 writeLeShort(method == STORED
378 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
379 writeLeShort(entry.flags);
380 writeLeShort(method);
381 writeLeInt(entry.getDOSTime());
382 writeLeInt((int)entry.getCrc());
383 writeLeInt((int)entry.getCompressedSize());
384 writeLeInt((int)entry.getSize());
385
386 byte[] name;
387 try
388 {
389 name = entry.getName().getBytes("UTF-8");
390 }
391 catch (UnsupportedEncodingException uee)
392 {
393 throw new AssertionError(uee);
394 }
395 if (name.length > 0xffff)
396 throw new ZipException("Name too long.");
397 byte[] extra = entry.getExtra();
398 if (extra == null)
399 extra = new byte[0];
400 String str = entry.getComment();
401 byte[] comment;
402 try
403 {
404 comment = str != null ? str.getBytes("UTF-8") : new byte[0];
405 }
406 catch (UnsupportedEncodingException uee)
407 {
408 throw new AssertionError(uee);
409 }
410 if (comment.length > 0xffff)
411 throw new ZipException("Comment too long.");
412
413 writeLeShort(name.length);
414 writeLeShort(extra.length);
415 writeLeShort(comment.length);
416 writeLeShort(0); /* disk number */
417 writeLeShort(0); /* internal file attr */
418 writeLeInt(0); /* external file attr */
419 writeLeInt(entry.offset);
420
421 out.write(name);
422 out.write(extra);
423 out.write(comment);
424 numEntries++;
425 sizeEntries += CENHDR + name.length + extra.length + comment.length;
426 }
427
428 writeLeInt(ENDSIG);
429 writeLeShort(0); /* disk number */
430 writeLeShort(0); /* disk with start of central dir */
431 writeLeShort(numEntries);
432 writeLeShort(numEntries);
433 writeLeInt(sizeEntries);
434 writeLeInt(offset);
435 writeLeShort(zipComment.length);
436 out.write(zipComment);
437 out.flush();
438 entries = null;
439 }
440 }