001: /*
002: * @(#)JarOutputStream.java 1.26 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program 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 version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.util.jar;
029:
030: import java.util.zip.*;
031: import java.io.*;
032:
033: /**
034: * The <code>JarOutputStream</code> class is used to write the contents
035: * of a JAR file to any output stream. It extends the class
036: * <code>java.util.zip.ZipOutputStream</code> with support
037: * for writing an optional <code>Manifest</code> entry. The
038: * <code>Manifest</code> can be used to specify meta-information about
039: * the JAR file and its entries.
040: *
041: * @author David Connelly
042: * @version 1.19, 02/02/00
043: * @see Manifest
044: * @see java.util.zip.ZipOutputStream
045: * @since 1.2
046: */
047: public class JarOutputStream extends ZipOutputStream {
048: private static final int JAR_MAGIC = 0xCAFE;
049:
050: /**
051: * Creates a new <code>JarOutputStream</code> with the specified
052: * <code>Manifest</code>. The manifest is written as the first
053: * entry to the output stream.
054: *
055: * @param out the actual output stream
056: * @param man the optional <code>Manifest</code>
057: * @exception IOException if an I/O error has occurred
058: */
059: public JarOutputStream(OutputStream out, Manifest man)
060: throws IOException {
061: super (out);
062: if (man == null) {
063: throw new NullPointerException("man");
064: }
065: ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
066: putNextEntry(e);
067: man.write(new BufferedOutputStream(this ));
068: closeEntry();
069: }
070:
071: /**
072: * Creates a new <code>JarOutputStream</code> with no manifest.
073: * @param out the actual output stream
074: * @exception IOException if an I/O error has occurred
075: */
076: public JarOutputStream(OutputStream out) throws IOException {
077: super (out);
078: }
079:
080: /**
081: * Begins writing a new JAR file entry and positions the stream
082: * to the start of the entry data. This method will also close
083: * any previous entry. The default compression method will be
084: * used if no compression method was specified for the entry.
085: * The current time will be used if the entry has no set modification
086: * time.
087: *
088: * @param ze the ZIP/JAR entry to be written
089: * @exception ZipException if a ZIP error has occurred
090: * @exception IOException if an I/O error has occurred
091: */
092: public void putNextEntry(ZipEntry ze) throws IOException {
093: if (firstEntry) {
094: // Make sure that extra field data for first JAR
095: // entry includes JAR magic number id.
096: byte[] edata = ze.getExtra();
097: if (edata != null && !hasMagic(edata)) {
098: // Prepend magic to existing extra data
099: byte[] tmp = new byte[edata.length + 4];
100: System.arraycopy(tmp, 4, edata, 0, edata.length);
101: edata = tmp;
102: } else {
103: edata = new byte[4];
104: }
105: set16(edata, 0, JAR_MAGIC); // extra field id
106: set16(edata, 2, 0); // extra field size
107: ze.setExtra(edata);
108: firstEntry = false;
109: }
110: super .putNextEntry(ze);
111: }
112:
113: private boolean firstEntry = true;
114:
115: /*
116: * Returns true if specified byte array contains the
117: * jar magic extra field id.
118: */
119: private static boolean hasMagic(byte[] edata) {
120: try {
121: int i = 0;
122: while (i < edata.length) {
123: if (get16(edata, i) == JAR_MAGIC) {
124: return true;
125: }
126: i += get16(edata, i + 2) + 4;
127: }
128: } catch (ArrayIndexOutOfBoundsException e) {
129: // Invalid extra field data
130: }
131: return false;
132: }
133:
134: /*
135: * Fetches unsigned 16-bit value from byte array at specified offset.
136: * The bytes are assumed to be in little-endian byte order.
137: */
138: private static int get16(byte[] b, int off) {
139: return (b[off] & 0xff) | ((b[off + 1] & 0xff) << 8);
140: }
141:
142: /*
143: * Sets 16-bit value at specified offset. The bytes are assumed to
144: * be in little-endian byte order.
145: */
146: private static void set16(byte[] b, int off, int value) {
147: b[off + 0] = (byte) value;
148: b[off + 1] = (byte) (value >> 8);
149: }
150: }
|