/* Copyright (C) 1999 Cygnus Solutions This file is part of libgcj. This software is copyrighted work licensed under the terms of the Libgcj License. Please consult the file "LIBGCJ_LICENSE" for details. */ package java.util.zip; import java.io.*; /* Written using on-line Java Platform 1.2 API Specification * and JCL book. * Believed complete and correct. */ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { public static final int STORED = 0; public static final int DEFLATED = 8; public void close () throws IOException { finish (); out.close(); } public void closeEntry () throws IOException { int uncompressed_size = def.getTotalIn(); int compressed_size = def.getTotalOut(); int crc = (int) (filter.getChecksum().getValue()); bytes_written += compressed_size; bytes_written += put4 (0x08074b50); if (current.getCrc() == -1 || current.getCompressedSize() == -1 || current.getSize() == -1) { current.setCrc(crc); current.compressedSize = compressed_size; current.setSize(uncompressed_size); } else { if (current.getCrc() != crc || current.getCompressedSize() != compressed_size || current.getSize() != uncompressed_size) throw new ZipException ("zip entry field incorrect"); } bytes_written += put4 ((int) (current.getCrc())); bytes_written += put4 ((int) (current.getCompressedSize())); bytes_written += put4 ((int) (current.getSize())); current.next = chain; chain = current; current = null; filter = null; } public void finish () throws IOException { if (current != null) closeEntry (); // Write the central directory. long offset = bytes_written; int count = 0; int bytes = 0; while (chain != null) { bytes += write_entry (chain, false); ++count; chain = chain.next; } // Write the end of the central directory record. put4 (0x06054b50); // Disk number. put2 (0); // Another disk number. put2 (0); put2 (count); put4 (bytes); put4 ((int) offset); byte[] c = comment.getBytes("8859_1"); put2 (c.length); out.write(c); out.write((byte) 0); } // Helper for finish and putNextEntry. private int write_entry (ZipEntry entry, boolean is_local) throws IOException { long offset = bytes_written; int bytes = put4 (is_local ? 0x04034b50 : 0x02014b50); if (! is_local) bytes += put_version (); bytes += put_version (); boolean crc_after = false; if (is_local && (current.getCrc() == -1 || current.getCompressedSize() == -1 || current.getSize() == -1)) crc_after = true; // For the bits field we always indicate `normal' compression, // even if that isn't true. bytes += put2 (crc_after ? (1 << 3) : 0); bytes += put2 (entry.method); bytes += put2(0); // time - FIXME bytes += put2(0); // date - FIXME if (crc_after) { // CRC, compressedSize, and Size are always 0 in this header. // The actual values are given after the entry. bytes += put4 (0); bytes += put4 (0); bytes += put4 (0); } else { bytes += put4 ((int) (entry.getCrc())); bytes += put4 ((int) (entry.getCompressedSize())); bytes += put4 ((int) (entry.getSize())); } byte[] name = entry.name.getBytes("8859_1"); bytes += put2 (name.length); bytes += put2 (entry.extra == null ? 0 : entry.extra.length); byte[] comment = null; if (! is_local) { if (entry.getComment() == null) bytes += put2 (0); else { comment = entry.getComment().getBytes("8859_1"); bytes += put2 (comment.length); } // Disk number start. bytes += put2 (0); // Internal file attributes. bytes += put2 (0); // External file attributes. bytes += put2 (0); // Relative offset of local header. bytes += put2 ((int) offset); } out.write (name); out.write ((byte) 0); bytes += name.length + 1; if (entry.extra != null) { out.write(entry.extra); out.write((byte) 0); bytes += entry.extra.length + 1; } if (comment != null) { out.write(comment); out.write((byte) 0); bytes += comment.length + 1; } bytes_written += bytes; return bytes; } public void putNextEntry (ZipEntry entry) throws IOException { if (current != null) closeEntry (); if (entry.method < 0 ) entry.method = method; if (entry.method == STORED) { if (entry.getSize() == -1 || entry.getCrc() == -1) throw new ZipException ("required entry not set"); // Just in case. entry.compressedSize = entry.getSize(); } write_entry (entry, true); current = entry; int compr = (method == STORED) ? Deflater.NO_COMPRESSION : level; def.reset(); def.setLevel(compr); filter = new CheckedOutputStream (new DeflaterOutputStream (out, def), new CRC32 ()); } public void setLevel (int level) { if (level != Deflater.DEFAULT_COMPRESSION && (level < Deflater.NO_COMPRESSION || level > Deflater.BEST_COMPRESSION)) throw new IllegalArgumentException (); this.level = level; } public void setMethod (int method) { if (method != DEFLATED && method != STORED) throw new IllegalArgumentException (); this.method = method; } public void setComment (String comment) { if (comment.length() > 65535) throw new IllegalArgumentException (); this.comment = comment; } public synchronized void write (byte[] buf, int off, int len) throws IOException { if (filter == null) throw new ZipException ("no open zip entry"); filter.write(buf, off, len); } public ZipOutputStream (OutputStream out) { super (out); def = new Deflater (level, true); } private int put2 (int i) throws IOException { out.write (i); out.write (i >> 8); return 2; } private int put4 (int i) throws IOException { out.write (i); out.write (i >> 8); out.write (i >> 16); out.write (i >> 24); return 4; } private int put_version () throws IOException { // FIXME: for now we assume Unix, and we ignore the version // number. return put2 (3 << 8); } // The entry we are currently writing, or null if we've called // closeEntry. private ZipEntry current; // The chain of entries which have been written to this file. private ZipEntry chain; // The output stream to which data should be sent. private CheckedOutputStream filter; private int method = DEFLATED; private int level = Deflater.DEFAULT_COMPRESSION; private String comment = ""; private long bytes_written; // The Deflater we use. private Deflater def; }