**** Merged from MCS ****
[mono-project.git] / mcs / class / ICSharpCode.SharpZipLib / ICSharpCode.SharpZipLib / Zip / Compression / DeflaterEngine.cs
blobc543f8509808875aa9c8618b128a3d6652d3ff8b
1 // DeflaterEngine.cs
2 // Copyright (C) 2001 Mike Krueger
3 //
4 // This file was translated from java, it was part of the GNU Classpath
5 // Copyright (C) 2001 Free Software Foundation, Inc.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 // Linking this library statically or dynamically with other modules is
22 // making a combined work based on this library. Thus, the terms and
23 // conditions of the GNU General Public License cover the whole
24 // combination.
25 //
26 // As a special exception, the copyright holders of this library give you
27 // permission to link this library with independent modules to produce an
28 // executable, regardless of the license terms of these independent
29 // modules, and to copy and distribute the resulting executable under
30 // terms of your choice, provided that you also meet, for each linked
31 // independent module, the terms and conditions of the license of that
32 // module. An independent module is a module which is not derived from
33 // or based on this library. If you modify this library, you may extend
34 // this exception to your version of the library, but you are not
35 // obligated to do so. If you do not wish to do so, delete this
36 // exception statement from your version.
38 using System;
40 using ICSharpCode.SharpZipLib.Checksums;
42 namespace ICSharpCode.SharpZipLib.Zip.Compression
45 public enum DeflateStrategy
47 // The default strategy.
48 Default = 0,
50 // This strategy will only allow longer string repetitions. It is
51 // useful for random data with a small character set.
52 Filtered = 1,
54 // This strategy will not look for string repetitions at all. It
55 // only encodes with Huffman trees (which means, that more common
56 // characters get a smaller encoding.
57 HuffmanOnly = 2
60 public class DeflaterEngine : DeflaterConstants
62 static int TOO_FAR = 4096;
64 int ins_h;
65 // private byte[] buffer;
66 short[] head;
67 short[] prev;
69 int matchStart, matchLen;
70 bool prevAvailable;
71 int blockStart;
72 int strstart, lookahead;
73 byte[] window;
75 DeflateStrategy strategy;
76 int max_chain, max_lazy, niceLength, goodLength;
78 /// <summary>
79 /// The current compression function.
80 /// </summary>
81 int comprFunc;
83 /// <summary>
84 /// The input data for compression.
85 /// </summary>
86 byte[] inputBuf;
88 /// <summary>
89 /// The total bytes of input read.
90 /// </summary>
91 int totalIn;
93 /// <summary>
94 /// The offset into inputBuf, where input data starts.
95 /// </summary>
96 int inputOff;
98 /// <summary>
99 /// The end offset of the input data.
100 /// </summary>
101 int inputEnd;
103 DeflaterPending pending;
104 DeflaterHuffman huffman;
106 /// <summary>
107 /// The adler checksum
108 /// </summary>
109 Adler32 adler;
111 public DeflaterEngine(DeflaterPending pending)
113 this.pending = pending;
114 huffman = new DeflaterHuffman(pending);
115 adler = new Adler32();
117 window = new byte[2 * WSIZE];
118 head = new short[HASH_SIZE];
119 prev = new short[WSIZE];
121 /* We start at index 1, to avoid a implementation deficiency, that
122 * we cannot build a repeat pattern at index 0.
124 blockStart = strstart = 1;
127 public void Reset()
129 huffman.Reset();
130 adler.Reset();
131 blockStart = strstart = 1;
132 lookahead = 0;
133 totalIn = 0;
134 prevAvailable = false;
135 matchLen = MIN_MATCH - 1;
137 for (int i = 0; i < HASH_SIZE; i++) {
138 head[i] = 0;
141 for (int i = 0; i < WSIZE; i++) {
142 prev[i] = 0;
146 public void ResetAdler()
148 adler.Reset();
151 public int Adler {
152 get {
153 return (int)adler.Value;
157 public int TotalIn {
158 get {
159 return totalIn;
163 public DeflateStrategy Strategy {
164 get {
165 return strategy;
167 set {
168 strategy = value;
172 public void SetLevel(int lvl)
174 goodLength = DeflaterConstants.GOOD_LENGTH[lvl];
175 max_lazy = DeflaterConstants.MAX_LAZY[lvl];
176 niceLength = DeflaterConstants.NICE_LENGTH[lvl];
177 max_chain = DeflaterConstants.MAX_CHAIN[lvl];
179 if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) {
180 // if (DeflaterConstants.DEBUGGING) {
181 // //Console.WriteLine("Change from "+comprFunc +" to "
182 // + DeflaterConstants.COMPR_FUNC[lvl]);
183 // }
184 switch (comprFunc) {
185 case DEFLATE_STORED:
186 if (strstart > blockStart) {
187 huffman.FlushStoredBlock(window, blockStart,
188 strstart - blockStart, false);
189 blockStart = strstart;
191 UpdateHash();
192 break;
193 case DEFLATE_FAST:
194 if (strstart > blockStart) {
195 huffman.FlushBlock(window, blockStart, strstart - blockStart,
196 false);
197 blockStart = strstart;
199 break;
200 case DEFLATE_SLOW:
201 if (prevAvailable) {
202 huffman.TallyLit(window[strstart-1] & 0xff);
204 if (strstart > blockStart) {
205 huffman.FlushBlock(window, blockStart, strstart - blockStart, false);
206 blockStart = strstart;
208 prevAvailable = false;
209 matchLen = MIN_MATCH - 1;
210 break;
212 comprFunc = COMPR_FUNC[lvl];
216 void UpdateHash()
218 // if (DEBUGGING) {
219 // //Console.WriteLine("updateHash: "+strstart);
220 // }
221 ins_h = (window[strstart] << HASH_SHIFT) ^ window[strstart + 1];
224 int InsertString()
226 short match;
227 int hash = ((ins_h << HASH_SHIFT) ^ window[strstart + (MIN_MATCH -1)]) & HASH_MASK;
229 // if (DEBUGGING) {
230 // if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^
231 // (window[strstart + 1] << HASH_SHIFT) ^
232 // (window[strstart + 2])) & HASH_MASK)) {
233 // throw new Exception("hash inconsistent: "+hash+"/"
234 // +window[strstart]+","
235 // +window[strstart+1]+","
236 // +window[strstart+2]+","+HASH_SHIFT);
237 // }
238 // }
240 prev[strstart & WMASK] = match = head[hash];
241 head[hash] = (short)strstart;
242 ins_h = hash;
243 return match & 0xffff;
246 void SlideWindow()
248 Array.Copy(window, WSIZE, window, 0, WSIZE);
249 matchStart -= WSIZE;
250 strstart -= WSIZE;
251 blockStart -= WSIZE;
253 /* Slide the hash table (could be avoided with 32 bit values
254 * at the expense of memory usage).
256 for (int i = 0; i < HASH_SIZE; ++i) {
257 int m = head[i] & 0xffff;
258 head[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0);
261 /* Slide the prev table. */
262 for (int i = 0; i < WSIZE; i++) {
263 int m = prev[i] & 0xffff;
264 prev[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0);
268 public void FillWindow()
270 /* If the window is almost full and there is insufficient lookahead,
271 * move the upper half to the lower one to make room in the upper half.
273 if (strstart >= WSIZE + MAX_DIST) {
274 SlideWindow();
277 /* If there is not enough lookahead, but still some input left,
278 * read in the input
280 while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) {
281 int more = 2 * WSIZE - lookahead - strstart;
283 if (more > inputEnd - inputOff) {
284 more = inputEnd - inputOff;
287 System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more);
288 adler.Update(inputBuf, inputOff, more);
290 inputOff += more;
291 totalIn += more;
292 lookahead += more;
295 if (lookahead >= MIN_MATCH) {
296 UpdateHash();
300 bool FindLongestMatch(int curMatch)
302 int chainLength = this.max_chain;
303 int niceLength = this.niceLength;
304 short[] prev = this.prev;
305 int scan = this.strstart;
306 int match;
307 int best_end = this.strstart + matchLen;
308 int best_len = Math.Max(matchLen, MIN_MATCH - 1);
310 int limit = Math.Max(strstart - MAX_DIST, 0);
312 int strend = strstart + MAX_MATCH - 1;
313 byte scan_end1 = window[best_end - 1];
314 byte scan_end = window[best_end];
316 /* Do not waste too much time if we already have a good match: */
317 if (best_len >= this.goodLength) {
318 chainLength >>= 2;
321 /* Do not look for matches beyond the end of the input. This is necessary
322 * to make deflate deterministic.
324 if (niceLength > lookahead) {
325 niceLength = lookahead;
328 if (DeflaterConstants.DEBUGGING && strstart > 2 * WSIZE - MIN_LOOKAHEAD) {
329 throw new InvalidOperationException("need lookahead");
332 do {
333 if (DeflaterConstants.DEBUGGING && curMatch >= strstart) {
334 throw new InvalidOperationException("future match");
336 if (window[curMatch + best_len] != scan_end ||
337 window[curMatch + best_len - 1] != scan_end1 ||
338 window[curMatch] != window[scan] ||
339 window[curMatch + 1] != window[scan + 1]) {
340 continue;
343 match = curMatch + 2;
344 scan += 2;
346 /* We check for insufficient lookahead only every 8th comparison;
347 * the 256th check will be made at strstart+258.
349 while (window[++scan] == window[++match] &&
350 window[++scan] == window[++match] &&
351 window[++scan] == window[++match] &&
352 window[++scan] == window[++match] &&
353 window[++scan] == window[++match] &&
354 window[++scan] == window[++match] &&
355 window[++scan] == window[++match] &&
356 window[++scan] == window[++match] && scan < strend) ;
358 if (scan > best_end) {
359 // if (DeflaterConstants.DEBUGGING && ins_h == 0)
360 // System.err.println("Found match: "+curMatch+"-"+(scan-strstart));
361 matchStart = curMatch;
362 best_end = scan;
363 best_len = scan - strstart;
365 if (best_len >= niceLength) {
366 break;
369 scan_end1 = window[best_end - 1];
370 scan_end = window[best_end];
372 scan = strstart;
373 } while ((curMatch = (prev[curMatch & WMASK] & 0xffff)) > limit && --chainLength != 0);
375 matchLen = Math.Min(best_len, lookahead);
376 return matchLen >= MIN_MATCH;
379 public void SetDictionary(byte[] buffer, int offset, int length)
381 if (DeflaterConstants.DEBUGGING && strstart != 1) {
382 throw new InvalidOperationException("strstart not 1");
384 adler.Update(buffer, offset, length);
385 if (length < MIN_MATCH) {
386 return;
388 if (length > MAX_DIST) {
389 offset += length - MAX_DIST;
390 length = MAX_DIST;
393 System.Array.Copy(buffer, offset, window, strstart, length);
395 UpdateHash();
396 --length;
397 while (--length > 0) {
398 InsertString();
399 strstart++;
401 strstart += 2;
402 blockStart = strstart;
405 bool DeflateStored(bool flush, bool finish)
407 if (!flush && lookahead == 0) {
408 return false;
411 strstart += lookahead;
412 lookahead = 0;
414 int storedLen = strstart - blockStart;
416 if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE) || /* Block is full */
417 (blockStart < WSIZE && storedLen >= MAX_DIST) || /* Block may move out of window */
418 flush) {
419 bool lastBlock = finish;
420 if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) {
421 storedLen = DeflaterConstants.MAX_BLOCK_SIZE;
422 lastBlock = false;
425 // if (DeflaterConstants.DEBUGGING) {
426 // //Console.WriteLine("storedBlock["+storedLen+","+lastBlock+"]");
427 // }
429 huffman.FlushStoredBlock(window, blockStart, storedLen, lastBlock);
430 blockStart += storedLen;
431 return !lastBlock;
433 return true;
436 private bool DeflateFast(bool flush, bool finish)
438 if (lookahead < MIN_LOOKAHEAD && !flush) {
439 return false;
442 while (lookahead >= MIN_LOOKAHEAD || flush) {
443 if (lookahead == 0) {
444 /* We are flushing everything */
445 huffman.FlushBlock(window, blockStart, strstart - blockStart, finish);
446 blockStart = strstart;
447 return false;
450 if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) {
451 /* slide window, as findLongestMatch need this.
452 * This should only happen when flushing and the window
453 * is almost full.
455 SlideWindow();
458 int hashHead;
459 if (lookahead >= MIN_MATCH &&
460 (hashHead = InsertString()) != 0 &&
461 strategy != DeflateStrategy.HuffmanOnly &&
462 strstart - hashHead <= MAX_DIST &&
463 FindLongestMatch(hashHead)) {
464 /* longestMatch sets matchStart and matchLen */
465 // if (DeflaterConstants.DEBUGGING) {
466 // for (int i = 0 ; i < matchLen; i++) {
467 // if (window[strstart+i] != window[matchStart + i]) {
468 // throw new Exception();
469 // }
470 // }
471 // }
473 // -jr- Hak hak hak this stops problems with fast/low compression and index out of range
474 if (huffman.TallyDist(strstart - matchStart, matchLen)) {
475 bool lastBlock = finish && lookahead == 0;
476 huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock);
477 blockStart = strstart;
480 lookahead -= matchLen;
481 if (matchLen <= max_lazy && lookahead >= MIN_MATCH) {
482 while (--matchLen > 0) {
483 ++strstart;
484 InsertString();
486 ++strstart;
487 } else {
488 strstart += matchLen;
489 if (lookahead >= MIN_MATCH - 1) {
490 UpdateHash();
493 matchLen = MIN_MATCH - 1;
494 continue;
495 } else {
496 /* No match found */
497 huffman.TallyLit(window[strstart] & 0xff);
498 ++strstart;
499 --lookahead;
502 if (huffman.IsFull()) {
503 bool lastBlock = finish && lookahead == 0;
504 huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock);
505 blockStart = strstart;
506 return !lastBlock;
509 return true;
512 bool DeflateSlow(bool flush, bool finish)
514 if (lookahead < MIN_LOOKAHEAD && !flush) {
515 return false;
518 while (lookahead >= MIN_LOOKAHEAD || flush) {
519 if (lookahead == 0) {
520 if (prevAvailable) {
521 huffman.TallyLit(window[strstart-1] & 0xff);
523 prevAvailable = false;
525 /* We are flushing everything */
526 if (DeflaterConstants.DEBUGGING && !flush) {
527 throw new Exception("Not flushing, but no lookahead");
529 huffman.FlushBlock(window, blockStart, strstart - blockStart,
530 finish);
531 blockStart = strstart;
532 return false;
535 if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) {
536 /* slide window, as findLongestMatch need this.
537 * This should only happen when flushing and the window
538 * is almost full.
540 SlideWindow();
543 int prevMatch = matchStart;
544 int prevLen = matchLen;
545 if (lookahead >= MIN_MATCH) {
546 int hashHead = InsertString();
547 if (strategy != DeflateStrategy.HuffmanOnly && hashHead != 0 && strstart - hashHead <= MAX_DIST && FindLongestMatch(hashHead)) {
548 /* longestMatch sets matchStart and matchLen */
550 /* Discard match if too small and too far away */
551 if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == MIN_MATCH && strstart - matchStart > TOO_FAR))) {
552 matchLen = MIN_MATCH - 1;
557 /* previous match was better */
558 if (prevLen >= MIN_MATCH && matchLen <= prevLen) {
559 // if (DeflaterConstants.DEBUGGING) {
560 // for (int i = 0 ; i < matchLen; i++) {
561 // if (window[strstart-1+i] != window[prevMatch + i])
562 // throw new Exception();
563 // }
564 // }
565 huffman.TallyDist(strstart - 1 - prevMatch, prevLen);
566 prevLen -= 2;
567 do {
568 strstart++;
569 lookahead--;
570 if (lookahead >= MIN_MATCH) {
571 InsertString();
573 } while (--prevLen > 0);
574 strstart ++;
575 lookahead--;
576 prevAvailable = false;
577 matchLen = MIN_MATCH - 1;
578 } else {
579 if (prevAvailable) {
580 huffman.TallyLit(window[strstart-1] & 0xff);
582 prevAvailable = true;
583 strstart++;
584 lookahead--;
587 if (huffman.IsFull()) {
588 int len = strstart - blockStart;
589 if (prevAvailable) {
590 len--;
592 bool lastBlock = (finish && lookahead == 0 && !prevAvailable);
593 huffman.FlushBlock(window, blockStart, len, lastBlock);
594 blockStart += len;
595 return !lastBlock;
598 return true;
601 public bool Deflate(bool flush, bool finish)
603 bool progress;
604 do {
605 FillWindow();
606 bool canFlush = flush && inputOff == inputEnd;
607 // if (DeflaterConstants.DEBUGGING) {
608 // //Console.WriteLine("window: ["+blockStart+","+strstart+","
609 // +lookahead+"], "+comprFunc+","+canFlush);
610 // }
611 switch (comprFunc) {
612 case DEFLATE_STORED:
613 progress = DeflateStored(canFlush, finish);
614 break;
615 case DEFLATE_FAST:
616 progress = DeflateFast(canFlush, finish);
617 break;
618 case DEFLATE_SLOW:
619 progress = DeflateSlow(canFlush, finish);
620 break;
621 default:
622 throw new InvalidOperationException("unknown comprFunc");
624 } while (pending.IsFlushed && progress); /* repeat while we have no pending output and progress was made */
625 return progress;
628 public void SetInput(byte[] buf, int off, int len)
630 if (inputOff < inputEnd) {
631 throw new InvalidOperationException("Old input was not completely processed");
634 int end = off + len;
636 /* We want to throw an ArrayIndexOutOfBoundsException early. The
637 * check is very tricky: it also handles integer wrap around.
639 if (0 > off || off > end || end > buf.Length) {
640 throw new ArgumentOutOfRangeException();
643 inputBuf = buf;
644 inputOff = off;
645 inputEnd = end;
648 public bool NeedsInput()
650 return inputEnd == inputOff;