001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.util.zip;
019:
020: import java.io.ByteArrayOutputStream;
021: import java.io.IOException;
022: import java.io.OutputStream;
023: import java.util.Vector;
024:
025: import org.apache.harmony.archive.internal.nls.Messages;
026:
027: /**
028: * ZipOutputStream is used to write ZipEntries to the underlying stream. Output
029: * from ZipOutputStream conforms to the ZipFile file format.
030: *
031: * @see ZipInputStream
032: * @see ZipEntry
033: */
034: public class ZipOutputStream extends DeflaterOutputStream implements
035: ZipConstants {
036:
037: /** Method for compressed entries */
038: public static final int DEFLATED = 8;
039:
040: /** Method for uncompressed entries */
041: public static final int STORED = 0;
042:
043: static final int ZIPDataDescriptorFlag = 8;
044:
045: static final int ZIPLocalHeaderVersionNeeded = 20;
046:
047: private String comment;
048:
049: private final Vector<String> entries = new Vector<String>();
050:
051: private int compressMethod = DEFLATED;
052:
053: private int compressLevel = Deflater.DEFAULT_COMPRESSION;
054:
055: private ByteArrayOutputStream cDir = new ByteArrayOutputStream();
056:
057: private ZipEntry currentEntry;
058:
059: private final CRC32 crc = new CRC32();
060:
061: private int offset = 0, curOffset = 0, nameLength;
062:
063: private byte[] nameBytes;
064:
065: /**
066: * Constructs a new ZipOutputStream on p1
067: *
068: * @param p1
069: * OutputStream The InputStream to output to
070: */
071: public ZipOutputStream(OutputStream p1) {
072: super (p1, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
073: }
074:
075: /**
076: * Closes the current ZipEntry if any. Closes the underlying output stream.
077: * If the stream is already closed this method does nothing.
078: *
079: * @exception IOException
080: * If an error occurs closing the stream
081: */
082: @Override
083: public void close() throws IOException {
084: if (out != null) {
085: finish();
086: out.close();
087: out = null;
088: }
089: }
090:
091: /**
092: * Closes the current ZipEntry. Any entry terminal data is written to the
093: * underlying stream.
094: *
095: * @exception IOException
096: * If an error occurs closing the entry
097: */
098: public void closeEntry() throws IOException {
099: if (cDir == null) {
100: throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
101: }
102: if (currentEntry == null) {
103: return;
104: }
105: if (currentEntry.getMethod() == DEFLATED) {
106: super .finish();
107: }
108:
109: // Verify values for STORED types
110: if (currentEntry.getMethod() == STORED) {
111: if (crc.getValue() != currentEntry.crc) {
112: throw new ZipException(Messages.getString("archive.20")); //$NON-NLS-1$
113: }
114: if (currentEntry.size != crc.tbytes) {
115: throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$
116: }
117: }
118: curOffset = LOCHDR;
119:
120: // Write the DataDescriptor
121: if (currentEntry.getMethod() != STORED) {
122: curOffset += EXTHDR;
123: writeLong(out, EXTSIG);
124: writeLong(out, currentEntry.crc = crc.getValue());
125: writeLong(out, currentEntry.compressedSize = def
126: .getTotalOut());
127: writeLong(out, currentEntry.size = def.getTotalIn());
128: }
129: // Update the CentralDirectory
130: writeLong(cDir, CENSIG);
131: writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version created
132: writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version to extract
133: writeShort(cDir, currentEntry.getMethod() == STORED ? 0
134: : ZIPDataDescriptorFlag);
135: writeShort(cDir, currentEntry.getMethod());
136: writeShort(cDir, currentEntry.time);
137: writeShort(cDir, currentEntry.modDate);
138: writeLong(cDir, crc.getValue());
139: if (currentEntry.getMethod() == DEFLATED) {
140: curOffset += writeLong(cDir, def.getTotalOut());
141: writeLong(cDir, def.getTotalIn());
142: } else {
143: curOffset += writeLong(cDir, crc.tbytes);
144: writeLong(cDir, crc.tbytes);
145: }
146: curOffset += writeShort(cDir, nameLength);
147: if (currentEntry.extra != null) {
148: curOffset += writeShort(cDir, currentEntry.extra.length);
149: } else {
150: writeShort(cDir, 0);
151: }
152: String c;
153: if ((c = currentEntry.getComment()) != null) {
154: writeShort(cDir, c.length());
155: } else {
156: writeShort(cDir, 0);
157: }
158: writeShort(cDir, 0); // Disk Start
159: writeShort(cDir, 0); // Internal File Attributes
160: writeLong(cDir, 0); // External File Attributes
161: writeLong(cDir, offset);
162: cDir.write(nameBytes);
163: nameBytes = null;
164: if (currentEntry.extra != null) {
165: cDir.write(currentEntry.extra);
166: }
167: offset += curOffset;
168: if (c != null) {
169: cDir.write(c.getBytes());
170: }
171: currentEntry = null;
172: crc.reset();
173: def.reset();
174: done = false;
175: }
176:
177: /**
178: * Indicates that all entries have been written to the stream. Any terminal
179: * ZipFile information is written to the underlying stream.
180: *
181: * @exception IOException
182: * If an error occurs while finishing
183: */
184: @Override
185: public void finish() throws IOException {
186: if (out == null) {
187: throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
188: }
189: if (cDir == null) {
190: return;
191: }
192: if (entries.size() == 0) {
193: throw new ZipException(Messages.getString("archive.28")); //$NON-NLS-1$;
194: }
195: if (currentEntry != null) {
196: closeEntry();
197: }
198: int cdirSize = cDir.size();
199: // Write Central Dir End
200: writeLong(cDir, ENDSIG);
201: writeShort(cDir, 0); // Disk Number
202: writeShort(cDir, 0); // Start Disk
203: writeShort(cDir, entries.size()); // Number of entries
204: writeShort(cDir, entries.size()); // Number of entries
205: writeLong(cDir, cdirSize); // Size of central dir
206: writeLong(cDir, offset); // Offset of central dir
207: if (comment != null) {
208: writeShort(cDir, comment.length());
209: cDir.write(comment.getBytes());
210: } else {
211: writeShort(cDir, 0);
212: }
213: // Write the central dir
214: out.write(cDir.toByteArray());
215: cDir = null;
216:
217: }
218:
219: /**
220: * Writes entry information for ze to the underlying stream. Data associated
221: * with the entry can then be written using write(). After data is written
222: * closeEntry() must be called to complete the storing of ze on the
223: * underlying stream.
224: *
225: * @param ze
226: * ZipEntry to store
227: * @exception IOException
228: * If an error occurs storing the entry
229: * @see #write
230: */
231: public void putNextEntry(ZipEntry ze) throws java.io.IOException {
232: if (currentEntry != null) {
233: closeEntry();
234: }
235: if (ze.getMethod() == STORED
236: || (compressMethod == STORED && ze.getMethod() == -1)) {
237: if (ze.crc == -1) {
238: /* [MSG "archive.20", "Crc mismatch"] */
239: throw new ZipException(Messages.getString("archive.20")); //$NON-NLS-1$
240: }
241: if (ze.size == -1 && ze.compressedSize == -1) {
242: /* [MSG "archive.21", "Size mismatch"] */
243: throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$
244: }
245: if (ze.size != ze.compressedSize && ze.compressedSize != -1
246: && ze.size != -1) {
247: /* [MSG "archive.21", "Size mismatch"] */
248: throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$
249: }
250: }
251: /* [MSG "archive.1E", "Stream is closed"] */
252: if (cDir == null) {
253: throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
254: }
255: if (entries.contains(ze.name)) {
256: /* [MSG "archive.29", "Entry already exists: {0}"] */
257: throw new ZipException(Messages.getString(
258: "archive.29", ze.name)); //$NON-NLS-1$
259: }
260: nameLength = utf8Count(ze.name);
261: if (nameLength > 0xffff) {
262: /* [MSG "archive.2A", "Name too long: {0}"] */
263: throw new IllegalArgumentException(Messages.getString(
264: "archive.2A", ze.name)); //$NON-NLS-1$
265: }
266:
267: def.setLevel(compressLevel);
268: currentEntry = ze;
269: entries.add(currentEntry.name);
270: if (currentEntry.getMethod() == -1) {
271: currentEntry.setMethod(compressMethod);
272: }
273: writeLong(out, LOCSIG); // Entry header
274: writeShort(out, ZIPLocalHeaderVersionNeeded); // Extraction version
275: writeShort(out, currentEntry.getMethod() == STORED ? 0
276: : ZIPDataDescriptorFlag);
277: writeShort(out, currentEntry.getMethod());
278: if (currentEntry.getTime() == -1) {
279: currentEntry.setTime(System.currentTimeMillis());
280: }
281: writeShort(out, currentEntry.time);
282: writeShort(out, currentEntry.modDate);
283:
284: if (currentEntry.getMethod() == STORED) {
285: if (currentEntry.size == -1) {
286: currentEntry.size = currentEntry.compressedSize;
287: } else if (currentEntry.compressedSize == -1) {
288: currentEntry.compressedSize = currentEntry.size;
289: }
290: writeLong(out, currentEntry.crc);
291: writeLong(out, currentEntry.size);
292: writeLong(out, currentEntry.size);
293: } else {
294: writeLong(out, 0);
295: writeLong(out, 0);
296: writeLong(out, 0);
297: }
298: writeShort(out, nameLength);
299: if (currentEntry.extra != null) {
300: writeShort(out, currentEntry.extra.length);
301: } else {
302: writeShort(out, 0);
303: }
304: nameBytes = toUTF8Bytes(currentEntry.name, nameLength);
305: out.write(nameBytes);
306: if (currentEntry.extra != null) {
307: out.write(currentEntry.extra);
308: }
309: }
310:
311: /**
312: * Sets the ZipFile comment associated with the file being written.
313: *
314: * @param comment
315: * the file comment
316: */
317: public void setComment(String comment) {
318: if (comment.length() > 0xFFFF) {
319: throw new IllegalArgumentException(Messages
320: .getString("archive.2B")); //$NON-NLS-1$
321: }
322: this .comment = comment;
323: }
324:
325: /**
326: * Sets the compression level to be used for writing entry data. This level
327: * may be set on a per entry basis.
328: *
329: * @param level
330: * the compression level, must have a value between 0 and 10.
331: */
332: public void setLevel(int level) {
333: if (level < Deflater.DEFAULT_COMPRESSION
334: || level > Deflater.BEST_COMPRESSION) {
335: throw new IllegalArgumentException();
336: }
337: compressLevel = level;
338: }
339:
340: /**
341: * Sets the compression method to be used when compressing entry data.
342: * method must be one of STORED or DEFLATED.
343: *
344: * @param method
345: * Compression method to use
346: */
347: public void setMethod(int method) {
348: if (method != STORED && method != DEFLATED) {
349: throw new IllegalArgumentException();
350: }
351: compressMethod = method;
352:
353: }
354:
355: private long writeLong(OutputStream os, long i)
356: throws java.io.IOException {
357: // Write out the long value as an unsigned int
358: os.write((int) (i & 0xFF));
359: os.write((int) (i >> 8) & 0xFF);
360: os.write((int) (i >> 16) & 0xFF);
361: os.write((int) (i >> 24) & 0xFF);
362: return i;
363: }
364:
365: private int writeShort(OutputStream os, int i)
366: throws java.io.IOException {
367: os.write(i & 0xFF);
368: os.write((i >> 8) & 0xFF);
369: return i;
370:
371: }
372:
373: /**
374: * Writes data for the current entry to the underlying stream.
375: *
376: * @exception IOException
377: * If an error occurs writing to the stream
378: */
379: @Override
380: public void write(byte[] buffer, int off, int nbytes)
381: throws java.io.IOException {
382: // avoid int overflow, check null buf
383: if ((off < 0 || (nbytes < 0) || off > buffer.length)
384: || (buffer.length - off < nbytes)) {
385: throw new IndexOutOfBoundsException();
386: }
387:
388: if (currentEntry == null) {
389: /* [MSG "archive.2C", "No active entry"] */
390: throw new ZipException(Messages.getString("archive.2C")); //$NON-NLS-1$
391: }
392:
393: if (currentEntry.getMethod() == STORED) {
394: out.write(buffer, off, nbytes);
395: } else {
396: super .write(buffer, off, nbytes);
397: }
398: crc.update(buffer, off, nbytes);
399: }
400:
401: static int utf8Count(String value) {
402: int total = 0;
403: for (int i = value.length(); --i >= 0;) {
404: char ch = value.charAt(i);
405: if (ch < 0x80) {
406: total++;
407: } else if (ch < 0x800) {
408: total += 2;
409: } else {
410: total += 3;
411: }
412: }
413: return total;
414: }
415:
416: static byte[] toUTF8Bytes(String value, int length) {
417: byte[] result = new byte[length];
418: int pos = result.length;
419: for (int i = value.length(); --i >= 0;) {
420: char ch = value.charAt(i);
421: if (ch < 0x80) {
422: result[--pos] = (byte) ch;
423: } else if (ch < 0x800) {
424: result[--pos] = (byte) (0x80 | (ch & 0x3f));
425: result[--pos] = (byte) (0xc0 | (ch >> 6));
426: } else {
427: result[--pos] = (byte) (0x80 | (ch & 0x3f));
428: result[--pos] = (byte) (0x80 | ((ch >> 6) & 0x3f));
429: result[--pos] = (byte) (0xe0 | (ch >> 12));
430: }
431: }
432: return result;
433: }
434: }
|