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