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}