1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
/* * @(#)JarOutputStream.java 1.24 07/01/09 * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.util.jar; import java.util.zip.*; import java.io.*; /** * The <code>JarOutputStream</code> class is used to write the contents * of a JAR file to any output stream. It extends the class * <code>java.util.zip.ZipOutputStream</code> with support * for writing an optional <code>Manifest</code> entry. The * <code>Manifest</code> can be used to specify meta-information about * the JAR file and its entries. * * @author David Connelly * @version 1.24, 01/09/07 * @see Manifest * @see java.util.zip.ZipOutputStream * @since 1.2 */ public class JarOutputStream extends ZipOutputStream { private static final int JAR_MAGIC = 0xCAFE; /** * Creates a new <code>JarOutputStream</code> with the specified * <code>Manifest</code>. The manifest is written as the first * entry to the output stream. * * @param out the actual output stream * @param man the optional <code>Manifest</code> * @exception IOException if an I/O error has occurred */ public JarOutputStream(OutputStream out, Manifest man) throws IOException { super(out); if (man == null) { throw new NullPointerException("man"); } ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME); putNextEntry(e); man.write(new BufferedOutputStream(this)); closeEntry(); } /** * Creates a new <code>JarOutputStream</code> with no manifest. * @param out the actual output stream * @exception IOException if an I/O error has occurred */ public JarOutputStream(OutputStream out) throws IOException { super(out); } /** * Begins writing a new JAR file entry and positions the stream * to the start of the entry data. This method will also close * any previous entry. The default compression method will be * used if no compression method was specified for the entry. * The current time will be used if the entry has no set modification * time. * * @param ze the ZIP/JAR entry to be written * @exception ZipException if a ZIP error has occurred * @exception IOException if an I/O error has occurred */ public void putNextEntry(ZipEntry ze) throws IOException { if (firstEntry) { // Make sure that extra field data for first JAR // entry includes JAR magic number id. byte[] edata = ze.getExtra(); if (edata == null || !hasMagic(edata)) { if (edata == null) { edata = new byte[4]; } else { // Prepend magic to existing extra data byte[] tmp = new byte[edata.length + 4]; System.arraycopy(edata, 0, tmp, 4, edata.length); edata = tmp; } set16(edata, 0, JAR_MAGIC); // extra field id set16(edata, 2, 0); // extra field size ze.setExtra(edata); } firstEntry = false; } super.putNextEntry(ze); } private boolean firstEntry = true; /* * Returns true if specified byte array contains the * jar magic extra field id. */ private static boolean hasMagic(byte[] edata) { try { int i = 0; while (i < edata.length) { if (get16(edata, i) == JAR_MAGIC) { return true; } i += get16(edata, i + 2) + 4; } } catch (ArrayIndexOutOfBoundsException e) { // Invalid extra field data } return false; } /* * Fetches unsigned 16-bit value from byte array at specified offset. * The bytes are assumed to be in Intel (little-endian) byte order. */ private static int get16(byte[] b, int off) { return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8); } /* * Sets 16-bit value at specified offset. The bytes are assumed to * be in Intel (little-endian) byte order. */ private static void set16(byte[] b, int off, int value) { b[off+0] = (byte)value; b[off+1] = (byte)(value >> 8); } }