001/* 002 * $Id: JarOutputStream.java,v 1.3 2005/08/29 03:18:21 jponge Exp $ 003 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 004 * 005 * http://www.izforge.com/izpack/ 006 * http://developer.berlios.de/projects/izpack/ 007 * 008 * Copyright 2005 Klaus Bartz 009 * 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 */ 022 023package com.izforge.izpack.util; 024 025import java.io.BufferedOutputStream; 026import java.io.File; 027import java.io.IOException; 028import java.io.OutputStream; 029import java.util.jar.JarFile; 030import java.util.jar.Manifest; 031//import java.util.zip.ZipException; 032 033//The declarations for ZipOutputStreams will be done 034//as full qualified to clear at the use point that 035//we do not use the standard class else the extended 036//from apache. 037//import org.apache.tools.zip.ZipOutputStream; 038//import org.apache.tools.zip.ZipEntry; 039 040/** 041 * IzPack will be able to support different compression methods for the 042 * packs included in the installation jar file. 043 * For this a jar output stream will be needed with which the info 044 * data (size, CRC) can be written after the compressed data. 045 * This is not possible with the standard class 046 * java.util.jar.JarOutputStream. Therefore we create an own class 047 * which supports it. Really the hole work will be delegated to the 048 * ZipOutputStream from the apache team which solves the problem. 049 * 050 * @author Klaus Bartz 051 */ 052public class JarOutputStream extends org.apache.tools.zip.ZipOutputStream 053{ 054 private static final int JAR_MAGIC = 0xCAFE; 055 private boolean firstEntry = true; 056 private boolean preventClose = false; 057 058 /** 059 * Creates a new <code>JarOutputStream</code> with no manifest. 060 * Using this constructor it will be NOT possible to write 061 * data with compression format STORED to the stream without 062 * declare the info data (size, CRC) at <code>putNextEntry</code>. 063 * @param out the actual output stream 064 * @exception IOException if an I/O error has occurred 065 */ 066 public JarOutputStream(OutputStream out) throws IOException 067 { 068 super(out); 069 } 070 071 /** 072 * Creates a new <code>JarOutputStream</code> with the specified 073 * <code>Manifest</code>. The manifest is written as the first 074 * entry to the output stream which will be created from the 075 * file argument. 076 * 077 * @param fout the file object with which the output stream 078 * should be created 079 * @param man the <code>Manifest</code> 080 * @exception IOException if an I/O error has occurred 081 */ 082 public JarOutputStream(File fout, Manifest man) throws IOException 083 { 084 super( fout ); 085 if (man == null) 086 { 087 throw new NullPointerException("man"); 088 } 089 org.apache.tools.zip.ZipEntry e = 090 new org.apache.tools.zip.ZipEntry(JarFile.MANIFEST_NAME); 091 putNextEntry(e); 092 man.write(new BufferedOutputStream(this)); 093 closeEntry(); 094 } 095 /** 096 * Creates a new <code>JarOutputStream</code> with no manifest. 097 * Will use random access if possible. 098 * @param arg0 the file object with which the output stream 099 * should be created 100 * @throws java.io.IOException 101 */ 102 public JarOutputStream(File arg0) throws IOException 103 { 104 super(arg0); 105 } 106 /** 107 * Begins writing a new JAR file entry and positions the stream 108 * to the start of the entry data. This method will also close 109 * any previous entry. The default compression method will be 110 * used if no compression method was specified for the entry. 111 * The current time will be used if the entry has no set modification 112 * time. 113 * 114 * @param ze the ZIP/JAR entry to be written 115 * @exception ZipException if a ZIP error has occurred 116 * @exception IOException if an I/O error has occurred 117 */ 118 public void putNextEntry(org.apache.tools.zip.ZipEntry ze) throws IOException 119 { 120 if (firstEntry) 121 { 122 // Make sure that extra field data for first JAR 123 // entry includes JAR magic number id. 124 byte[] edata = ze.getExtra(); 125 if (edata != null && !hasMagic(edata)) 126 { 127 // Prepend magic to existing extra data 128 byte[] tmp = new byte[edata.length + 4]; 129 System.arraycopy(tmp, 4, edata, 0, edata.length); 130 edata = tmp; 131 } 132 else 133 { 134 edata = new byte[4]; 135 } 136 set16(edata, 0, JAR_MAGIC); // extra field id 137 set16(edata, 2, 0); // extra field size 138 ze.setExtra(edata); 139 firstEntry = false; 140 } 141 super.putNextEntry(ze); 142 } 143 144 /** 145 * @return Returns the preventClose. 146 */ 147 public boolean isPreventClose() 148 { 149 return preventClose; 150 } 151 /** 152 * Determine whether a call of the close method 153 * will be performed or not. This is a hack for 154 * FilterOutputStreams like the CBZip2OutputStream 155 * of apache which calls close of the slave via 156 * the final method which will be called from 157 * the garbage collector. 158 * @param preventClose The preventClose to set. 159 */ 160 public void setPreventClose(boolean preventClose) 161 { 162 this.preventClose = preventClose; 163 } 164 165 /** 166 * Closes this output stream and releases any system resources 167 * associated with the stream if isPreventClose is not true. 168 * Else nothing will be done. This is a hack for 169 * FilterOutputStreams like the CBZip2OutputStream which 170 * calls the close method of the slave at finalizing the class 171 * may be triggert by the GC. 172 * 173 * @exception IOException if an I/O error occurs. 174 */ 175 public void close() throws IOException 176 { 177 if( ! isPreventClose() ) 178 super.close(); 179 } 180 181 /** 182 * Closes this output stream and releases any system resources 183 * associated with the stream also isPreventClose is true. 184 * This is a hack for FilterOutputStreams like the CBZip2OutputStream which 185 * calls the close method of the slave at finalizing the class 186 * may be triggert by the GC. 187 * 188 * @exception IOException if an I/O error occurs. 189 */ 190 public void closeAlways() throws IOException 191 { 192 setPreventClose( false ); 193 close(); 194 } 195 196 /* 197 * Returns true if specified byte array contains the 198 * jar magic extra field id. 199 */ 200 private static boolean hasMagic(byte[] edata) 201 { 202 203 try 204 { 205 int i = 0; 206 while (i < edata.length) 207 { 208 if (get16(edata, i) == JAR_MAGIC) 209 { 210 return true; 211 } 212 i += get16(edata, i + 2) + 4; 213 } 214 } 215 catch (ArrayIndexOutOfBoundsException e) 216 { 217 // Invalid extra field data 218 } 219 return false; 220 } 221 222 /* 223 * Fetches unsigned 16-bit value from byte array at specified offset. 224 * The bytes are assumed to be in Intel (little-endian) byte order. 225 */ 226 private static int get16(byte[] b, int off) 227 { 228 return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8); 229 } 230 231 /* 232 * Sets 16-bit value at specified offset. The bytes are assumed to 233 * be in Intel (little-endian) byte order. 234 */ 235 private static void set16(byte[] b, int off, int value) 236 { 237 b[off+0] = (byte)value; 238 b[off+1] = (byte)(value >> 8); 239 } 240 241}