1 /* ZipOutputStream.java --
2 Copyright (C) 2001, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java
.util
.zip
;
41 import java
.io
.IOException
;
42 import java
.io
.OutputStream
;
43 import java
.util
.Enumeration
;
44 import java
.util
.Vector
;
47 * This is a FilterOutputStream that writes the files into a zip
48 * archive one after another. It has a special method to start a new
49 * zip entry. The zip entries contains information about the file name
50 * size, compressed size, CRC, etc.
52 * It includes support for STORED and DEFLATED entries.
54 * This class is not thread safe.
56 * @author Jochen Hoenicke
58 public class ZipOutputStream
extends DeflaterOutputStream
implements ZipConstants
60 private Vector entries
= new Vector();
61 private CRC32 crc
= new CRC32();
62 private ZipEntry curEntry
= null;
64 private int curMethod
;
66 private int offset
= 0;
68 private byte[] zipComment
= new byte[0];
69 private int defaultMethod
= DEFLATED
;
72 * Our Zip version is hard coded to 1.0 resp. 2.0
74 private static final int ZIP_STORED_VERSION
= 10;
75 private static final int ZIP_DEFLATED_VERSION
= 20;
78 * Compression method. This method doesn't compress at all.
80 public static final int STORED
= 0;
83 * Compression method. This method uses the Deflater.
85 public static final int DEFLATED
= 8;
88 * Creates a new Zip output stream, writing a zip archive.
89 * @param out the output stream to which the zip archive is written.
91 public ZipOutputStream(OutputStream out
)
93 super(out
, new Deflater(Deflater
.DEFAULT_COMPRESSION
, true));
97 * Set the zip file comment.
98 * @param comment the comment.
99 * @exception IllegalArgumentException if encoding of comment is
100 * longer than 0xffff bytes.
102 public void setComment(String comment
)
105 commentBytes
= comment
.getBytes();
106 if (commentBytes
.length
> 0xffff)
107 throw new IllegalArgumentException("Comment too long.");
108 zipComment
= commentBytes
;
112 * Sets default compression method. If the Zip entry specifies
113 * another method its method takes precedence.
114 * @param method the method.
115 * @exception IllegalArgumentException if method is not supported.
119 public void setMethod(int method
)
121 if (method
!= STORED
&& method
!= DEFLATED
)
122 throw new IllegalArgumentException("Method not supported.");
123 defaultMethod
= method
;
127 * Sets default compression level. The new level will be activated
129 * @exception IllegalArgumentException if level is not supported.
132 public void setLevel(int level
)
138 * Write an unsigned short in little endian byte order.
140 private void writeLeShort(int value
) throws IOException
142 out
.write(value
& 0xff);
143 out
.write((value
>> 8) & 0xff);
147 * Write an int in little endian byte order.
149 private void writeLeInt(int value
) throws IOException
152 writeLeShort(value
>> 16);
156 * Starts a new Zip entry. It automatically closes the previous
157 * entry if present. If the compression method is stored, the entry
158 * must have a valid size and crc, otherwise all elements (except
159 * name) are optional, but must be correct if present. If the time
160 * is not set in the entry, the current time is used.
161 * @param entry the entry.
162 * @exception IOException if an I/O error occured.
163 * @exception ZipException if stream was finished.
165 public void putNextEntry(ZipEntry entry
) throws IOException
168 throw new ZipException("ZipOutputStream was finished");
170 int method
= entry
.getMethod();
173 method
= defaultMethod
;
175 if (method
== STORED
)
177 if (entry
.getCompressedSize() >= 0)
179 if (entry
.getSize() < 0)
180 entry
.setSize(entry
.getCompressedSize());
181 else if (entry
.getSize() != entry
.getCompressedSize())
182 throw new ZipException
183 ("Method STORED, but compressed size != size");
186 entry
.setCompressedSize(entry
.getSize());
188 if (entry
.getSize() < 0)
189 throw new ZipException("Method STORED, but size not set");
190 if (entry
.getCrc() < 0)
191 throw new ZipException("Method STORED, but crc not set");
193 else if (method
== DEFLATED
)
195 if (entry
.getCompressedSize() < 0
196 || entry
.getSize() < 0 || entry
.getCrc() < 0)
200 if (curEntry
!= null)
203 if (entry
.getTime() < 0)
204 entry
.setTime(System
.currentTimeMillis());
207 entry
.offset
= offset
;
208 entry
.setMethod(method
);
210 /* Write the local file header */
212 writeLeShort(method
== STORED
213 ? ZIP_STORED_VERSION
: ZIP_DEFLATED_VERSION
);
215 writeLeShort(method
);
216 writeLeInt(entry
.getDOSTime());
217 if ((flags
& 8) == 0)
219 writeLeInt((int)entry
.getCrc());
220 writeLeInt((int)entry
.getCompressedSize());
221 writeLeInt((int)entry
.getSize());
229 byte[] name
= entry
.getName().getBytes();
230 if (name
.length
> 0xffff)
231 throw new ZipException("Name too long.");
232 byte[] extra
= entry
.getExtra();
235 writeLeShort(name
.length
);
236 writeLeShort(extra
.length
);
240 offset
+= LOCHDR
+ name
.length
+ extra
.length
;
242 /* Activate the entry. */
246 if (method
== DEFLATED
)
252 * Closes the current entry.
253 * @exception IOException if an I/O error occured.
254 * @exception ZipException if no entry is active.
256 public void closeEntry() throws IOException
258 if (curEntry
== null)
259 throw new ZipException("No open entry");
261 /* First finish the deflater, if appropriate */
262 if (curMethod
== DEFLATED
)
265 int csize
= curMethod
== DEFLATED ? def
.getTotalOut() : size
;
267 if (curEntry
.getSize() < 0)
268 curEntry
.setSize(size
);
269 else if (curEntry
.getSize() != size
)
270 throw new ZipException("size was "+size
271 +", but I expected "+curEntry
.getSize());
273 if (curEntry
.getCompressedSize() < 0)
274 curEntry
.setCompressedSize(csize
);
275 else if (curEntry
.getCompressedSize() != csize
)
276 throw new ZipException("compressed size was "+csize
277 +", but I expected "+curEntry
.getSize());
279 if (curEntry
.getCrc() < 0)
280 curEntry
.setCrc(crc
.getValue());
281 else if (curEntry
.getCrc() != crc
.getValue())
282 throw new ZipException("crc was " + Long
.toHexString(crc
.getValue())
283 + ", but I expected "
284 + Long
.toHexString(curEntry
.getCrc()));
288 /* Now write the data descriptor entry if needed. */
289 if (curMethod
== DEFLATED
&& (curEntry
.flags
& 8) != 0)
292 writeLeInt((int)curEntry
.getCrc());
293 writeLeInt((int)curEntry
.getCompressedSize());
294 writeLeInt((int)curEntry
.getSize());
298 entries
.addElement(curEntry
);
303 * Writes the given buffer to the current entry.
304 * @exception IOException if an I/O error occured.
305 * @exception ZipException if no entry is active.
307 public void write(byte[] b
, int off
, int len
) throws IOException
309 if (curEntry
== null)
310 throw new ZipException("No open entry.");
315 super.write(b
, off
, len
);
319 out
.write(b
, off
, len
);
323 crc
.update(b
, off
, len
);
328 * Finishes the stream. This will write the central directory at the
329 * end of the zip file and flush the stream.
330 * @exception IOException if an I/O error occured.
332 public void finish() throws IOException
336 if (curEntry
!= null)
342 Enumeration e
= entries
.elements();
343 while (e
.hasMoreElements())
345 ZipEntry entry
= (ZipEntry
) e
.nextElement();
347 int method
= entry
.getMethod();
349 writeLeShort(method
== STORED
350 ? ZIP_STORED_VERSION
: ZIP_DEFLATED_VERSION
);
351 writeLeShort(method
== STORED
352 ? ZIP_STORED_VERSION
: ZIP_DEFLATED_VERSION
);
353 writeLeShort(entry
.flags
);
354 writeLeShort(method
);
355 writeLeInt(entry
.getDOSTime());
356 writeLeInt((int)entry
.getCrc());
357 writeLeInt((int)entry
.getCompressedSize());
358 writeLeInt((int)entry
.getSize());
360 byte[] name
= entry
.getName().getBytes();
361 if (name
.length
> 0xffff)
362 throw new ZipException("Name too long.");
363 byte[] extra
= entry
.getExtra();
366 String strComment
= entry
.getComment();
367 byte[] comment
= strComment
!= null
368 ? strComment
.getBytes() : new byte[0];
369 if (comment
.length
> 0xffff)
370 throw new ZipException("Comment too long.");
372 writeLeShort(name
.length
);
373 writeLeShort(extra
.length
);
374 writeLeShort(comment
.length
);
375 writeLeShort(0); /* disk number */
376 writeLeShort(0); /* internal file attr */
377 writeLeInt(0); /* external file attr */
378 writeLeInt(entry
.offset
);
384 sizeEntries
+= CENHDR
+ name
.length
+ extra
.length
+ comment
.length
;
388 writeLeShort(0); /* disk number */
389 writeLeShort(0); /* disk with start of central dir */
390 writeLeShort(numEntries
);
391 writeLeShort(numEntries
);
392 writeLeInt(sizeEntries
);
394 writeLeShort(zipComment
.length
);
395 out
.write(zipComment
);