1 /*
2 * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package java.util.zip;
27
28 import java.io.FilterOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31
32 /**
33 * Implements an output stream filter for uncompressing data stored in the
34 * "deflate" compression format.
35 *
36 * @since 1.6
37 * @author David R Tribble (david@tribble.com)
38 *
39 * @see InflaterInputStream
40 * @see DeflaterInputStream
41 * @see DeflaterOutputStream
42 */
43
44 public class InflaterOutputStream extends FilterOutputStream {
45 /** Decompressor for this stream. */
46 protected final Inflater inf;
47
48 /** Output buffer for writing uncompressed data. */
49 protected final byte[] buf;
50
51 /** Temporary write buffer. */
52 private final byte[] wbuf = new byte[1];
53
54 /** Default decompressor is used. */
55 private boolean usesDefaultInflater = false;
56
57 /** true iff {@link #close()} has been called. */
58 private boolean closed = false;
59
60 /**
61 * Checks to make sure that this stream has not been closed.
62 */
63 private void ensureOpen() throws IOException {
64 if (closed) {
65 throw new IOException("Stream closed");
66 }
67 }
68
69 /**
70 * Creates a new output stream with a default decompressor and buffer
71 * size.
72 *
73 * @param out output stream to write the uncompressed data to
74 * @throws NullPointerException if {@code out} is null
75 */
76 public InflaterOutputStream(OutputStream out) {
77 this(out, new Inflater());
78 usesDefaultInflater = true;
79 }
80
81 /**
82 * Creates a new output stream with the specified decompressor and a
83 * default buffer size.
84 *
85 * @param out output stream to write the uncompressed data to
86 * @param infl decompressor ("inflater") for this stream
87 * @throws NullPointerException if {@code out} or {@code infl} is null
88 */
89 public InflaterOutputStream(OutputStream out, Inflater infl) {
90 this(out, infl, 512);
91 }
92
93 /**
94 * Creates a new output stream with the specified decompressor and
95 * buffer size.
96 *
97 * @param out output stream to write the uncompressed data to
98 * @param infl decompressor ("inflater") for this stream
99 * @param bufLen decompression buffer size
100 * @throws IllegalArgumentException if {@code bufLen} is <= 0
101 * @throws NullPointerException if {@code out} or {@code infl} is null
102 */
103 public InflaterOutputStream(OutputStream out, Inflater infl, int bufLen) {
104 super(out);
105
106 // Sanity checks
107 if (out == null)
108 throw new NullPointerException("Null output");
109 if (infl == null)
110 throw new NullPointerException("Null inflater");
111 if (bufLen <= 0)
112 throw new IllegalArgumentException("Buffer size < 1");
113
114 // Initialize
115 inf = infl;
116 buf = new byte[bufLen];
117 }
118
119 /**
120 * Writes any remaining uncompressed data to the output stream and closes
121 * the underlying output stream.
122 *
123 * @throws IOException if an I/O error occurs
124 */
125 public void close() throws IOException {
126 if (!closed) {
127 // Complete the uncompressed output
128 try {
129 finish();
130 } finally {
131 out.close();
132 closed = true;
133 }
134 }
135 }
136
137 /**
138 * Flushes this output stream, forcing any pending buffered output bytes to be
139 * written.
140 *
141 * @throws IOException if an I/O error occurs or this stream is already
142 * closed
143 */
144 public void flush() throws IOException {
145 ensureOpen();
146
147 // Finish decompressing and writing pending output data
148 if (!inf.finished()) {
149 try {
150 while (!inf.finished() && !inf.needsInput()) {
151 int n;
152
153 // Decompress pending output data
154 n = inf.inflate(buf, 0, buf.length);
155 if (n < 1) {
156 break;
157 }
158
159 // Write the uncompressed output data block
160 out.write(buf, 0, n);
161 }
162 super.flush();
163 } catch (DataFormatException ex) {
164 // Improperly formatted compressed (ZIP) data
165 String msg = ex.getMessage();
166 if (msg == null) {
167 msg = "Invalid ZLIB data format";
168 }
169 throw new ZipException(msg);
170 }
171 }
172 }
173
174 /**
175 * Finishes writing uncompressed data to the output stream without closing
176 * the underlying stream. Use this method when applying multiple filters in
177 * succession to the same output stream.
178 *
179 * @throws IOException if an I/O error occurs or this stream is already
180 * closed
181 */
182 public void finish() throws IOException {
183 ensureOpen();
184
185 // Finish decompressing and writing pending output data
186 flush();
187 if (usesDefaultInflater) {
188 inf.end();
189 }
190 }
191
192 /**
193 * Writes a byte to the uncompressed output stream.
194 *
195 * @param b a single byte of compressed data to decompress and write to
196 * the output stream
197 * @throws IOException if an I/O error occurs or this stream is already
198 * closed
199 * @throws ZipException if a compression (ZIP) format error occurs
200 */
201 public void write(int b) throws IOException {
202 // Write a single byte of data
203 wbuf[0] = (byte) b;
204 write(wbuf, 0, 1);
205 }
206
207 /**
208 * Writes an array of bytes to the uncompressed output stream.
209 *
210 * @param b buffer containing compressed data to decompress and write to
211 * the output stream
212 * @param off starting offset of the compressed data within {@code b}
213 * @param len number of bytes to decompress from {@code b}
214 * @throws IndexOutOfBoundsException if {@code off} < 0, or if
215 * {@code len} < 0, or if {@code len} > {@code b.length - off}
216 * @throws IOException if an I/O error occurs or this stream is already
217 * closed
218 * @throws NullPointerException if {@code b} is null
219 * @throws ZipException if a compression (ZIP) format error occurs
220 */
221 public void write(byte[] b, int off, int len) throws IOException {
222 // Sanity checks
223 ensureOpen();
224 if (b == null) {
225 throw new NullPointerException("Null buffer for read");
226 } else if (off < 0 || len < 0 || len > b.length - off) {
227 throw new IndexOutOfBoundsException();
228 } else if (len == 0) {
229 return;
230 }
231
232 // Write uncompressed data to the output stream
233 try {
234 for (;;) {
235 int n;
236
237 // Fill the decompressor buffer with output data
238 if (inf.needsInput()) {
239 int part;
240
241 if (len < 1) {
242 break;
243 }
244
245 part = (len < 512 ? len : 512);
246 inf.setInput(b, off, part);
247 off += part;
248 len -= part;
249 }
250
251 // Decompress and write blocks of output data
252 do {
253 n = inf.inflate(buf, 0, buf.length);
254 if (n > 0) {
255 out.write(buf, 0, n);
256 }
257 } while (n > 0);
258
259 // Check the decompressor
260 if (inf.finished()) {
261 break;
262 }
263 if (inf.needsDictionary()) {
264 throw new ZipException("ZLIB dictionary missing");
265 }
266 }
267 } catch (DataFormatException ex) {
268 // Improperly formatted compressed (ZIP) data
269 String msg = ex.getMessage();
270 if (msg == null) {
271 msg = "Invalid ZLIB data format";
272 }
273 throw new ZipException(msg);
274 }
275 }
276 }