001: /*
002: * @(#)UncompressInputStream.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.IOException;
032: import java.io.EOFException;
033: import java.io.InputStream;
034: import java.io.FileInputStream;
035: import java.io.FilterInputStream;
036:
037: /**
038: * This class decompresses an input stream containing data compressed with
039: * the unix "compress" utility (LZC, a LZW variant). This code is based
040: * heavily on the <var>unlzw.c</var> code in <var>gzip-1.2.4</var> (written
041: * by Peter Jannesen) and the original compress code.
042: *
043: * @version 0.3-2 18/06/1999
044: * @author Ronald Tschalär
045: */
046: class UncompressInputStream extends FilterInputStream {
047: /**
048: * @param is the input stream to decompress
049: * @exception IOException if the header is malformed
050: */
051: public UncompressInputStream(InputStream is) throws IOException {
052: super (is);
053: parse_header();
054: }
055:
056: byte[] one = new byte[1];
057:
058: public synchronized int read() throws IOException {
059: int b = in.read(one, 0, 1);
060: if (b == 1)
061: return (one[0] & 0xff);
062: else
063: return -1;
064: }
065:
066: // string table stuff
067: private static final int TBL_CLEAR = 0x100;
068: private static final int TBL_FIRST = TBL_CLEAR + 1;
069:
070: private int[] tab_prefix;
071: private byte[] tab_suffix;
072: private int[] zeros = new int[256];
073: private byte[] stack;
074:
075: // various state
076: private boolean block_mode;
077: private int n_bits;
078: private int maxbits;
079: private int maxmaxcode;
080: private int maxcode;
081: private int bitmask;
082: private int oldcode;
083: private byte finchar;
084: private int stackp;
085: private int free_ent;
086:
087: // input buffer
088: private byte[] data = new byte[10000];
089: private int bit_pos = 0, end = 0, got = 0;
090: private boolean eof = false;
091: private static final int EXTRA = 64;
092:
093: public synchronized int read(byte[] buf, int off, int len)
094: throws IOException {
095: if (eof)
096: return -1;
097: int start = off;
098:
099: /* Using local copies of various variables speeds things up by as
100: * much as 30% !
101: */
102: int[] l_tab_prefix = tab_prefix;
103: byte[] l_tab_suffix = tab_suffix;
104: byte[] l_stack = stack;
105: int l_n_bits = n_bits;
106: int l_maxcode = maxcode;
107: int l_maxmaxcode = maxmaxcode;
108: int l_bitmask = bitmask;
109: int l_oldcode = oldcode;
110: byte l_finchar = finchar;
111: int l_stackp = stackp;
112: int l_free_ent = free_ent;
113: byte[] l_data = data;
114: int l_bit_pos = bit_pos;
115:
116: // empty stack if stuff still left
117:
118: int s_size = l_stack.length - l_stackp;
119: if (s_size > 0) {
120: int num = (s_size >= len) ? len : s_size;
121: System.arraycopy(l_stack, l_stackp, buf, off, num);
122: off += num;
123: len -= num;
124: l_stackp += num;
125: }
126:
127: if (len == 0) {
128: stackp = l_stackp;
129: return off - start;
130: }
131:
132: // loop, filling local buffer until enough data has been decompressed
133:
134: main_loop: do {
135: if (end < EXTRA)
136: fill();
137:
138: int bit_in = (got > 0) ? (end - end % l_n_bits) << 3
139: : (end << 3) - (l_n_bits - 1);
140:
141: while (l_bit_pos < bit_in) {
142: // check for code-width expansion
143:
144: if (l_free_ent > l_maxcode) {
145: int n_bytes = l_n_bits << 3;
146: l_bit_pos = (l_bit_pos - 1) + n_bytes
147: - (l_bit_pos - 1 + n_bytes) % n_bytes;
148:
149: l_n_bits++;
150: l_maxcode = (l_n_bits == maxbits) ? l_maxmaxcode
151: : (1 << l_n_bits) - 1;
152:
153: if (debug)
154: System.err.println("Code-width expanded to "
155: + l_n_bits);
156:
157: l_bitmask = (1 << l_n_bits) - 1;
158: l_bit_pos = resetbuf(l_bit_pos);
159: continue main_loop;
160: }
161:
162: // read next code
163:
164: int pos = l_bit_pos >> 3;
165: int code = (((l_data[pos] & 0xFF)
166: | ((l_data[pos + 1] & 0xFF) << 8) | ((l_data[pos + 2] & 0xFF) << 16)) >> (l_bit_pos & 0x7))
167: & l_bitmask;
168: l_bit_pos += l_n_bits;
169:
170: // handle first iteration
171:
172: if (l_oldcode == -1) {
173: if (code >= 256)
174: throw new IOException("corrupt input: " + code
175: + " > 255");
176: l_finchar = (byte) (l_oldcode = code);
177: buf[off++] = l_finchar;
178: len--;
179: continue;
180: }
181:
182: // handle CLEAR code
183:
184: if (code == TBL_CLEAR && block_mode) {
185: System.arraycopy(zeros, 0, l_tab_prefix, 0,
186: zeros.length);
187: l_free_ent = TBL_FIRST - 1;
188:
189: int n_bytes = l_n_bits << 3;
190: l_bit_pos = (l_bit_pos - 1) + n_bytes
191: - (l_bit_pos - 1 + n_bytes) % n_bytes;
192: l_n_bits = INIT_BITS;
193: l_maxcode = (1 << l_n_bits) - 1;
194: l_bitmask = l_maxcode;
195:
196: if (debug)
197: System.err.println("Code tables reset");
198:
199: l_bit_pos = resetbuf(l_bit_pos);
200: continue main_loop;
201: }
202:
203: // setup
204:
205: int incode = code;
206: l_stackp = l_stack.length;
207:
208: // Handle KwK case
209:
210: if (code >= l_free_ent) {
211: if (code > l_free_ent)
212: throw new IOException("corrupt input: code="
213: + code + ", free_ent=" + l_free_ent);
214:
215: l_stack[--l_stackp] = l_finchar;
216: code = l_oldcode;
217: }
218:
219: // Generate output characters in reverse order
220:
221: while (code >= 256) {
222: l_stack[--l_stackp] = l_tab_suffix[code];
223: code = l_tab_prefix[code];
224: }
225: l_finchar = l_tab_suffix[code];
226: buf[off++] = l_finchar;
227: len--;
228:
229: // And put them out in forward order
230:
231: s_size = l_stack.length - l_stackp;
232: int num = (s_size >= len) ? len : s_size;
233: System.arraycopy(l_stack, l_stackp, buf, off, num);
234: off += num;
235: len -= num;
236: l_stackp += num;
237:
238: // generate new entry in table
239:
240: if (l_free_ent < l_maxmaxcode) {
241: l_tab_prefix[l_free_ent] = l_oldcode;
242: l_tab_suffix[l_free_ent] = l_finchar;
243: l_free_ent++;
244: }
245:
246: // Remember previous code
247:
248: l_oldcode = incode;
249:
250: // if output buffer full, then return
251:
252: if (len == 0) {
253: n_bits = l_n_bits;
254: maxcode = l_maxcode;
255: bitmask = l_bitmask;
256: oldcode = l_oldcode;
257: finchar = l_finchar;
258: stackp = l_stackp;
259: free_ent = l_free_ent;
260: bit_pos = l_bit_pos;
261:
262: return off - start;
263: }
264: }
265:
266: l_bit_pos = resetbuf(l_bit_pos);
267: } while (got > 0);
268:
269: n_bits = l_n_bits;
270: maxcode = l_maxcode;
271: bitmask = l_bitmask;
272: oldcode = l_oldcode;
273: finchar = l_finchar;
274: stackp = l_stackp;
275: free_ent = l_free_ent;
276: bit_pos = l_bit_pos;
277:
278: eof = true;
279: return off - start;
280: }
281:
282: /**
283: * Moves the unread data in the buffer to the beginning and resets
284: * the pointers.
285: */
286: private final int resetbuf(int bit_pos) {
287: int pos = bit_pos >> 3;
288: System.arraycopy(data, pos, data, 0, end - pos);
289: end -= pos;
290: return 0;
291: }
292:
293: private final void fill() throws IOException {
294: got = in.read(data, end, data.length - 1 - end);
295: if (got > 0)
296: end += got;
297: }
298:
299: public synchronized long skip(long num) throws IOException {
300: byte[] tmp = new byte[(int) num];
301: int got = read(tmp, 0, (int) num);
302:
303: if (got > 0)
304: return (long) got;
305: else
306: return 0L;
307: }
308:
309: public synchronized int available() throws IOException {
310: if (eof)
311: return 0;
312:
313: return in.available();
314: }
315:
316: private static final int LZW_MAGIC = 0x1f9d;
317: private static final int MAX_BITS = 16;
318: private static final int INIT_BITS = 9;
319: private static final int HDR_MAXBITS = 0x1f;
320: private static final int HDR_EXTENDED = 0x20;
321: private static final int HDR_FREE = 0x40;
322: private static final int HDR_BLOCK_MODE = 0x80;
323:
324: private void parse_header() throws IOException {
325: // read in and check magic number
326:
327: int t = in.read();
328: if (t < 0)
329: throw new EOFException("Failed to read magic number");
330: int magic = (t & 0xff) << 8;
331: t = in.read();
332: if (t < 0)
333: throw new EOFException("Failed to read magic number");
334: magic += t & 0xff;
335: if (magic != LZW_MAGIC)
336: throw new IOException("Input not in compress format (read "
337: + "magic number 0x" + Integer.toHexString(magic)
338: + ")");
339:
340: // read in header byte
341:
342: int header = in.read();
343: if (header < 0)
344: throw new EOFException("Failed to read header");
345:
346: block_mode = (header & HDR_BLOCK_MODE) > 0;
347: maxbits = header & HDR_MAXBITS;
348:
349: if (maxbits > MAX_BITS)
350: throw new IOException("Stream compressed with " + maxbits
351: + " bits, but can only handle " + MAX_BITS
352: + " bits");
353:
354: if ((header & HDR_EXTENDED) > 0)
355: throw new IOException("Header extension bit set");
356:
357: if ((header & HDR_FREE) > 0)
358: throw new IOException("Header bit 6 set");
359:
360: if (debug) {
361: System.err.println("block mode: " + block_mode);
362: System.err.println("max bits: " + maxbits);
363: }
364:
365: // initialize stuff
366:
367: maxmaxcode = 1 << maxbits;
368: n_bits = INIT_BITS;
369: maxcode = (1 << n_bits) - 1;
370: bitmask = maxcode;
371: oldcode = -1;
372: finchar = 0;
373: free_ent = block_mode ? TBL_FIRST : 256;
374:
375: tab_prefix = new int[1 << maxbits];
376: tab_suffix = new byte[1 << maxbits];
377: stack = new byte[1 << maxbits];
378: stackp = stack.length;
379:
380: for (int idx = 255; idx >= 0; idx--)
381: tab_suffix[idx] = (byte) idx;
382: }
383:
384: private static final boolean debug = false;
385:
386: public static void main(String args[]) throws Exception {
387: if (args.length != 1) {
388: System.err.println("Usage: UncompressInputStream <file>");
389: System.exit(1);
390: }
391:
392: InputStream in = new UncompressInputStream(new FileInputStream(
393: args[0]));
394:
395: byte[] buf = new byte[100000];
396: int tot = 0;
397: long beg = System.currentTimeMillis();
398:
399: while (true) {
400: int got = in.read(buf);
401: if (got < 0)
402: break;
403: System.out.write(buf, 0, got);
404: tot += got;
405: }
406:
407: long end = System.currentTimeMillis();
408: System.err.println("Decompressed " + tot + " bytes");
409: System.err.println("Time: " + (end - beg) / 1000. + " seconds");
410: }
411: }
|