From 0cb46169c79ddedd5434d14344e05f692ca13611 Mon Sep 17 00:00:00 2001 From: drone1400 <> Date: Sat, 6 Dec 2025 16:46:57 +0200 Subject: [PATCH 1/3] Initial Implementation of Multi Threaded BZip2 - currently causes BZip2_Reader_Factory test to fail due to some implementation details, to figure out later... - have not replaced original BZip2 implementation in LZMA Registry.cs due to BZip2MT implementation not having a built in concatenated stream support --- src/SharpCompress/Common/Zip/ZipFilePart.cs | 4 +- .../BZip2MT/Algorithm/BZip2BlockCompressor.cs | 256 ++ .../Algorithm/BZip2BlockDecompressor.cs | 483 ++++ .../BZip2MT/Algorithm/BZip2DivSufSort.cs | 2406 +++++++++++++++++ .../Algorithm/BZip2HuffmanStageDecoder.cs | 155 ++ .../Algorithm/BZip2HuffmanStageEncoder.cs | 344 +++ .../Algorithm/Bzip2MTFAndRLE2StageEncoder.cs | 168 ++ .../Compressors/BZip2MT/Algorithm/CRC32.cs | 74 + .../BZip2MT/Algorithm/HuffmanAllocator.cs | 201 ++ .../BZip2MT/Algorithm/MoveToFront.cs | 60 + .../InputStream/BZip2BitInputStream.cs | 111 + .../BZip2BitInputStreamSplitter.cs | 184 ++ .../BZip2MT/InputStream/BZip2InputStream.cs | 248 ++ .../BZip2ParallelInputDataBlock.cs | 108 + .../InputStream/BZip2ParallelInputStream.cs | 344 +++ .../BZip2MT/Interface/BZip2Constants.cs | 43 + .../BZip2MT/Interface/IBZip2BitInputStream.cs | 28 + .../Interface/IBZip2BitOutputStream.cs | 47 + .../OutputStream/BZip2BitOutputStream.cs | 92 + .../BZip2MT/OutputStream/BZip2OutputStream.cs | 179 ++ .../BZip2ParallelOutputDataBlock.cs | 171 ++ .../OutputStream/BZip2ParallelOutputStream.cs | 382 +++ src/SharpCompress/Factories/TarFactory.cs | 7 +- src/SharpCompress/Readers/Tar/TarReader.cs | 8 +- src/SharpCompress/Writers/Tar/TarWriter.cs | 4 +- src/SharpCompress/Writers/Zip/ZipWriter.cs | 4 +- .../BZip2MT/BZip2MTReaderTests.cs | 28 + .../BZip2MT/BZip2MTStreamAsyncTests.cs | 201 ++ .../SharpCompress.Test/BZip2MT/TestCommon.cs | 206 ++ tests/SharpCompress.Test/BZip2MT/Tests.cs | 139 + .../BZip2MT/TestsRandomData.cs | 53 + 31 files changed, 6726 insertions(+), 12 deletions(-) create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs create mode 100644 src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs create mode 100644 tests/SharpCompress.Test/BZip2MT/BZip2MTReaderTests.cs create mode 100644 tests/SharpCompress.Test/BZip2MT/BZip2MTStreamAsyncTests.cs create mode 100644 tests/SharpCompress.Test/BZip2MT/TestCommon.cs create mode 100644 tests/SharpCompress.Test/BZip2MT/Tests.cs create mode 100644 tests/SharpCompress.Test/BZip2MT/TestsRandomData.cs diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 77dc4abba..63d2a39fa 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -4,7 +4,7 @@ using System.Linq; using SharpCompress.Common.Zip.Headers; using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.BZip2MT.InputStream; using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.Deflate64; using SharpCompress.Compressors.Explode; @@ -122,7 +122,7 @@ protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod m } case ZipCompressionMethod.BZip2: { - return new BZip2Stream(stream, CompressionMode.Decompress, false); + return new BZip2ParallelInputStream(stream); } case ZipCompressionMethod.LZMA: { diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs new file mode 100644 index 000000000..260397eef --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs @@ -0,0 +1,256 @@ +// Bzip2 library for .net +// Modified by drone1400 +// Location: https://github.com/drone1400/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 +// Modified from the .net implementation by Jaime Olivares: http://github.com/jaime-olivares/bzip2 + +using System; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// Compresses and writes a single BZip2 block + /// + /// Block encoding consists of the following stages: + /// 1. Run-Length Encoding[1] - write() + /// 2. Burrows Wheeler Transform - close() (through BZip2DivSufSort) + /// 3. Write block header - close() + /// 4. Move To Front Transform - close() (through BZip2HuffmanStageEncoder) + /// 5. Run-Length Encoding[2] - close() (through BZip2HuffmanStageEncoder) + /// 6. Create and write Huffman tables - close() (through BZip2HuffmanStageEncoder) + /// 7. Huffman encode and write data - close() (through BZip2HuffmanStageEncoder) + /// + internal class BZip2BlockCompressor + { + // The stream to which compressed BZip2 data is written + private readonly IBZip2BitOutputStream bitOutputStream; + + // CRC builder for the block + private readonly CRC32 crc = new CRC32(); + + // The RLE'd block data + private readonly byte[] block; + + // Current length of the data within the block array + private int blockLength; + + // A limit beyond which new data will not be accepted into the block + private readonly int blockLengthLimit; + + // The values that are present within the RLE'd block data. For each index, true if that + // value is present within the data, otherwise false + private readonly bool[] blockValuesPresent = new bool[256]; + + // The Burrows Wheeler Transformed block data + private readonly int[] bwtBlock; + + // The current RLE value being accumulated (undefined when rleLength is 0) + private int rleCurrentValue = -1; + + // The repeat count of the current RLE value + private int rleLength; + + /// + /// Determines if any bytes have been written to the block. + /// True if one or more bytes has been written to the block, otherwise false. + /// + public bool IsEmpty + { + get { return ((this.blockLength == 0) && (this.rleLength == 0)); } + } + + /// + /// Gets the CRC of the completed block. Only valid after calling Close(). + /// + public uint CRC + { + get { return this.crc.CRC; } + } + + /// + /// Public constructor + /// + /// The BZip2BitOutputStream to which compressed BZip2 data is written + /// The declared block size in bytes. Up to this many bytes will be accepted + /// into the block after Run-Length Encoding is applied + public BZip2BlockCompressor(IBZip2BitOutputStream bitOutputStream, int blockSize) + { + this.bitOutputStream = bitOutputStream; + + // One extra byte is added to allow for the block wrap applied in close() + this.block = new byte[blockSize + 1]; + this.bwtBlock = new int[blockSize + 1]; + this.blockLengthLimit = blockSize - 6; // 5 bytes for one RLE run plus one byte - see Write(int) + } + + /// + /// Writes a byte to the block, accumulating to an RLE run where possible + /// + /// The byte to write + /// True if the byte was written, or false if the block is already full + public bool Write(int value) + { + if (this.blockLength > this.blockLengthLimit) + return false; + + if (this.rleLength == 0) + { + this.rleCurrentValue = value; + this.rleLength = 1; + } else if (this.rleCurrentValue != value) + { + // This path commits us to write 6 bytes - one RLE run (5 bytes) plus one extra + this.WriteRun(this.rleCurrentValue & 0xff, this.rleLength); + this.rleCurrentValue = value; + this.rleLength = 1; + } else + { + if (this.rleLength == 254) + { + this.WriteRun(this.rleCurrentValue & 0xff, 255); + this.rleLength = 0; + } else + { + this.rleLength++; + } + } + + return true; + } + + /// + /// Writes an array to the block + /// + /// The array to write + /// The offset within the input data to write from + /// The number of bytes of input data to write + /// The actual number of input bytes written. May be less than the number requested, or + /// zero if the block is already full + public int Write(byte[] data, int offset, int length) + { + var written = 0; + + while (length-- > 0) + { + if (!this.Write(data[offset++])) + break; + written++; + } + + return written; + } + + /// Compresses and writes out the block. + /// Exception on any I/O error writing the data + public void CloseBlock() + { + // If an RLE run is in progress, write it out + if (this.rleLength > 0) + this.WriteRun(this.rleCurrentValue & 0xff, this.rleLength); + + // Apply a one byte block wrap required by the BWT implementation + this.block[this.blockLength] = this.block[0]; + + // Perform the Burrows Wheeler Transform + var divSufSort = new BZip2DivSufSort(this.block, this.bwtBlock, this.blockLength); + var bwtStartPointer = divSufSort.BWT(); + + // Write out the block header + this.bitOutputStream.WriteBits(24, BZip2Constants.BLOCK_HEADER_MARKER_1); + this.bitOutputStream.WriteBits(24, BZip2Constants.BLOCK_HEADER_MARKER_2); + this.bitOutputStream.WriteInteger(this.crc.CRC); + this.bitOutputStream.WriteBoolean(false); // Randomised block flag. We never create randomised blocks + this.bitOutputStream.WriteBits(24, (uint)bwtStartPointer); + + // Write out the symbol map + this.WriteSymbolMap(); + + // Perform the Move To Front Transform and Run-Length Encoding[2] stages + var mtfEncoder = new BZip2MTFAndRLE2StageEncoder(this.bwtBlock, this.blockLength, this.blockValuesPresent); + mtfEncoder.Encode(); + + // Perform the Huffman Encoding stage and write out the encoded data + var huffmanEncoder = new BZip2HuffmanStageEncoder(this.bitOutputStream, mtfEncoder.MtfBlock, mtfEncoder.MtfLength, mtfEncoder.MtfAlphabetSize, mtfEncoder.MtfSymbolFrequencies); + huffmanEncoder.Encode(); + } + + /// + /// Write the Huffman symbol to output byte map + /// + /// on any I/O error writing the data + private void WriteSymbolMap() + { + var condensedInUse = new bool[16]; + + for (var i = 0; i < 16; i++) + { + for (int j = 0, k = i << 4; j < 16; j++, k++) + { + if (this.blockValuesPresent[k]) + { + condensedInUse[i] = true; + } + } + } + + for (var i = 0; i < 16; i++) + { + this.bitOutputStream.WriteBoolean(condensedInUse[i]); + } + + for (var i = 0; i < 16; i++) + { + if (condensedInUse[i]) + { + for (int j = 0, k = i * 16; j < 16; j++, k++) + { + this.bitOutputStream.WriteBoolean(this.blockValuesPresent[k]); + } + } + } + } + + /// + /// Writes an RLE run to the block array, updating the block CRC and present values array as required + /// + /// The value to write + /// The run length of the value to write + private void WriteRun ( int value, int runLength) + { + this.blockValuesPresent[value] = true; + this.crc.UpdateCrc (value, runLength); + + var byteValue = (byte)value; + switch (runLength) + { + case 1: + this.block[this.blockLength] = byteValue; + this.blockLength = this.blockLength + 1; + break; + + case 2: + this.block[this.blockLength] = byteValue; + this.block[this.blockLength + 1] = byteValue; + this.blockLength = this.blockLength + 2; + break; + + case 3: + this.block[this.blockLength] = byteValue; + this.block[this.blockLength + 1] = byteValue; + this.block[this.blockLength + 2] = byteValue; + this.blockLength = this.blockLength + 3; + break; + + default: + runLength -= 4; + this.blockValuesPresent[runLength] = true; + this.block[this.blockLength] = byteValue; + this.block[this.blockLength + 1] = byteValue; + this.block[this.blockLength + 2] = byteValue; + this.block[this.blockLength + 3] = byteValue; + this.block[this.blockLength + 4] = (byte)runLength; + this.blockLength = this.blockLength + 5; + break; + } + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs new file mode 100644 index 000000000..2a61f2ca7 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs @@ -0,0 +1,483 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// Reads and decompresses a single BZip2 block + /// + /// Block decoding consists of the following stages: + /// 1. Read block header - BZip2BlockDecompressor() + /// 2. Read Huffman tables - readHuffmanTables() + /// 3. Read and decode Huffman encoded data - decodeHuffmanData() + /// 4. Run-Length Decoding[2] - decodeHuffmanData() + /// 5. Inverse Move To Front Transform - decodeHuffmanData() + /// 6. Inverse Burrows Wheeler Transform - initialiseInverseBWT() + /// 7. Run-Length Decoding[1] - read() + /// 8. Optional Block De-Randomisation - read() (through decodeNextBWTByte()) + /// + internal class BZip2BlockDecompressor + { + /// + /// The BZip2 specification originally included the optional addition of a slight pseudo-random + /// perturbation to the input data, in order to work around the block sorting algorithm's non- + /// optimal performance on some types of input. The current mainline bzip2 does not require this + /// and will not create randomised blocks, but compatibility is still required for old data (and + /// third party compressors that haven't caught up). When decompressing a randomised block, for + /// each value N in this array, a 1 will be XOR'd onto the output of the Burrows-Wheeler + /// transform stage after N bytes, then the next N taken from the following entry. + /// + private static readonly int[] RNUMS = + { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, 985, 724, 205, 454, 863, 491, + 741, 242, 949, 214, 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, 419, 436, + 278, 496, 867, 210, 399, 680, 480, 51, 878, 465, 811, 169, 869, 675, 611, 697, + 867, 561, 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, 150, 238, 59, 379, + 684, 877, 625, 169, 643, 105, 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, 909, 545, 703, 919, 874, 474, + 882, 500, 594, 612, 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, 161, 604, + 958, 533, 221, 400, 386, 867, 600, 782, 382, 596, 414, 171, 516, 375, 682, 485, + 911, 276, 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, 227, 730, 475, 186, + 263, 647, 537, 686, 600, 224, 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, 715, 67, 618, 276, 204, 918, + 873, 777, 604, 560, 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, 652, 934, + 970, 447, 318, 353, 859, 672, 112, 785, 645, 863, 803, 350, 139, 93, 354, 99, + 820, 908, 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, 653, 282, 762, 623, + 680, 81, 927, 626, 789, 125, 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, 857, 956, 358, 619, 580, 124, + 737, 594, 701, 612, 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, 944, 375, + 748, 52, 600, 747, 642, 182, 862, 81, 344, 805, 988, 739, 511, 655, 814, 334, + 249, 515, 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, 433, 837, 553, 268, + 926, 240, 102, 654, 459, 51, 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, 978, 321, 576, 617, 626, 502, + 894, 679, 243, 440, 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, 707, 151, + 457, 449, 797, 195, 791, 558, 945, 679, 297, 59, 87, 824, 713, 663, 412, 693, + 342, 606, 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, 343, 97, 430, 751, + 497, 314, 983, 374, 822, 928, 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, 369, 970, 294, 750, 807, 827, + 150, 790, 288, 923, 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, 896, 831, + 547, 261, 524, 462, 293, 465, 502, 56, 661, 821, 976, 991, 658, 869, 905, 758, + 745, 193, 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, 61, 688, 793, 644, + 986, 403, 106, 366, 905, 644, 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, 920, 176, 193, 713, 857, 265, + 203, 50, 668, 108, 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, 936, 638 + }; + + // Maximum possible number of Huffman table selectors + private const int HUFFMAN_MAXIMUM_SELECTORS = (900000 / BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH) + 1; + + // Provides bits of input to decode + private readonly IBZip2BitInputStream bitInputStream; + + // Calculates the block CRC from the fully decoded bytes of the block + private readonly CRC32 crc = new CRC32(); + + // The CRC of the current block as read from the block header + private readonly uint blockCRC; + + // true if the current block is randomised, otherwise false + private readonly bool blockRandomised; + + // + // Huffman Decoding stage + // + + // The end-of-block Huffman symbol. Decoding of the block ends when this is encountered + private int huffmanEndOfBlockSymbol; + + /// + /// A map from Huffman symbol index to output character. Some types of data (e.g. ASCII text) + /// may contain only a limited number of byte values; Huffman symbols are only allocated to + /// those values that actually occur in the uncompressed data. + /// + private readonly byte[] huffmanSymbolMap = new byte[256]; + + // + // Move To Front stage + // + + /// + /// Counts of each byte value within the bwtTransformedArray data. Collected at the Move + /// To Front stage, consumed by the Inverse Burrows Wheeler Transform stage + /// + private readonly int[] bwtByteCounts = new int[256]; + + /// + /// The Burrows-Wheeler Transform processed data. Read at the Move To Front stage, consumed by the + /// Inverse Burrows Wheeler Transform stage + /// + private byte[] bwtBlock; + + // + // Inverse Burrows-Wheeler Transform stage + // + + /// + /// At each position contains the union of :- + /// An output character (8 bits) + /// A pointer from each position to its successor (24 bits, left shifted 8 bits) + /// As the pointer cannot exceed the maximum block size of 900k, 24 bits is more than enough to + /// hold it; Folding the character data into the spare bits while performing the inverse BWT, + /// when both pieces of information are available, saves a large number of memory accesses in + /// the final decoding stages. + /// + private int[] bwtMergedPointers = Array.Empty(); + + // The current merged pointer into the Burrow-Wheeler Transform array + private int bwtCurrentMergedPointer; + + /// + /// The actual length in bytes of the current block at the Inverse Burrows Wheeler Transform + /// stage (before final Run-Length Decoding) + /// + private int bwtBlockLength; + + // The number of output bytes that have been decoded up to the Inverse Burrows Wheeler Transform stage + private int bwtBytesDecoded; + + // + // Run-Length Encoding and Random Perturbation stage + // + + // The most recently RLE decoded byte + private int rleLastDecodedByte = -1; + + /// + /// The number of previous identical output bytes decoded. After 4 identical bytes, the next byte + /// decoded is an RLE repeat count + /// + private int rleAccumulator; + + // The RLE repeat count of the current decoded byte. When this reaches zero, a new byte is decoded + private int rleRepeat; + + // If the current block is randomised, the position within the RNUMS randomisation array + private int randomIndex; + + // If the current block is randomised, the remaining count at the current RNUMS position + private int randomCount = BZip2BlockDecompressor.RNUMS[0] - 1; + + // Minimum number of alternative Huffman tables + public const int HUFFMAN_MINIMUM_TABLES = 2; + + // Maximum number of alternative Huffman tables + public const int HUFFMAN_MAXIMUM_TABLES = 6; + + /// + /// Read and decode the block's Huffman tables + /// @return A decoder for the Huffman stage that uses the decoded tables + /// + /// if the input stream reaches EOF before all table data has been read + private BZip2HuffmanStageDecoder ReadHuffmanTables() + { + var tableCodeLengths = new byte[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; + + // Read Huffman symbol to output byte map + uint huffmanUsedRanges = this.bitInputStream.ReadBits(16); + int huffmanSymbolCount = 0; + + for (int i = 0; i < 16; i++) + { + if ((huffmanUsedRanges & ((1 << 15) >> i)) == 0) + continue; + for (int j = 0, k = i << 4; j < 16; j++, k++) + { + if (this.bitInputStream.ReadBoolean()) + { + this.huffmanSymbolMap[huffmanSymbolCount++] = (byte)k; + } + } + } + var endOfBlockSymbol = huffmanSymbolCount + 1; + this.huffmanEndOfBlockSymbol = endOfBlockSymbol; + + // Read total number of tables and selectors + uint totalTables = this.bitInputStream.ReadBits(3); + uint totalSelectors = this.bitInputStream.ReadBits(15); + if ((totalTables < BZip2BlockDecompressor.HUFFMAN_MINIMUM_TABLES) + || (totalTables > BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES) + || (totalSelectors < 1) + || (totalSelectors > BZip2BlockDecompressor.HUFFMAN_MAXIMUM_SELECTORS)) + { + throw new IOException("BZip2 block Huffman tables invalid"); + } + + // Read and decode MTFed Huffman selector list + var tableMTF = new MoveToFront(); + var selectors = new byte[totalSelectors]; + for (var selector = 0; selector < totalSelectors; selector++) + { + selectors[selector] = tableMTF.IndexToFront((int)this.bitInputStream.ReadUnary()); + } + + // Read the Canonical Huffman code lengths for each table + for (var table = 0; table < totalTables; table++) + { + int currentLength = (int)this.bitInputStream.ReadBits(5); + for (var i = 0; i <= endOfBlockSymbol; i++) + { + while (this.bitInputStream.ReadBoolean()) + { + currentLength += this.bitInputStream.ReadBoolean() ? -1 : 1; + } + tableCodeLengths[table, i] = (byte)currentLength; + } + } + + return new BZip2HuffmanStageDecoder(this.bitInputStream, endOfBlockSymbol + 1, tableCodeLengths, selectors); + } + + /// + /// Reads the Huffman encoded data from the input stream, performs Run-Length Decoding and + /// applies the Move To Front transform to reconstruct the Burrows-Wheeler Transform array + /// + /// The Huffman decoder through which symbols are read + /// if an end-of-block symbol was not decoded within the declared block size + private void DecodeHuffmanData(BZip2HuffmanStageDecoder huffmanDecoder) + { + var symbolMTF = new MoveToFront(); + int _bwtBlockLength = 0; + int repeatCount = 0; + int repeatIncrement = 1; + int mtfValue = 0; + + while (true) + { + var nextSymbol = huffmanDecoder.NextSymbol(); + + if (nextSymbol == BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA) + { + repeatCount += repeatIncrement; + repeatIncrement <<= 1; + } else if (nextSymbol == BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB) + { + repeatCount += repeatIncrement << 1; + repeatIncrement <<= 1; + } else + { + byte nextByte; + if (repeatCount > 0) + { + if (_bwtBlockLength + repeatCount > this.bwtBlock.Length) + throw new IOException("BZip2 block exceeds declared block size"); + + nextByte = this.huffmanSymbolMap[mtfValue]; + this.bwtByteCounts[nextByte & 0xff] += repeatCount; + while (--repeatCount >= 0) + { + this.bwtBlock[_bwtBlockLength++] = nextByte; + } + + repeatCount = 0; + repeatIncrement = 1; + } + + if (nextSymbol == this.huffmanEndOfBlockSymbol) + break; + + if (_bwtBlockLength >= this.bwtBlock.Length) + throw new IOException("BZip2 block exceeds declared block size"); + + mtfValue = symbolMTF.IndexToFront(nextSymbol - 1) & 0xff; + + nextByte = this.huffmanSymbolMap[mtfValue]; + this.bwtByteCounts[nextByte & 0xff]++; + this.bwtBlock[_bwtBlockLength++] = nextByte; + } + } + + this.bwtBlockLength = _bwtBlockLength; + } + + /// + /// Set up the Inverse Burrows-Wheeler Transform merged pointer array + /// + /// The start pointer into the BWT array + /// if the given start pointer is invalid + private void InitialiseInverseBWT(uint bwtStartPointer) + { + var _bwtMergedPointers = new int[this.bwtBlockLength]; + var characterBase = new int[256]; + + if ((bwtStartPointer < 0) || (bwtStartPointer >= this.bwtBlockLength)) + throw new IOException("BZip2 start pointer invalid"); + + // Cumulatise character counts + Array.ConstrainedCopy(this.bwtByteCounts, 0, characterBase, 1, 255); + for (int i = 2; i <= 255; i++) + { + characterBase[i] += characterBase[i - 1]; + } + + // Merged-Array Inverse Burrows-Wheeler Transform + // Combining the output characters and forward pointers into a single array here, where we + // have already read both of the corresponding values, cuts down on memory accesses in the + // final walk through the array + for (int i = 0; i < this.bwtBlockLength; i++) + { + int value = this.bwtBlock[i] & 0xff; + _bwtMergedPointers[characterBase[value]++] = (i << 8) + value; + } + + //this.bwtBlock = null; + this.bwtMergedPointers = _bwtMergedPointers; + this.bwtCurrentMergedPointer = _bwtMergedPointers[bwtStartPointer]; + } + + /// + /// Decodes a byte from the Burrows-Wheeler Transform stage. If the block has randomisation + /// applied, reverses the randomisation + /// + /// The decoded byte + private int decodeNextBWTByte() + { + int nextDecodedByte = this.bwtCurrentMergedPointer & 0xff; + this.bwtCurrentMergedPointer = this.bwtMergedPointers[this.bwtCurrentMergedPointer >> 8]; + + if (this.blockRandomised) + { + if (--this.randomCount == 0) + { + nextDecodedByte ^= 1; + this.randomIndex = (this.randomIndex + 1) % 512; + this.randomCount = BZip2BlockDecompressor.RNUMS[this.randomIndex]; + } + } + + this.bwtBytesDecoded++; + + return nextDecodedByte; + } + + /// + /// Public constructor + /// + /// The BZip2BitInputStream to read from + /// The maximum decoded size of the block + /// If the block could not be decoded + public BZip2BlockDecompressor(IBZip2BitInputStream bitInputStream, uint blockSize) + { + this.bitInputStream = bitInputStream; + this.bwtBlock = new byte[blockSize]; + + // Read block header + this.blockCRC = this.bitInputStream.ReadInteger(); //.ReadBits(32); + this.blockRandomised = this.bitInputStream.ReadBoolean(); + uint bwtStartPointer = this.bitInputStream.ReadBits(24); + + // Read block data and decode through to the Inverse Burrows Wheeler Transform stage + var huffmanDecoder = this.ReadHuffmanTables(); + this.DecodeHuffmanData(huffmanDecoder); + this.InitialiseInverseBWT(bwtStartPointer); + } + + + /// + /// Decodes a byte from the final Run-Length Encoding stage, pulling a new byte from the + /// Burrows-Wheeler Transform stage when required + /// + /// The decoded byte, or -1 if there are no more bytes + public int Read() + { + while (this.rleRepeat < 1) + { + if (this.bwtBytesDecoded == this.bwtBlockLength) + return -1; + + int nextByte = this.decodeNextBWTByte(); + if (nextByte != this.rleLastDecodedByte) + { + // New byte, restart accumulation + this.rleLastDecodedByte = nextByte; + this.rleRepeat = 1; + this.rleAccumulator = 1; + this.crc.UpdateCrc(nextByte); + } else + { + if (++this.rleAccumulator == 4) + { + // Accumulation complete, start repetition + int _rleRepeat = this.decodeNextBWTByte() + 1; + this.rleRepeat = _rleRepeat; + this.rleAccumulator = 0; + this.crc.UpdateCrc(nextByte, _rleRepeat); + } else + { + this.rleRepeat = 1; + this.crc.UpdateCrc(nextByte); + } + } + } + + this.rleRepeat--; + + return this.rleLastDecodedByte; + } + + /// + /// Decodes multiple bytes from the final Run-Length Encoding stage, pulling new bytes from the + /// Burrows-Wheeler Transform stage when required + /// + /// The array to write to + /// The starting position within the array + /// The number of bytes to read + /// The number of bytes actually read, or -1 if there are no bytes left in the block + public int Read(byte[] destination, int offset, int length) + { + int i; + for (i = 0; i < length; i++, offset++) + { + var decoded = this.Read(); + if (decoded == -1) + return (i == 0) ? -1 : i; + + destination[offset] = (byte)decoded; + } + return i; + } + + public int Read(Stream x, int maxLength) + { + int i; + for (i = 0; i < maxLength; i++) + { + var decoded = this.Read(); + if (decoded == -1) + return (i == 0) ? -1 : i; + + x.WriteByte((byte)decoded); + } + return i; + } + + public int ReadAll(Stream x) + { + int count = 0; + while (true) + { + var decoded = this.Read(); + if (decoded == -1) + return (count == 0) ? -1 : count; + count++; + x.WriteByte((byte)decoded); + } + } + + /// + /// Verify and return the block CRC. This method may only be called after all of the block's bytes have been read + /// + /// The block CRC + /// if the CRC verification failed + public uint CheckCrc() + { + if (this.blockCRC != this.crc.CRC) + throw new IOException("BZip2 block CRC error"); + + return this.crc.CRC; + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs new file mode 100644 index 000000000..b121dfa97 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs @@ -0,0 +1,2406 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +using System; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// + /// DivSufSort suffix array generator + /// Based on libdivsufsort 1.2.3 patched to support BZip2 + /// + /// + /// This is a simple conversion of the original C with two minor bugfixes applied(see "BUGFIX" + /// comments within the class). Documentation within the class is largely absent. + /// + internal class BZip2DivSufSort + { + private class StackEntry + { + readonly public int a; + readonly public int b; + readonly public int c; + readonly public int d; + + public StackEntry(int a, int b, int c, int d) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + } + + private class PartitionResult + { + readonly public int first; + readonly public int last; + + public PartitionResult(int first, int last) + { + this.first = first; + this.last = last; + } + } + + private class TRBudget + { + private int budget; + public int chance; + + public bool update(int size, int n) + { + + this.budget -= n; + if (this.budget <= 0) + { + if (--this.chance == 0) + { + return false; + } + this.budget += size; + } + + return true; + } + + public TRBudget(int budget, int chance) + { + this.budget = budget; + this.chance = chance; + } + } + + private const int STACK_SIZE = 64; + private const int BUCKET_A_SIZE = 256; + private const int BUCKET_B_SIZE = 65536; + private const int SS_BLOCKSIZE = 1024; + private const int INSERTIONSORT_THRESHOLD = 8; + + private static readonly int[] log2table = + { + -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; + + private readonly int[] SA; + private readonly byte[] T; + private readonly int n; + + /// + /// Public constructor + /// + /// T The input array + /// SA The output array + /// The length of the input data + public BZip2DivSufSort(byte[] T, int[] SA, int n) + { + this.T = T; + this.SA = SA; + this.n = n; + } + + /// + /// Performs a Burrows Wheeler Transform on the input array + /// + /// the index of the first character of the input array within the output array + public int BWT() + { + if (this.n == 0) + return 0; + + if (this.n == 1) + { + this.SA[0] = this.T[0]; + return 0; + } + + var bucketA = new int[BZip2DivSufSort.BUCKET_A_SIZE]; + var bucketB = new int[BZip2DivSufSort.BUCKET_B_SIZE]; + + int m = this.sortTypeBstar(bucketA, bucketB); + return 0 < m ? this.constructBWT(bucketA, bucketB) : 0; + } + + // ReSharper disable LoopVariableIsNeverChangedInsideLoop + private static void swapElements (int[] array1, int index1, int[] array2, int index2) + { + var temp = array1[index1]; + array1[index1] = array2[index2]; + array2[index2] = temp; + } + + private int ssCompare (int p1, int p2, int depth) + { + int U1n, U2n; // pointers within T + int U1, U2; + + for ( + U1 = depth + this.SA[p1], U2 = depth + this.SA[p2], U1n = this.SA[p1 + 1] + 2, U2n = this.SA[p2 + 1] + 2; + (U1 < U1n) && (U2 < U2n) && (this.T[U1] == this.T[U2]); + ++U1, ++U2 + ) { } + + return U1 < U1n ? (U2 < U2n ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1) : (U2 < U2n ? -1 : 0); + } + + private int ssCompareLast (int PA, int p1, int p2, int depth, int size) + { + int U1, U2, U1n, U2n; + + for ( + U1 = depth + this.SA[p1], U2 = depth + this.SA[p2], U1n = size, U2n = this.SA[(p2 + 1)] + 2; + (U1 < U1n) && (U2 < U2n) && (this.T[U1] == this.T[U2]); + ++U1, ++U2 + ) { } + + if (U1 < U1n) + return (U2 < U2n) ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1; + + if (U2 == U2n) + return 1; + + for ( + U1 = U1 % size, U1n = this.SA[PA] + 2; + (U1 < U1n) && (U2 < U2n) && (this.T[U1] == this.T[U2]); + ++U1, ++U2 + ) { } + + return U1 < U1n ? + (U2 < U2n ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1) + : (U2 < U2n ? -1 : 0); + } + + private void ssInsertionSort (int PA, int first, int last, int depth) + { + int i; // pointer within SA + + for (i = last - 2; first <= i; --i) + { + int j; // pointer within SA + int t; + int r; + for (t = this.SA[i], j = i + 1; 0 < (r = this.ssCompare (PA + t, PA + this.SA[j], depth));) + { + do + { + this.SA[j - 1] = this.SA[j]; + } while ((++j < last) && (this.SA[j] < 0)); + if (last <= j) + { + break; + } + } + if (r == 0) + { + this.SA[j] = ~this.SA[j]; + } + this.SA[j - 1] = t; + } + } + + private void ssFixdown (int Td, int PA, int sa, int i, int size) + { + int j, k; + int v; + int c; + + for (v = this.SA[sa + i], c = (this.T[Td + this.SA[PA + v]]) & 0xff; (j = 2 * i + 1) < size; this.SA[sa + i] = this.SA[sa + k], i = k) + { + int d = this.T[Td + this.SA[PA + this.SA[sa + (k = j++)]]] & 0xff; + int e; + if (d < (e = this.T[Td + this.SA[PA + this.SA[sa + j]]] & 0xff)) + { + k = j; + d = e; + } + if (d <= c) break; + } + this.SA[sa + i] = v; + } + + private void ssHeapSort (int Td, int PA, int sa, int size) + { + int i; + + int m = size; + if ((size % 2) == 0) + { + m--; + if ((this.T[Td + this.SA[PA + this.SA[sa + (m / 2)]]] & 0xff) < (this.T[Td + this.SA[PA + this.SA[sa + m]]] & 0xff)) + { + swapElements (this.SA, sa + m, this.SA, sa + (m / 2)); + } + } + + for (i = m / 2 - 1; 0 <= i; --i) + { + this.ssFixdown (Td, PA, sa, i, m); + } + + if ((size % 2) == 0) + { + swapElements (this.SA, sa, this.SA, sa + m); + this.ssFixdown (Td, PA, sa, 0, m); + } + + for (i = m - 1; 0 < i; --i) + { + int t = this.SA[sa]; + this.SA[sa] = this.SA[sa + i]; + this.ssFixdown (Td, PA, sa, 0, i); + this.SA[sa + i] = t; + } + } + + private int ssMedian3 (int Td, int PA, int v1, int v2, int v3) + { + var T_v1 = this.T[Td + this.SA[PA + this.SA[v1]]] & 0xff; + var T_v2 = this.T[Td + this.SA[PA + this.SA[v2]]] & 0xff; + var T_v3 = this.T[Td + this.SA[PA + this.SA[v3]]] & 0xff; + + if (T_v1 > T_v2) + { + var temp = v1; + v1 = v2; + v2 = temp; + var T_vtemp = T_v1; + T_v1 = T_v2; + T_v2 = T_vtemp; + } + if (T_v2 > T_v3) + { + return T_v1 > T_v3 ? v1 : v3; + } + return v2; + } + + private int ssMedian5 (int Td, int PA, int v1, int v2, int v3, int v4, int v5) + { + var T_v1 = this.T[Td + this.SA[PA + this.SA[v1]]] & 0xff; + var T_v2 = this.T[Td + this.SA[PA + this.SA[v2]]] & 0xff; + var T_v3 = this.T[Td + this.SA[PA + this.SA[v3]]] & 0xff; + var T_v4 = this.T[Td + this.SA[PA + this.SA[v4]]] & 0xff; + var T_v5 = this.T[Td + this.SA[PA + this.SA[v5]]] & 0xff; + int temp; + int T_vtemp; + + // ReSharper disable RedundantAssignment + if (T_v2 > T_v3) + { + temp = v2; + v2 = v3; + v3 = temp; + T_vtemp = T_v2; + T_v2 = T_v3; + T_v3 = T_vtemp; + } + if (T_v4 > T_v5) + { + temp = v4; + v4 = v5; + v5 = temp; + T_vtemp = T_v4; + T_v4 = T_v5; + T_v5 = T_vtemp; + } + if (T_v2 > T_v4) + { + temp = v2; + v2 = v4; + v4 = temp; + T_vtemp = T_v2; + T_v2 = T_v4; + T_v4 = T_vtemp; + temp = v3; + v3 = v5; + v5 = temp; + T_vtemp = T_v3; + T_v3 = T_v5; + T_v5 = T_vtemp; + } + if (T_v1 > T_v3) + { + temp = v1; + v1 = v3; + v3 = temp; + T_vtemp = T_v1; + T_v1 = T_v3; + T_v3 = T_vtemp; + } + if (T_v1 > T_v4) + { + temp = v1; + v1 = v4; + v4 = temp; + T_vtemp = T_v1; + T_v1 = T_v4; + T_v4 = T_vtemp; + temp = v3; + v3 = v5; + v5 = temp; + T_vtemp = T_v3; + T_v3 = T_v5; + T_v5 = T_vtemp; + } + // ReSharper restore RedundantAssignment + + return T_v3 > T_v4 ? v4 : v3; + } + + private int ssPivot (int Td, int PA, int first, int last) + { + int t = last - first; + int middle = first + t / 2; + + if (t <= 512) + { + if (t <= 32) + { + return this.ssMedian3 (Td, PA, first, middle, last - 1); + } + t >>= 2; + return this.ssMedian5 (Td, PA, first, first + t, middle, last - 1 - t, last - 1); + } + t >>= 3; + return this.ssMedian3 ( + Td, PA, + this.ssMedian3 (Td, PA, first, first + t, first + (t << 1)), + this.ssMedian3 (Td, PA, middle - t, middle, middle + t), + this.ssMedian3 (Td, PA, last - 1 - (t << 1), last - 1 - t, last - 1) + ); + } + + private static int ssLog (int x) + { + return ((x & 0xff00) != 0) ? 8 + BZip2DivSufSort.log2table[(x >> 8) & 0xff] : BZip2DivSufSort.log2table[x & 0xff]; + } + + private int ssSubstringPartition (int PA, int first, int last, int depth) + { + int a, b; + + for (a = first - 1, b = last;;) + { + for (; (++a < b) && ((this.SA[PA + this.SA[a]] + depth) >= (this.SA[PA + this.SA[a] + 1] + 1));) + { + this.SA[a] = ~this.SA[a]; + } + for (; (a < --b) && ((this.SA[PA + this.SA[b]] + depth) < (this.SA[PA + this.SA[b] + 1] + 1));) { } + if (b <= a) + { + break; + } + int t = ~this.SA[b]; + this.SA[b] = this.SA[a]; + this.SA[a] = t; + } + if (first < a) + this.SA[first] = ~this.SA[first]; + + return a; + } + + private void ssMultiKeyIntroSort (int PA, int first, int last, int depth) + { + var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; + + int ssize; + int limit; + int x = 0; + + for (ssize = 0, limit = ssLog (last - first);;) + { + if ((last - first) <= BZip2DivSufSort.INSERTIONSORT_THRESHOLD) + { + if (1 < (last - first)) + { + this.ssInsertionSort (PA, first, last, depth); + } + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + last = entry.b; + depth = entry.c; + limit = entry.d; + continue; + } + + int Td = depth; + if (limit-- == 0) + { + this.ssHeapSort (Td, PA, first, last - first); + } + int a; + int v; + if (limit < 0) + { + for (a = first + 1, v = this.T[Td + this.SA[PA + this.SA[first]]] & 0xff; a < last; ++a) + { + if ((x = (this.T[Td + this.SA[PA + this.SA[a]]] & 0xff)) != v) + { + if (1 < (a - first)) { break; } + v = x; + first = a; + } + } + if ((this.T[Td + this.SA[PA + this.SA[first]] - 1] & 0xff) < v) + { + first = this.ssSubstringPartition (PA, first, a, depth); + } + if ((a - first) <= (last - a)) + { + if (1 < (a - first)) + { + stack[ssize++] = new StackEntry (a, last, depth, -1); + last = a; + depth += 1; + limit = ssLog (a - first); + } else + { + first = a; + limit = -1; + } + } else + { + if (1 < (last - a)) + { + stack[ssize++] = new StackEntry (first, a, depth + 1, ssLog (a - first)); + first = a; + limit = -1; + } else + { + last = a; + depth += 1; + limit = ssLog (a - first); + } + } + continue; + } + + a = this.ssPivot (Td, PA, first, last); + v = this.T[Td + this.SA[PA + this.SA[a]]] & 0xff; + swapElements (this.SA, first, this.SA, a); + + int b; + for (b = first; (++b < last) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) == v); ) { } + if (((a = b) < last) && (x < v)) + { + for (; (++b < last) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + } + int c; + for (c = last; (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) == v);) { } + int d; + if ((b < (d = c)) && (x > v)) + { + for (; (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + for (; b < c;) + { + swapElements (this.SA, b, this.SA, c); + for (; (++b < c) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + for (; (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + + if (a <= d) + { + c = b - 1; + + int s; + int t; + if ((s = a - first) > (t = b - a)) + { + s = t; + } + int e; + int f; + for (e = first, f = b - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + if ((s = d - c) > (t = last - d - 1)) + { + s = t; + } + for (e = b, f = last - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + + a = first + (b - a); + c = last - (d - c); + b = (v <= (this.T[Td + this.SA[PA + this.SA[a]] - 1] & 0xff)) ? a : this.ssSubstringPartition (PA, a, c, depth); + + if ((a - first) <= (last - c)) + { + if ((last - c) <= (c - b)) + { + stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); + stack[ssize++] = new StackEntry (c, last, depth, limit); + last = a; + } else if ((a - first) <= (c - b)) + { + stack[ssize++] = new StackEntry (c, last, depth, limit); + stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); + last = a; + } else + { + stack[ssize++] = new StackEntry (c, last, depth, limit); + stack[ssize++] = new StackEntry (first, a, depth, limit); + first = b; + last = c; + depth += 1; + limit = ssLog (c - b); + } + } else + { + if ((a - first) <= (c - b)) + { + stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); + stack[ssize++] = new StackEntry (first, a, depth, limit); + first = c; + } else if ((last - c) <= (c - b)) + { + stack[ssize++] = new StackEntry (first, a, depth, limit); + stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); + first = c; + } else + { + stack[ssize++] = new StackEntry (first, a, depth, limit); + stack[ssize++] = new StackEntry (c, last, depth, limit); + first = b; + last = c; + depth += 1; + limit = ssLog (c - b); + } + } + } else + { + limit += 1; + if ((this.T[Td + this.SA[PA + this.SA[first]] - 1] & 0xff) < v) + { + first = this.ssSubstringPartition (PA, first, last, depth); + limit = ssLog (last - first); + } + depth += 1; + } + } + } + + private static void ssBlockSwap (int[] array1, int first1, int[] array2, int first2, int size) + { + int a, b; + int i; + for (i = size, a = first1, b = first2; 0 < i; --i, ++a, ++b) + { + swapElements (array1, a, array2, b); + } + } + + private void ssMergeForward (int PA, int[] buf, int bufoffset, int first, int middle, int last, int depth) + { + int i, j, k; + int t; + + int bufend = bufoffset + (middle - first) - 1; + ssBlockSwap (buf, bufoffset, this.SA, first, middle - first); + + for (t = this.SA[first], i = first, j = bufoffset, k = middle;;) + { + int r = this.ssCompare (PA + buf[j], PA + this.SA[k], depth); + if (r < 0) + { + do + { + this.SA[i++] = buf[j]; + if (bufend <= j) + { + buf[j] = t; + return; + } + buf[j++] = this.SA[i]; + } while (buf[j] < 0); + } else if (r > 0) + { + do + { + this.SA[i++] = this.SA[k]; + this.SA[k++] = this.SA[i]; + if (last <= k) + { + while (j < bufend) + { + this.SA[i++] = buf[j]; + buf[j++] = this.SA[i]; + } + this.SA[i] = buf[j]; + buf[j] = t; + return; + } + } while (this.SA[k] < 0); + } else + { + this.SA[k] = ~this.SA[k]; + do + { + this.SA[i++] = buf[j]; + if (bufend <= j) + { + buf[j] = t; + return; + } + buf[j++] = this.SA[i]; + } while (buf[j] < 0); + + do + { + this.SA[i++] = this.SA[k]; + this.SA[k++] = this.SA[i]; + if (last <= k) + { + while (j < bufend) + { + this.SA[i++] = buf[j]; + buf[j++] = this.SA[i]; + } + this.SA[i] = buf[j]; + buf[j] = t; + return; + } + } while (this.SA[k] < 0); + } + } + } + + private void ssMergeBackward (int PA, int[] buf, int bufoffset, int first, int middle, int last, int depth) + { + int p1, p2; + int i, j, k; + int t; + + int bufend = bufoffset + (last - middle); + ssBlockSwap (buf, bufoffset, this.SA, middle, last - middle); + + int x = 0; + if (buf[bufend - 1] < 0) + { + x |= 1; + p1 = PA + ~buf[bufend - 1]; + } else + { + p1 = PA + buf[bufend - 1]; + } + if (this.SA[middle - 1] < 0) + { + x |= 2; + p2 = PA + ~this.SA[middle - 1]; + } else + { + p2 = PA + this.SA[middle - 1]; + } + for (t = this.SA[last - 1], i = last - 1, j = bufend - 1, k = middle - 1;;) + { + int r = this.ssCompare (p1, p2, depth); + if (r > 0) + { + if ((x & 1) != 0) + { + do + { + this.SA[i--] = buf[j]; + buf[j--] = this.SA[i]; + } while (buf[j] < 0); + x ^= 1; + } + this.SA[i--] = buf[j]; + if (j <= bufoffset) + { + buf[j] = t; + return; + } + buf[j--] = this.SA[i]; + + if (buf[j] < 0) + { + x |= 1; + p1 = PA + ~buf[j]; + } else + { + p1 = PA + buf[j]; + } + } else if (r < 0) + { + if ((x & 2) != 0) + { + do + { + this.SA[i--] = this.SA[k]; + this.SA[k--] = this.SA[i]; + } while (this.SA[k] < 0); + x ^= 2; + } + this.SA[i--] = this.SA[k]; + this.SA[k--] = this.SA[i]; + if (k < first) + { + while (bufoffset < j) + { + this.SA[i--] = buf[j]; + buf[j--] = this.SA[i]; + } + this.SA[i] = buf[j]; + buf[j] = t; + return; + } + + if (this.SA[k] < 0) + { + x |= 2; + p2 = PA + ~this.SA[k]; + } else + { + p2 = PA + this.SA[k]; + } + } else + { + if ((x & 1) != 0) + { + do + { + this.SA[i--] = buf[j]; + buf[j--] = this.SA[i]; + } while (buf[j] < 0); + x ^= 1; + } + this.SA[i--] = ~buf[j]; + if (j <= bufoffset) + { + buf[j] = t; + return; + } + buf[j--] = this.SA[i]; + + if ((x & 2) != 0) + { + do + { + this.SA[i--] = this.SA[k]; + this.SA[k--] = this.SA[i]; + } while (this.SA[k] < 0); + x ^= 2; + } + this.SA[i--] = this.SA[k]; + this.SA[k--] = this.SA[i]; + if (k < first) + { + while (bufoffset < j) + { + this.SA[i--] = buf[j]; + buf[j--] = this.SA[i]; + } + this.SA[i] = buf[j]; + buf[j] = t; + return; + } + + if (buf[j] < 0) + { + x |= 1; + p1 = PA + ~buf[j]; + } else + { + p1 = PA + buf[j]; + } + if (this.SA[k] < 0) + { + x |= 2; + p2 = PA + ~this.SA[k]; + } else + { + p2 = PA + this.SA[k]; + } + } + } + } + + private static int getIDX (int a) + { + return (0 <= a) ? a : ~a; + } + + private void ssMergeCheckEqual (int PA, int depth, int a) + { + if ((0 <= this.SA[a]) && (this.ssCompare (PA + getIDX (this.SA[a - 1]), PA + this.SA[a], depth) == 0)) + { + this.SA[a] = ~this.SA[a]; + } + } + + private void ssMerge (int PA, int first, int middle, int last, int[] buf, int bufoffset, int bufsize, int depth) + { + var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; + + int ssize; + int check; + + for (check = 0, ssize = 0;;) + { + if ((last - middle) <= bufsize) + { + if ((first < middle) && (middle < last)) + { + this.ssMergeBackward (PA, buf, bufoffset, first, middle, last, depth); + } + + if ((check & 1) != 0) + { + this.ssMergeCheckEqual (PA, depth, first); + } + if ((check & 2) != 0) + { + this.ssMergeCheckEqual (PA, depth, last); + } + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + middle = entry.b; + last = entry.c; + check = entry.d; + continue; + } + + if ((middle - first) <= bufsize) + { + if (first < middle) + { + this.ssMergeForward ( PA, buf, bufoffset, first, middle, last, depth); + } + if ((check & 1) != 0) + { + this.ssMergeCheckEqual (PA, depth, first); + } + if ((check & 2) != 0) + { + this.ssMergeCheckEqual (PA, depth, last); + } + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + middle = entry.b; + last = entry.c; + check = entry.d; + continue; + } + + int m; + int len; + int half; + for ( + m = 0, len = Math.Min (middle - first, last - middle), half = len >> 1; + 0 < len; + len = half, half >>= 1 + ) + { + if (this.ssCompare (PA + getIDX (this.SA[middle + m + half]), + PA + getIDX (this.SA[middle - m - half - 1]), depth) < 0) + { + m += half + 1; + half -= (len & 1) ^ 1; + } + } + + if (0 < m) + { + ssBlockSwap (this.SA, middle - m, this.SA, middle, m); + int j; + int i = j = middle; + int next = 0; + if ((middle + m) < last) + { + if (this.SA[middle + m] < 0) + { + for (; this.SA[i - 1] < 0; --i) + { } + this.SA[middle + m] = ~this.SA[middle + m]; + } + for (j = middle; this.SA[j] < 0; ++j) + { } + next = 1; + } + if ((i - first) <= (last - j)) + { + stack[ssize++] = new StackEntry (j, middle + m, last, (check & 2) | (next & 1)); + middle -= m; + last = i; + check = (check & 1); + } else + { + if ((i == middle) && (middle == j)) + { + next <<= 1; + } + stack[ssize++] = new StackEntry (first, middle - m, i, (check & 1) | (next & 2)); + first = j; + middle += m; + check = (check & 2) | (next & 1); + } + } else + { + if ((check & 1) != 0) + { + this.ssMergeCheckEqual (PA, depth, first); + } + this.ssMergeCheckEqual (PA, depth, middle); + if ((check & 2) != 0) + { + this.ssMergeCheckEqual (PA, depth, last); + } + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + middle = entry.b; + last = entry.c; + check = entry.d; + } + } + } + + private void subStringSort (int PA, int first, int last, int[] buf, int bufoffset, int bufsize, int depth, bool lastsuffix, int size) + { + int a; + int i; + int k; + + if (lastsuffix) + ++first; + + for (a = first, i = 0; (a + BZip2DivSufSort.SS_BLOCKSIZE) < last; a += BZip2DivSufSort.SS_BLOCKSIZE, ++i) + { + this.ssMultiKeyIntroSort (PA, a, a + BZip2DivSufSort.SS_BLOCKSIZE, depth); + int[] curbuf = this.SA; + int curbufoffset = a + BZip2DivSufSort.SS_BLOCKSIZE; + int curbufsize = last - (a + BZip2DivSufSort.SS_BLOCKSIZE); + if (curbufsize <= bufsize) + { + curbufsize = bufsize; + curbuf = buf; + curbufoffset = bufoffset; + } + int b; + int j; + for (b = a, k = BZip2DivSufSort.SS_BLOCKSIZE, j = i; (j & 1) != 0; b -= k, k <<= 1, j >>= 1) + this.ssMerge (PA, b - k, b, b + k, curbuf, curbufoffset, curbufsize, depth); + } + + this.ssMultiKeyIntroSort (PA, a, last, depth); + + for (k = BZip2DivSufSort.SS_BLOCKSIZE; i != 0; k <<= 1, i >>= 1) + { + if ((i & 1) != 0) + { + this.ssMerge (PA, a - k, a, last, buf, bufoffset, bufsize, depth); + a -= k; + } + } + + if (lastsuffix) + { + int r; + for ( + a = first, i = this.SA[first - 1], r = 1; + (a < last) && ((this.SA[a] < 0) || (0 < (r = this.ssCompareLast (PA, PA + i, PA + this.SA[a], depth, size)))); + ++a + ) + { + this.SA[a - 1] = this.SA[a]; + } + if (r == 0) + { + this.SA[a] = ~this.SA[a]; + } + this.SA[a - 1] = i; + } + } + + private int trGetC (int ISA, int ISAd, int ISAn, int p) + { + return (((ISAd + p) < ISAn) ? this.SA[ISAd + p] : this.SA[ISA + ((ISAd - ISA + p) % (ISAn - ISA))]); + } + + private void trFixdown (int ISA, int ISAd, int ISAn, int sa, int i, int size) + { + int j, k; + int v; + int c; + + for (v = this.SA[sa + i], c = this.trGetC (ISA, ISAd, ISAn, v); (j = 2 * i + 1) < size; this.SA[sa + i] = this.SA[sa + k], i = k) + { + k = j++; + int d = this.trGetC (ISA, ISAd, ISAn, this.SA[sa + k]); + int e; + if (d < (e = this.trGetC (ISA, ISAd, ISAn, this.SA[sa + j]))) + { + k = j; + d = e; + } + if (d <= c) + { + break; + } + } + this.SA[sa + i] = v; + } + + private void trHeapSort (int ISA, int ISAd, int ISAn, int sa, int size) + { + int i; + + int m = size; + if ((size % 2) == 0) + { + m--; + if (this.trGetC (ISA, ISAd, ISAn, this.SA[sa + (m / 2)]) < this.trGetC (ISA, ISAd, ISAn, this.SA[sa + m])) + { + swapElements (this.SA, sa + m, this.SA, sa + (m / 2)); + } + } + + for (i = m / 2 - 1; 0 <= i; --i) + { + this.trFixdown (ISA, ISAd, ISAn, sa, i, m); + } + + if ((size % 2) == 0) + { + swapElements (this.SA, sa + 0, this.SA, sa + m); + this.trFixdown (ISA, ISAd, ISAn, sa, 0, m); + } + + for (i = m - 1; 0 < i; --i) + { + int t = this.SA[sa + 0]; + this.SA[sa + 0] = this.SA[sa + i]; + this.trFixdown (ISA, ISAd, ISAn, sa, 0, i); + this.SA[sa + i] = t; + } + } + + private void trInsertionSort (int ISA, int ISAd, int ISAn, int first, int last) + { + int a; + + for (a = first + 1; a < last; ++a) + { + int b; + int t; + int r; + for (t = this.SA[a], b = a - 1; 0 > (r = this.trGetC (ISA, ISAd, ISAn, t) - this.trGetC (ISA, ISAd, ISAn, this.SA[b]));) + { + do + { + this.SA[b + 1] = this.SA[b]; + } while ((first <= --b) && (this.SA[b] < 0)); + if (b < first) + { + break; + } + } + if (r == 0) + { + this.SA[b] = ~this.SA[b]; + } + this.SA[b + 1] = t; + } + } + + private static int trLog (int x) + { + return ((x & 0xffff0000) != 0) ? + (((x & 0xff000000) != 0) ? 24 + BZip2DivSufSort.log2table[(x >> 24) & 0xff] : 16 + BZip2DivSufSort.log2table[(x >> 16) & 0xff]) + : (((x & 0x0000ff00) != 0) ? 8 + BZip2DivSufSort.log2table[(x >> 8) & 0xff] : 0 + BZip2DivSufSort.log2table[(x >> 0) & 0xff]); + } + + private int trMedian3 (int ISA, int ISAd, int ISAn, int v1, int v2, int v3) + { + var SA_v1 = this.trGetC (ISA, ISAd, ISAn, this.SA[v1]); + var SA_v2 = this.trGetC (ISA, ISAd, ISAn, this.SA[v2]); + var SA_v3 = this.trGetC (ISA, ISAd, ISAn, this.SA[v3]); + + if (SA_v1 > SA_v2) + { + var temp = v1; + v1 = v2; + v2 = temp; + var SA_vtemp = SA_v1; + SA_v1 = SA_v2; + SA_v2 = SA_vtemp; + } + if (SA_v2 > SA_v3) + { + return SA_v1 > SA_v3 ? v1 : v3; + } + + return v2; + } + + private int trMedian5 (int ISA, int ISAd, int ISAn, int v1, int v2, int v3, int v4, int v5) + { + var SA_v1 = this.trGetC (ISA, ISAd, ISAn, this.SA[v1]); + var SA_v2 = this.trGetC (ISA, ISAd, ISAn, this.SA[v2]); + var SA_v3 = this.trGetC (ISA, ISAd, ISAn, this.SA[v3]); + var SA_v4 = this.trGetC (ISA, ISAd, ISAn, this.SA[v4]); + var SA_v5 = this.trGetC (ISA, ISAd, ISAn, this.SA[v5]); + int temp; + int SA_vtemp; + + // ReSharper disable RedundantAssignment + if (SA_v2 > SA_v3) + { + temp = v2; + v2 = v3; + v3 = temp; + SA_vtemp = SA_v2; + SA_v2 = SA_v3; + SA_v3 = SA_vtemp; + } + if (SA_v4 > SA_v5) + { + temp = v4; + v4 = v5; + v5 = temp; + SA_vtemp = SA_v4; + SA_v4 = SA_v5; + SA_v5 = SA_vtemp; + } + if (SA_v2 > SA_v4) + { + temp = v2; + v2 = v4; + v4 = temp; + SA_vtemp = SA_v2; + SA_v2 = SA_v4; + SA_v4 = SA_vtemp; + temp = v3; + v3 = v5; + v5 = temp; + SA_vtemp = SA_v3; + SA_v3 = SA_v5; + SA_v5 = SA_vtemp; + } + if (SA_v1 > SA_v3) + { + temp = v1; + v1 = v3; + v3 = temp; + SA_vtemp = SA_v1; + SA_v1 = SA_v3; + SA_v3 = SA_vtemp; + } + if (SA_v1 > SA_v4) + { + temp = v1; + v1 = v4; + v4 = temp; + SA_vtemp = SA_v1; + SA_v1 = SA_v4; + SA_v4 = SA_vtemp; + temp = v3; + v3 = v5; + v5 = temp; + SA_vtemp = SA_v3; + SA_v3 = SA_v5; + SA_v5 = SA_vtemp; + } + // ReSharper restore RedundantAssignment + + return SA_v3 > SA_v4 ? v4 : v3; + } + + private int trPivot (int ISA, int ISAd, int ISAn, int first, int last) + { + int t = last - first; + int middle = first + t / 2; + + if (t <= 512) + { + if (t <= 32) + { + return this.trMedian3 (ISA, ISAd, ISAn, first, middle, last - 1); + } + t >>= 2; + return this.trMedian5 ( + ISA, ISAd, ISAn, + first, first + t, + middle, + last - 1 - t, last - 1 + ); + } + t >>= 3; + return this.trMedian3 ( + ISA, ISAd, ISAn, + this.trMedian3 (ISA, ISAd, ISAn, first, first + t, first + (t << 1)), + this.trMedian3 (ISA, ISAd, ISAn, middle - t, middle, middle + t), + this.trMedian3 (ISA, ISAd, ISAn, last - 1 - (t << 1), last - 1 - t, last - 1) + ); + } + + private void lsUpdateGroup (int ISA, int first, int last) + { + int a; + + for (a = first; a < last; ++a) + { + int b; + if (0 <= this.SA[a]) + { + b = a; + do + { + this.SA[ISA + this.SA[a]] = a; + } while ((++a < last) && (0 <= this.SA[a])); + this.SA[b] = b - a; + if (last <= a) + { + break; + } + } + b = a; + do + { + this.SA[a] = ~this.SA[a]; + } while (this.SA[++a] < 0); + int t = a; + do + { + this.SA[ISA + this.SA[b]] = t; + } while (++b <= a); + } + } + + private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) + { + var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; + + int limit; + int x = 0; + int ssize; + + for (ssize = 0, limit = trLog (last - first);;) + { + + if ((last - first) <= BZip2DivSufSort.INSERTIONSORT_THRESHOLD) + { + if (1 < (last - first)) + { + this.trInsertionSort (ISA, ISAd, ISAn, first, last); + this.lsUpdateGroup (ISA, first, last); + } else if ((last - first) == 1) + { + this.SA[first] = -1; + } + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + last = entry.b; + limit = entry.c; + continue; + } + + int a; + int b; + if (limit-- == 0) + { + this.trHeapSort (ISA, ISAd, ISAn, first, last - first); + for (a = last - 1; first < a; a = b) + { + for ( + x = this.trGetC (ISA, ISAd, ISAn, this.SA[a]), b = a - 1; + (first <= b) && (this.trGetC (ISA, ISAd, ISAn, this.SA[b]) == x); + --b + ) + { + this.SA[b] = ~this.SA[b]; + } + } + this.lsUpdateGroup (ISA, first, last); + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + last = entry.b; + limit = entry.c; + continue; + } + + a = this.trPivot (ISA, ISAd, ISAn, first, last); + swapElements (this.SA, first, this.SA, a); + int v = this.trGetC (ISA, ISAd, ISAn, this.SA[first]); + + for (b = first; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) == v);) + { } + if (((a = b) < last) && (x < v)) + { + for (; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + } + int c; + for (c = last; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) == v);) + { } + int d; + if ((b < (d = c)) && (x > v)) + { + for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + for (; b < c;) + { + swapElements (this.SA, b, this.SA, c); + for (; (++b < c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + + if (a <= d) + { + c = b - 1; + + int s; + int t; + if ((s = a - first) > (t = b - a)) + { + s = t; + } + int e; + int f; + for (e = first, f = b - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + if ((s = d - c) > (t = last - d - 1)) + { + s = t; + } + for (e = b, f = last - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + + a = first + (b - a); + b = last - (d - c); + + for (c = first, v = a - 1; c < a; ++c) + { + this.SA[ISA + this.SA[c]] = v; + } + if (b < last) + { + for (c = a, v = b - 1; c < b; ++c) + { + this.SA[ISA + this.SA[c]] = v; + } + } + if ((b - a) == 1) + { + this.SA[a] = - 1; + } + + if ((a - first) <= (last - b)) + { + if (first < a) + { + stack[ssize++] = new StackEntry (b, last, limit, 0); + last = a; + } else + { + first = b; + } + } else + { + if (b < last) + { + stack[ssize++] = new StackEntry (first, a, limit, 0); + first = b; + } else + { + last = a; + } + } + } else + { + if (ssize == 0) return; + var entry = stack[--ssize]; + first = entry.a; + last = entry.b; + limit = entry.c; + } + } + } + + private void lsSort (int ISA, int x, int depth) + { + int ISAd; + + for (ISAd = ISA + depth; -x < this.SA[0]; ISAd += (ISAd - ISA)) + { + int first = 0; + int skip = 0; + int last; + int t; + do + { + if ((t = this.SA[first]) < 0) + { + first -= t; + skip += t; + } else + { + if (skip != 0) + { + this.SA[first + skip] = skip; + skip = 0; + } + last = this.SA[ISA + t] + 1; + this.lsIntroSort (ISA, ISAd, ISA + x, first, last); + first = last; + } + } while (first < x); + + if (skip != 0) + this.SA[first + skip] = skip; + + if (x < (ISAd - ISA)) + { + first = 0; + do + { + if ((t = this.SA[first]) < 0) + first -= t; + else + { + last = this.SA[ISA + t] + 1; + int i; + for (i = first; i < last; ++i) + this.SA[ISA + this.SA[i]] = i; + first = last; + } + } while (first < x); + break; + } + } + } + + private PartitionResult trPartition (int ISA, int ISAd, int ISAn, int first, int last, int v) + { + int a, b, c, d; + var x = 0; + + for (b = first - 1; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) == v);) + { } + if (((a = b) < last) && (x < v)) + { + for (; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + } + for (c = last; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) == v);) + { } + if ((b < (d = c)) && (x > v)) + { + for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + for (; b < c;) + { + swapElements (this.SA, b, this.SA, c); + for (; (++b < c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + + if (a <= d) + { + c = b - 1; + int t; + int s; + if ((s = a - first) > (t = b - a)) + s = t; + + int e; + int f; + for (e = first, f = b - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + if ((s = d - c) > (t = last - d - 1)) + { + s = t; + } + for (e = b, f = last - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + first += (b - a); + last -= (d - c); + } + + return new PartitionResult (first, last); + } + + private void trCopy (int ISA, int ISAn, int first, int a, int b, int last, int depth) + { + int c, d, e; + int s; + int v = b - 1; + + for (c = first, d = a - 1; c <= d; ++c) + { + if ((s = this.SA[c] - depth) < 0) + { + s += ISAn - ISA; + } + if (this.SA[ISA + s] == v) + { + this.SA[++d] = s; + this.SA[ISA + s] = d; + } + } + for (c = last - 1, e = d + 1, d = b; e < d; --c) + { + if ((s = this.SA[c] - depth) < 0) + { + s += ISAn - ISA; + } + if (this.SA[ISA + s] == v) + { + this.SA[--d] = s; + this.SA[ISA + s] = d; + } + } + } + + private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRBudget budget, int size) + { + var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; + + int s; + int x = 0; + int limit; + int ssize; + + for (ssize = 0, limit = trLog (last - first);;) + { + int a; + int b; + int c; + int v; + int next; + if (limit < 0) + { + if (limit == -1) + { + if (!budget.update (size, last - first)) break; + var result = this.trPartition (ISA, ISAd - 1, ISAn, first, last, last - 1); + a = result.first; + b = result.last; + if ((first < a) || (b < last)) + { + if (a < last) + { + for (c = first, v = a - 1; c < a; ++c) + { + this.SA[ISA + this.SA[c]] = v; + } + } + if (b < last) + { + for (c = a, v = b - 1; c < b; ++c) + { + this.SA[ISA + this.SA[c]] = v; + } + } + + stack[ssize++] = new StackEntry (0, a, b, 0); + stack[ssize++] = new StackEntry (ISAd - 1, first, last, -2); + if ((a - first) <= (last - b)) + { + if (1 < (a - first)) + { + stack[ssize++] = new StackEntry (ISAd, b, last, trLog (last - b)); + last = a; + limit = trLog (a - first); + } else if (1 < (last - b)) + { + first = b; + limit = trLog (last - b); + } else + { + if (ssize == 0) return; + var entry = stack[--ssize]; + ISAd = entry.a; + first = entry.b; + last = entry.c; + limit = entry.d; + } + } else + { + if (1 < (last - b)) + { + stack[ssize++] = new StackEntry (ISAd, first, a, trLog (a - first)); + first = b; + limit = trLog (last - b); + } else if (1 < (a - first)) + { + last = a; + limit = trLog (a - first); + } else + { + if (ssize == 0) return; + var entry = stack[--ssize]; + ISAd = entry.a; + first = entry.b; + last = entry.c; + limit = entry.d; + } + } + } else + { + for (c = first; c < last; ++c) + { + this.SA[ISA + this.SA[c]] = c; + } + if (ssize == 0) return; + var entry = stack[--ssize]; + ISAd = entry.a; + first = entry.b; + last = entry.c; + limit = entry.d; + } + } else if (limit == -2) + { + a = stack[--ssize].b; + b = stack[ssize].c; + this.trCopy (ISA, ISAn, first, a, b, last, ISAd - ISA); + if (ssize == 0) return; + var entry = stack[--ssize]; + ISAd = entry.a; + first = entry.b; + last = entry.c; + limit = entry.d; + } else + { + if (0 <= this.SA[first]) + { + a = first; + do + { + this.SA[ISA + this.SA[a]] = a; + } while ((++a < last) && (0 <= this.SA[a])); + first = a; + } + if (first < last) + { + a = first; + do + { + this.SA[a] = ~this.SA[a]; + } while (this.SA[++a] < 0); + next = (this.SA[ISA + this.SA[a]] != this.SA[ISAd + this.SA[a]]) ? trLog (a - first + 1) : -1; + if (++a < last) + { + for (b = first, v = a - 1; b < a; ++b) + { + this.SA[ISA + this.SA[b]] = v; + } + } + + if ((a - first) <= (last - a)) + { + stack[ssize++] = new StackEntry (ISAd, a, last, -3); + ISAd += 1; + last = a; + limit = next; + } else + { + if (1 < (last - a)) + { + stack[ssize++] = new StackEntry (ISAd + 1, first, a, next); + first = a; + limit = -3; + } else + { + ISAd += 1; + last = a; + limit = next; + } + } + } else + { + if (ssize == 0) return; + var entry = stack[--ssize]; + ISAd = entry.a; + first = entry.b; + last = entry.c; + limit = entry.d; + } + } + continue; + } + + if ((last - first) <= BZip2DivSufSort.INSERTIONSORT_THRESHOLD) + { + if (!budget.update (size, last - first)) break; + this.trInsertionSort (ISA, ISAd, ISAn, first, last); + limit = -3; + continue; + } + + if (limit-- == 0) + { + if (!budget.update (size, last - first)) break; + this.trHeapSort (ISA, ISAd, ISAn, first, last - first); + for (a = last - 1; first < a; a = b) + { + for ( + x = this.trGetC (ISA, ISAd, ISAn, this.SA[a]), b = a - 1; + (first <= b) && (this.trGetC (ISA, ISAd, ISAn, this.SA[b]) == x); + --b + ) + { + this.SA[b] = ~this.SA[b]; + } + } + limit = -3; + continue; + } + + a = this.trPivot (ISA, ISAd, ISAn, first, last); + + swapElements (this.SA, first, this.SA, a); + v = this.trGetC (ISA, ISAd, ISAn, this.SA[first]); + for (b = first; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) == v);) + { } + if (((a = b) < last) && (x < v)) + { + for (; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + } + for (c = last; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) == v);) + { } + int d; + if ((b < (d = c)) && (x > v)) + { + for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + for (; b < c;) + { + swapElements (this.SA, b, this.SA, c); + for (; (++b < c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + { + if (x == v) + { + swapElements (this.SA, b, this.SA, a); + ++a; + } + } + for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + { + if (x == v) + { + swapElements (this.SA, c, this.SA, d); + --d; + } + } + } + + if (a <= d) + { + c = b - 1; + + int t; + if ((s = a - first) > (t = b - a)) + { + s = t; + } + int e; + int f; + for (e = first, f = b - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + if ((s = d - c) > (t = last - d - 1)) + { + s = t; + } + for (e = b, f = last - s; 0 < s; --s, ++e, ++f) + { + swapElements (this.SA, e, this.SA, f); + } + + a = first + (b - a); + b = last - (d - c); + next = (this.SA[ISA + this.SA[a]] != v) ? trLog (b - a) : -1; + + for (c = first, v = a - 1; c < a; ++c) + { + this.SA[ISA + this.SA[c]] = v; + } + if (b < last) + { + for (c = a, v = b - 1; c < b; ++c) + { + this.SA[ISA + this.SA[c]] = v; + } + } + + if ((a - first) <= (last - b)) + { + if ((last - b) <= (b - a)) + { + if (1 < (a - first)) + { + stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry (ISAd, b, last, limit); + last = a; + } else if (1 < (last - b)) + { + stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + first = b; + } else if (1 < (b - a)) + { + ISAd += 1; + first = a; + last = b; + limit = next; + } else + { + if (ssize == 0) return; + var entry = stack[--ssize]; + ISAd = entry.a; + first = entry.b; + last = entry.c; + limit = entry.d; + } + } else if ((a - first) <= (b - a)) + { + if (1 < (a - first)) + { + stack[ssize++] = new StackEntry (ISAd, b, last, limit); + stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + last = a; + } else if (1 < (b - a)) + { + stack[ssize++] = new StackEntry (ISAd, b, last, limit); + ISAd += 1; + first = a; + last = b; + limit = next; + } else + { + first = b; + } + } else + { + if (1 < (b - a)) + { + stack[ssize++] = new StackEntry (ISAd, b, last, limit); + stack[ssize++] = new StackEntry (ISAd, first, a, limit); + ISAd += 1; + first = a; + last = b; + limit = next; + } else + { + stack[ssize++] = new StackEntry (ISAd, b, last, limit); + last = a; + } + } + } else + { + if ((a - first) <= (b - a)) + { + if (1 < (last - b)) + { + stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry (ISAd, first, a, limit); + first = b; + } else if (1 < (a - first)) + { + stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + last = a; + } else if (1 < (b - a)) + { + ISAd += 1; + first = a; + last = b; + limit = next; + } else + { + stack[ssize++] = new StackEntry (ISAd, first, last, limit); + } + } else if ((last - b) <= (b - a)) + { + if (1 < (last - b)) + { + stack[ssize++] = new StackEntry (ISAd, first, a, limit); + stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + first = b; + } else if (1 < (b - a)) + { + stack[ssize++] = new StackEntry (ISAd, first, a, limit); + ISAd += 1; + first = a; + last = b; + limit = next; + } else + { + last = a; + } + } else + { + if (1 < (b - a)) + { + stack[ssize++] = new StackEntry (ISAd, first, a, limit); + stack[ssize++] = new StackEntry (ISAd, b, last, limit); + ISAd += 1; + first = a; + last = b; + limit = next; + } else + { + stack[ssize++] = new StackEntry (ISAd, first, a, limit); + first = b; + } + } + } + } else + { + if (!budget.update (size, last - first)) break; // BUGFIX : Added to prevent an infinite loop in the original code + limit += 1; + ISAd += 1; + } + } + + for (s = 0; s < ssize; ++s) + { + if (stack[s].d == -3) + { + this.lsUpdateGroup (ISA, stack[s].b, stack[s].c); + } + } + } + + private void trSort (int ISA, int x, int depth) + { + int first = 0; + + if (-x < this.SA[0]) + { + var budget = new TRBudget (x, trLog (x) * 2 / 3 + 1); + do + { + int t; + if ((t = this.SA[first]) < 0) + { + first -= t; + } else + { + int last = this.SA[ISA + t] + 1; + if (1 < (last - first)) + { + this.trIntroSort (ISA, ISA + depth, ISA + x, first, last, budget, x); + if (budget.chance == 0) + { + // Switch to Larsson-Sadakane sorting algorithm. + if (0 < first) + { + this.SA[0] = -first; + } + this.lsSort (ISA, x, depth); + break; + } + } + first = last; + + } + } while (first < x); + } + } + + private static int BUCKET_B (int c0, int c1) + { + return (c1 << 8) | c0; + } + + private static int BUCKET_BSTAR (int c0, int c1) + { + return (c0 << 8) | c1; + } + + private int sortTypeBstar (int[] bucketA, int[] bucketB) + { + var tempbuf = new int[256]; + + int i, j, k, t; + int c0, c1; + int flag; + + for (i = 1, flag = 1; i < this.n; ++i) + { + if (this.T[i - 1] != this.T[i]) + { + if ((this.T[i - 1] & 0xff) > (this.T[i] & 0xff)) + { + flag = 0; + } + break; + } + } + i = this.n - 1; + int m = this.n; + + int ti, ti1, t0; + if (((ti = (this.T[i] & 0xff)) < (t0 = (this.T[0] & 0xff))) || ((this.T[i] == this.T[0]) && (flag != 0))) + { + if (flag == 0) + { + ++bucketB[BUCKET_BSTAR (ti, t0)]; + this.SA[--m] = i; + } else + { + ++bucketB[BUCKET_B (ti, t0)]; + } + for (--i; (0 <= i) && ((ti = (this.T[i] & 0xff)) <= (ti1 = (this.T[i + 1] & 0xff))); --i) + { + ++bucketB[BUCKET_B (ti, ti1)]; + } + } + + for (; 0 <= i;) + { + do + { + ++bucketA[this.T[i] & 0xff]; + } while ((0 <= --i) && ((this.T[i] & 0xff) >= (this.T[i + 1] & 0xff))); + + if (0 <= i) + { + ++bucketB[BUCKET_BSTAR (this.T[i] & 0xff, this.T[i + 1] & 0xff)]; + this.SA[--m] = i; + for (--i; (0 <= i) && ((ti = (this.T[i] & 0xff)) <= (ti1 = (this.T[i + 1] & 0xff))); --i) + { + ++bucketB[BUCKET_B (ti, ti1)]; + } + } + } + m = this.n - m; + if (m == 0) + { + for (i = 0; i < this.n; ++i) + { + this.SA[i] = i; + } + return 0; + } + + for (c0 = 0, i = -1, j = 0; c0 < 256; ++c0) + { + t = i + bucketA[c0]; + bucketA[c0] = i + j; + i = t + bucketB[BUCKET_B (c0, c0)]; + for (c1 = c0 + 1; c1 < 256; ++c1) + { + j += bucketB[BUCKET_BSTAR (c0, c1)]; + bucketB[(c0 << 8) | c1] = j; + i += bucketB[BUCKET_B (c0, c1)]; + } + } + + int PAb = this.n - m; + int ISAb = m; + for (i = m - 2; 0 <= i; --i) + { + t = this.SA[PAb + i]; + c0 = this.T[t] & 0xff; + c1 = this.T[t + 1] & 0xff; + this.SA[--bucketB[BUCKET_BSTAR (c0, c1)]] = i; + } + t = this.SA[PAb + m - 1]; + c0 = this.T[t] & 0xff; + c1 = this.T[t + 1] & 0xff; + this.SA[--bucketB[BUCKET_BSTAR (c0, c1)]] = m - 1; + + int[] buf = this.SA; + int bufoffset = m; + int bufsize = this.n - (2 * m); + if (bufsize <= 256) + { + buf = tempbuf; + bufoffset = 0; + bufsize = 256; + } + + for (c0 = 255, j = m; 0 < j; --c0) + { + for (c1 = 255; c0 < c1; j = i, --c1) + { + i = bucketB[BUCKET_BSTAR (c0, c1)]; + if (1 < (j - i)) + { + this.subStringSort (PAb, i, j, buf, bufoffset, bufsize, 2, this.SA[i] == (m - 1), this.n); + } + } + } + + for (i = m - 1; 0 <= i; --i) + { + if (0 <= this.SA[i]) + { + j = i; + do + { + this.SA[ISAb + this.SA[i]] = i; + } while ((0 <= --i) && (0 <= this.SA[i])); + this.SA[i + 1] = i - j; + if (i <= 0) + { + break; + } + } + j = i; + do + { + this.SA[ISAb + (this.SA[i] = ~this.SA[i])] = j; + } while (this.SA[--i] < 0); + this.SA[ISAb + this.SA[i]] = j; + } + + this.trSort (ISAb, m, 1); + + i = this.n - 1; + j = m; + if (((this.T[i] & 0xff) < (this.T[0] & 0xff)) || ((this.T[i] == this.T[0]) && (flag != 0))) + { + if (flag == 0) + { + this.SA[this.SA[ISAb + --j]] = i; + } + for (--i; (0 <= i) && ((this.T[i] & 0xff) <= (this.T[i + 1] & 0xff)); --i) + { } + } + for (; 0 <= i;) + { + for (--i; (0 <= i) && ((this.T[i] & 0xff) >= (this.T[i + 1] & 0xff)); --i) + { } + if (0 <= i) + { + this.SA[this.SA[ISAb + --j]] = i; + for (--i; (0 <= i) && ((this.T[i] & 0xff) <= (this.T[i + 1] & 0xff)); --i) + { } + } + } + + for (c0 = 255, i = this.n - 1, k = m - 1; 0 <= c0; --c0) + { + for (c1 = 255; c0 < c1; --c1) + { + t = i - bucketB[BUCKET_B (c0, c1)]; + bucketB[BUCKET_B (c0, c1)] = i + 1; + + for (i = t, j = bucketB[BUCKET_BSTAR (c0, c1)]; j <= k; --i, --k) + { + this.SA[i] = this.SA[k]; + } + } + t = i - bucketB[BUCKET_B (c0, c0)]; + bucketB[BUCKET_B (c0, c0)] = i + 1; + if (c0 < 255) + { + bucketB[BUCKET_BSTAR (c0, c0 + 1)] = t + 1; + } + i = bucketA[c0]; + } + + return m; + } + + private int constructBWT (int[] bucketA, int[] bucketB) + { + int i; + int t = 0; + int s, s1; + int c0, c1, c2 = 0; + var orig = -1; + + for (c1 = 254; 0 <= c1; --c1) + { + int j; + for (i = bucketB[BUCKET_BSTAR (c1, c1 + 1)], j = bucketA[c1 + 1], t = 0, c2 = -1; i <= j; --j) + { + if (0 <= (s1 = s = this.SA[j])) + { + if (--s < 0) + s = this.n - 1; + + if ((c0 = (this.T[s] & 0xff)) <= c1) + { + this.SA[j] = ~s1; + if ((0 < s) && ((this.T[s - 1] & 0xff) > c0)) + { + s = ~s; + } + if (c2 == c0) + { + this.SA[--t] = s; + } else + { + if (0 <= c2) + bucketB[BUCKET_B (c2, c1)] = t; + + this.SA[t = bucketB[BUCKET_B (c2 = c0, c1)] - 1] = s; + } + } + } else + { + this.SA[j] = ~s; + } + } + } + + for (i = 0; i < this.n; ++i) + { + if (0 <= (s1 = s = this.SA[i])) + { + if (--s < 0) + s = this.n - 1; + + if ((c0 = (this.T[s] & 0xff)) >= (this.T[s + 1] & 0xff)) + { + if ((0 < s) && ((this.T[s - 1] & 0xff) < c0)) + s = ~s; + + if (c0 == c2) + { + this.SA[++t] = s; + } else + { + if (c2 != -1) // BUGFIX: Original code can write to bucketA[-1] + bucketA[c2] = t; + this.SA[t = bucketA[c2 = c0] + 1] = s; + } + } + + } else + { + s1 = ~s1; + } + + if (s1 == 0) + { + this.SA[i] = this.T[this.n - 1]; + orig = i; + } else + { + this.SA[i] = this.T[s1 - 1]; + } + } + + return orig; + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs new file mode 100644 index 000000000..790cc7cfb --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs @@ -0,0 +1,155 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// + /// A decoder for the BZip2 Huffman coding stage + /// + internal class BZip2HuffmanStageDecoder + { + // The BZip2BitInputStream from which Huffman codes are read + private readonly IBZip2BitInputStream bitInputStream; + + // The longest Huffman code length accepted by the decoder + private const int HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH = 23; + + // The Huffman table number to use for each group of 50 symbols + private readonly byte[] selectors; + + // The minimum code length for each Huffman table + private readonly int[] minimumLengths = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES]; + + /// + /// An array of values for each Huffman table that must be subtracted from the numerical value of + /// a Huffman code of a given bit length to give its canonical code index + /// + private readonly int[,] codeBases = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2]; + + /// + /// An array of values for each Huffman table that gives the highest numerical value of a Huffman + /// code of a given bit length + /// + private readonly int[,] codeLimits = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 1]; + + // A mapping for each Huffman table from canonical code index to output symbol + private readonly int[,] codeSymbols = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; + + // The Huffman table for the current group + private int currentTable; + + // The index of the current group within the selectors array + private int groupIndex = -1; + + // The byte position within the current group. A new group is selected every 50 decoded bytes + private int groupPosition = -1; + + /// + /// Public constructor + /// + /// The BZip2BitInputStream from which Huffman codes are read + /// The total number of codes (uniform for each table) + /// The Canonical Huffman code lengths for each table + /// The Huffman table number to use for each group of 50 symbols + public BZip2HuffmanStageDecoder(IBZip2BitInputStream bitInputStream, int alphabetSize, byte[,] tableCodeLengths, byte[] selectors) + { + this.bitInputStream = bitInputStream; + this.selectors = selectors; + this.currentTable = this.selectors[0]; + this.CreateHuffmanDecodingTables(alphabetSize, tableCodeLengths); + } + + /// + /// Decodes and returns the next symbol + /// + /// The decoded symbol + /// if the end of the input stream is reached while decoding + public int NextSymbol() + { + // Move to next group selector if required + if (((++this.groupPosition % BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH) == 0)) + { + this.groupIndex++; + if (this.groupIndex == this.selectors.Length) + throw new IOException("Error decoding BZip2 block"); + + this.currentTable = this.selectors[this.groupIndex] & 0xff; + } + + var codeLength = this.minimumLengths[this.currentTable]; + + // Starting with the minimum bit length for the table, read additional bits one at a time + // until a complete code is recognised + for (uint codeBits = this.bitInputStream.ReadBits(codeLength); codeLength <= BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH; codeLength++) + { + if (codeBits <= this.codeLimits[this.currentTable, codeLength]) + { + // Convert the code to a symbol index and return + return this.codeSymbols[this.currentTable, codeBits - this.codeBases[this.currentTable, codeLength]]; + } + codeBits = (codeBits << 1) | this.bitInputStream.ReadBits(1); + } + + // A valid code was not recognised + throw new IOException("Error decoding BZip2 block"); + } + + /// + /// Constructs Huffman decoding tables from lists of Canonical Huffman code lengths + /// + /// The total number of codes (uniform for each table) + /// The Canonical Huffman code lengths for each table + private void CreateHuffmanDecodingTables (int alphabetSize, byte[,] tableCodeLengths) + { + + for (int table = 0; table < tableCodeLengths.GetLength(0); table++) + { + int minimumLength = BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH; + int maximumLength = 0; + + // Find the minimum and maximum code length for the table + for (int i = 0; i < alphabetSize; i++) + { + maximumLength = Math.Max (tableCodeLengths[table, i], maximumLength); + minimumLength = Math.Min (tableCodeLengths[table, i], minimumLength); + } + this.minimumLengths[table] = minimumLength; + + // Calculate the first output symbol for each code length + for (int i = 0; i < alphabetSize; i++) + { + this.codeBases[table, tableCodeLengths[table, i] + 1]++; + } + for (int i = 1; i < BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2; i++) + { + this.codeBases[table, i] += this.codeBases[table, i - 1]; + } + + // Calculate the first and last Huffman code for each code length (codes at a given length are sequential in value) + for (int i = minimumLength, code = 0; i <= maximumLength; i++) + { + int base1 = code; + code += this.codeBases[table, i + 1] - this.codeBases[table, i]; + this.codeBases[table, i] = base1 - this.codeBases[table, i]; + this.codeLimits[table, i] = code - 1; + code <<= 1; + } + + // Populate the mapping from canonical code index to output symbol + for (int bitLength = minimumLength, codeIndex = 0; bitLength <= maximumLength; bitLength++) + { + for (int symbol = 0; symbol < alphabetSize; symbol++) + { + if (tableCodeLengths[table, symbol] == bitLength) + this.codeSymbols[table, codeIndex++] = symbol; + } + } + } + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs new file mode 100644 index 000000000..c03334e30 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs @@ -0,0 +1,344 @@ +// Bzip2 library for .net +// Modified by drone1400 +// Location: https://github.com/drone1400/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 +// Modified from the .net implementation by Jaime Olivares: http://github.com/jaime-olivares/bzip2 + +using System; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// + /// An encoder for the BZip2 Huffman encoding stage + /// + internal class BZip2HuffmanStageEncoder + { + // Used in initial Huffman table generation + private const int HUFFMAN_HIGH_SYMBOL_COST = 15; + + // The longest Huffman code length created by the encoder + private const int HUFFMAN_ENCODE_MAXIMUM_CODE_LENGTH = 20; + + // Number of symbols decoded after which a new Huffman table is selected + public const int HUFFMAN_GROUP_RUN_LENGTH = 50; + + // The BZip2BitOutputStream to which the Huffman tables and data is written + private readonly IBZip2BitOutputStream bitOutputStream; + + // The output of the Move To Front Transform and Run Length Encoding[2] stages + private readonly ushort[] mtfBlock; + + // The actual number of values contained in the mtfBlock array + private readonly int mtfLength; + + // The number of unique values in the mtfBlock array + private readonly int mtfAlphabetSize; + + // The global frequencies of values within the mtfBlock array + private readonly int[] mtfSymbolFrequencies; + + // The Canonical Huffman code lengths for each table + private readonly int[,] huffmanCodeLengths; + + // Merged code symbols for each table. The value at each position is ((code length << 24) | code) + private readonly int[,] huffmanMergedCodeSymbols; + + // The selectors for each segment + private readonly byte[] selectors; + + /// + /// Public constructor + /// + /// The BZip2BitOutputStream to write to + /// The MTF block data + /// The actual length of the MTF block + /// The size of the MTF block's alphabet + /// The frequencies the MTF block's symbols + public BZip2HuffmanStageEncoder(IBZip2BitOutputStream bitOutputStream, ushort[] mtfBlock, int mtfLength, int mtfAlphabetSize, int[] mtfSymbolFrequencies) + { + this.bitOutputStream = bitOutputStream; + this.mtfBlock = mtfBlock; + this.mtfSymbolFrequencies = mtfSymbolFrequencies; + this.mtfAlphabetSize = mtfAlphabetSize; + this.mtfLength = mtfLength; + + var totalTables = selectTableCount(mtfLength); + + this.huffmanCodeLengths = new int[totalTables, mtfAlphabetSize]; + this.huffmanMergedCodeSymbols = new int[totalTables, mtfAlphabetSize]; + this.selectors = new byte[(mtfLength + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH - 1) / BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH]; + } + + /// + /// Encodes and writes the block data + /// + /// on any I/O error writing the data + public void Encode() + { + // Create optimised selector list and Huffman tables + this.generateHuffmanOptimisationSeeds(); + for (var i = 3; i >= 0; i--) + { + this.optimiseSelectorsAndHuffmanTables(i == 0); + } + this.assignHuffmanCodeSymbols(); + + // Write out the tables and the block data encoded with them + this.writeSelectorsAndHuffmanTables(); + this.writeBlockData(); + } + + /// + /// Selects an appropriate table count for a given MTF length + /// + /// The length to select a table count for + /// The selected table count + private static int selectTableCount (int mtfLength) + { + if (mtfLength >= 2400) + return 6; + if (mtfLength >= 1200) + return 5; + if (mtfLength >= 600) + return 4; + return mtfLength >= 200 ? 3 : 2; + } + + /// + /// Generate a Huffman code length table for a given list of symbol frequencies + /// + /// The total number of symbols + /// The frequencies of the symbols + /// The array to which the generated code lengths should be written + /// + private static void generateHuffmanCodeLengths (int alphabetSize, int[,] symbolFrequencies, int[,] codeLengths, int index) + { + var mergedFrequenciesAndIndices = new int[alphabetSize]; + var sortedFrequencies = new int[alphabetSize]; + + // The Huffman allocator needs its input symbol frequencies to be sorted, but we need to return code lengths in the same order as the + // corresponding frequencies are passed in + + // The symbol frequency and index are merged into a single array of integers - frequency in the high 23 bits, index in the low 9 bits. + // 2^23 = 8,388,608 which is higher than the maximum possible frequency for one symbol in a block + // 2^9 = 512 which is higher than the maximum possible alphabet size (== 258) + // Sorting this array simultaneously sorts the frequencies and leaves a lookup that can be used to cheaply invert the sort + for (int i = 0; i < alphabetSize; i++) + mergedFrequenciesAndIndices[i] = (symbolFrequencies[index, i] << 9) | i; + + Array.Sort(mergedFrequenciesAndIndices); + for (int i = 0; i < alphabetSize; i++) + sortedFrequencies[i] = mergedFrequenciesAndIndices[i] >> 9; + + // Allocate code lengths - the allocation is in place, so the code lengths will be in the sortedFrequencies array afterwards + HuffmanAllocator.AllocateHuffmanCodeLengths (sortedFrequencies, BZip2HuffmanStageEncoder.HUFFMAN_ENCODE_MAXIMUM_CODE_LENGTH); + + // Reverse the sort to place the code lengths in the same order as the symbols whose frequencies were passed in + for (int i = 0; i < alphabetSize; i++) + codeLengths[index, mergedFrequenciesAndIndices[i] & 0x1ff] = sortedFrequencies[i]; + } + + /// + /// Generate initial Huffman code length tables, giving each table a different low cost section + /// of the alphabet that is roughly equal in overall cumulative frequency. Note that the initial + /// tables are invalid for actual Huffman code generation, and only serve as the seed for later + /// iterative optimisation in optimiseSelectorsAndHuffmanTables(int) + /// + private void generateHuffmanOptimisationSeeds () + { + int totalTables = this.huffmanCodeLengths.GetLength(0); + int remainingLength = this.mtfLength; + int lowCostEnd = -1; + + for (int i = 0; i < totalTables; i++) + { + int targetCumulativeFrequency = remainingLength / (totalTables - i); + int lowCostStart = lowCostEnd + 1; + int actualCumulativeFrequency = 0; + + while ((actualCumulativeFrequency < targetCumulativeFrequency) && (lowCostEnd < (this.mtfAlphabetSize - 1))) + { + actualCumulativeFrequency += this.mtfSymbolFrequencies[++lowCostEnd]; + } + + if ((lowCostEnd > lowCostStart) && (i != 0) && (i != (totalTables - 1)) && (((totalTables - i) & 1) == 0)) + actualCumulativeFrequency -= this.mtfSymbolFrequencies[lowCostEnd--]; + + for (var j = 0; j < this.mtfAlphabetSize; j++) + { + if ((j < lowCostStart) || (j > lowCostEnd)) + this.huffmanCodeLengths[i, j] = BZip2HuffmanStageEncoder.HUFFMAN_HIGH_SYMBOL_COST; + } + + remainingLength -= actualCumulativeFrequency; + } + } + + /// + /// Co-optimise the selector list and the alternative Huffman table code lengths. This method is + /// called repeatedly in the hope that the total encoded size of the selectors, the Huffman code + /// lengths and the block data encoded with them will converge towards a minimum.
+ /// If the data is highly incompressible, it is possible that the total encoded size will + /// instead diverge (increase) slightly.
+ ///
+ /// If true, write out the (final) chosen selectors + private void optimiseSelectorsAndHuffmanTables (bool storeSelectors) + { + int totalTables = this.huffmanCodeLengths.GetLength(0); + var tableFrequencies = new int[totalTables, this.mtfAlphabetSize]; + + int selectorIndex = 0; + + // Find the best table for each group of 50 block bytes based on the current Huffman code lengths + for (int groupStart = 0; groupStart < this.mtfLength;) + { + int groupEnd = Math.Min(groupStart + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH, this.mtfLength) - 1; + + // Calculate the cost of this group when encoded by each table + var cost = new int[totalTables]; + for (int i = groupStart; i <= groupEnd; i++) + { + int value = this.mtfBlock[i]; + for (var j = 0; j < totalTables; j++) + { + cost[j] += this.huffmanCodeLengths[j, value]; + } + } + + // Find the table with the least cost for this group + byte bestTable = 0; + var bestCost = cost[0]; + for (byte i = 1 ; i < totalTables; i++) + { + var tableCost = cost[i]; + if (tableCost < bestCost) + { + bestCost = tableCost; + bestTable = i; + } + } + + // Accumulate symbol frequencies for the table chosen for this block + for (int i = groupStart; i <= groupEnd; i++) + { + tableFrequencies[bestTable, this.mtfBlock[i]]++; + } + + // Store a selector indicating the table chosen for this block + if (storeSelectors) + { + this.selectors[selectorIndex++] = bestTable; + } + + groupStart = groupEnd + 1; + } + + // Generate new Huffman code lengths based on the frequencies for each table accumulated in this iteration + for (int i = 0; i < totalTables; i++) + { + generateHuffmanCodeLengths (this.mtfAlphabetSize, tableFrequencies, this.huffmanCodeLengths, i); + } + } + + // Assigns Canonical Huffman codes based on the calculated lengths + private void assignHuffmanCodeSymbols() + { + int totalTables = this.huffmanCodeLengths.GetLength(0); + + for (int i = 0; i < totalTables; i++) + { + int minimumLength = 32; + int maximumLength = 0; + for (var j = 0; j < this.mtfAlphabetSize; j++) + { + var length = this.huffmanCodeLengths[i, j]; + if (length > maximumLength) + { + maximumLength = length; + } + if (length < minimumLength) + { + minimumLength = length; + } + } + + int code = 0; + for (int j = minimumLength; j <= maximumLength; j++) + { + for (var k = 0; k < this.mtfAlphabetSize; k++) + { + if ((this.huffmanCodeLengths[i, k] & 0xff) == j) + { + this.huffmanMergedCodeSymbols[i, k] = (j << 24) | code; + code++; + } + } + code <<= 1; + } + } + } + + /// + /// Write out the selector list and Huffman tables + /// + /// on any I/O error writing the data + private void writeSelectorsAndHuffmanTables() + { + int totalSelectors = this.selectors.Length; + int totalTables = this.huffmanCodeLengths.GetLength(0); + + this.bitOutputStream.WriteBits (3, (uint)totalTables); + this.bitOutputStream.WriteBits (15, (uint)totalSelectors); + + // Write the selectors + var selectorMTF = new MoveToFront(); + for (int i = 0; i < totalSelectors; i++) + { + this.bitOutputStream.WriteUnary (selectorMTF.ValueToFront (this.selectors[i])); + } + + // Write the Huffman tables + for (int i = 0; i < totalTables; i++) + { + var currentLength = this.huffmanCodeLengths[i, 0]; + + this.bitOutputStream.WriteBits (5, (uint)currentLength); + + for (var j = 0; j < this.mtfAlphabetSize; j++) + { + var codeLength = this.huffmanCodeLengths[i, j]; + var value = (currentLength < codeLength) ? 2u : 3u; + var delta = Math.Abs (codeLength - currentLength); + while (delta-- > 0) + { + this.bitOutputStream.WriteBits (2, value); + } + this.bitOutputStream.WriteBoolean (false); + currentLength = codeLength; + } + } + } + + /// + /// Writes out the encoded block data + /// + /// on any I/O error writing the data + private void writeBlockData() + { + int selectorIndex = 0; + + for (int mtfIndex = 0; mtfIndex < this.mtfLength;) + { + int groupEnd = Math.Min (mtfIndex + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH, this.mtfLength) - 1; + + int index = this.selectors [selectorIndex++]; + + while (mtfIndex <= groupEnd) + { + var mergedCodeSymbol = this.huffmanMergedCodeSymbols[index, this.mtfBlock[mtfIndex++]]; + this.bitOutputStream.WriteBits(mergedCodeSymbol >> 24, (uint)mergedCodeSymbol); + } + } + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs new file mode 100644 index 000000000..be28b7fb1 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs @@ -0,0 +1,168 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// An encoder for the BZip2 Move To Front Transform and Run-Length Encoding[2] stages + /// + /// An encoder for the BZip2 Move To Front Transform and Run-Length Encoding[2] stages. + /// Although conceptually these two stages are separate, it is computationally efficient to perform them in one pass. + /// + internal class BZip2MTFAndRLE2StageEncoder + { + // The Burrows-Wheeler transformed block + private readonly int[] bwtBlock; + + // Actual length of the data in the bwtBlock array + private readonly int bwtLength; + + // At each position, true if the byte value with that index is present within the block, otherwise false + private readonly bool[] bwtValuesInUse; + + // The output of the Move To Front Transform and Run-Length Encoding[2] stages + private readonly ushort[] mtfBlock; + + // The global frequencies of values within the mtfBlock array + private readonly int[] mtfSymbolFrequencies = new int[BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; + + // Maximum possible Huffman alphabet size + public const int HUFFMAN_MAXIMUM_ALPHABET_SIZE = 258; + + // Huffman symbol used for run-length encoding + public const ushort RLE_SYMBOL_RUNA = 0; + + // Huffman symbol used for run-length encoding + public const ushort RLE_SYMBOL_RUNB = 1; + + // Gets the encoded MTF block + public ushort[] MtfBlock + { + get + { + return this.mtfBlock; + } + } + + // Gets The actual length of the MTF block + public int MtfLength { get; private set; } + + // Gets the size of the MTF block's alphabet + public int MtfAlphabetSize { get; private set; } + + // Gets the frequencies of the MTF block's symbols + public int[] MtfSymbolFrequencies + { + get + { + return this.mtfSymbolFrequencies; + } + } + + /// + /// Public constructor + /// + /// The Burrows Wheeler Transformed block data + /// The actual length of the BWT data + /// The values that are present within the BWT data. For each index, + /// true if that value is present within the data, otherwise false + public BZip2MTFAndRLE2StageEncoder(int[] bwtBlock, int bwtLength, bool[] bwtValuesPresent) + { + this.bwtBlock = bwtBlock; + this.bwtLength = bwtLength; + this.bwtValuesInUse = bwtValuesPresent; + this.mtfBlock = new ushort[bwtLength + 1]; + } + + // Performs the Move To Front transform and Run Length Encoding[1] stages + public void Encode() + { + var huffmanSymbolMap = new byte[256]; + var symbolMTF = new MoveToFront(); + + int totalUniqueValues = 0; + for (var i = 0; i < 256; i++) + { + if (this.bwtValuesInUse[i]) + huffmanSymbolMap[i] = (byte) totalUniqueValues++; + } + + int endOfBlockSymbol = totalUniqueValues + 1; + int mtfIndex = 0; + int repeatCount = 0; + int totalRunAs = 0; + int totalRunBs = 0; + + for (var i = 0; i < this.bwtLength; i++) + { + // Move To Front + int mtfPosition = symbolMTF.ValueToFront(huffmanSymbolMap[this.bwtBlock[i] & 0xff]); + + // Run Length Encode + if (mtfPosition == 0) + { + repeatCount++; + } else + { + if (repeatCount > 0) + { + repeatCount--; + while (true) + { + if ((repeatCount & 1) == 0) + { + this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA; + totalRunAs++; + } else + { + this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB; + totalRunBs++; + } + + if (repeatCount <= 1) + break; + + repeatCount = (repeatCount - 2) >> 1; + } + repeatCount = 0; + } + + this.mtfBlock[mtfIndex++] = (char) (mtfPosition + 1); + this.mtfSymbolFrequencies[mtfPosition + 1]++; + } + } + + if (repeatCount > 0) + { + repeatCount--; + while (true) + { + if ((repeatCount & 1) == 0) + { + this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA; + totalRunAs++; + } else + { + this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB; + totalRunBs++; + } + + if (repeatCount <= 1) + break; + + repeatCount = (repeatCount - 2) >> 1; + } + } + + this.mtfBlock[mtfIndex] = (ushort)endOfBlockSymbol; + this.mtfSymbolFrequencies[endOfBlockSymbol]++; + this.mtfSymbolFrequencies[BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA] += totalRunAs; + this.mtfSymbolFrequencies[BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB] += totalRunBs; + + this.MtfLength = mtfIndex + 1; + this.MtfAlphabetSize = endOfBlockSymbol + 1; + + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs new file mode 100644 index 000000000..2bbffb9bb --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs @@ -0,0 +1,74 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// + /// A CRC32 calculator + /// + internal class CRC32 + { + /// The CRC lookup table + private static readonly uint[] Crc32Lookup = + { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + /// The current CRC + private uint crc = 0xffffffff; + + /// Gets the current CRC + public uint CRC => ~this.crc; + + /// Updates the CRC with a single byte + /// The value to update the CRC with + public void UpdateCrc (int value) + { + this.crc = (this.crc << 8) ^ CRC32.Crc32Lookup[((this.crc >> 24) ^ value) & 0xff]; + } + + /// Update the CRC with a sequence of identical bytes + /// The value to update the CRC with + /// The number of bytes + public void UpdateCrc (int value, int count) + { + while (count-- > 0) + { + this.crc = (this.crc << 8) ^ CRC32.Crc32Lookup[((this.crc >> 24) ^ value) & 0xff]; + } + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs new file mode 100644 index 000000000..2c5ca5502 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs @@ -0,0 +1,201 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +using System; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// An in-place, length restricted Canonical Huffman code length allocator + /// + /// Based on the algorithm proposed by R.L.Milidiú, A.A.Pessoa and E.S.Laber + /// in "In-place Length-Restricted Prefix Coding" (see: http://www-di.inf.puc-rio.br/~laber/public/spire98.ps) + /// and incorporating additional ideas from the implementation of "shcodec" by Simakov Alexander + /// (see: http://webcenter.ru/~xander/) + /// + internal static class HuffmanAllocator + { + /// Allocates Canonical Huffman code lengths in place based on a sorted frequency array + /// On input, a sorted array of symbol frequencies; On output, an array of Canonical Huffman code lenghts + /// The maximum code length. Must be at least ceil(log2(array.length)) + public static void AllocateHuffmanCodeLengths(int[] array, int maximumLength) + { + switch (array.Length) + { + case 2: + array[1] = 1; + break; + case 1: + array[0] = 1; + return; + } + + // Pass 1 : Set extended parent pointers + SetExtendedParentPointers(array); + + // Pass 2 : Find number of nodes to relocate in order to achieve maximum code length + int nodesToRelocate = FindNodesToRelocate(array, maximumLength); + + // Pass 3 : Generate code lengths + if ((array[0] % array.Length) >= nodesToRelocate) + { + AllocateNodeLengths(array); + } else + { + var insertDepth = maximumLength - SignificantBits(nodesToRelocate - 1); + AllocateNodeLengthsWithRelocation(array, nodesToRelocate, insertDepth); + } + } + + /// + /// + /// + /// The code length array + /// The input position + /// The number of internal nodes to be relocated + /// The smallest k such that nodesToMove <= k <= i and i <= (array[k] % array.length) + private static int First (int[] array, int i, int nodesToMove) + { + int length = array.Length; + int limit = i; + int k = array.Length - 2; + + while ((i >= nodesToMove) && ((array[i] % length) > limit)) + { + k = i; + i -= (limit - i + 1); + } + i = Math.Max (nodesToMove - 1, i); + + while (k > (i + 1)) + { + var temp = (i + k) >> 1; + if ((array[temp] % length) > limit) + k = temp; + else + i = temp; + } + + return k; + } + + /// + /// Fills the code array with extended parent pointers + /// + /// The code length array + private static void SetExtendedParentPointers(int[] array) + { + int length = array.Length; + + array[0] += array[1]; + + for (int headNode = 0, tailNode = 1, topNode = 2; tailNode < (length - 1); tailNode++) + { + int temp; + if ((topNode >= length) || (array[headNode] < array[topNode])) + { + temp = array[headNode]; + array[headNode++] = tailNode; + } else + { + temp = array[topNode++]; + } + + if ((topNode >= length) || ((headNode < tailNode) && (array[headNode] < array[topNode]))) + { + temp += array[headNode]; + array[headNode++] = tailNode + length; + } else + { + temp += array[topNode++]; + } + + array[tailNode] = temp; + } + } + + /// + /// Finds the number of nodes to relocate in order to achieve a given code length limit + /// + /// The code length array + /// The maximum bit length for the generated codes + /// The number of nodes to relocate + private static int FindNodesToRelocate (int[] array, int maximumLength) + { + int currentNode = array.Length - 2; + + for (var currentDepth = 1; (currentDepth < (maximumLength - 1)) && (currentNode > 1); currentDepth++) + currentNode = First (array, currentNode - 1, 0); + + return currentNode; + } + + /// + /// A final allocation pass with no code length limit + /// + /// The code length array + private static void AllocateNodeLengths (int[] array) + { + int firstNode = array.Length - 2; + int nextNode = array.Length - 1; + + for (int currentDepth = 1, availableNodes = 2; availableNodes > 0; currentDepth++) + { + int lastNode = firstNode; + firstNode = First (array, lastNode - 1, 0); + + for (var i = availableNodes - (lastNode - firstNode); i > 0; i--) + array[nextNode--] = currentDepth; + + availableNodes = (lastNode - firstNode) << 1; + } + } + + /// + /// A final allocation pass that relocates nodes in order to achieve a maximum code length limit + /// + /// The code length array + /// The number of internal nodes to be relocated + /// The depth at which to insert relocated nodes + private static void AllocateNodeLengthsWithRelocation (int[] array, int nodesToMove, int insertDepth) + { + int firstNode = array.Length - 2; + int nextNode = array.Length - 1; + int currentDepth = (insertDepth == 1) ? 2 : 1; + int nodesLeftToMove = (insertDepth == 1) ? nodesToMove - 2 : nodesToMove; + + for (int availableNodes = currentDepth << 1; availableNodes > 0; currentDepth++) + { + int lastNode = firstNode; + firstNode = (firstNode <= nodesToMove) ? firstNode : First (array, lastNode - 1, nodesToMove); + + int offset = 0; + if (currentDepth >= insertDepth) + { + offset = Math.Min (nodesLeftToMove, 1 << (currentDepth - insertDepth)); + } else if (currentDepth == (insertDepth - 1)) + { + offset = 1; + if ((array[firstNode]) == lastNode) + firstNode++; + } + + for (var i = availableNodes - (lastNode - firstNode + offset); i > 0; i--) + array[nextNode--] = currentDepth; + + nodesLeftToMove -= offset; + availableNodes = (lastNode - firstNode + offset) << 1; + } + } + + private static int SignificantBits(int x) + { + int n; + for (n = 0; x > 0; n++) + { + x >>= 1; + } + return n; + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs new file mode 100644 index 000000000..047985067 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs @@ -0,0 +1,60 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +using System; +namespace SharpCompress.Compressors.BZip2MT.Algorithm +{ + /// + /// A 256 entry Move To Front transform + /// + internal class MoveToFront + { + /// The Move To Front list + private readonly byte[] mtf; + + /// Public constructor + public MoveToFront() + { + this.mtf = new byte[256]; + for (int i = 0; i < 256; i++) + this.mtf[i] = (byte)i; + } + + /// Moves a value to the head of the MTF list (forward Move To Front transform) + /// The value to move + /// The position the value moved from + public int ValueToFront(byte value) + { + int index = 0; + byte temp = this.mtf[0]; + + if (value == temp) + return index; + + this.mtf[0] = value; + while (temp != value) + { + index++; + byte temp2 = temp; + temp = this.mtf[index]; + this.mtf[index] = temp2; + } + + return index; + } + + /// Gets the value from a given index and moves it to the front of the MTF list (inverse Move To Front transform) + /// The index to move + /// The value at the given index + public byte IndexToFront(int index) + { + byte value = this.mtf[index]; + Array.ConstrainedCopy(this.mtf, 0, this.mtf, 1, index); + this.mtf[0] = value; + + return value; + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs new file mode 100644 index 000000000..a59594021 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs @@ -0,0 +1,111 @@ +// Bzip2 library for .net +// By Jaime Olivares +// Location: http://github.com/jaime-olivares/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 + +using System.IO; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.InputStream +{ + /// + /// Implements a bit-wise input stream + /// + internal class BZip2BitInputStream : IBZip2BitInputStream + { + // The stream from which bits are read + private Stream _inputStream; + + // A buffer of bits read from the input stream that have not yet been returned + private uint _bitBuffer; + + // The number of bits currently buffered in bitBuffer + private int _bitCount; + + /// Public constructor + /// The input stream to wrap + public BZip2BitInputStream(Stream inputStream) => this._inputStream = inputStream; + + public void Dispose() + { + // do nothing, the BZip2BitInputStream is not the owner of the _inputStream so don't dispose it + } + + /// Reads a single bit from the wrapped input stream + /// true if the bit read was 1, otherwise false + /// if no more bits are available in the input stream + public bool ReadBoolean() + { + if (this._bitCount > 0) + { + this._bitCount--; + } else + { + int byteRead = this._inputStream.ReadByte(); + + if (byteRead < 0) + throw new IOException("Insufficient data"); + + this._bitBuffer = (this._bitBuffer << 8) | (uint)byteRead; + this._bitCount += 7; + } + + return ((this._bitBuffer & (1 << this._bitCount))) != 0; + } + + /// Reads a zero-terminated unary number from the wrapped input stream + /// The unary number + /// if no more bits are available in the input stream + public uint ReadUnary() + { + for (uint unaryCount = 0; ; unaryCount++) + { + if (this._bitCount > 0) + { + this._bitCount--; + } else + { + var byteRead = this._inputStream.ReadByte(); + + if (byteRead < 0) + throw new IOException("Insufficient data"); + + this._bitBuffer = (this._bitBuffer << 8) | (uint)byteRead; + this._bitCount += 7; + } + + if (((this._bitBuffer & (1 << this._bitCount))) == 0) + return unaryCount; + } + } + + /// Reads up to 32 bits from the wrapped input stream + /// The number of bits to read (maximum 32) + /// The bits requested, right-aligned within the integer + /// if no more bits are available in the input stream + public uint ReadBits(int count) + { + if (this._bitCount < count) + { + while (this._bitCount < count) + { + int byteRead = this._inputStream.ReadByte(); + + if (byteRead < 0) + throw new IOException("Insufficient data"); + + this._bitBuffer = (this._bitBuffer << 8) | (uint)byteRead; + this._bitCount += 8; + } + } + + this._bitCount -= count; + + return (uint)((this._bitBuffer >> this._bitCount) & ((1 << count) - 1)); + } + + /// Reads 32 bits of input as an integer + /// The integer read + /// if 32 bits are not available in the input stream + public uint ReadInteger() => (this.ReadBits(16) << 16) | (this.ReadBits(16)); + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs new file mode 100644 index 000000000..6d4f6866a --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs @@ -0,0 +1,184 @@ +// Added by drone1400, December 2025 +// Location: https://github.com/drone1400/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.InputStream +{ + + /// + /// Helper class for splitting a BZip2 stream into multiple Memory Streams made up of individual BZip2 blocks. + /// The spitting is done by looking for the magic block header 6 byte sequence. + /// The output MemoryStreams bits are left aligned. + /// + internal class BZip2BitInputStreamSplitter : IDisposable + { + + // The stream from which bits are read + private BZip2BitInputStream? _bitInputStream; + + // internal temporary buffer + private MemoryStream? _buffer = null; + + private int _blockLevel = 0; + private int _blockSizeBytes = 0; + private uint _finalCrc = 0; + private ulong _crtVal = 0x00; + private int _bitCount = 0; + + public bool IsStreamComplete => this._bitInputStream is null; + public int BlockLevel => this._blockLevel; + public int BlockSizeBytes => this._blockSizeBytes; + public uint FinalCrc => this._finalCrc; + + public BZip2BitInputStreamSplitter(Stream inputStream, InputStreamHeaderCheckType inputStreamHeaderCheck = InputStreamHeaderCheckType.FULL_HEADER, int manualBlockLevel = 9) + { + this._bitInputStream = new BZip2BitInputStream(inputStream); + this.Initialize(inputStreamHeaderCheck, manualBlockLevel); + } + + private void Initialize(InputStreamHeaderCheckType inputStreamHeaderCheck, int blockLevel) + { + if (this._bitInputStream is null) + return; + + /* Read the stream header */ + try + { + switch (inputStreamHeaderCheck) + { + case InputStreamHeaderCheckType.FULL_HEADER: + { + uint marker1 = this._bitInputStream.ReadBits(16); + uint marker2 = this._bitInputStream.ReadBits(8); + blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); + if (marker1 != BZip2Constants.STREAM_START_MARKER_1 || + marker2 != BZip2Constants.STREAM_START_MARKER_2 || + blockLevel < 1 || blockLevel > 9) + { + throw new IOException("Invalid BZip2 header"); + } + break; + } + case InputStreamHeaderCheckType.NO_BZ: + { + uint marker2 = this._bitInputStream.ReadBits(8); + blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); + if (marker2 != BZip2Constants.STREAM_START_MARKER_2 || + blockLevel < 1 || blockLevel > 9) + { + throw new IOException("Invalid BZip2 header"); + } + break; + } + case InputStreamHeaderCheckType.NO_BZH: + { + blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); + if (blockLevel < 1 || blockLevel > 9) + { + throw new IOException("Invalid BZip2 header"); + } + break; + } + } + + this._blockLevel = blockLevel; + this._blockSizeBytes = blockLevel * 100000; + + this._crtVal = this._bitInputStream.ReadBits(24); + this._crtVal <<= 24; + this._crtVal |= this._bitInputStream.ReadBits(24); + + // expecting the first block header immediately after + if (this._crtVal != BZip2Constants.BLOCK_HEADER_MARKER) + { + throw new IOException("BZip2 stream format error"); + } + + this._buffer = new MemoryStream(this._blockSizeBytes + 100000); + } catch (IOException) + { + // If the stream header was not valid, stop trying to read more data + this._bitInputStream = null; + this._buffer = null; + throw; + } + } + + /// + /// Finds the next BZip2 block and returns an in memory copy of it + /// + /// MemoryStream + public MemoryStream? CopyNextBlock() + { + void FlushByte() + { + this._buffer.WriteByte((byte)(this._crtVal >> 48)); + this._crtVal &= 0x0000FFFF_FFFFFFFF; + this._bitCount = 0; + } + + void FlushPartialByte() + { + byte partialByte = (byte)(this._crtVal >> 48); + partialByte <<= (8 - this._bitCount); + this._buffer.WriteByte(partialByte); + + this._crtVal &= 0x0000FFFF_FFFFFFFF; + this._bitCount = 0; + } + + if (this._bitInputStream is null || this._buffer is null) + return null; + + while (true) + { + this._crtVal <<= 1; + this._crtVal |= this._bitInputStream.ReadBits(1); + ulong testVal = this._crtVal & 0x0000FFFF_FFFFFFFF; + this._bitCount++; + if (this._bitCount == 8) + { + FlushByte(); + } + + if (testVal == BZip2Constants.STREAM_END_MARKER) + { + // we found the magic bit sequence for end of stream, that means we're done! + if (this._bitCount > 0) + { + FlushPartialByte(); + } + + // read the 32 bit CRC at the end of the file + this._finalCrc = this._bitInputStream.ReadInteger(); + + MemoryStream retBuffer = this._buffer; + + // clear buffer and input stream to mark that we are done processing blocks for good + this._buffer = null; + this._bitInputStream = null; + return retBuffer; + } + + if (testVal == BZip2Constants.BLOCK_HEADER_MARKER) + { + // we found the magic bit sequence! + if (this._bitCount > 0) + { + FlushPartialByte(); + } + MemoryStream retBuffer = this._buffer; + this._buffer = new MemoryStream(this._blockSizeBytes); + return retBuffer; + } + } + } + public void Dispose() + { + this._bitInputStream?.Dispose(); + this._buffer?.Dispose(); + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs new file mode 100644 index 000000000..3ac770f22 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs @@ -0,0 +1,248 @@ +// Bzip2 library for .net +// Modified by drone1400 +// Location: https://github.com/drone1400/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 +// Modified from the .net implementation by Jaime Olivares: http://github.com/jaime-olivares/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Algorithm; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.InputStream +{ + /// An InputStream wrapper that decompresses BZip2 data + /// Instances of this class are not threadsafe + public class BZip2InputStream : Stream + { + // The stream from which compressed BZip2 data is read and decoded + private Stream _inputStream; + + // True if the underlying stream will be closed with the current Stream + private readonly bool _isOwner; + + // An InputStream wrapper that provides bit-level reads + private BZip2BitInputStream _bitInputStream; + + // (@code true} if the end of the compressed stream has been reached, otherwise false + private bool _streamComplete; + + /// + /// The declared block size of the stream (before final run-length decoding). The final block + /// will usually be smaller, but no block in the stream has to be exactly this large, and an + /// encoder could in theory choose to mix blocks of any size up to this value. Its function is + /// therefore as a hint to the decompressor as to how much working space is sufficient to + /// decompress blocks in a given stream + /// + private uint _streamBlockSize; + + // The merged CRC of all blocks decompressed so far + private uint _streamCrc; + + // The decompressor for the current block + private BZip2BlockDecompressor? _blockDecompressor; + + + /// Public constructor + /// The InputStream to wrap + /// if true, will close the stream when done + /// + /// Used when is NoHeader + public BZip2InputStream(Stream inputStream, bool isOwner = true, InputStreamHeaderCheckType inputStreamHeaderCheck = InputStreamHeaderCheckType.FULL_HEADER, int manualBlockLevel = 9) + { + this._inputStream = inputStream; + this._bitInputStream = new BZip2BitInputStream(inputStream); + this._isOwner = isOwner; + + // initialize stream immediately + this.InitializeStream(inputStreamHeaderCheck, manualBlockLevel); + // prepare first block + this.InitializeNextBlock(); + } + + #region Implementation of abstract members of Stream + + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Flush() => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'Flush()' method."); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); + public override void SetLength(long value) => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'SetLength(long value)' method."); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'Write(byte[] buffer, int offset, int count)' method."); + public override bool CanRead => this._inputStream.CanRead; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => this._inputStream.Length; + public override long Position + { + get => this._inputStream.Position; + set =>throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support Set operation for property 'Position'."); + } + + public override int ReadByte() + { + var nextByte = this._blockDecompressor?.Read() ?? -1; + + // if current block has reached its end, prepare next block and try reading again + if (nextByte == -1) + { + if (this.InitializeNextBlock()) + { + nextByte = this._blockDecompressor?.Read() ?? -1; + } + } + + return nextByte; + } + + public override int Read(byte[] destination, int offset, int length) + { + int bytesRead = this._blockDecompressor?.Read(destination, offset, length) ?? 0; + + // if current block has reached its end, prepare next block and try reading again + if (bytesRead == -1) + { + if (this.InitializeNextBlock()) + { + bytesRead = this._blockDecompressor?.Read(destination, offset, length) ?? 0; + } + } + + if (bytesRead == -1) bytesRead = 0; + + return bytesRead; + } + + // overriding Dispose instead of Close as recommended in https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.close?view=net-6.0 + protected override void Dispose(bool disposing) + { + this._streamComplete = true; + this._blockDecompressor = null; + + this._bitInputStream.Dispose(); + + if (this._isOwner) + { + //this._inputStream.Close(); + this._inputStream.Dispose(); + } + } + + #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + #endregion + + /// Reads the stream header and checks that the data appears to be a valid BZip2 stream + /// if the stream header is not valid + private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, int blockLevel) + { + /* If the stream has been explicitly closed, throw an exception */ + if (this._bitInputStream is null) + throw new IOException("Stream closed"); + + // If we're already at the end of the stream, do nothing + if (this._streamComplete) + return; + + // Read the stream header + try + { + switch (inputStreamHeaderCheck) + { + case InputStreamHeaderCheckType.FULL_HEADER: + { + uint marker1 = this._bitInputStream.ReadBits(16); + uint marker2 = this._bitInputStream.ReadBits(8); + blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); + if (marker1 != BZip2Constants.STREAM_START_MARKER_1 || + marker2 != BZip2Constants.STREAM_START_MARKER_2 || + blockLevel < 1 || blockLevel > 9) + { + throw new IOException("Invalid BZip2 header"); + } + break; + } + case InputStreamHeaderCheckType.NO_BZ: + { + uint marker2 = this._bitInputStream.ReadBits(8); + blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); + if (marker2 != BZip2Constants.STREAM_START_MARKER_2 || + blockLevel < 1 || blockLevel > 9) + { + throw new IOException("Invalid BZip2 header"); + } + break; + } + case InputStreamHeaderCheckType.NO_BZH: + { + blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); + if (blockLevel < 1 || blockLevel > 9) + { + throw new IOException("Invalid BZip2 header"); + } + break; + } + } + + this._streamBlockSize = (uint)(blockLevel * 100000); + } catch (IOException) + { + // If the stream header was not valid, stop trying to read more data + this._streamComplete = true; + throw; + } + } + + /// Prepares a new block for decompression if any remain in the stream + /// If a previous block has completed, its CRC is checked and merged into the stream CRC. + /// If the previous block was the final block in the stream, the stream CRC is validated + /// true if a block was successfully initialised, or false if the end of file marker was encountered + /// If either the block or stream CRC check failed, if the following data is + /// not a valid block-header or end-of-file marker, or if the following block could not be decoded + private bool InitializeNextBlock() + { + + // If we're already at the end of the stream, do nothing + if (this._streamComplete) + return false; + + // If a block is complete, check the block CRC and integrate it into the stream CRC + if (this._blockDecompressor != null) + { + uint blockCrc = this._blockDecompressor.CheckCrc(); + this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; + } + + // Read block-header or end-of-stream marker + uint marker1 = this._bitInputStream.ReadBits(24); + uint marker2 = this._bitInputStream.ReadBits(24); + + if (marker1 == BZip2Constants.BLOCK_HEADER_MARKER_1 && marker2 == BZip2Constants.BLOCK_HEADER_MARKER_2) + { + // Initialise a new block + try + { + this._blockDecompressor = new BZip2BlockDecompressor(this._bitInputStream, this._streamBlockSize); + } catch (IOException) + { + // If the block could not be decoded, stop trying to read more data + this._streamComplete = true; + throw; + } + return true; + } + if (marker1 == BZip2Constants.STREAM_END_MARKER_1 && marker2 == BZip2Constants.STREAM_END_MARKER_2) + { + // Read and verify the end-of-stream CRC + this._streamComplete = true; + uint storedCombinedCrc = this._bitInputStream.ReadInteger(); // .ReadBits(32); + + if (storedCombinedCrc != this._streamCrc) + throw new IOException("BZip2 stream CRC error"); + + return false; + } + + // If what was read is not a valid block-header or end-of-stream marker, the stream is broken + this._streamComplete = true; + throw new IOException("BZip2 stream format error"); + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs new file mode 100644 index 000000000..d265dfeaa --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Algorithm; +namespace SharpCompress.Compressors.BZip2MT.InputStream +{ + internal class BZip2ParallelInputDataBlock : IDisposable + { + + private int _blockId = 0; + private MemoryStream? _inputBlockBuffer; + private MemoryStream? _outputBuffer; + private int _outputBufferSize; + private int _blockSizeBytes; + private bool _isCrcOk = false; + private uint _crcValue; + + public long Length => this._outputBuffer?.Length ?? 0; + public long Position => this._outputBuffer?.Position ?? 0; + public bool IsDone => this.Position >= this.Length; + public int BlockId => this._blockId; + public bool IsCrcOk => this._isCrcOk; + public uint CrcValue => this._crcValue; + + public BZip2ParallelInputDataBlock(int blockId, MemoryStream inputBlockBuffer, int blockSizeBytes, int outputBufferSize) + { + this._blockId = blockId; + this._inputBlockBuffer = inputBlockBuffer; + this._blockSizeBytes = blockSizeBytes; + this._outputBufferSize = outputBufferSize; + } + + public void Dispose() + { + this._inputBlockBuffer?.Dispose(); + this._outputBuffer?.Dispose(); + } + + + /// + /// Decompresses the full block + /// + public void Decompress() + { + if (this._inputBlockBuffer is null) + throw new IOException("Attempted to Decompress the same block twice"); + + this._outputBuffer = new MemoryStream(this._outputBufferSize); + + // note: we need to skip the first 6 magic bytes... + this._inputBlockBuffer.Position = 6; + using BZip2BitInputStream inputStream = new BZip2BitInputStream(this._inputBlockBuffer); + BZip2BlockDecompressor blockDecompressor = new BZip2BlockDecompressor(inputStream, (uint)this._blockSizeBytes); + + int readCount = blockDecompressor.ReadAll(this._outputBuffer); + + // reset buffer position + this._outputBuffer.Position = 0; + + try + { + this._crcValue = blockDecompressor.CheckCrc(); + this._isCrcOk = true; + } catch (Exception) + { + this._crcValue = 0; + this._isCrcOk = false; + } + + this._inputBlockBuffer.Dispose(); + this._inputBlockBuffer = null; + } + + /// + /// reads a single byte from the output buffer + /// + /// + public int Read() + { + if (this._outputBuffer is null) + throw new IOException("Attempted to read decompressed data before decompressing block"); + + if (this._isCrcOk == false) + return -1; + if (this._outputBuffer.Position < this._outputBuffer.Length) + return this._outputBuffer.ReadByte(); + return -1; + } + + /// + /// Reads a number of bytes from the output buffer into the destination buffer + /// + /// destination buffer + /// starting position within destination buffer + /// maximum number of bytes to read + /// + public int Read(byte[] destination, int offset, int length) + { + if (this._outputBuffer is null) + throw new IOException("Attempted to read decompressed data before decompressing block"); + + if (this._isCrcOk == false) + return 0; + + return this._outputBuffer.Read(destination, offset, length); + } + + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs new file mode 100644 index 000000000..d7cbf54bf --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs @@ -0,0 +1,344 @@ +// Added by drone1400, July 2025 +// Location: https://github.com/drone1400/bzip2 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.InputStream +{ + /// An InputStream wrapper that decompresses BZip2 data using multiple threads + /// Instances of this class are not threadsafe + public class BZip2ParallelInputStream : Stream + { + #region Private fields + + // block size fields + private readonly int _blockLevel; + private readonly int _blockSizeBytes; + + private int _mtNextInputBlockId = 0; + private int _mtNextOutputBlockId = 0; + + // yikes! sounds bad right? + // if one of the worker threads, this is set to true and any further attempt to + // write/flush/close the stream will throw an exception + private bool _unsafeFatalException = false; + private Exception? _lastWorkerException = null; + + // dictionary of processed blocks + private readonly Dictionary _mtDecodedBlocks = new Dictionary(); + + private int _mtPendingBlocks = 0; + private readonly int _mtMaxPendingBlocks; + + private readonly int _mtWorkerBufferSize; + + private readonly object _syncRootProcesing = new object(); + + + // The stream from which compressed BZip2 data is read and decoded + private Stream _inputStream; + + // True if the underlying stream will be closed with the current Stream + private readonly bool _isOwner; + + // An InputStream wrapper that provides bit-level reads + private BZip2BitInputStreamSplitter _inputStreamSplitter; + + + // The merged CRC of all blocks decompressed so far + private uint _streamCrc; + + #endregion + + #region Public methods + + /// Public constructor + /// The InputStream to wrap + /// True if the underlying stream will be closed with the current Stream + /// + /// Used when is NoHeader + /// Maximum number of blocks to read/decompress at the same time + /// Minimum buffer size for each worker thread during decompression, in bytes. + public BZip2ParallelInputStream(Stream inputStream, bool isOwner = true, InputStreamHeaderCheckType inputStreamHeaderCheck = InputStreamHeaderCheckType.FULL_HEADER, int manualBlockLevel = 9, int maxPendingBlocks = 0, int workerBufferSize = 12582912) + { + if (maxPendingBlocks == 0) + { + maxPendingBlocks = Environment.ProcessorCount; + } + this._mtMaxPendingBlocks = maxPendingBlocks; + this._mtWorkerBufferSize = workerBufferSize; + + // initialize stream + this._inputStream = inputStream; + this._isOwner = isOwner; + this._inputStreamSplitter = new BZip2BitInputStreamSplitter(inputStream, inputStreamHeaderCheck, manualBlockLevel); + + this._blockLevel = this._inputStreamSplitter.BlockLevel; + this._blockSizeBytes = this._inputStreamSplitter.BlockSizeBytes; + } + + #endregion + + /// + /// Decompress a block of data + /// + /// + /// if compressing the block somehow fails... + private void MultiThreadWorkerAction(object? blockData) + { + try + { + if (blockData is BZip2ParallelInputDataBlock dataBlock) + { + // do the work + dataBlock.Decompress(); + + // queue up the finished decompressed block data + lock (this._syncRootProcesing) + { + this._mtDecodedBlocks.Add(dataBlock.BlockId, dataBlock); + } + } + } catch (Exception ex) + { + // set this without any locks... + this._lastWorkerException = ex; + this._unsafeFatalException = true; + + throw new IOException("BZip2 Processing thread somehow crashed... See inner exception for details!", ex); + } finally + { + lock (this._syncRootProcesing) + { + this._mtPendingBlocks --; + } + } + } + + /// + /// If there are not too many pending blocks being processed, reads another block from the input stream and adds it to the work queue. + /// + /// True if the data block was queued up, false otherwise + /// if one of the worker threads or writing to the final bit stream experienced an exception + /// Should only be called from within or + private bool TryToQueueUpAnotherBlock() + { + if (this._unsafeFatalException) + { + throw new IOException("One of the decompression threads somehow failed... This should never happen.", this._lastWorkerException); + } + + if (this._inputStreamSplitter.IsStreamComplete) + { + return false; + } + + lock (this._syncRootProcesing) + { + if (this._mtPendingBlocks >= this._mtMaxPendingBlocks) + return false; + } + + // extract the next block... + MemoryStream? ms = this._inputStreamSplitter.CopyNextBlock(); + + // this should be impossible if this._inputStreamSplitter.IsStreamComplete is not true + if (ms is null) + return false; + + lock (this._syncRootProcesing) + { + this._mtPendingBlocks ++; + BZip2ParallelInputDataBlock data = new BZip2ParallelInputDataBlock(this._mtNextInputBlockId++, ms, this._blockSizeBytes, this._mtWorkerBufferSize); + ThreadPool.QueueUserWorkItem(this.MultiThreadWorkerAction, data); + } + + return true; + } + + /// + /// Checks if we are done processing the stream. This is done by checing if there are any pending blocks to be decoded or read. + /// Also by checking if the input stream splitter helper has reached the end of stream markers. + /// + /// True if we are done, false otherwise + /// If there's a CRC error at end of the stream. + /// /// Should only be called from within or + private bool CheckIfCompletelyDone() + { + if (this._unsafeFatalException) + { + throw new IOException("One of the decompression threads somehow failed... This should never happen.", this._lastWorkerException); + } + + lock (this._syncRootProcesing) + { + if (this._mtPendingBlocks == 0 && + this._mtDecodedBlocks.Count == 0 && + this._inputStreamSplitter.IsStreamComplete) + { + if (this._inputStreamSplitter.FinalCrc != this._streamCrc) + { + throw new IOException("BZip2 stream CRC error"); + } + // we are compeltely done, can no longer do anything! + return true; + } + } + + return false; + } + + #region Implementation of abstract members of Stream + + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Flush() => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'Flush()' method."); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); + public override void SetLength(long value) => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'SetLength(long value)' method."); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'Write(byte[] buffer, int offset, int count)' method."); + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => this._inputStream.Length; + public override long Position + { + get => this._inputStream.Position; + set => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support Set operation for property 'Position'."); + } + + public override int ReadByte() + { + while (true) + { + // check if the next output block is done + BZip2ParallelInputDataBlock? data = null; + lock (this._syncRootProcesing) + { + if (this._mtDecodedBlocks.ContainsKey(this._mtNextOutputBlockId)) + { + data = this._mtDecodedBlocks[this._mtNextOutputBlockId]; + } + } + + if (data != null) + { + if (data.IsCrcOk == false) + { + this._unsafeFatalException = true; + throw new IOException("BZip2 block decompression CRC error..."); + } + + if (data.IsDone == false) + { + // can read next byte + return data.Read(); + } + + // decompressor is done! + lock (this._syncRootProcesing) + { + uint blockCrc = data.CrcValue; + this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; + + this._mtDecodedBlocks.Remove(this._mtNextOutputBlockId); + this._mtNextOutputBlockId ++; + + // continue to processing next block that is already decoded, skip queueing up more blocks... + continue; + } + } + + // queue up the next block + while (this.TryToQueueUpAnotherBlock()) + { } + + // check if we are completely done + if (this.CheckIfCompletelyDone()) + return -1; + + Thread.Sleep(1); + } + } + + public override int Read(byte[] destination, int offset, int length) + { + int totalRead = 0; + int remaining = length; + + while (true) + { + // check if the next output block is done + BZip2ParallelInputDataBlock? data = null; + lock (this._syncRootProcesing) + { + if (this._mtDecodedBlocks.ContainsKey(this._mtNextOutputBlockId)) + { + data = this._mtDecodedBlocks[this._mtNextOutputBlockId]; + } + } + + if (data != null) + { + if (data.IsCrcOk == false) + { + this._unsafeFatalException = true; + throw new IOException("BZip2 block decompression CRC error..."); + } + + int readCount = data.Read(destination, offset, remaining); + remaining -= readCount; + offset += readCount; + totalRead += readCount; + + if (totalRead >= length) + return totalRead; + + if (data.IsDone) + { + // decompressor is done! + lock (this._syncRootProcesing) + { + uint blockCrc = data.CrcValue; + this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; + + this._mtDecodedBlocks.Remove(this._mtNextOutputBlockId); + this._mtNextOutputBlockId ++; + + // continue to processing next block that is already decoded, skip queueing up more blocks... + continue; + } + } + } + + // queue up the next block + while (this.TryToQueueUpAnotherBlock()) + { } + + // check if we are completely done + if (this.CheckIfCompletelyDone()) + return totalRead; + + Thread.Sleep(1); + } + } + + // overriding Dispose instead of Close as recommended in https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.close?view=net-6.0 + protected override void Dispose(bool disposing) + { + this._mtDecodedBlocks.Clear(); + + if (this._isOwner) + { + //this._inputStream.Close(); + this._inputStream.Dispose(); + } + } + + #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + #endregion + + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs b/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs new file mode 100644 index 000000000..8518911ff --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs @@ -0,0 +1,43 @@ +// Added by drone1400, December 2025 +// Location: https://github.com/drone1400/bzip2 + +namespace SharpCompress.Compressors.BZip2MT.Interface { + internal static class BZip2Constants { + public const uint STREAM_START_MARKER = 0x425A68; + public const ulong STREAM_END_MARKER = 0x177245385090; + public const ulong BLOCK_HEADER_MARKER = 0x314159265359; + + /// The first 2 bytes of a Bzip2 marker, BZ + public const uint STREAM_START_MARKER_1 = 0x425a; + + /// The 'h' that distinguishes BZip from BZip2 + public const uint STREAM_START_MARKER_2 = 0x68; + + /// First three bytes of the end of stream marker + public const uint STREAM_END_MARKER_1 = 0x177245; + + /// Last three bytes of the end of stream marker + public const uint STREAM_END_MARKER_2 = 0x385090; + + // First three bytes of the block header marker + public const uint BLOCK_HEADER_MARKER_1 = 0x314159; + + // Last three bytes of the block header marker + public const uint BLOCK_HEADER_MARKER_2 = 0x265359; + } + + public enum InputStreamHeaderCheckType + { + // check for full header (ex: BZh9 ) + FULL_HEADER, + + // skips BZ part of header + NO_BZ, + + // skips BZh part of header + NO_BZH, + + // skips BZh and block level, aka the whole header + NO_HEADER, + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs new file mode 100644 index 000000000..1501992e7 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +namespace SharpCompress.Compressors.BZip2MT.Interface +{ + internal interface IBZip2BitInputStream : IDisposable + { + /// Reads a single bit from the wrapped input stream + /// true if the bit read was 1, otherwise false + /// if no more bits are available in the input stream + public bool ReadBoolean(); + + /// Reads a zero-terminated unary number from the wrapped input stream + /// The unary number + /// if no more bits are available in the input stream + public uint ReadUnary(); + + /// Reads up to 32 bits from the wrapped input stream + /// The number of bits to read (maximum 32) + /// The bits requested, right-aligned within the integer + /// if no more bits are available in the input stream + public uint ReadBits(int count); + + /// Reads 32 bits of input as an integer + /// The integer read + /// if 32 bits are not available in the input stream + public uint ReadInteger(); + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs new file mode 100644 index 000000000..4855e7b4a --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs @@ -0,0 +1,47 @@ +// Added by drone1400, July 2022 +// Location: https://github.com/drone1400/bzip2 + +using System; +namespace SharpCompress.Compressors.BZip2MT.Interface +{ + /// + /// Interface for a stream wrapper that implements bit-wise write operations + /// + internal interface IBZip2BitOutputStream : IDisposable + { + /// + /// Writes a single bit to the wrapped output stream + /// + /// The bit to write + /// if an error occurs writing to the stream + public void WriteBoolean (bool value); + + /// + /// Writes a zero-terminated unary number to the wrapped output stream + /// + /// The number to write (must be non-negative) + /// if an error occurs writing to the stream + public void WriteUnary (int value); + + /// + /// Writes up to 24 bits to the wrapped output stream + /// + /// The number of bits to write (maximum 24) + /// The bits to write + /// if an error occurs writing to the stream + public void WriteBits (int count, uint value) ; + + /// + /// Writes an integer as 32 bits of output + /// + /// The integer to write + /// if an error occurs writing to the stream + public void WriteInteger (uint value) ; + + /// + /// Writes any remaining bits to the output stream, zero padding to a whole byte as required + /// + /// if an error occurs writing to the stream + public void Flush() ; + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs new file mode 100644 index 000000000..f947f7680 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs @@ -0,0 +1,92 @@ +// Bzip2 library for .net +// Modified by drone1400 +// Location: https://github.com/drone1400/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 +// Modified from the .net implementation by Jaime Olivares: http://github.com/jaime-olivares/bzip2 + +using System.IO; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.OutputStream +{ + /// Implements a bit-wise output stream + /// + /// Allows the writing of single bit booleans, unary numbers, bit + /// strings of arbitrary length(up to 24 bits), and bit aligned 32-bit integers.A single byte at a + /// time is written to the wrapped stream when sufficient bits have been accumulated + /// + internal class BZip2BitOutputStream : IBZip2BitOutputStream + { + // The stream to which bits are written + private readonly Stream _outputStream; + + // A buffer of bits waiting to be written to the output stream + private uint _bitBuffer; + + // The number of bits currently buffered in bitBuffer + private int _bitCount; + + /// + /// Public constructor + /// + /// The OutputStream to wrap + public BZip2BitOutputStream(Stream outputStream) => this._outputStream = outputStream; + + public void Dispose() + { + // do nothing, the BZip2BitOutputStream is not the owner of the _outputStream so don't dispose it + } + + #region IBZip2BitOutputStream implementation + + public void WriteBoolean (bool value) + { + this._bitCount++; + this._bitBuffer |= ((value ? 1u : 0u) << (32 - this._bitCount)); + + if (this._bitCount == 8) + { + this._outputStream.WriteByte((byte)(this._bitBuffer >> 24)); + this._bitBuffer = 0; + this._bitCount = 0; + } + } + + public void WriteUnary (int value) + { + while (value-- > 0) + { + this.WriteBoolean (true); + } + this.WriteBoolean (false); + } + + public void WriteBits (int count, uint value) + { + this._bitBuffer |= ((value << (32 - count)) >> this._bitCount); + this._bitCount += count; + + while (this._bitCount >= 8) + { + this._outputStream.WriteByte((byte)(this._bitBuffer >> 24)); + this._bitBuffer <<= 8; + this._bitCount -= 8; + } + } + + public void WriteInteger (uint value) + { + this.WriteBits (16, (value >> 16) & 0xffff); + this.WriteBits (16, value & 0xffff); + } + + public void Flush() + { + if (this._bitCount > 0) + { + this.WriteBits (8 - this._bitCount, 0); + } + } + + #endregion + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs new file mode 100644 index 000000000..437803384 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs @@ -0,0 +1,179 @@ +// Bzip2 library for .net +// Modified by drone1400 +// Location: https://github.com/drone1400/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 +// Modified from the .net implementation by Jaime Olivares: http://github.com/jaime-olivares/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Algorithm; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.OutputStream +{ + /// An OutputStream wrapper that compresses BZip2 data + /// Instances of this class are not threadsafe + public class BZip2OutputStream : Stream + { + // The stream to which compressed BZip2 data is written + private Stream _outputStream; + + // An OutputStream wrapper that provides bit-level writes + private readonly BZip2BitOutputStream _bitOutputStream; + + // (@code true} if the compressed stream has been finished, otherwise false + private bool _streamFinished; + + // The declared maximum block size of the stream (before final run-length decoding) + private readonly int _streamBlockSize; + + // The merged CRC of all blocks compressed so far + private uint _streamCrc; + + // The compressor for the current block + private BZip2BlockCompressor? _blockCompressor; + + // True if the underlying stream will be closed with the current Stream + private bool _isOwner; + + /// Public constructor + /// The output stream to write to + /// The BZip2 block size as a multiple of 100,000 bytes (minimum 1, maximum 9) + /// True if the underlying stream will be closed with the current Stream + /// On any I/O error writing to the output stream + /// Larger block sizes require more memory for both compression and decompression, + /// but give better compression ratios. 9 will usually be the best value to use + public BZip2OutputStream(Stream outputStream, bool isOwner = true, int blockSizeMultiplier = 9) + { + if (outputStream is null) + throw new ArgumentException("Null output stream"); + + if ((blockSizeMultiplier < 1) || (blockSizeMultiplier > 9)) + throw new ArgumentException("Invalid BZip2 block size" + blockSizeMultiplier); + + this._streamBlockSize = blockSizeMultiplier * 100000; + this._outputStream = outputStream; + this._bitOutputStream = new BZip2BitOutputStream(this._outputStream); + this._isOwner = isOwner; + + this._bitOutputStream.WriteBits(16, BZip2Constants.STREAM_START_MARKER_1); + this._bitOutputStream.WriteBits(8, BZip2Constants.STREAM_START_MARKER_2); + this._bitOutputStream.WriteBits(8, (uint)('0' + blockSizeMultiplier)); + + this.InitialiseNextBlock(); + } + + #region Implementation of abstract members of Stream + + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Flush () => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'Flush()' method."); + + public override int Read (byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'Read(byte[] buffer, int offset, int count)' method."); + + public override long Seek (long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); + + public override void SetLength (long value) => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'SetLength(long value)' method."); + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => this._outputStream.CanWrite; + + public override long Length => this._outputStream.Length; + + public override long Position + { + get => this._outputStream.Position; + set =>throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support Set operation for property 'Position'."); + } + + public override void WriteByte(byte value) + { + if (this._outputStream is null) + throw new IOException("Stream closed"); + + if (this._blockCompressor is null || this._streamFinished) + throw new IOException("Write beyond end of stream"); + + if (!this._blockCompressor.Write(value & 0xff)) + { + this.CloseBlock(); + this.InitialiseNextBlock(); + this._blockCompressor.Write(value & 0xff); + } + } + + public override void Write(byte[] data, int offset, int length) + { + if (this._outputStream is null) + throw new IOException("Stream closed"); + + if (this._blockCompressor is null || this._streamFinished) + throw new IOException("Write beyond end of stream"); + + while (length > 0) + { + int bytesWritten; + if ((bytesWritten = this._blockCompressor.Write(data, offset, length)) < length) + { + this.CloseBlock(); + this.InitialiseNextBlock(); + } + offset += bytesWritten; + length -= bytesWritten; + } + } + + // overriding Dispose instead of Close as recommended in https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.close?view=net-6.0 + protected override void Dispose(bool disposing) + { + this.Finish(); + if (this._isOwner) + { + this._outputStream.Dispose(); + } + } + + #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + #endregion + + /// Initialises a new block for compression + private void InitialiseNextBlock() => this._blockCompressor = new BZip2BlockCompressor (this._bitOutputStream, this._streamBlockSize); + + /// Compress and write out the block currently in progress + /// If no bytes have been written to the block, it is discarded + /// On any I/O error writing to the output stream + private void CloseBlock() + { + if (this._blockCompressor is null || this._blockCompressor.IsEmpty) + return; + + this._blockCompressor.CloseBlock(); + this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ this._blockCompressor.CRC; + } + + /// Compresses and writes out any as yet unwritten data, then writes the end of the BZip2 stream + /// The underlying OutputStream is not closed + /// On any I/O error writing to the output stream + private void Finish() + { + if (!this._streamFinished) + { + this._streamFinished = true; + try + { + this.CloseBlock(); + this._bitOutputStream.WriteBits(24, BZip2Constants.STREAM_END_MARKER_1); + this._bitOutputStream.WriteBits(24, BZip2Constants.STREAM_END_MARKER_2); + this._bitOutputStream.WriteInteger(this._streamCrc); + this._bitOutputStream.Flush(); + this._outputStream.Flush(); + } finally + { + this._blockCompressor = null; + } + } + } + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs new file mode 100644 index 000000000..e9a52e593 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs @@ -0,0 +1,171 @@ +// Added by drone1400, July 2022 +// Location: https://github.com/drone1400/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Algorithm; +using SharpCompress.Compressors.BZip2MT.Interface; +namespace SharpCompress.Compressors.BZip2MT.OutputStream +{ + /// A collection of bit output data + /// + /// Allows the writing of single bit booleans, unary numbers, bit + /// strings of arbitrary length(up to 24 bits), and bit aligned 32-bit integers.A single byte at a + /// time is written to a list of structures that serves as a buffer for use in parallelized + /// execution of block compression + /// + internal class BZip2ParallelOutputDataBlock : IBZip2BitOutputStream + { + private BZip2BitOutputStream _internalBitStream; + private MemoryStream _buffer; + private long _bitCount = 0; + + /// + /// Compressed block CRC to be stored here when block is finished + /// + public uint BlockCrc => this._blockCrc; + private uint _blockCrc = 0; + + /// + /// Indicates that the compression block is full + /// + public bool IsFull => this._isFull; + private bool _isFull = false; + + /// + /// Block numeric id for distinguishing blocks + /// + public int BlockId => this._blockId; + private int _blockId; + + /// + /// Number of bytes loaded into the block compressor + /// + public int LoadedBytes => this._loadedBytes; + private int _loadedBytes = 0; + private readonly BZip2BlockCompressor _compressor; + + /// + /// Public constructor + /// + /// block size in bytes, also initial internal buffer list capacity + /// Block number id, used to distinguish blocks in multithreadding + public BZip2ParallelOutputDataBlock(int blockSizeBytes, int blockId) + { + this._buffer = new MemoryStream(blockSizeBytes + 100000); + this._internalBitStream = new BZip2BitOutputStream(this._buffer); + this._blockId = blockId; + this._compressor = new BZip2BlockCompressor(this, blockSizeBytes); + } + + public void Dispose() + { + this._internalBitStream.Dispose(); + this._buffer.Dispose(); + } + + /// + /// Loads a byte into the 's first RLE stage + /// + /// Byte + /// True if byte was loaded, false if byte could not be loaded because block compressor is full + public bool LoadByte(byte value) + { + if (this._compressor.Write(value)) + { + this._loadedBytes++; + return true; + } + + // could not load the byte, means block is full + this._isFull = true; + + return false; + } + + /// + /// Loads bytes from a buffer into the 's first RLE stage + /// + /// Byte buffer + /// Byte buffer offset + /// Number of bytes to load + /// Number of bytes actually loaded + public int LoadBytes(byte[] buff, int offset, int length) + { + int count = this._compressor.Write(buff, offset, length); + this._loadedBytes += count; + if (count < length) + { + // could not load all the bytes, means block is full + this._isFull = true; + } + return count; + } + + /// + /// Starts the actual compression + /// + public void CompressBytes() + { + this._compressor.CloseBlock(); + this._blockCrc = this._compressor.CRC; + } + + /// + /// Writes all the buffer data to the real + /// + /// The real bit output stream + /// if an error occurs writing to the stream + public void WriteToRealOutputStream(BZip2BitOutputStream stream) + { + this._internalBitStream.Flush(); + + this._buffer.Position = 0; + while (this._bitCount >= 8) + { + int b = this._buffer.ReadByte(); + stream.WriteBits(8, (uint)b); + this._bitCount -= 8; + } + + if (this._bitCount > 0) + { + int b = this._buffer.ReadByte(); + b = (b >> (8 - (int)this._bitCount)); + stream.WriteBits((int)this._bitCount, (uint)b); + this._bitCount = 0; + } + } + + #region IBZip2BitOutputStream implementation + + public void WriteBoolean(bool value) + { + this._bitCount++; + this._internalBitStream.WriteBoolean(value); + } + public void WriteUnary(int value) + { + while (value-- > 0) + { + this._bitCount++; + this._internalBitStream.WriteBoolean (true); + } + this._bitCount++; + this._internalBitStream.WriteBoolean (false); + } + public void WriteBits(int count, uint value) + { + this._bitCount += count; + this._internalBitStream.WriteBits(count, value); + } + public void WriteInteger(uint value) + { + this._bitCount += 32; + this._internalBitStream.WriteInteger(value); + } + public void Flush() => this._internalBitStream.Flush(); + + #endregion + } +} diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs new file mode 100644 index 000000000..20152c048 --- /dev/null +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs @@ -0,0 +1,382 @@ +// Added by drone1400, July 2022 +// Location: https://github.com/drone1400/bzip2 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +namespace SharpCompress.Compressors.BZip2MT.OutputStream +{ + + /// An OutputStream wrapper that compresses BZip2 data using multiple threads + /// Instances of this class are not threadsafe + public class BZip2ParallelOutputStream : Stream + { + // block size fields + private readonly int _compressBlockSize; + private readonly int _blockLevel; + + private int _mtNextInputBlockId = 0; + private int _mtNextOutputBlockId = 0; + + // flag indicating input stream is finished + private bool _mtStreamIsFinished = false; + + // yikes! sounds bad right? + // if one of the worker threads, this is set to true and any furhter attempt to + // write/flush/close the stream will throw an exception + private bool _unsafeFatalException = false; + private Exception? _lastWorkerException = null; + + // dictionary of processed blocks + private readonly Dictionary _mtProcessedBlocks = new Dictionary(); + + private int _mtPendingBlocks = 0; + + private readonly object _syncRootProcesing = new object(); + private readonly object _syncRootOutputStream = new object(); + + // The output stream + private readonly Stream _outputStream; + + // The bit output stream + private readonly BZip2BitOutputStream _bitStream; + + // The merged CRC of all blocks compressed so far + private uint _streamCrc = 0; + + // + private BZip2ParallelOutputDataBlock _currentBlockBuffer; + + // True if the underlying stream will be closed with the current Stream + private readonly bool _isOwner; + + // for debug purposes... + // private int _debugMaxMetaBufferDataPairCount = 0; + + /// + /// Public constructor + /// + /// The output stream in which to write the compressed data + /// True if the underlying stream will be closed with the current Stream + /// The BZip2 block size as a multiple of 100,000 bytes (minimum 1, maximum 9) + /// For best performance, compressor thread number should be equal to CPU core count, but results may vary + public BZip2ParallelOutputStream(Stream output, bool isOwner = true, int blockLevel = 9) + { + this._outputStream = output; + this._bitStream = new BZip2BitOutputStream(output); + + if (blockLevel < 1 ) blockLevel = 1; + if (blockLevel > 9 ) blockLevel = 9; + this._blockLevel = blockLevel; + + // evaluate thread count + // if (compressorThreads < 1) compressorThreads = 1; + // if (compressorThreads > ABSOLUTE_MAX_THREADS) compressorThreads = ABSOLUTE_MAX_THREADS; + // this._mtCompressorThreads = compressorThreads; + + // supposedly a block can only expand 1.25x, so 0.8 of normal block size should always be safe... + this._compressBlockSize = 100000 * this._blockLevel; + + // initialize initial meta buffer + this._currentBlockBuffer = new BZip2ParallelOutputDataBlock(this._compressBlockSize, this._mtNextInputBlockId++); + + this._isOwner = isOwner; + + // write the bz2 header... + this.WriteBz2Header(); + } + + /// + /// Writes the next pending Output data block to the output bit stream + /// + /// True if a block was written, false if didn't find any pending blocks + /// + private bool TryWriteOutputBlockAndIncrementId() + { + if (this._unsafeFatalException) + { + throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + } + + + try + { + BZip2ParallelOutputDataBlock? currentOutput = null; + + lock (this._syncRootProcesing) + { + // check if the next output block can be extracted + if (this._mtProcessedBlocks.ContainsKey(this._mtNextOutputBlockId)) + { + currentOutput = this._mtProcessedBlocks[this._mtNextOutputBlockId]; + this._mtProcessedBlocks.Remove(this._mtNextOutputBlockId); + } + + // check if we got anything to write + if (currentOutput is null) + return false; + + this._mtNextOutputBlockId++; + } + + lock (this._syncRootOutputStream) + { + // update file CRC + this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ currentOutput.BlockCrc; + currentOutput.WriteToRealOutputStream(this._bitStream); + } + return true; + } catch (Exception ex) + { + // set this without any locks... + this._lastWorkerException = ex; + this._unsafeFatalException = true; + + // rethrow exception, hopefully something catches it?... + throw new IOException("BZip2 error writing output data! See inner exception for details!", ex); + } + } + + /// + /// Compresses a block of data + /// + /// + /// if compressing the block somehow fails... + private void MultiThreadWorkerAction(object? blockData) + { + try + { + if (blockData is BZip2ParallelOutputDataBlock buffer) + { + // do the work + buffer.CompressBytes(); + + // queue up the finished compressed block data + lock (this._syncRootProcesing) + { + this._mtProcessedBlocks.Add(buffer.BlockId, buffer); + } + } + } catch (Exception ex) + { + // set this without any locks... + this._lastWorkerException = ex; + this._unsafeFatalException = true; + + throw new IOException("BZip2 Processing thread somehow crashed... See inner exception for details!", ex); + } finally + { + lock (this._syncRootProcesing) + { + this._mtPendingBlocks --; + } + } + } + + /// + /// Writes the BZ2 header to the bit stream + /// + private void WriteBz2Header() + { + lock (this._syncRootOutputStream) + { + // write BZIP file header + this._bitStream.WriteBits(8, 0x42); // B + this._bitStream.WriteBits(8, 0x5A); // Z + this._bitStream.WriteBits(8, 0x68); // h + this._bitStream.WriteBits(8, (uint)(0x30 + this._blockLevel)); // block level digit + } + } + + /// + /// Writes the BZ2 footer tot he bit stream and flushes the stream + /// + private void WriteBz2FooterAndFlush() + { + lock (this._syncRootOutputStream) + { + // end magic + this._bitStream.WriteBits(8, 0x17); + this._bitStream.WriteBits(8, 0x72); + this._bitStream.WriteBits(8, 0x45); + this._bitStream.WriteBits(8, 0x38); + this._bitStream.WriteBits(8, 0x50); + this._bitStream.WriteBits(8, 0x90); + + // write combined CRC + this._bitStream.WriteBits(16, (this._streamCrc >> 16) & 0xFFFF); + this._bitStream.WriteBits(16, this._streamCrc & 0xFFFF); + + // flush all remaining bits + this._bitStream.Flush(); + this._outputStream.Flush(); + } + } + + /// + /// Queues up the current data block if it is not empty + /// + /// True if the data block was queued up, false if the current block is empty + /// if one of the worker threads or writing to the final bit stream experienced an exception + private bool TryEnqueueCurrentBlockBuffer() + { + if (this._unsafeFatalException) + { + throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + } + + // make sure current block has data + if (this._currentBlockBuffer.LoadedBytes <= 0) + return false; + + lock (this._syncRootProcesing) + { + this._mtPendingBlocks ++; + ThreadPool.QueueUserWorkItem(this.MultiThreadWorkerAction, this._currentBlockBuffer); + this._currentBlockBuffer = new BZip2ParallelOutputDataBlock(this._compressBlockSize, this._mtNextInputBlockId++); + } + + return true; + } + + + /// + /// Waits for any pending data blocks to be written to the bit stream and writes the BZ2 footer + /// + /// If there was an exception in a worker thread or when writing to the final bitstream + private void FinishBitstream() + { + if (this._unsafeFatalException) + { + throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + } + + lock (this._syncRootProcesing) + { + if (this._mtStreamIsFinished) + return; + this._mtStreamIsFinished = true; + } + + // check if there is still data left to write + this.TryEnqueueCurrentBlockBuffer(); + + // decrement next input block since no longer getting another block + this._mtNextInputBlockId--; + + while (true) + { + if (this._unsafeFatalException) + { + throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + } + + lock (this._syncRootProcesing) + { + if (this._mtNextInputBlockId == this._mtNextOutputBlockId) + { + // all done, can safely exit + break; + } + } + + // try writing output block and keep doing so while successful + while (this.TryWriteOutputBlockAndIncrementId()) + { } + Thread.Sleep(1); + } + + // sanity check, this should be impossible and should be caught by some test right?... + lock (this._syncRootProcesing) + { + if (this._mtPendingBlocks != 0 || + this._mtProcessedBlocks.Count > 0) + { + throw new IOException("BZip2 dispose operation sanity check failed!..."); + } + } + + // finally, write the footer! + this.WriteBz2FooterAndFlush(); + + //Console.WriteLine($"Max number of data pair entries was {this._debugMaxMetaBufferDataPairCount}"); + } + + #region Implementation of abstract members of Stream + + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + + // overriding Dispose instead of Close as recommended in https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.close?view=net-6.0 + protected override void Dispose(bool disposing) + { + this.FinishBitstream(); + base.Dispose(disposing); + if (this._isOwner) + { + this._outputStream.Close(); + } + } + + public override void Flush() + { + // try writing output block and keep doing so while successful + while (this.TryWriteOutputBlockAndIncrementId()) + { } + } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); + public override void SetLength(long value) => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support 'SetLength(long value)' method."); + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support 'Read(byte[] buffer, int offset, int count)' method."); + + public override void WriteByte(byte value) + { + if (!this._currentBlockBuffer.LoadByte(value)) + { + // byte could not be loaded, this happens when current block buffer is full + + this.TryEnqueueCurrentBlockBuffer(); + this._currentBlockBuffer.LoadByte(value); + + // try writing output block and keep doing so while successful + while (this.TryWriteOutputBlockAndIncrementId()) { } + } + } + + public override void Write(byte[] data, int offset, int length) + { + while (length > 0) + { + if (!this._currentBlockBuffer.IsFull) + { + int count = this._currentBlockBuffer.LoadBytes(data, offset, length); + offset += count; + length -= count; + } + + if (this._currentBlockBuffer.IsFull) + { + this.TryEnqueueCurrentBlockBuffer(); + } + + // try writing output block and keep doing so while successful + while (this.TryWriteOutputBlockAndIncrementId()) { } + } + } + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => this._outputStream.CanWrite; + public override long Length => this._outputStream.Length; + + public override long Position + { + get => this._outputStream.Position; + set => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support Set operation for property 'Position'."); + } + + + #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + #endregion + } +} diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index d32020fd7..64dd2d611 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -6,7 +6,8 @@ using SharpCompress.Archives.Tar; using SharpCompress.Common; using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.BZip2MT; +using SharpCompress.Compressors.BZip2MT.InputStream; using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.Lzw; @@ -120,8 +121,8 @@ public TestOption( new(CompressionType.None, (stream) => true, (stream) => stream, ["tar"], false), // We always do a test for IsTarFile later new( CompressionType.BZip2, - BZip2Stream.IsBZip2, - (stream) => new BZip2Stream(stream, CompressionMode.Decompress, false), + Compressors.BZip2.BZip2Stream.IsBZip2, + (stream) => new BZip2ParallelInputStream(stream), ["tar.bz2", "tb2", "tbz", "tbz2", "tz2"] ), new( diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index c92188856..3efe0c277 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -6,7 +6,7 @@ using SharpCompress.Common; using SharpCompress.Common.Tar; using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.BZip2MT.InputStream; using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.Lzw; @@ -34,7 +34,7 @@ protected override Stream RequestInitialStream() var stream = base.RequestInitialStream(); return compressionType switch { - CompressionType.BZip2 => new BZip2Stream(stream, CompressionMode.Decompress, false), + CompressionType.BZip2 => new BZip2ParallelInputStream(stream), CompressionType.GZip => new GZipStream(stream, CompressionMode.Decompress), CompressionType.ZStandard => new ZStandardStream(stream), CompressionType.LZip => new LZipStream(stream, CompressionMode.Decompress), @@ -74,10 +74,10 @@ public static TarReader Open(Stream stream, ReaderOptions? options = null) } ((IStreamStack)rewindableStream).StackSeek(pos); - if (BZip2Stream.IsBZip2(rewindableStream)) + if (Compressors.BZip2.BZip2Stream.IsBZip2(rewindableStream)) { ((IStreamStack)rewindableStream).StackSeek(pos); - var testStream = new BZip2Stream(rewindableStream, CompressionMode.Decompress, false); + var testStream = new BZip2ParallelInputStream(rewindableStream); if (TarArchive.IsTarFile(testStream)) { ((IStreamStack)rewindableStream).StackSeek(pos); diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index 96346e2bc..e542654cf 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -6,6 +6,8 @@ using SharpCompress.Common.Tar.Headers; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.BZip2MT; +using SharpCompress.Compressors.BZip2MT.OutputStream; using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.LZMA; using SharpCompress.IO; @@ -35,7 +37,7 @@ public TarWriter(Stream destination, TarWriterOptions options) break; case CompressionType.BZip2: { - destination = new BZip2Stream(destination, CompressionMode.Compress, false); + destination = new BZip2ParallelOutputStream(destination); } break; case CompressionType.GZip: diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index f867c8a91..8580424c1 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -9,7 +9,7 @@ using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.BZip2MT.OutputStream; using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.PPMd; @@ -441,7 +441,7 @@ private Stream GetWriteStream(Stream writeStream) } case ZipCompressionMethod.BZip2: { - return new BZip2Stream(counting, CompressionMode.Compress, false); + return new BZip2ParallelOutputStream(counting); } case ZipCompressionMethod.LZMA: { diff --git a/tests/SharpCompress.Test/BZip2MT/BZip2MTReaderTests.cs b/tests/SharpCompress.Test/BZip2MT/BZip2MTReaderTests.cs new file mode 100644 index 000000000..3321c3260 --- /dev/null +++ b/tests/SharpCompress.Test/BZip2MT/BZip2MTReaderTests.cs @@ -0,0 +1,28 @@ +using System.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.Tar; +using Xunit; +namespace SharpCompress.Test.BZip2MT; + +public class BZip2MTReaderTests : ReaderTests +{ + [Fact] + public void BZip2MT_Reader_Factory_ValidTarBz2File() + { + Stream stream = new MemoryStream( + new byte[] + { + 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xd2, 0xeb, 0x13, 0xbb, 0x00, 0x00, + 0x2f, 0xdf, 0x81, 0xce, 0x00, 0x40, 0x01, 0x6f, 0x80, 0x20, 0x00, 0x00, 0x20, 0x60, 0x00, 0x1e, + 0x40, 0x02, 0x00, 0x04, 0x00, 0xa0, 0x00, 0x54, 0x42, 0x8f, 0xd5, 0x18, 0x00, 0x03, 0x20, 0x94, + 0x53, 0xc1, 0x40, 0x03, 0x40, 0x5a, 0xbe, 0xa5, 0x09, 0x81, 0x03, 0x52, 0x89, 0x46, 0x40, 0x53, + 0x65, 0xab, 0x3b, 0xb4, 0xe7, 0x20, 0x9c, 0x48, 0x6f, 0x0f, 0xa3, 0x4c, 0x3d, 0xc1, 0x58, 0x06, + 0xdc, 0x7a, 0xc8, 0x99, 0x22, 0x82, 0xc6, 0xd1, 0x55, 0x15, 0x8d, 0x33, 0xe8, 0xbb, 0x92, 0x29, + 0xc2, 0x84, 0x86, 0x97, 0x58, 0x9d, 0xd8, + } + ); + var reader = ReaderFactory.Open(stream); + + Assert.IsType(typeof(TarReader), reader); + } +} diff --git a/tests/SharpCompress.Test/BZip2MT/BZip2MTStreamAsyncTests.cs b/tests/SharpCompress.Test/BZip2MT/BZip2MTStreamAsyncTests.cs new file mode 100644 index 000000000..b6e8c6922 --- /dev/null +++ b/tests/SharpCompress.Test/BZip2MT/BZip2MTStreamAsyncTests.cs @@ -0,0 +1,201 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using SharpCompress.Compressors.BZip2MT.InputStream; +using SharpCompress.Compressors.BZip2MT.OutputStream; +using Xunit; +namespace SharpCompress.Test.BZip2MT; + +public class BZip2MTStreamAsyncTests +{ + private byte[] CreateTestData(int size) + { + var data = new byte[size]; + // Create compressible data with repetitive pattern + for (int i = 0; i < size; i++) + { + data[i] = (byte)('A' + (i % 26)); + } + return data; + } + + [Fact] + public async Task BZip2MTCompressDecompressAsyncTest() + { + var testData = this.CreateTestData(10000); + byte[] compressed; + + // Compress + using (var memoryStream = new MemoryStream()) + { + using ( + var bzip2Stream = new BZip2ParallelOutputStream(memoryStream) + ) + { + await bzip2Stream.WriteAsync(testData, 0, testData.Length); + bzip2Stream.Close(); + } + compressed = memoryStream.ToArray(); + } + + // Verify compression occurred + Assert.True(compressed.Length > 0); + Assert.True(compressed.Length < testData.Length); + + // Decompress + byte[] decompressed; + using (var memoryStream = new MemoryStream(compressed)) + { + using ( + var bzip2Stream = new BZip2ParallelInputStream(memoryStream) + ) + { + decompressed = new byte[testData.Length]; + var totalRead = 0; + int bytesRead; + while ( + ( + bytesRead = await bzip2Stream.ReadAsync( + decompressed, + totalRead, + testData.Length - totalRead + ) + ) > 0 + ) + { + totalRead += bytesRead; + } + } + } + + // Verify decompression + Assert.Equal(testData, decompressed); + } + + [Fact] + public async Task BZip2MTReadAsyncWithCancellationTest() + { + var testData = Encoding.ASCII.GetBytes(new string('A', 5000)); // Repetitive data compresses well + byte[] compressed; + + // Compress + using (var memoryStream = new MemoryStream()) + { + using ( + var bzip2Stream = new BZip2ParallelOutputStream(memoryStream) + ) + { + await bzip2Stream.WriteAsync(testData, 0, testData.Length); + bzip2Stream.Close(); + } + compressed = memoryStream.ToArray(); + } + + // Decompress with cancellation support + using (var memoryStream = new MemoryStream(compressed)) + { + using ( + var bzip2Stream = new BZip2ParallelInputStream(memoryStream) + ) + { + var buffer = new byte[1024]; + using var cts = new System.Threading.CancellationTokenSource(); + + // Read should complete without cancellation + var bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length, cts.Token); + Assert.True(bytesRead > 0); + } + } + } + + [Fact] + public async Task BZip2MTMultipleAsyncWritesTest() + { + using (var memoryStream = new MemoryStream()) + { + using ( + var bzip2Stream = new BZip2ParallelOutputStream(memoryStream, false) + ) + { + var data1 = Encoding.ASCII.GetBytes("Hello "); + var data2 = Encoding.ASCII.GetBytes("World"); + var data3 = Encoding.ASCII.GetBytes("!"); + + await bzip2Stream.WriteAsync(data1, 0, data1.Length); + await bzip2Stream.WriteAsync(data2, 0, data2.Length); + await bzip2Stream.WriteAsync(data3, 0, data3.Length); + + bzip2Stream.Close(); + } + + var compressed = memoryStream.ToArray(); + Assert.True(compressed.Length > 0); + + // Decompress and verify + using (var readStream = new MemoryStream(compressed)) + { + // reset memory stream position + memoryStream.Position = 0; + + using ( + var bzip2Stream = new BZip2ParallelInputStream(memoryStream) + ) + { + var result = new StringBuilder(); + var buffer = new byte[256]; + int bytesRead; + while ((bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + result.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); + } + + Assert.Equal("Hello World!", result.ToString()); + } + } + } + } + + [Fact] + public async Task BZip2MTLargeDataAsyncTest() + { + var largeData = this.CreateTestData(100000); + + // Compress + byte[] compressed; + using (var memoryStream = new MemoryStream()) + { + using ( + var bzip2Stream = new BZip2ParallelOutputStream(memoryStream) + ) + { + await bzip2Stream.WriteAsync(largeData, 0, largeData.Length); + bzip2Stream.Close(); + } + compressed = memoryStream.ToArray(); + } + + // Decompress + byte[] decompressed; + using (var memoryStream = new MemoryStream(compressed)) + { + using ( + var bzip2Stream = new BZip2ParallelInputStream(memoryStream) + ) + { + decompressed = new byte[largeData.Length]; + var totalRead = 0; + int bytesRead; + var buffer = new byte[4096]; + while ((bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + Array.Copy(buffer, 0, decompressed, totalRead, bytesRead); + totalRead += bytesRead; + } + } + } + + // Verify + Assert.Equal(largeData, decompressed); + } +} diff --git a/tests/SharpCompress.Test/BZip2MT/TestCommon.cs b/tests/SharpCompress.Test/BZip2MT/TestCommon.cs new file mode 100644 index 000000000..e50142952 --- /dev/null +++ b/tests/SharpCompress.Test/BZip2MT/TestCommon.cs @@ -0,0 +1,206 @@ +using System; +using System.Diagnostics; +using System.IO; +using SharpCompress.Compressors.BZip2MT.InputStream; +using SharpCompress.Compressors.BZip2MT.OutputStream; +using Xunit; +using Xunit.Abstractions; +namespace SharpCompress.Test.BZip2MT +{ + public static class TestCommon + { + + /// + /// Common test routine for Multi Threaded compression + Single Threaded decompression + /// + /// + /// Input stream, must be seekable + /// If true, will use Parallel Compressor + /// If true, will use Parallel Decompressor + /// Maximum number of threads to use for compression, if 0 will use Environment.ProcessorCount + /// Size for temporary output memory buffer + /// Size for temporary copy buffer + /// If true, will save inputStream data to a file + /// (Time Compression, Time Decompression) + public static (double, double) GenericTest(ITestOutputHelper console, Stream inputStream, bool compressMultiThread, bool decompressMultiThread, int outputBufferSize = 8388608, int copyBufferSize = 8388608, bool saveFileOnFail = false) + { + void DebugSaveInputStream() + { + string randomFile = Path.GetRandomFileName(); + console.WriteLine($" Saving input data to {randomFile}"); + using FileStream fs = new FileStream(randomFile, FileMode.Create, FileAccess.Write); + inputStream.Position = 0; + inputStream.CopyTo(fs); + fs.Flush(); + fs.Close(); + } + + double timeCompress = 0; + double timeDecompress = 0; + + using MemoryStream output = new MemoryStream(outputBufferSize); + using MemoryStream outputDecompressed = new MemoryStream(outputBufferSize); + + try + { + // compress input + Stopwatch swCompress = new Stopwatch(); + swCompress.Start(); + using Stream compressor = compressMultiThread + ? new BZip2ParallelOutputStream(output, false, 9) + : new BZip2OutputStream(output, false, 9); + inputStream.CopyTo(compressor, copyBufferSize); + compressor.Close(); + swCompress.Stop(); + timeCompress = swCompress.ElapsedMilliseconds; + console.WriteLine($" {timeCompress} ms {(compressMultiThread ? "MT": "ST")} compression time... "); + + // reset output position + output.Position = 0; + + // decompress output + Stopwatch SwDecompress = new Stopwatch(); + SwDecompress.Start(); + using Stream decompressor = decompressMultiThread + ? new BZip2ParallelInputStream(output, false) + : new BZip2InputStream(output, false); + decompressor.CopyTo(outputDecompressed, copyBufferSize); + SwDecompress.Stop(); + timeDecompress = SwDecompress.ElapsedMilliseconds; + console.WriteLine($" {timeDecompress} ms {(decompressMultiThread ? "MT": "ST")} decompression time"); + } catch (Exception ex) + { + if (saveFileOnFail) + { + DebugSaveInputStream(); + } + + Assert.Fail($"Exception was thrown... {ex}"); + } + + if (inputStream.Length != outputDecompressed.Length) + { + Assert.Fail($"Decompressed stream length mismatch, expecting {inputStream.Length}, got {outputDecompressed.Length}"); + } + + inputStream.Position = 0; + outputDecompressed.Position = 0; + + for (int i = 0; i < inputStream.Length; i++) + { + int expect = inputStream.ReadByte(); + int value = outputDecompressed.ReadByte(); + if ( expect != value) + { + if (saveFileOnFail) + { + DebugSaveInputStream(); + Assert.Fail($"bytes differ at position {i}, expected {expect}, got {value}"); + } + } + } + + return (timeCompress, timeDecompress); + } + + public enum RandomDataMode + { + SingleByteValue, + RandomBytes, + RandomBytesRepeat, + } + + public enum TestMode + { + CMT_DST, // multi thread compress, single thread decompress + CST_DST, // single thread compress, single thread decompress + CMT_DMT, // multi thread compress, multi thread decompress + CST_DMT, // single thread compress, multi thread decompress + } + + public static void RandomLongTest_X(ITestOutputHelper console, int repeat, RandomDataMode dataMode, TestMode testMode, int len = 9000000) + { + Random random = new Random(); + + int copyBufferSize = 8388608; + int outBufferSize = 8388608; + int repeatStreaks = 64; + + double totalCompressionTimeMs = 0; + double totalDecompressionTimeMs = 0; + + for (int r = 0; r < repeat; r++) + { + byte[] bigBuffer = new byte[len]; + switch (dataMode) + { + case RandomDataMode.RandomBytes: + { + random.NextBytes(bigBuffer); + break; + } + case RandomDataMode.RandomBytesRepeat: + { + random.NextBytes(bigBuffer); + + int offset = 0; + for (int rs = 0; rs < repeatStreaks; rs++) + { + int newoffset = random.Next(0, (len - 10000) / repeatStreaks); + offset += newoffset; + int count = random.Next(0, 512); + byte val = bigBuffer[offset++]; + for (int i = 0; i < count; i++) + { + bigBuffer[offset++] = val; + } + } + break; + } + case RandomDataMode.SingleByteValue: + { + byte value = (byte)(random.Next() & 0xFF); + for (int i = 0; i < len; i++) + { + bigBuffer[i] = value; + } + break; + } + } + + MemoryStream ms = new MemoryStream(bigBuffer); + double timeC = 0, timeD = 0; + switch (testMode) + { + default: throw new ArgumentException("Unknown test mode"); + case TestMode.CMT_DMT: + { + (timeC, timeD) = GenericTest(console, ms, true, true, outBufferSize, copyBufferSize, true); + break; + } + case TestMode.CST_DMT: + { + (timeC, timeD) = GenericTest(console, ms, false, true, outBufferSize, copyBufferSize, true); + break; + } + case TestMode.CST_DST: + { + (timeC, timeD) = GenericTest(console, ms, false, false, outBufferSize, copyBufferSize, true); + break; + } + case TestMode.CMT_DST: + { + (timeC, timeD) = GenericTest(console, ms, true, false, outBufferSize, copyBufferSize, true); + break; + } + } + + totalCompressionTimeMs += timeC; + totalDecompressionTimeMs += timeD; + } + console.WriteLine($"DONE {testMode}"); + console.WriteLine($"AVERAGE {totalCompressionTimeMs / repeat} ms compression time... "); + console.WriteLine($"AVERAGE {totalDecompressionTimeMs / repeat} ms decompression time... "); + } + } +} diff --git a/tests/SharpCompress.Test/BZip2MT/Tests.cs b/tests/SharpCompress.Test/BZip2MT/Tests.cs new file mode 100644 index 000000000..02db3b341 --- /dev/null +++ b/tests/SharpCompress.Test/BZip2MT/Tests.cs @@ -0,0 +1,139 @@ +// Bzip2 library for .net +// Modified by drone1400 +// Location: https://github.com/drone1400/bzip2 +// Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 +// Modified from the .net implementation by Jaime Olivares: http://github.com/jaime-olivares/bzip2 + +using System; +using System.IO; +using SharpCompress.Compressors.BZip2MT.Algorithm; +using SharpCompress.Compressors.BZip2MT.InputStream; +using SharpCompress.Compressors.BZip2MT.OutputStream; +using Xunit; +using Xunit.Abstractions; +namespace SharpCompress.Test.BZip2MT +{ + + /// + /// Unit test for the BZip2 compression library + /// + public class Tests + { + + private const int BufferSizeLarge = 10000000; // Almost 10 Mb + private const int BufferSizeSmall = 100000; // Around 100 Kb + private static byte[] Buffer = new byte[Tests.BufferSizeLarge]; + + private readonly ITestOutputHelper _console; + private readonly Random _random; + + /// + /// Fills the test buffer with random values + /// + public Tests(ITestOutputHelper console) + { + this._random = new Random(); + this._console = console; + + this._random.NextBytes(Tests.Buffer); + } + + /// + /// Performs a CRC check and compare against well-known results + /// The buffer has different values + /// + [Fact] + public void CrcAlgorithmDifferentValues() + { + byte[] buffer = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA + }; + + var crc = new CRC32(); + for (int i = 0; i < buffer.Length; i++) + crc.UpdateCrc(buffer[i]); + + Assert.Equal(0x8AEE127A, crc.CRC); + } + + /// + /// Performs a CRC check and compare against well-known results + /// The buffer has different values + /// + [Fact] + public void CrcAlgorithmSameValues() + { + var crc = new CRC32(); + crc.UpdateCrc(0x55, 10); + Assert.Equal(0xA1E07747, crc.CRC); + } + + /// + /// Compresses the full buffer and checks for a reasonable compressed size + /// + [Fact] + public void CompressSmokeLarge() + { + var input = new MemoryStream(Tests.Buffer); + var output = new MemoryStream(); + + var compressor = new BZip2OutputStream(output, false); + input.CopyTo(compressor); + compressor.Close(); + + // Estimated size between inputSize*0.5 and inputSize*1.1 + Assert.True(output.Length > Tests.BufferSizeLarge * 0.5); + Assert.True(output.Length < Tests.BufferSizeLarge * 1.1); + } + + /// + /// Compresses a portion of the buffer and checks for a reasonable compressed size + /// + [Fact] + public void CompressSmokeSmall() + { + var input = new MemoryStream(Tests.Buffer, 0, Tests.BufferSizeSmall); + var output = new MemoryStream(); + + var compressor = new BZip2OutputStream(output, false); + input.CopyTo(compressor); + compressor.Close(); + + // Estimated size between inputSize*0.5 and inputSize*1.1 + Assert.True(output.Length > Tests.BufferSizeSmall * 0.5); + Assert.True(output.Length < Tests.BufferSizeSmall * 1.1); + } + + /// + /// Compresses and decompresses a long random buffer + /// + [Fact] + public void CompressAndDecompress() + { + var input = new MemoryStream(Tests.Buffer); + var output = new MemoryStream(); + + var compressor = new BZip2OutputStream(output, false); + input.CopyTo(compressor); + compressor.Close(); + + Assert.True(output.Length > 4); + + output.Position = 0; + var output2 = new MemoryStream(); + var decompressor = new BZip2InputStream(output, false); + decompressor.CopyTo(output2); + + Assert.Equal(Tests.Buffer.Length, output2.Length); + output2.Position = 0; + for (int i = 0; i < Tests.Buffer.Length; i++) + { + if (Tests.Buffer[i] != (byte)output2.ReadByte()) + { + Assert.Fail($"bytes differ at position {i}"); + } + } + } + } +} diff --git a/tests/SharpCompress.Test/BZip2MT/TestsRandomData.cs b/tests/SharpCompress.Test/BZip2MT/TestsRandomData.cs new file mode 100644 index 000000000..5deec2dce --- /dev/null +++ b/tests/SharpCompress.Test/BZip2MT/TestsRandomData.cs @@ -0,0 +1,53 @@ +using Xunit; +using Xunit.Abstractions; +namespace SharpCompress.Test.BZip2MT +{ + public class TestsRandomData + { + private readonly ITestOutputHelper _console; + protected int _repeatCount = 1; + + public TestsRandomData(ITestOutputHelper console) + { + this._console = console; + } + + [Fact] + public void RandomSingleByteLongTest_CST_DST() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.SingleByteValue, TestCommon.TestMode.CST_DST, 100000000); + [Fact] + public void RandomLongTest_CST_DST() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytes, TestCommon.TestMode.CST_DST); + [Fact] + public void RandomLongTestWithRepeatedValues_CST_DST() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytesRepeat, TestCommon.TestMode.CST_DST); + [Fact] + public void RandomSingleByteLongTest_CMT_DST() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.SingleByteValue, TestCommon.TestMode.CMT_DST, 100000000); + [Fact] + public void RandomLongTest_CMT_DST() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytes, TestCommon.TestMode.CMT_DST); + [Fact] + public void RandomLongTestWithRepeatedValues_CMT_DST() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytesRepeat, TestCommon.TestMode.CMT_DST); + + [Fact] + public void RandomSingleByteLongTest_CST_DMT() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.SingleByteValue, TestCommon.TestMode.CST_DMT, 100000000); + [Fact] + public void RandomLongTest_CST_DMT() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytes, TestCommon.TestMode.CST_DMT); + [Fact] + public void RandomLongTestWithRepeatedValues_CST_DMT() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytesRepeat, TestCommon.TestMode.CST_DMT); + [Fact] + public void RandomSingleByteLongTest_CMT_DMT() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.SingleByteValue, TestCommon.TestMode.CMT_DMT, 100000000); + [Fact] + public void RandomLongTest_CMT_DMT() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytes, TestCommon.TestMode.CMT_DMT); + [Fact] + public void RandomLongTestWithRepeatedValues_CMT_DMT() => + TestCommon.RandomLongTest_X(this._console, this._repeatCount, TestCommon.RandomDataMode.RandomBytesRepeat, TestCommon.TestMode.CMT_DMT); + } +} From f8daa0882db5468465badfda8e62bae8459a2390 Mon Sep 17 00:00:00 2001 From: drone1400 <> Date: Sat, 6 Dec 2025 19:39:34 +0200 Subject: [PATCH 2/3] Fixed oversight in BZip2ParallelInputStream.cs causing very slow reads when reading very few bytes --- .../InputStream/BZip2ParallelInputStream.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs index d7cbf54bf..9196e4e02 100644 --- a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs @@ -251,14 +251,16 @@ public override int ReadByte() } // queue up the next block - while (this.TryToQueueUpAnotherBlock()) - { } + bool queueSuccessful = this.TryToQueueUpAnotherBlock(); // check if we are completely done if (this.CheckIfCompletelyDone()) return -1; - Thread.Sleep(1); + if (!queueSuccessful) + { + Thread.Sleep(1); + } } } @@ -313,14 +315,16 @@ public override int Read(byte[] destination, int offset, int length) } // queue up the next block - while (this.TryToQueueUpAnotherBlock()) - { } + bool queueSuccessful = this.TryToQueueUpAnotherBlock(); // check if we are completely done if (this.CheckIfCompletelyDone()) return totalRead; - Thread.Sleep(1); + if (!queueSuccessful) + { + Thread.Sleep(1); + } } } From 1f2195e13b02258f6bba382f54378c3e87c5c9c3 Mon Sep 17 00:00:00 2001 From: drone1400 <> Date: Mon, 8 Dec 2025 17:51:29 +0200 Subject: [PATCH 3/3] Reformatted modified files with csharpier --- .../BZip2MT/Algorithm/BZip2BlockCompressor.cs | 28 +- .../Algorithm/BZip2BlockDecompressor.cs | 591 ++++++- .../BZip2MT/Algorithm/BZip2DivSufSort.cs | 1461 ++++++++++++----- .../Algorithm/BZip2HuffmanStageDecoder.cs | 59 +- .../Algorithm/BZip2HuffmanStageEncoder.cs | 97 +- .../Algorithm/Bzip2MTFAndRLE2StageEncoder.cs | 40 +- .../Compressors/BZip2MT/Algorithm/CRC32.cs | 300 +++- .../BZip2MT/Algorithm/HuffmanAllocator.cs | 53 +- .../BZip2MT/Algorithm/MoveToFront.cs | 3 +- .../InputStream/BZip2BitInputStream.cs | 7 +- .../BZip2BitInputStreamSplitter.cs | 34 +- .../BZip2MT/InputStream/BZip2InputStream.cs | 93 +- .../BZip2ParallelInputDataBlock.cs | 27 +- .../InputStream/BZip2ParallelInputStream.cs | 108 +- .../BZip2MT/Interface/BZip2Constants.cs | 6 +- .../BZip2MT/Interface/IBZip2BitInputStream.cs | 1 + .../Interface/IBZip2BitOutputStream.cs | 11 +- .../OutputStream/BZip2BitOutputStream.cs | 19 +- .../BZip2MT/OutputStream/BZip2OutputStream.cs | 48 +- .../BZip2ParallelOutputDataBlock.cs | 9 +- .../OutputStream/BZip2ParallelOutputStream.cs | 117 +- 21 files changed, 2358 insertions(+), 754 deletions(-) diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs index 260397eef..d8e703f44 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockCompressor.cs @@ -6,6 +6,7 @@ using System; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// Compresses and writes a single BZip2 block @@ -96,19 +97,22 @@ public bool Write(int value) { this.rleCurrentValue = value; this.rleLength = 1; - } else if (this.rleCurrentValue != value) + } + else if (this.rleCurrentValue != value) { // This path commits us to write 6 bytes - one RLE run (5 bytes) plus one extra this.WriteRun(this.rleCurrentValue & 0xff, this.rleLength); this.rleCurrentValue = value; this.rleLength = 1; - } else + } + else { if (this.rleLength == 254) { this.WriteRun(this.rleCurrentValue & 0xff, 255); this.rleLength = 0; - } else + } + else { this.rleLength++; } @@ -165,11 +169,21 @@ public void CloseBlock() this.WriteSymbolMap(); // Perform the Move To Front Transform and Run-Length Encoding[2] stages - var mtfEncoder = new BZip2MTFAndRLE2StageEncoder(this.bwtBlock, this.blockLength, this.blockValuesPresent); + var mtfEncoder = new BZip2MTFAndRLE2StageEncoder( + this.bwtBlock, + this.blockLength, + this.blockValuesPresent + ); mtfEncoder.Encode(); // Perform the Huffman Encoding stage and write out the encoded data - var huffmanEncoder = new BZip2HuffmanStageEncoder(this.bitOutputStream, mtfEncoder.MtfBlock, mtfEncoder.MtfLength, mtfEncoder.MtfAlphabetSize, mtfEncoder.MtfSymbolFrequencies); + var huffmanEncoder = new BZip2HuffmanStageEncoder( + this.bitOutputStream, + mtfEncoder.MtfBlock, + mtfEncoder.MtfLength, + mtfEncoder.MtfAlphabetSize, + mtfEncoder.MtfSymbolFrequencies + ); huffmanEncoder.Encode(); } @@ -214,10 +228,10 @@ private void WriteSymbolMap() /// /// The value to write /// The run length of the value to write - private void WriteRun ( int value, int runLength) + private void WriteRun(int value, int runLength) { this.blockValuesPresent[value] = true; - this.crc.UpdateCrc (value, runLength); + this.crc.UpdateCrc(value, runLength); var byteValue = (byte)value; switch (runLength) diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs index 2a61f2ca7..b1c4bb2c4 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2BlockDecompressor.cs @@ -6,6 +6,7 @@ using System; using System.IO; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// Reads and decompresses a single BZip2 block @@ -33,42 +34,523 @@ internal class BZip2BlockDecompressor /// private static readonly int[] RNUMS = { - 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, 985, 724, 205, 454, 863, 491, - 741, 242, 949, 214, 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, 419, 436, - 278, 496, 867, 210, 399, 680, 480, 51, 878, 465, 811, 169, 869, 675, 611, 697, - 867, 561, 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, 150, 238, 59, 379, - 684, 877, 625, 169, 643, 105, 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, - 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, 909, 545, 703, 919, 874, 474, - 882, 500, 594, 612, 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, 161, 604, - 958, 533, 221, 400, 386, 867, 600, 782, 382, 596, 414, 171, 516, 375, 682, 485, - 911, 276, 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, 227, 730, 475, 186, - 263, 647, 537, 686, 600, 224, 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, - 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, 715, 67, 618, 276, 204, 918, - 873, 777, 604, 560, 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, 652, 934, - 970, 447, 318, 353, 859, 672, 112, 785, 645, 863, 803, 350, 139, 93, 354, 99, - 820, 908, 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, 653, 282, 762, 623, - 680, 81, 927, 626, 789, 125, 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, - 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, 857, 956, 358, 619, 580, 124, - 737, 594, 701, 612, 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, 944, 375, - 748, 52, 600, 747, 642, 182, 862, 81, 344, 805, 988, 739, 511, 655, 814, 334, - 249, 515, 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, 433, 837, 553, 268, - 926, 240, 102, 654, 459, 51, 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, - 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, 978, 321, 576, 617, 626, 502, - 894, 679, 243, 440, 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, 707, 151, - 457, 449, 797, 195, 791, 558, 945, 679, 297, 59, 87, 824, 713, 663, 412, 693, - 342, 606, 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, 343, 97, 430, 751, - 497, 314, 983, 374, 822, 928, 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, - 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, 369, 970, 294, 750, 807, 827, - 150, 790, 288, 923, 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, 896, 831, - 547, 261, 524, 462, 293, 465, 502, 56, 661, 821, 976, 991, 658, 869, 905, 758, - 745, 193, 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, 61, 688, 793, 644, - 986, 403, 106, 366, 905, 644, 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, - 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, 920, 176, 193, 713, 857, 265, - 203, 50, 668, 108, 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, 936, 638 + 619, + 720, + 127, + 481, + 931, + 816, + 813, + 233, + 566, + 247, + 985, + 724, + 205, + 454, + 863, + 491, + 741, + 242, + 949, + 214, + 733, + 859, + 335, + 708, + 621, + 574, + 73, + 654, + 730, + 472, + 419, + 436, + 278, + 496, + 867, + 210, + 399, + 680, + 480, + 51, + 878, + 465, + 811, + 169, + 869, + 675, + 611, + 697, + 867, + 561, + 862, + 687, + 507, + 283, + 482, + 129, + 807, + 591, + 733, + 623, + 150, + 238, + 59, + 379, + 684, + 877, + 625, + 169, + 643, + 105, + 170, + 607, + 520, + 932, + 727, + 476, + 693, + 425, + 174, + 647, + 73, + 122, + 335, + 530, + 442, + 853, + 695, + 249, + 445, + 515, + 909, + 545, + 703, + 919, + 874, + 474, + 882, + 500, + 594, + 612, + 641, + 801, + 220, + 162, + 819, + 984, + 589, + 513, + 495, + 799, + 161, + 604, + 958, + 533, + 221, + 400, + 386, + 867, + 600, + 782, + 382, + 596, + 414, + 171, + 516, + 375, + 682, + 485, + 911, + 276, + 98, + 553, + 163, + 354, + 666, + 933, + 424, + 341, + 533, + 870, + 227, + 730, + 475, + 186, + 263, + 647, + 537, + 686, + 600, + 224, + 469, + 68, + 770, + 919, + 190, + 373, + 294, + 822, + 808, + 206, + 184, + 943, + 795, + 384, + 383, + 461, + 404, + 758, + 839, + 887, + 715, + 67, + 618, + 276, + 204, + 918, + 873, + 777, + 604, + 560, + 951, + 160, + 578, + 722, + 79, + 804, + 96, + 409, + 713, + 940, + 652, + 934, + 970, + 447, + 318, + 353, + 859, + 672, + 112, + 785, + 645, + 863, + 803, + 350, + 139, + 93, + 354, + 99, + 820, + 908, + 609, + 772, + 154, + 274, + 580, + 184, + 79, + 626, + 630, + 742, + 653, + 282, + 762, + 623, + 680, + 81, + 927, + 626, + 789, + 125, + 411, + 521, + 938, + 300, + 821, + 78, + 343, + 175, + 128, + 250, + 170, + 774, + 972, + 275, + 999, + 639, + 495, + 78, + 352, + 126, + 857, + 956, + 358, + 619, + 580, + 124, + 737, + 594, + 701, + 612, + 669, + 112, + 134, + 694, + 363, + 992, + 809, + 743, + 168, + 974, + 944, + 375, + 748, + 52, + 600, + 747, + 642, + 182, + 862, + 81, + 344, + 805, + 988, + 739, + 511, + 655, + 814, + 334, + 249, + 515, + 897, + 955, + 664, + 981, + 649, + 113, + 974, + 459, + 893, + 228, + 433, + 837, + 553, + 268, + 926, + 240, + 102, + 654, + 459, + 51, + 686, + 754, + 806, + 760, + 493, + 403, + 415, + 394, + 687, + 700, + 946, + 670, + 656, + 610, + 738, + 392, + 760, + 799, + 887, + 653, + 978, + 321, + 576, + 617, + 626, + 502, + 894, + 679, + 243, + 440, + 680, + 879, + 194, + 572, + 640, + 724, + 926, + 56, + 204, + 700, + 707, + 151, + 457, + 449, + 797, + 195, + 791, + 558, + 945, + 679, + 297, + 59, + 87, + 824, + 713, + 663, + 412, + 693, + 342, + 606, + 134, + 108, + 571, + 364, + 631, + 212, + 174, + 643, + 304, + 329, + 343, + 97, + 430, + 751, + 497, + 314, + 983, + 374, + 822, + 928, + 140, + 206, + 73, + 263, + 980, + 736, + 876, + 478, + 430, + 305, + 170, + 514, + 364, + 692, + 829, + 82, + 855, + 953, + 676, + 246, + 369, + 970, + 294, + 750, + 807, + 827, + 150, + 790, + 288, + 923, + 804, + 378, + 215, + 828, + 592, + 281, + 565, + 555, + 710, + 82, + 896, + 831, + 547, + 261, + 524, + 462, + 293, + 465, + 502, + 56, + 661, + 821, + 976, + 991, + 658, + 869, + 905, + 758, + 745, + 193, + 768, + 550, + 608, + 933, + 378, + 286, + 215, + 979, + 792, + 961, + 61, + 688, + 793, + 644, + 986, + 403, + 106, + 366, + 905, + 644, + 372, + 567, + 466, + 434, + 645, + 210, + 389, + 550, + 919, + 135, + 780, + 773, + 635, + 389, + 707, + 100, + 626, + 958, + 165, + 504, + 920, + 176, + 193, + 713, + 857, + 265, + 203, + 50, + 668, + 108, + 645, + 990, + 626, + 197, + 510, + 357, + 358, + 850, + 858, + 364, + 936, + 638, }; // Maximum possible number of Huffman table selectors - private const int HUFFMAN_MAXIMUM_SELECTORS = (900000 / BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH) + 1; + private const int HUFFMAN_MAXIMUM_SELECTORS = + (900000 / BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH) + 1; // Provides bits of input to decode private readonly IBZip2BitInputStream bitInputStream; @@ -101,9 +583,9 @@ internal class BZip2BlockDecompressor // /// - /// Counts of each byte value within the bwtTransformedArray data. Collected at the Move - /// To Front stage, consumed by the Inverse Burrows Wheeler Transform stage - /// + /// Counts of each byte value within the bwtTransformedArray data. Collected at the Move + /// To Front stage, consumed by the Inverse Burrows Wheeler Transform stage + /// private readonly int[] bwtByteCounts = new int[256]; /// @@ -174,7 +656,10 @@ internal class BZip2BlockDecompressor /// if the input stream reaches EOF before all table data has been read private BZip2HuffmanStageDecoder ReadHuffmanTables() { - var tableCodeLengths = new byte[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; + var tableCodeLengths = new byte[ + BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, + BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE + ]; // Read Huffman symbol to output byte map uint huffmanUsedRanges = this.bitInputStream.ReadBits(16); @@ -198,10 +683,12 @@ private BZip2HuffmanStageDecoder ReadHuffmanTables() // Read total number of tables and selectors uint totalTables = this.bitInputStream.ReadBits(3); uint totalSelectors = this.bitInputStream.ReadBits(15); - if ((totalTables < BZip2BlockDecompressor.HUFFMAN_MINIMUM_TABLES) + if ( + (totalTables < BZip2BlockDecompressor.HUFFMAN_MINIMUM_TABLES) || (totalTables > BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES) || (totalSelectors < 1) - || (totalSelectors > BZip2BlockDecompressor.HUFFMAN_MAXIMUM_SELECTORS)) + || (totalSelectors > BZip2BlockDecompressor.HUFFMAN_MAXIMUM_SELECTORS) + ) { throw new IOException("BZip2 block Huffman tables invalid"); } @@ -228,7 +715,12 @@ private BZip2HuffmanStageDecoder ReadHuffmanTables() } } - return new BZip2HuffmanStageDecoder(this.bitInputStream, endOfBlockSymbol + 1, tableCodeLengths, selectors); + return new BZip2HuffmanStageDecoder( + this.bitInputStream, + endOfBlockSymbol + 1, + tableCodeLengths, + selectors + ); } /// @@ -253,11 +745,13 @@ private void DecodeHuffmanData(BZip2HuffmanStageDecoder huffmanDecoder) { repeatCount += repeatIncrement; repeatIncrement <<= 1; - } else if (nextSymbol == BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB) + } + else if (nextSymbol == BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB) { repeatCount += repeatIncrement << 1; repeatIncrement <<= 1; - } else + } + else { byte nextByte; if (repeatCount > 0) @@ -336,7 +830,9 @@ private void InitialiseInverseBWT(uint bwtStartPointer) private int decodeNextBWTByte() { int nextDecodedByte = this.bwtCurrentMergedPointer & 0xff; - this.bwtCurrentMergedPointer = this.bwtMergedPointers[this.bwtCurrentMergedPointer >> 8]; + this.bwtCurrentMergedPointer = this.bwtMergedPointers[ + this.bwtCurrentMergedPointer >> 8 + ]; if (this.blockRandomised) { @@ -365,7 +861,7 @@ public BZip2BlockDecompressor(IBZip2BitInputStream bitInputStream, uint blockSiz this.bwtBlock = new byte[blockSize]; // Read block header - this.blockCRC = this.bitInputStream.ReadInteger(); //.ReadBits(32); + this.blockCRC = this.bitInputStream.ReadInteger(); //.ReadBits(32); this.blockRandomised = this.bitInputStream.ReadBoolean(); uint bwtStartPointer = this.bitInputStream.ReadBits(24); @@ -375,7 +871,6 @@ public BZip2BlockDecompressor(IBZip2BitInputStream bitInputStream, uint blockSiz this.InitialiseInverseBWT(bwtStartPointer); } - /// /// Decodes a byte from the final Run-Length Encoding stage, pulling a new byte from the /// Burrows-Wheeler Transform stage when required @@ -396,7 +891,8 @@ public int Read() this.rleRepeat = 1; this.rleAccumulator = 1; this.crc.UpdateCrc(nextByte); - } else + } + else { if (++this.rleAccumulator == 4) { @@ -405,7 +901,8 @@ public int Read() this.rleRepeat = _rleRepeat; this.rleAccumulator = 0; this.crc.UpdateCrc(nextByte, _rleRepeat); - } else + } + else { this.rleRepeat = 1; this.crc.UpdateCrc(nextByte); diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs index b121dfa97..6d52a1ab6 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2DivSufSort.cs @@ -4,6 +4,7 @@ // Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 using System; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// @@ -18,10 +19,10 @@ internal class BZip2DivSufSort { private class StackEntry { - readonly public int a; - readonly public int b; - readonly public int c; - readonly public int d; + public readonly int a; + public readonly int b; + public readonly int c; + public readonly int d; public StackEntry(int a, int b, int c, int d) { @@ -34,8 +35,8 @@ public StackEntry(int a, int b, int c, int d) private class PartitionResult { - readonly public int first; - readonly public int last; + public readonly int first; + public readonly int last; public PartitionResult(int first, int last) { @@ -51,7 +52,6 @@ private class TRBudget public bool update(int size, int n) { - this.budget -= n; if (this.budget <= 0) { @@ -80,14 +80,262 @@ public TRBudget(int budget, int chance) private static readonly int[] log2table = { - -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + -1, + 0, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, }; private readonly int[] SA; @@ -130,33 +378,42 @@ public int BWT() } // ReSharper disable LoopVariableIsNeverChangedInsideLoop - private static void swapElements (int[] array1, int index1, int[] array2, int index2) + private static void swapElements(int[] array1, int index1, int[] array2, int index2) { var temp = array1[index1]; array1[index1] = array2[index2]; array2[index2] = temp; } - private int ssCompare (int p1, int p2, int depth) + private int ssCompare(int p1, int p2, int depth) { - int U1n, U2n; // pointers within T - int U1, U2; + int U1n, + U2n; // pointers within T + int U1, + U2; for ( - U1 = depth + this.SA[p1], U2 = depth + this.SA[p2], U1n = this.SA[p1 + 1] + 2, U2n = this.SA[p2 + 1] + 2; + U1 = depth + this.SA[p1], U2 = depth + this.SA[p2], U1n = this.SA[p1 + 1] + 2, U2n = + this.SA[p2 + 1] + 2; (U1 < U1n) && (U2 < U2n) && (this.T[U1] == this.T[U2]); ++U1, ++U2 ) { } - return U1 < U1n ? (U2 < U2n ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1) : (U2 < U2n ? -1 : 0); + return U1 < U1n + ? (U2 < U2n ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1) + : (U2 < U2n ? -1 : 0); } - private int ssCompareLast (int PA, int p1, int p2, int depth, int size) + private int ssCompareLast(int PA, int p1, int p2, int depth, int size) { - int U1, U2, U1n, U2n; + int U1, + U2, + U1n, + U2n; for ( - U1 = depth + this.SA[p1], U2 = depth + this.SA[p2], U1n = size, U2n = this.SA[(p2 + 1)] + 2; + U1 = depth + this.SA[p1], U2 = depth + this.SA[p2], U1n = size, U2n = + this.SA[(p2 + 1)] + 2; (U1 < U1n) && (U2 < U2n) && (this.T[U1] == this.T[U2]); ++U1, ++U2 ) { } @@ -173,12 +430,12 @@ private int ssCompareLast (int PA, int p1, int p2, int depth, int size) ++U1, ++U2 ) { } - return U1 < U1n ? - (U2 < U2n ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1) + return U1 < U1n + ? (U2 < U2n ? (this.T[U1] & 0xff) - (this.T[U2] & 0xff) : 1) : (U2 < U2n ? -1 : 0); } - private void ssInsertionSort (int PA, int first, int last, int depth) + private void ssInsertionSort(int PA, int first, int last, int depth) { int i; // pointer within SA @@ -187,7 +444,11 @@ private void ssInsertionSort (int PA, int first, int last, int depth) int j; // pointer within SA int t; int r; - for (t = this.SA[i], j = i + 1; 0 < (r = this.ssCompare (PA + t, PA + this.SA[j], depth));) + for ( + t = this.SA[i], j = i + 1; + 0 < (r = this.ssCompare(PA + t, PA + this.SA[j], depth)); + + ) { do { @@ -206,13 +467,18 @@ private void ssInsertionSort (int PA, int first, int last, int depth) } } - private void ssFixdown (int Td, int PA, int sa, int i, int size) + private void ssFixdown(int Td, int PA, int sa, int i, int size) { - int j, k; + int j, + k; int v; int c; - for (v = this.SA[sa + i], c = (this.T[Td + this.SA[PA + v]]) & 0xff; (j = 2 * i + 1) < size; this.SA[sa + i] = this.SA[sa + k], i = k) + for ( + v = this.SA[sa + i], c = (this.T[Td + this.SA[PA + v]]) & 0xff; + (j = 2 * i + 1) < size; + this.SA[sa + i] = this.SA[sa + k], i = k + ) { int d = this.T[Td + this.SA[PA + this.SA[sa + (k = j++)]]] & 0xff; int e; @@ -221,12 +487,13 @@ private void ssFixdown (int Td, int PA, int sa, int i, int size) k = j; d = e; } - if (d <= c) break; + if (d <= c) + break; } this.SA[sa + i] = v; } - private void ssHeapSort (int Td, int PA, int sa, int size) + private void ssHeapSort(int Td, int PA, int sa, int size) { int i; @@ -234,33 +501,36 @@ private void ssHeapSort (int Td, int PA, int sa, int size) if ((size % 2) == 0) { m--; - if ((this.T[Td + this.SA[PA + this.SA[sa + (m / 2)]]] & 0xff) < (this.T[Td + this.SA[PA + this.SA[sa + m]]] & 0xff)) + if ( + (this.T[Td + this.SA[PA + this.SA[sa + (m / 2)]]] & 0xff) + < (this.T[Td + this.SA[PA + this.SA[sa + m]]] & 0xff) + ) { - swapElements (this.SA, sa + m, this.SA, sa + (m / 2)); + swapElements(this.SA, sa + m, this.SA, sa + (m / 2)); } } for (i = m / 2 - 1; 0 <= i; --i) { - this.ssFixdown (Td, PA, sa, i, m); + this.ssFixdown(Td, PA, sa, i, m); } if ((size % 2) == 0) { - swapElements (this.SA, sa, this.SA, sa + m); - this.ssFixdown (Td, PA, sa, 0, m); + swapElements(this.SA, sa, this.SA, sa + m); + this.ssFixdown(Td, PA, sa, 0, m); } for (i = m - 1; 0 < i; --i) { int t = this.SA[sa]; this.SA[sa] = this.SA[sa + i]; - this.ssFixdown (Td, PA, sa, 0, i); + this.ssFixdown(Td, PA, sa, 0, i); this.SA[sa + i] = t; } } - private int ssMedian3 (int Td, int PA, int v1, int v2, int v3) + private int ssMedian3(int Td, int PA, int v1, int v2, int v3) { var T_v1 = this.T[Td + this.SA[PA + this.SA[v1]]] & 0xff; var T_v2 = this.T[Td + this.SA[PA + this.SA[v2]]] & 0xff; @@ -282,7 +552,7 @@ private int ssMedian3 (int Td, int PA, int v1, int v2, int v3) return v2; } - private int ssMedian5 (int Td, int PA, int v1, int v2, int v3, int v4, int v5) + private int ssMedian5(int Td, int PA, int v1, int v2, int v3, int v4, int v5) { var T_v1 = this.T[Td + this.SA[PA + this.SA[v1]]] & 0xff; var T_v2 = this.T[Td + this.SA[PA + this.SA[v2]]] & 0xff; @@ -355,7 +625,7 @@ private int ssMedian5 (int Td, int PA, int v1, int v2, int v3, int v4, int v5) return T_v3 > T_v4 ? v4 : v3; } - private int ssPivot (int Td, int PA, int first, int last) + private int ssPivot(int Td, int PA, int first, int last) { int t = last - first; int middle = first + t / 2; @@ -364,36 +634,54 @@ private int ssPivot (int Td, int PA, int first, int last) { if (t <= 32) { - return this.ssMedian3 (Td, PA, first, middle, last - 1); + return this.ssMedian3(Td, PA, first, middle, last - 1); } t >>= 2; - return this.ssMedian5 (Td, PA, first, first + t, middle, last - 1 - t, last - 1); + return this.ssMedian5(Td, PA, first, first + t, middle, last - 1 - t, last - 1); } t >>= 3; - return this.ssMedian3 ( - Td, PA, - this.ssMedian3 (Td, PA, first, first + t, first + (t << 1)), - this.ssMedian3 (Td, PA, middle - t, middle, middle + t), - this.ssMedian3 (Td, PA, last - 1 - (t << 1), last - 1 - t, last - 1) - ); + return this.ssMedian3( + Td, + PA, + this.ssMedian3(Td, PA, first, first + t, first + (t << 1)), + this.ssMedian3(Td, PA, middle - t, middle, middle + t), + this.ssMedian3(Td, PA, last - 1 - (t << 1), last - 1 - t, last - 1) + ); } - private static int ssLog (int x) + private static int ssLog(int x) { - return ((x & 0xff00) != 0) ? 8 + BZip2DivSufSort.log2table[(x >> 8) & 0xff] : BZip2DivSufSort.log2table[x & 0xff]; + return ((x & 0xff00) != 0) + ? 8 + BZip2DivSufSort.log2table[(x >> 8) & 0xff] + : BZip2DivSufSort.log2table[x & 0xff]; } - private int ssSubstringPartition (int PA, int first, int last, int depth) + private int ssSubstringPartition(int PA, int first, int last, int depth) { - int a, b; + int a, + b; - for (a = first - 1, b = last;;) + for (a = first - 1, b = last; ; ) { - for (; (++a < b) && ((this.SA[PA + this.SA[a]] + depth) >= (this.SA[PA + this.SA[a] + 1] + 1));) + for ( + ; + (++a < b) + && ( + (this.SA[PA + this.SA[a]] + depth) >= (this.SA[PA + this.SA[a] + 1] + 1) + ); + + ) { this.SA[a] = ~this.SA[a]; } - for (; (a < --b) && ((this.SA[PA + this.SA[b]] + depth) < (this.SA[PA + this.SA[b] + 1] + 1));) { } + for ( + ; + (a < --b) + && ( + (this.SA[PA + this.SA[b]] + depth) < (this.SA[PA + this.SA[b] + 1] + 1) + ); + + ) { } if (b <= a) { break; @@ -408,7 +696,7 @@ private int ssSubstringPartition (int PA, int first, int last, int depth) return a; } - private void ssMultiKeyIntroSort (int PA, int first, int last, int depth) + private void ssMultiKeyIntroSort(int PA, int first, int last, int depth) { var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; @@ -416,15 +704,16 @@ private void ssMultiKeyIntroSort (int PA, int first, int last, int depth) int limit; int x = 0; - for (ssize = 0, limit = ssLog (last - first);;) + for (ssize = 0, limit = ssLog(last - first); ; ) { if ((last - first) <= BZip2DivSufSort.INSERTIONSORT_THRESHOLD) { if (1 < (last - first)) { - this.ssInsertionSort (PA, first, last, depth); + this.ssInsertionSort(PA, first, last, depth); } - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; last = entry.b; @@ -436,102 +725,136 @@ private void ssMultiKeyIntroSort (int PA, int first, int last, int depth) int Td = depth; if (limit-- == 0) { - this.ssHeapSort (Td, PA, first, last - first); + this.ssHeapSort(Td, PA, first, last - first); } int a; int v; if (limit < 0) { - for (a = first + 1, v = this.T[Td + this.SA[PA + this.SA[first]]] & 0xff; a < last; ++a) + for ( + a = first + 1, v = this.T[Td + this.SA[PA + this.SA[first]]] & 0xff; + a < last; + ++a + ) { if ((x = (this.T[Td + this.SA[PA + this.SA[a]]] & 0xff)) != v) { - if (1 < (a - first)) { break; } + if (1 < (a - first)) + { + break; + } v = x; first = a; } } if ((this.T[Td + this.SA[PA + this.SA[first]] - 1] & 0xff) < v) { - first = this.ssSubstringPartition (PA, first, a, depth); + first = this.ssSubstringPartition(PA, first, a, depth); } if ((a - first) <= (last - a)) { if (1 < (a - first)) { - stack[ssize++] = new StackEntry (a, last, depth, -1); + stack[ssize++] = new StackEntry(a, last, depth, -1); last = a; depth += 1; - limit = ssLog (a - first); - } else + limit = ssLog(a - first); + } + else { first = a; limit = -1; } - } else + } + else { if (1 < (last - a)) { - stack[ssize++] = new StackEntry (first, a, depth + 1, ssLog (a - first)); + stack[ssize++] = new StackEntry(first, a, depth + 1, ssLog(a - first)); first = a; limit = -1; - } else + } + else { last = a; depth += 1; - limit = ssLog (a - first); + limit = ssLog(a - first); } } continue; } - a = this.ssPivot (Td, PA, first, last); + a = this.ssPivot(Td, PA, first, last); v = this.T[Td + this.SA[PA + this.SA[a]]] & 0xff; - swapElements (this.SA, first, this.SA, a); + swapElements(this.SA, first, this.SA, a); int b; - for (b = first; (++b < last) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) == v); ) { } + for ( + b = first; + (++b < last) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) == v); + + ) { } if (((a = b) < last) && (x < v)) { - for (; (++b < last) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) <= v);) + for ( + ; + (++b < last) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) <= v); + + ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } } int c; - for (c = last; (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) == v);) { } + for ( + c = last; + (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) == v); + + ) { } int d; if ((b < (d = c)) && (x > v)) { - for (; (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) >= v);) + for ( + ; + (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) >= v); + + ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } } - for (; b < c;) + for (; b < c; ) { - swapElements (this.SA, b, this.SA, c); - for (; (++b < c) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) <= v);) + swapElements(this.SA, b, this.SA, c); + for ( + ; + (++b < c) && ((x = (this.T[Td + this.SA[PA + this.SA[b]]] & 0xff)) <= v); + + ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } - for (; (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) >= v);) + for ( + ; + (b < --c) && ((x = (this.T[Td + this.SA[PA + this.SA[c]]] & 0xff)) >= v); + + ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } @@ -551,7 +874,7 @@ private void ssMultiKeyIntroSort (int PA, int first, int last, int depth) int f; for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } if ((s = d - c) > (t = last - d - 1)) { @@ -559,90 +882,116 @@ private void ssMultiKeyIntroSort (int PA, int first, int last, int depth) } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } a = first + (b - a); c = last - (d - c); - b = (v <= (this.T[Td + this.SA[PA + this.SA[a]] - 1] & 0xff)) ? a : this.ssSubstringPartition (PA, a, c, depth); + b = + (v <= (this.T[Td + this.SA[PA + this.SA[a]] - 1] & 0xff)) + ? a + : this.ssSubstringPartition(PA, a, c, depth); if ((a - first) <= (last - c)) { if ((last - c) <= (c - b)) { - stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); - stack[ssize++] = new StackEntry (c, last, depth, limit); + stack[ssize++] = new StackEntry(b, c, depth + 1, ssLog(c - b)); + stack[ssize++] = new StackEntry(c, last, depth, limit); last = a; - } else if ((a - first) <= (c - b)) + } + else if ((a - first) <= (c - b)) { - stack[ssize++] = new StackEntry (c, last, depth, limit); - stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); + stack[ssize++] = new StackEntry(c, last, depth, limit); + stack[ssize++] = new StackEntry(b, c, depth + 1, ssLog(c - b)); last = a; - } else + } + else { - stack[ssize++] = new StackEntry (c, last, depth, limit); - stack[ssize++] = new StackEntry (first, a, depth, limit); + stack[ssize++] = new StackEntry(c, last, depth, limit); + stack[ssize++] = new StackEntry(first, a, depth, limit); first = b; last = c; depth += 1; - limit = ssLog (c - b); + limit = ssLog(c - b); } - } else + } + else { if ((a - first) <= (c - b)) { - stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); - stack[ssize++] = new StackEntry (first, a, depth, limit); + stack[ssize++] = new StackEntry(b, c, depth + 1, ssLog(c - b)); + stack[ssize++] = new StackEntry(first, a, depth, limit); first = c; - } else if ((last - c) <= (c - b)) + } + else if ((last - c) <= (c - b)) { - stack[ssize++] = new StackEntry (first, a, depth, limit); - stack[ssize++] = new StackEntry (b, c, depth + 1, ssLog (c - b)); + stack[ssize++] = new StackEntry(first, a, depth, limit); + stack[ssize++] = new StackEntry(b, c, depth + 1, ssLog(c - b)); first = c; - } else + } + else { - stack[ssize++] = new StackEntry (first, a, depth, limit); - stack[ssize++] = new StackEntry (c, last, depth, limit); + stack[ssize++] = new StackEntry(first, a, depth, limit); + stack[ssize++] = new StackEntry(c, last, depth, limit); first = b; last = c; depth += 1; - limit = ssLog (c - b); + limit = ssLog(c - b); } } - } else + } + else { limit += 1; if ((this.T[Td + this.SA[PA + this.SA[first]] - 1] & 0xff) < v) { - first = this.ssSubstringPartition (PA, first, last, depth); - limit = ssLog (last - first); + first = this.ssSubstringPartition(PA, first, last, depth); + limit = ssLog(last - first); } depth += 1; } } } - private static void ssBlockSwap (int[] array1, int first1, int[] array2, int first2, int size) + private static void ssBlockSwap( + int[] array1, + int first1, + int[] array2, + int first2, + int size + ) { - int a, b; + int a, + b; int i; for (i = size, a = first1, b = first2; 0 < i; --i, ++a, ++b) { - swapElements (array1, a, array2, b); + swapElements(array1, a, array2, b); } } - private void ssMergeForward (int PA, int[] buf, int bufoffset, int first, int middle, int last, int depth) + private void ssMergeForward( + int PA, + int[] buf, + int bufoffset, + int first, + int middle, + int last, + int depth + ) { - int i, j, k; + int i, + j, + k; int t; int bufend = bufoffset + (middle - first) - 1; - ssBlockSwap (buf, bufoffset, this.SA, first, middle - first); + ssBlockSwap(buf, bufoffset, this.SA, first, middle - first); - for (t = this.SA[first], i = first, j = bufoffset, k = middle;;) + for (t = this.SA[first], i = first, j = bufoffset, k = middle; ; ) { - int r = this.ssCompare (PA + buf[j], PA + this.SA[k], depth); + int r = this.ssCompare(PA + buf[j], PA + this.SA[k], depth); if (r < 0) { do @@ -655,7 +1004,8 @@ private void ssMergeForward (int PA, int[] buf, int bufoffset, int first, int } buf[j++] = this.SA[i]; } while (buf[j] < 0); - } else if (r > 0) + } + else if (r > 0) { do { @@ -673,7 +1023,8 @@ private void ssMergeForward (int PA, int[] buf, int bufoffset, int first, int return; } } while (this.SA[k] < 0); - } else + } + else { this.SA[k] = ~this.SA[k]; do @@ -707,35 +1058,48 @@ private void ssMergeForward (int PA, int[] buf, int bufoffset, int first, int } } - private void ssMergeBackward (int PA, int[] buf, int bufoffset, int first, int middle, int last, int depth) + private void ssMergeBackward( + int PA, + int[] buf, + int bufoffset, + int first, + int middle, + int last, + int depth + ) { - int p1, p2; - int i, j, k; + int p1, + p2; + int i, + j, + k; int t; int bufend = bufoffset + (last - middle); - ssBlockSwap (buf, bufoffset, this.SA, middle, last - middle); + ssBlockSwap(buf, bufoffset, this.SA, middle, last - middle); int x = 0; if (buf[bufend - 1] < 0) { - x |= 1; + x |= 1; p1 = PA + ~buf[bufend - 1]; - } else + } + else { - p1 = PA + buf[bufend - 1]; + p1 = PA + buf[bufend - 1]; } if (this.SA[middle - 1] < 0) { - x |= 2; + x |= 2; p2 = PA + ~this.SA[middle - 1]; - } else + } + else { - p2 = PA + this.SA[middle - 1]; + p2 = PA + this.SA[middle - 1]; } - for (t = this.SA[last - 1], i = last - 1, j = bufend - 1, k = middle - 1;;) + for (t = this.SA[last - 1], i = last - 1, j = bufend - 1, k = middle - 1; ; ) { - int r = this.ssCompare (p1, p2, depth); + int r = this.ssCompare(p1, p2, depth); if (r > 0) { if ((x & 1) != 0) @@ -757,13 +1121,15 @@ private void ssMergeBackward (int PA, int[] buf, int bufoffset, int first, in if (buf[j] < 0) { - x |= 1; + x |= 1; p1 = PA + ~buf[j]; - } else + } + else { - p1 = PA + buf[j]; + p1 = PA + buf[j]; } - } else if (r < 0) + } + else if (r < 0) { if ((x & 2) != 0) { @@ -790,13 +1156,15 @@ private void ssMergeBackward (int PA, int[] buf, int bufoffset, int first, in if (this.SA[k] < 0) { - x |= 2; + x |= 2; p2 = PA + ~this.SA[k]; - } else + } + else { - p2 = PA + this.SA[k]; + p2 = PA + this.SA[k]; } - } else + } + else { if ((x & 1) != 0) { @@ -840,62 +1208,77 @@ private void ssMergeBackward (int PA, int[] buf, int bufoffset, int first, in if (buf[j] < 0) { - x |= 1; + x |= 1; p1 = PA + ~buf[j]; - } else + } + else { - p1 = PA + buf[j]; + p1 = PA + buf[j]; } if (this.SA[k] < 0) { - x |= 2; + x |= 2; p2 = PA + ~this.SA[k]; - } else + } + else { - p2 = PA + this.SA[k]; + p2 = PA + this.SA[k]; } } } } - private static int getIDX (int a) + private static int getIDX(int a) { return (0 <= a) ? a : ~a; } - private void ssMergeCheckEqual (int PA, int depth, int a) + private void ssMergeCheckEqual(int PA, int depth, int a) { - if ((0 <= this.SA[a]) && (this.ssCompare (PA + getIDX (this.SA[a - 1]), PA + this.SA[a], depth) == 0)) + if ( + (0 <= this.SA[a]) + && (this.ssCompare(PA + getIDX(this.SA[a - 1]), PA + this.SA[a], depth) == 0) + ) { this.SA[a] = ~this.SA[a]; } } - private void ssMerge (int PA, int first, int middle, int last, int[] buf, int bufoffset, int bufsize, int depth) + private void ssMerge( + int PA, + int first, + int middle, + int last, + int[] buf, + int bufoffset, + int bufsize, + int depth + ) { var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; int ssize; int check; - for (check = 0, ssize = 0;;) + for (check = 0, ssize = 0; ; ) { if ((last - middle) <= bufsize) { if ((first < middle) && (middle < last)) { - this.ssMergeBackward (PA, buf, bufoffset, first, middle, last, depth); + this.ssMergeBackward(PA, buf, bufoffset, first, middle, last, depth); } if ((check & 1) != 0) { - this.ssMergeCheckEqual (PA, depth, first); + this.ssMergeCheckEqual(PA, depth, first); } if ((check & 2) != 0) { - this.ssMergeCheckEqual (PA, depth, last); + this.ssMergeCheckEqual(PA, depth, last); } - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; middle = entry.b; @@ -908,17 +1291,18 @@ private void ssMerge (int PA, int first, int middle, int last, int[] buf, int b { if (first < middle) { - this.ssMergeForward ( PA, buf, bufoffset, first, middle, last, depth); + this.ssMergeForward(PA, buf, bufoffset, first, middle, last, depth); } if ((check & 1) != 0) { - this.ssMergeCheckEqual (PA, depth, first); + this.ssMergeCheckEqual(PA, depth, first); } if ((check & 2) != 0) { - this.ssMergeCheckEqual (PA, depth, last); + this.ssMergeCheckEqual(PA, depth, last); } - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; middle = entry.b; @@ -931,13 +1315,18 @@ private void ssMerge (int PA, int first, int middle, int last, int[] buf, int b int len; int half; for ( - m = 0, len = Math.Min (middle - first, last - middle), half = len >> 1; + m = 0, len = Math.Min(middle - first, last - middle), half = len >> 1; 0 < len; len = half, half >>= 1 ) { - if (this.ssCompare (PA + getIDX (this.SA[middle + m + half]), - PA + getIDX (this.SA[middle - m - half - 1]), depth) < 0) + if ( + this.ssCompare( + PA + getIDX(this.SA[middle + m + half]), + PA + getIDX(this.SA[middle - m - half - 1]), + depth + ) < 0 + ) { m += half + 1; half -= (len & 1) ^ 1; @@ -946,7 +1335,7 @@ private void ssMerge (int PA, int first, int middle, int last, int[] buf, int b if (0 < m) { - ssBlockSwap (this.SA, middle - m, this.SA, middle, m); + ssBlockSwap(this.SA, middle - m, this.SA, middle, m); int j; int i = j = middle; int next = 0; @@ -954,43 +1343,54 @@ private void ssMerge (int PA, int first, int middle, int last, int[] buf, int b { if (this.SA[middle + m] < 0) { - for (; this.SA[i - 1] < 0; --i) - { } + for (; this.SA[i - 1] < 0; --i) { } this.SA[middle + m] = ~this.SA[middle + m]; } - for (j = middle; this.SA[j] < 0; ++j) - { } + for (j = middle; this.SA[j] < 0; ++j) { } next = 1; } if ((i - first) <= (last - j)) { - stack[ssize++] = new StackEntry (j, middle + m, last, (check & 2) | (next & 1)); + stack[ssize++] = new StackEntry( + j, + middle + m, + last, + (check & 2) | (next & 1) + ); middle -= m; last = i; check = (check & 1); - } else + } + else { if ((i == middle) && (middle == j)) { next <<= 1; } - stack[ssize++] = new StackEntry (first, middle - m, i, (check & 1) | (next & 2)); + stack[ssize++] = new StackEntry( + first, + middle - m, + i, + (check & 1) | (next & 2) + ); first = j; middle += m; check = (check & 2) | (next & 1); } - } else + } + else { if ((check & 1) != 0) { - this.ssMergeCheckEqual (PA, depth, first); + this.ssMergeCheckEqual(PA, depth, first); } - this.ssMergeCheckEqual (PA, depth, middle); + this.ssMergeCheckEqual(PA, depth, middle); if ((check & 2) != 0) { - this.ssMergeCheckEqual (PA, depth, last); + this.ssMergeCheckEqual(PA, depth, last); } - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; middle = entry.b; @@ -1000,7 +1400,17 @@ private void ssMerge (int PA, int first, int middle, int last, int[] buf, int b } } - private void subStringSort (int PA, int first, int last, int[] buf, int bufoffset, int bufsize, int depth, bool lastsuffix, int size) + private void subStringSort( + int PA, + int first, + int last, + int[] buf, + int bufoffset, + int bufsize, + int depth, + bool lastsuffix, + int size + ) { int a; int i; @@ -1009,9 +1419,13 @@ private void subStringSort (int PA, int first, int last, int[] buf, int bufof if (lastsuffix) ++first; - for (a = first, i = 0; (a + BZip2DivSufSort.SS_BLOCKSIZE) < last; a += BZip2DivSufSort.SS_BLOCKSIZE, ++i) + for ( + a = first, i = 0; + (a + BZip2DivSufSort.SS_BLOCKSIZE) < last; + a += BZip2DivSufSort.SS_BLOCKSIZE, ++i + ) { - this.ssMultiKeyIntroSort (PA, a, a + BZip2DivSufSort.SS_BLOCKSIZE, depth); + this.ssMultiKeyIntroSort(PA, a, a + BZip2DivSufSort.SS_BLOCKSIZE, depth); int[] curbuf = this.SA; int curbufoffset = a + BZip2DivSufSort.SS_BLOCKSIZE; int curbufsize = last - (a + BZip2DivSufSort.SS_BLOCKSIZE); @@ -1023,17 +1437,21 @@ private void subStringSort (int PA, int first, int last, int[] buf, int bufof } int b; int j; - for (b = a, k = BZip2DivSufSort.SS_BLOCKSIZE, j = i; (j & 1) != 0; b -= k, k <<= 1, j >>= 1) - this.ssMerge (PA, b - k, b, b + k, curbuf, curbufoffset, curbufsize, depth); + for ( + b = a, k = BZip2DivSufSort.SS_BLOCKSIZE, j = i; + (j & 1) != 0; + b -= k, k <<= 1, j >>= 1 + ) + this.ssMerge(PA, b - k, b, b + k, curbuf, curbufoffset, curbufsize, depth); } - this.ssMultiKeyIntroSort (PA, a, last, depth); + this.ssMultiKeyIntroSort(PA, a, last, depth); for (k = BZip2DivSufSort.SS_BLOCKSIZE; i != 0; k <<= 1, i >>= 1) { if ((i & 1) != 0) { - this.ssMerge (PA, a - k, a, last, buf, bufoffset, bufsize, depth); + this.ssMerge(PA, a - k, a, last, buf, bufoffset, bufsize, depth); a -= k; } } @@ -1043,7 +1461,14 @@ private void subStringSort (int PA, int first, int last, int[] buf, int bufof int r; for ( a = first, i = this.SA[first - 1], r = 1; - (a < last) && ((this.SA[a] < 0) || (0 < (r = this.ssCompareLast (PA, PA + i, PA + this.SA[a], depth, size)))); + (a < last) + && ( + (this.SA[a] < 0) + || ( + 0 + < (r = this.ssCompareLast(PA, PA + i, PA + this.SA[a], depth, size)) + ) + ); ++a ) { @@ -1057,23 +1482,32 @@ private void subStringSort (int PA, int first, int last, int[] buf, int bufof } } - private int trGetC (int ISA, int ISAd, int ISAn, int p) + private int trGetC(int ISA, int ISAd, int ISAn, int p) { - return (((ISAd + p) < ISAn) ? this.SA[ISAd + p] : this.SA[ISA + ((ISAd - ISA + p) % (ISAn - ISA))]); + return ( + ((ISAd + p) < ISAn) + ? this.SA[ISAd + p] + : this.SA[ISA + ((ISAd - ISA + p) % (ISAn - ISA))] + ); } - private void trFixdown (int ISA, int ISAd, int ISAn, int sa, int i, int size) + private void trFixdown(int ISA, int ISAd, int ISAn, int sa, int i, int size) { - int j, k; + int j, + k; int v; int c; - for (v = this.SA[sa + i], c = this.trGetC (ISA, ISAd, ISAn, v); (j = 2 * i + 1) < size; this.SA[sa + i] = this.SA[sa + k], i = k) + for ( + v = this.SA[sa + i], c = this.trGetC(ISA, ISAd, ISAn, v); + (j = 2 * i + 1) < size; + this.SA[sa + i] = this.SA[sa + k], i = k + ) { k = j++; - int d = this.trGetC (ISA, ISAd, ISAn, this.SA[sa + k]); + int d = this.trGetC(ISA, ISAd, ISAn, this.SA[sa + k]); int e; - if (d < (e = this.trGetC (ISA, ISAd, ISAn, this.SA[sa + j]))) + if (d < (e = this.trGetC(ISA, ISAd, ISAn, this.SA[sa + j]))) { k = j; d = e; @@ -1086,7 +1520,7 @@ private void trFixdown (int ISA, int ISAd, int ISAn, int sa, int i, int size this.SA[sa + i] = v; } - private void trHeapSort (int ISA, int ISAd, int ISAn, int sa, int size) + private void trHeapSort(int ISA, int ISAd, int ISAn, int sa, int size) { int i; @@ -1094,33 +1528,36 @@ private void trHeapSort (int ISA, int ISAd, int ISAn, int sa, int size) if ((size % 2) == 0) { m--; - if (this.trGetC (ISA, ISAd, ISAn, this.SA[sa + (m / 2)]) < this.trGetC (ISA, ISAd, ISAn, this.SA[sa + m])) + if ( + this.trGetC(ISA, ISAd, ISAn, this.SA[sa + (m / 2)]) + < this.trGetC(ISA, ISAd, ISAn, this.SA[sa + m]) + ) { - swapElements (this.SA, sa + m, this.SA, sa + (m / 2)); + swapElements(this.SA, sa + m, this.SA, sa + (m / 2)); } } for (i = m / 2 - 1; 0 <= i; --i) { - this.trFixdown (ISA, ISAd, ISAn, sa, i, m); + this.trFixdown(ISA, ISAd, ISAn, sa, i, m); } if ((size % 2) == 0) { - swapElements (this.SA, sa + 0, this.SA, sa + m); - this.trFixdown (ISA, ISAd, ISAn, sa, 0, m); + swapElements(this.SA, sa + 0, this.SA, sa + m); + this.trFixdown(ISA, ISAd, ISAn, sa, 0, m); } for (i = m - 1; 0 < i; --i) { int t = this.SA[sa + 0]; this.SA[sa + 0] = this.SA[sa + i]; - this.trFixdown (ISA, ISAd, ISAn, sa, 0, i); + this.trFixdown(ISA, ISAd, ISAn, sa, 0, i); this.SA[sa + i] = t; } } - private void trInsertionSort (int ISA, int ISAd, int ISAn, int first, int last) + private void trInsertionSort(int ISA, int ISAd, int ISAn, int first, int last) { int a; @@ -1129,7 +1566,16 @@ private void trInsertionSort (int ISA, int ISAd, int ISAn, int first, int last int b; int t; int r; - for (t = this.SA[a], b = a - 1; 0 > (r = this.trGetC (ISA, ISAd, ISAn, t) - this.trGetC (ISA, ISAd, ISAn, this.SA[b]));) + for ( + t = this.SA[a], b = a - 1; + 0 + > ( + r = + this.trGetC(ISA, ISAd, ISAn, t) + - this.trGetC(ISA, ISAd, ISAn, this.SA[b]) + ); + + ) { do { @@ -1148,18 +1594,26 @@ private void trInsertionSort (int ISA, int ISAd, int ISAn, int first, int last } } - private static int trLog (int x) + private static int trLog(int x) { - return ((x & 0xffff0000) != 0) ? - (((x & 0xff000000) != 0) ? 24 + BZip2DivSufSort.log2table[(x >> 24) & 0xff] : 16 + BZip2DivSufSort.log2table[(x >> 16) & 0xff]) - : (((x & 0x0000ff00) != 0) ? 8 + BZip2DivSufSort.log2table[(x >> 8) & 0xff] : 0 + BZip2DivSufSort.log2table[(x >> 0) & 0xff]); + return ((x & 0xffff0000) != 0) + ? ( + ((x & 0xff000000) != 0) + ? 24 + BZip2DivSufSort.log2table[(x >> 24) & 0xff] + : 16 + BZip2DivSufSort.log2table[(x >> 16) & 0xff] + ) + : ( + ((x & 0x0000ff00) != 0) + ? 8 + BZip2DivSufSort.log2table[(x >> 8) & 0xff] + : 0 + BZip2DivSufSort.log2table[(x >> 0) & 0xff] + ); } - private int trMedian3 (int ISA, int ISAd, int ISAn, int v1, int v2, int v3) + private int trMedian3(int ISA, int ISAd, int ISAn, int v1, int v2, int v3) { - var SA_v1 = this.trGetC (ISA, ISAd, ISAn, this.SA[v1]); - var SA_v2 = this.trGetC (ISA, ISAd, ISAn, this.SA[v2]); - var SA_v3 = this.trGetC (ISA, ISAd, ISAn, this.SA[v3]); + var SA_v1 = this.trGetC(ISA, ISAd, ISAn, this.SA[v1]); + var SA_v2 = this.trGetC(ISA, ISAd, ISAn, this.SA[v2]); + var SA_v3 = this.trGetC(ISA, ISAd, ISAn, this.SA[v3]); if (SA_v1 > SA_v2) { @@ -1178,13 +1632,13 @@ private int trMedian3 (int ISA, int ISAd, int ISAn, int v1, int v2, int v3) return v2; } - private int trMedian5 (int ISA, int ISAd, int ISAn, int v1, int v2, int v3, int v4, int v5) + private int trMedian5(int ISA, int ISAd, int ISAn, int v1, int v2, int v3, int v4, int v5) { - var SA_v1 = this.trGetC (ISA, ISAd, ISAn, this.SA[v1]); - var SA_v2 = this.trGetC (ISA, ISAd, ISAn, this.SA[v2]); - var SA_v3 = this.trGetC (ISA, ISAd, ISAn, this.SA[v3]); - var SA_v4 = this.trGetC (ISA, ISAd, ISAn, this.SA[v4]); - var SA_v5 = this.trGetC (ISA, ISAd, ISAn, this.SA[v5]); + var SA_v1 = this.trGetC(ISA, ISAd, ISAn, this.SA[v1]); + var SA_v2 = this.trGetC(ISA, ISAd, ISAn, this.SA[v2]); + var SA_v3 = this.trGetC(ISA, ISAd, ISAn, this.SA[v3]); + var SA_v4 = this.trGetC(ISA, ISAd, ISAn, this.SA[v4]); + var SA_v5 = this.trGetC(ISA, ISAd, ISAn, this.SA[v5]); int temp; int SA_vtemp; @@ -1251,7 +1705,7 @@ private int trMedian5 (int ISA, int ISAd, int ISAn, int v1, int v2, int v3, in return SA_v3 > SA_v4 ? v4 : v3; } - private int trPivot (int ISA, int ISAd, int ISAn, int first, int last) + private int trPivot(int ISA, int ISAd, int ISAn, int first, int last) { int t = last - first; int middle = first + t / 2; @@ -1260,26 +1714,32 @@ private int trPivot (int ISA, int ISAd, int ISAn, int first, int last) { if (t <= 32) { - return this.trMedian3 (ISA, ISAd, ISAn, first, middle, last - 1); + return this.trMedian3(ISA, ISAd, ISAn, first, middle, last - 1); } t >>= 2; - return this.trMedian5 ( - ISA, ISAd, ISAn, - first, first + t, + return this.trMedian5( + ISA, + ISAd, + ISAn, + first, + first + t, middle, - last - 1 - t, last - 1 - ); + last - 1 - t, + last - 1 + ); } t >>= 3; - return this.trMedian3 ( - ISA, ISAd, ISAn, - this.trMedian3 (ISA, ISAd, ISAn, first, first + t, first + (t << 1)), - this.trMedian3 (ISA, ISAd, ISAn, middle - t, middle, middle + t), - this.trMedian3 (ISA, ISAd, ISAn, last - 1 - (t << 1), last - 1 - t, last - 1) - ); + return this.trMedian3( + ISA, + ISAd, + ISAn, + this.trMedian3(ISA, ISAd, ISAn, first, first + t, first + (t << 1)), + this.trMedian3(ISA, ISAd, ISAn, middle - t, middle, middle + t), + this.trMedian3(ISA, ISAd, ISAn, last - 1 - (t << 1), last - 1 - t, last - 1) + ); } - private void lsUpdateGroup (int ISA, int first, int last) + private void lsUpdateGroup(int ISA, int first, int last) { int a; @@ -1312,7 +1772,7 @@ private void lsUpdateGroup (int ISA, int first, int last) } } - private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) + private void lsIntroSort(int ISA, int ISAd, int ISAn, int first, int last) { var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; @@ -1320,20 +1780,21 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) int x = 0; int ssize; - for (ssize = 0, limit = trLog (last - first);;) + for (ssize = 0, limit = trLog(last - first); ; ) { - if ((last - first) <= BZip2DivSufSort.INSERTIONSORT_THRESHOLD) { if (1 < (last - first)) { - this.trInsertionSort (ISA, ISAd, ISAn, first, last); - this.lsUpdateGroup (ISA, first, last); - } else if ((last - first) == 1) + this.trInsertionSort(ISA, ISAd, ISAn, first, last); + this.lsUpdateGroup(ISA, first, last); + } + else if ((last - first) == 1) { this.SA[first] = -1; } - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; last = entry.b; @@ -1345,20 +1806,21 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) int b; if (limit-- == 0) { - this.trHeapSort (ISA, ISAd, ISAn, first, last - first); + this.trHeapSort(ISA, ISAd, ISAn, first, last - first); for (a = last - 1; first < a; a = b) { for ( - x = this.trGetC (ISA, ISAd, ISAn, this.SA[a]), b = a - 1; - (first <= b) && (this.trGetC (ISA, ISAd, ISAn, this.SA[b]) == x); + x = this.trGetC(ISA, ISAd, ISAn, this.SA[a]), b = a - 1; + (first <= b) && (this.trGetC(ISA, ISAd, ISAn, this.SA[b]) == x); --b ) { this.SA[b] = ~this.SA[b]; } } - this.lsUpdateGroup (ISA, first, last); - if (ssize == 0) return; + this.lsUpdateGroup(ISA, first, last); + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; last = entry.b; @@ -1366,54 +1828,57 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) continue; } - a = this.trPivot (ISA, ISAd, ISAn, first, last); - swapElements (this.SA, first, this.SA, a); - int v = this.trGetC (ISA, ISAd, ISAn, this.SA[first]); + a = this.trPivot(ISA, ISAd, ISAn, first, last); + swapElements(this.SA, first, this.SA, a); + int v = this.trGetC(ISA, ISAd, ISAn, this.SA[first]); - for (b = first; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) == v);) - { } + for ( + b = first; + (++b < last) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) == v); + + ) { } if (((a = b) < last) && (x < v)) { - for (; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + for (; (++b < last) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) <= v); ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } } int c; - for (c = last; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) == v);) + for (c = last; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) == v); ) { } int d; if ((b < (d = c)) && (x > v)) { - for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + for (; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) >= v); ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } } - for (; b < c;) + for (; b < c; ) { - swapElements (this.SA, b, this.SA, c); - for (; (++b < c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + swapElements(this.SA, b, this.SA, c); + for (; (++b < c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) <= v); ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } - for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + for (; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) >= v); ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } @@ -1433,7 +1898,7 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) int f; for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } if ((s = d - c) > (t = last - d - 1)) { @@ -1441,7 +1906,7 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } a = first + (b - a); @@ -1460,33 +1925,38 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) } if ((b - a) == 1) { - this.SA[a] = - 1; + this.SA[a] = -1; } if ((a - first) <= (last - b)) { if (first < a) { - stack[ssize++] = new StackEntry (b, last, limit, 0); + stack[ssize++] = new StackEntry(b, last, limit, 0); last = a; - } else + } + else { first = b; } - } else + } + else { if (b < last) { - stack[ssize++] = new StackEntry (first, a, limit, 0); + stack[ssize++] = new StackEntry(first, a, limit, 0); first = b; - } else + } + else { last = a; } } - } else + } + else { - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; first = entry.a; last = entry.b; @@ -1495,7 +1965,7 @@ private void lsIntroSort (int ISA, int ISAd, int ISAn, int first, int last) } } - private void lsSort (int ISA, int x, int depth) + private void lsSort(int ISA, int x, int depth) { int ISAd; @@ -1511,7 +1981,8 @@ private void lsSort (int ISA, int x, int depth) { first -= t; skip += t; - } else + } + else { if (skip != 0) { @@ -1519,7 +1990,7 @@ private void lsSort (int ISA, int x, int depth) skip = 0; } last = this.SA[ISA + t] + 1; - this.lsIntroSort (ISA, ISAd, ISA + x, first, last); + this.lsIntroSort(ISA, ISAd, ISA + x, first, last); first = last; } } while (first < x); @@ -1548,53 +2019,58 @@ private void lsSort (int ISA, int x, int depth) } } - private PartitionResult trPartition (int ISA, int ISAd, int ISAn, int first, int last, int v) + private PartitionResult trPartition(int ISA, int ISAd, int ISAn, int first, int last, int v) { - int a, b, c, d; + int a, + b, + c, + d; var x = 0; - for (b = first - 1; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) == v);) - { } + for ( + b = first - 1; + (++b < last) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) == v); + + ) { } if (((a = b) < last) && (x < v)) { - for (; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + for (; (++b < last) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) <= v); ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } } - for (c = last; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) == v);) - { } + for (c = last; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) == v); ) { } if ((b < (d = c)) && (x > v)) { - for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + for (; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) >= v); ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } } - for (; b < c;) + for (; b < c; ) { - swapElements (this.SA, b, this.SA, c); - for (; (++b < c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + swapElements(this.SA, b, this.SA, c); + for (; (++b < c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) <= v); ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } - for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + for (; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) >= v); ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } @@ -1612,7 +2088,7 @@ private PartitionResult trPartition (int ISA, int ISAd, int ISAn, int first, i int f; for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } if ((s = d - c) > (t = last - d - 1)) { @@ -1620,18 +2096,20 @@ private PartitionResult trPartition (int ISA, int ISAd, int ISAn, int first, i } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } first += (b - a); last -= (d - c); } - return new PartitionResult (first, last); + return new PartitionResult(first, last); } - private void trCopy (int ISA, int ISAn, int first, int a, int b, int last, int depth) + private void trCopy(int ISA, int ISAn, int first, int a, int b, int last, int depth) { - int c, d, e; + int c, + d, + e; int s; int v = b - 1; @@ -1661,7 +2139,15 @@ private void trCopy (int ISA, int ISAn, int first, int a, int b, int last, } } - private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRBudget budget, int size) + private void trIntroSort( + int ISA, + int ISAd, + int ISAn, + int first, + int last, + TRBudget budget, + int size + ) { var stack = new StackEntry[BZip2DivSufSort.STACK_SIZE]; @@ -1670,7 +2156,7 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB int limit; int ssize; - for (ssize = 0, limit = trLog (last - first);;) + for (ssize = 0, limit = trLog(last - first); ; ) { int a; int b; @@ -1681,8 +2167,9 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB { if (limit == -1) { - if (!budget.update (size, last - first)) break; - var result = this.trPartition (ISA, ISAd - 1, ISAn, first, last, last - 1); + if (!budget.update(size, last - first)) + break; + var result = this.trPartition(ISA, ISAd - 1, ISAn, first, last, last - 1); a = result.first; b = result.last; if ((first < a) || (b < last)) @@ -1702,42 +2189,54 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB } } - stack[ssize++] = new StackEntry (0, a, b, 0); - stack[ssize++] = new StackEntry (ISAd - 1, first, last, -2); + stack[ssize++] = new StackEntry(0, a, b, 0); + stack[ssize++] = new StackEntry(ISAd - 1, first, last, -2); if ((a - first) <= (last - b)) { if (1 < (a - first)) { - stack[ssize++] = new StackEntry (ISAd, b, last, trLog (last - b)); + stack[ssize++] = new StackEntry(ISAd, b, last, trLog(last - b)); last = a; - limit = trLog (a - first); - } else if (1 < (last - b)) + limit = trLog(a - first); + } + else if (1 < (last - b)) { first = b; - limit = trLog (last - b); - } else + limit = trLog(last - b); + } + else { - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; } - } else + } + else { if (1 < (last - b)) { - stack[ssize++] = new StackEntry (ISAd, first, a, trLog (a - first)); + stack[ssize++] = new StackEntry( + ISAd, + first, + a, + trLog(a - first) + ); first = b; - limit = trLog (last - b); - } else if (1 < (a - first)) + limit = trLog(last - b); + } + else if (1 < (a - first)) { last = a; - limit = trLog (a - first); - } else + limit = trLog(a - first); + } + else { - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; ISAd = entry.a; first = entry.b; @@ -1745,31 +2244,36 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB limit = entry.d; } } - } else + } + else { for (c = first; c < last; ++c) { this.SA[ISA + this.SA[c]] = c; } - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; } - } else if (limit == -2) + } + else if (limit == -2) { a = stack[--ssize].b; b = stack[ssize].c; - this.trCopy (ISA, ISAn, first, a, b, last, ISAd - ISA); - if (ssize == 0) return; + this.trCopy(ISA, ISAn, first, a, b, last, ISAd - ISA); + if (ssize == 0) + return; var entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; - } else + } + else { if (0 <= this.SA[first]) { @@ -1787,7 +2291,10 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB { this.SA[a] = ~this.SA[a]; } while (this.SA[++a] < 0); - next = (this.SA[ISA + this.SA[a]] != this.SA[ISAd + this.SA[a]]) ? trLog (a - first + 1) : -1; + next = + (this.SA[ISA + this.SA[a]] != this.SA[ISAd + this.SA[a]]) + ? trLog(a - first + 1) + : -1; if (++a < last) { for (b = first, v = a - 1; b < a; ++b) @@ -1798,27 +2305,31 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB if ((a - first) <= (last - a)) { - stack[ssize++] = new StackEntry (ISAd, a, last, -3); + stack[ssize++] = new StackEntry(ISAd, a, last, -3); ISAd += 1; last = a; limit = next; - } else + } + else { if (1 < (last - a)) { - stack[ssize++] = new StackEntry (ISAd + 1, first, a, next); + stack[ssize++] = new StackEntry(ISAd + 1, first, a, next); first = a; limit = -3; - } else + } + else { ISAd += 1; last = a; limit = next; } } - } else + } + else { - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; ISAd = entry.a; first = entry.b; @@ -1831,21 +2342,23 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB if ((last - first) <= BZip2DivSufSort.INSERTIONSORT_THRESHOLD) { - if (!budget.update (size, last - first)) break; - this.trInsertionSort (ISA, ISAd, ISAn, first, last); + if (!budget.update(size, last - first)) + break; + this.trInsertionSort(ISA, ISAd, ISAn, first, last); limit = -3; continue; } if (limit-- == 0) { - if (!budget.update (size, last - first)) break; - this.trHeapSort (ISA, ISAd, ISAn, first, last - first); + if (!budget.update(size, last - first)) + break; + this.trHeapSort(ISA, ISAd, ISAn, first, last - first); for (a = last - 1; first < a; a = b) { for ( - x = this.trGetC (ISA, ISAd, ISAn, this.SA[a]), b = a - 1; - (first <= b) && (this.trGetC (ISA, ISAd, ISAn, this.SA[b]) == x); + x = this.trGetC(ISA, ISAd, ISAn, this.SA[a]), b = a - 1; + (first <= b) && (this.trGetC(ISA, ISAd, ISAn, this.SA[b]) == x); --b ) { @@ -1856,53 +2369,56 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB continue; } - a = this.trPivot (ISA, ISAd, ISAn, first, last); + a = this.trPivot(ISA, ISAd, ISAn, first, last); - swapElements (this.SA, first, this.SA, a); - v = this.trGetC (ISA, ISAd, ISAn, this.SA[first]); - for (b = first; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) == v);) - { } + swapElements(this.SA, first, this.SA, a); + v = this.trGetC(ISA, ISAd, ISAn, this.SA[first]); + for ( + b = first; + (++b < last) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) == v); + + ) { } if (((a = b) < last) && (x < v)) { - for (; (++b < last) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + for (; (++b < last) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) <= v); ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } } - for (c = last; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) == v);) + for (c = last; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) == v); ) { } int d; if ((b < (d = c)) && (x > v)) { - for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + for (; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) >= v); ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } } - for (; b < c;) + for (; b < c; ) { - swapElements (this.SA, b, this.SA, c); - for (; (++b < c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[b])) <= v);) + swapElements(this.SA, b, this.SA, c); + for (; (++b < c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[b])) <= v); ) { if (x == v) { - swapElements (this.SA, b, this.SA, a); + swapElements(this.SA, b, this.SA, a); ++a; } } - for (; (b < --c) && ((x = this.trGetC (ISA, ISAd, ISAn, this.SA[c])) >= v);) + for (; (b < --c) && ((x = this.trGetC(ISA, ISAd, ISAn, this.SA[c])) >= v); ) { if (x == v) { - swapElements (this.SA, c, this.SA, d); + swapElements(this.SA, c, this.SA, d); --d; } } @@ -1921,7 +2437,7 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB int f; for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } if ((s = d - c) > (t = last - d - 1)) { @@ -1929,12 +2445,12 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { - swapElements (this.SA, e, this.SA, f); + swapElements(this.SA, e, this.SA, f); } a = first + (b - a); b = last - (d - c); - next = (this.SA[ISA + this.SA[a]] != v) ? trLog (b - a) : -1; + next = (this.SA[ISA + this.SA[a]] != v) ? trLog(b - a) : -1; for (c = first, v = a - 1; c < a; ++c) { @@ -1954,123 +2470,143 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB { if (1 < (a - first)) { - stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); - stack[ssize++] = new StackEntry (ISAd, b, last, limit); + stack[ssize++] = new StackEntry(ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry(ISAd, b, last, limit); last = a; - } else if (1 < (last - b)) + } + else if (1 < (last - b)) { - stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry(ISAd + 1, a, b, next); first = b; - } else if (1 < (b - a)) + } + else if (1 < (b - a)) { ISAd += 1; first = a; last = b; limit = next; - } else + } + else { - if (ssize == 0) return; + if (ssize == 0) + return; var entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; } - } else if ((a - first) <= (b - a)) + } + else if ((a - first) <= (b - a)) { if (1 < (a - first)) { - stack[ssize++] = new StackEntry (ISAd, b, last, limit); - stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry(ISAd, b, last, limit); + stack[ssize++] = new StackEntry(ISAd + 1, a, b, next); last = a; - } else if (1 < (b - a)) + } + else if (1 < (b - a)) { - stack[ssize++] = new StackEntry (ISAd, b, last, limit); + stack[ssize++] = new StackEntry(ISAd, b, last, limit); ISAd += 1; first = a; last = b; limit = next; - } else + } + else { first = b; } - } else + } + else { if (1 < (b - a)) { - stack[ssize++] = new StackEntry (ISAd, b, last, limit); - stack[ssize++] = new StackEntry (ISAd, first, a, limit); + stack[ssize++] = new StackEntry(ISAd, b, last, limit); + stack[ssize++] = new StackEntry(ISAd, first, a, limit); ISAd += 1; first = a; last = b; limit = next; - } else + } + else { - stack[ssize++] = new StackEntry (ISAd, b, last, limit); + stack[ssize++] = new StackEntry(ISAd, b, last, limit); last = a; } } - } else + } + else { if ((a - first) <= (b - a)) { if (1 < (last - b)) { - stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); - stack[ssize++] = new StackEntry (ISAd, first, a, limit); + stack[ssize++] = new StackEntry(ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry(ISAd, first, a, limit); first = b; - } else if (1 < (a - first)) + } + else if (1 < (a - first)) { - stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry(ISAd + 1, a, b, next); last = a; - } else if (1 < (b - a)) + } + else if (1 < (b - a)) { ISAd += 1; first = a; last = b; limit = next; - } else + } + else { - stack[ssize++] = new StackEntry (ISAd, first, last, limit); + stack[ssize++] = new StackEntry(ISAd, first, last, limit); } - } else if ((last - b) <= (b - a)) + } + else if ((last - b) <= (b - a)) { if (1 < (last - b)) { - stack[ssize++] = new StackEntry (ISAd, first, a, limit); - stack[ssize++] = new StackEntry (ISAd + 1, a, b, next); + stack[ssize++] = new StackEntry(ISAd, first, a, limit); + stack[ssize++] = new StackEntry(ISAd + 1, a, b, next); first = b; - } else if (1 < (b - a)) + } + else if (1 < (b - a)) { - stack[ssize++] = new StackEntry (ISAd, first, a, limit); + stack[ssize++] = new StackEntry(ISAd, first, a, limit); ISAd += 1; first = a; last = b; limit = next; - } else + } + else { last = a; } - } else + } + else { if (1 < (b - a)) { - stack[ssize++] = new StackEntry (ISAd, first, a, limit); - stack[ssize++] = new StackEntry (ISAd, b, last, limit); + stack[ssize++] = new StackEntry(ISAd, first, a, limit); + stack[ssize++] = new StackEntry(ISAd, b, last, limit); ISAd += 1; first = a; last = b; limit = next; - } else + } + else { - stack[ssize++] = new StackEntry (ISAd, first, a, limit); + stack[ssize++] = new StackEntry(ISAd, first, a, limit); first = b; } } } - } else + } + else { - if (!budget.update (size, last - first)) break; // BUGFIX : Added to prevent an infinite loop in the original code + if (!budget.update(size, last - first)) + break; // BUGFIX : Added to prevent an infinite loop in the original code limit += 1; ISAd += 1; } @@ -2080,30 +2616,31 @@ private void trIntroSort (int ISA, int ISAd, int ISAn, int first, int last, TRB { if (stack[s].d == -3) { - this.lsUpdateGroup (ISA, stack[s].b, stack[s].c); + this.lsUpdateGroup(ISA, stack[s].b, stack[s].c); } } } - private void trSort (int ISA, int x, int depth) + private void trSort(int ISA, int x, int depth) { int first = 0; if (-x < this.SA[0]) { - var budget = new TRBudget (x, trLog (x) * 2 / 3 + 1); + var budget = new TRBudget(x, trLog(x) * 2 / 3 + 1); do { int t; if ((t = this.SA[first]) < 0) { first -= t; - } else + } + else { int last = this.SA[ISA + t] + 1; if (1 < (last - first)) { - this.trIntroSort (ISA, ISA + depth, ISA + x, first, last, budget, x); + this.trIntroSort(ISA, ISA + depth, ISA + x, first, last, budget, x); if (budget.chance == 0) { // Switch to Larsson-Sadakane sorting algorithm. @@ -2111,33 +2648,36 @@ private void trSort (int ISA, int x, int depth) { this.SA[0] = -first; } - this.lsSort (ISA, x, depth); + this.lsSort(ISA, x, depth); break; } } first = last; - } } while (first < x); } } - private static int BUCKET_B (int c0, int c1) + private static int BUCKET_B(int c0, int c1) { return (c1 << 8) | c0; } - private static int BUCKET_BSTAR (int c0, int c1) + private static int BUCKET_BSTAR(int c0, int c1) { return (c0 << 8) | c1; } - private int sortTypeBstar (int[] bucketA, int[] bucketB) + private int sortTypeBstar(int[] bucketA, int[] bucketB) { var tempbuf = new int[256]; - int i, j, k, t; - int c0, c1; + int i, + j, + k, + t; + int c0, + c1; int flag; for (i = 1, flag = 1; i < this.n; ++i) @@ -2154,24 +2694,34 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) i = this.n - 1; int m = this.n; - int ti, ti1, t0; - if (((ti = (this.T[i] & 0xff)) < (t0 = (this.T[0] & 0xff))) || ((this.T[i] == this.T[0]) && (flag != 0))) + int ti, + ti1, + t0; + if ( + ((ti = (this.T[i] & 0xff)) < (t0 = (this.T[0] & 0xff))) + || ((this.T[i] == this.T[0]) && (flag != 0)) + ) { if (flag == 0) { - ++bucketB[BUCKET_BSTAR (ti, t0)]; + ++bucketB[BUCKET_BSTAR(ti, t0)]; this.SA[--m] = i; - } else + } + else { - ++bucketB[BUCKET_B (ti, t0)]; + ++bucketB[BUCKET_B(ti, t0)]; } - for (--i; (0 <= i) && ((ti = (this.T[i] & 0xff)) <= (ti1 = (this.T[i + 1] & 0xff))); --i) + for ( + --i; + (0 <= i) && ((ti = (this.T[i] & 0xff)) <= (ti1 = (this.T[i + 1] & 0xff))); + --i + ) { - ++bucketB[BUCKET_B (ti, ti1)]; + ++bucketB[BUCKET_B(ti, ti1)]; } } - for (; 0 <= i;) + for (; 0 <= i; ) { do { @@ -2180,11 +2730,15 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) if (0 <= i) { - ++bucketB[BUCKET_BSTAR (this.T[i] & 0xff, this.T[i + 1] & 0xff)]; + ++bucketB[BUCKET_BSTAR(this.T[i] & 0xff, this.T[i + 1] & 0xff)]; this.SA[--m] = i; - for (--i; (0 <= i) && ((ti = (this.T[i] & 0xff)) <= (ti1 = (this.T[i + 1] & 0xff))); --i) + for ( + --i; + (0 <= i) && ((ti = (this.T[i] & 0xff)) <= (ti1 = (this.T[i + 1] & 0xff))); + --i + ) { - ++bucketB[BUCKET_B (ti, ti1)]; + ++bucketB[BUCKET_B(ti, ti1)]; } } } @@ -2202,12 +2756,12 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) { t = i + bucketA[c0]; bucketA[c0] = i + j; - i = t + bucketB[BUCKET_B (c0, c0)]; + i = t + bucketB[BUCKET_B(c0, c0)]; for (c1 = c0 + 1; c1 < 256; ++c1) { - j += bucketB[BUCKET_BSTAR (c0, c1)]; + j += bucketB[BUCKET_BSTAR(c0, c1)]; bucketB[(c0 << 8) | c1] = j; - i += bucketB[BUCKET_B (c0, c1)]; + i += bucketB[BUCKET_B(c0, c1)]; } } @@ -2218,12 +2772,12 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) t = this.SA[PAb + i]; c0 = this.T[t] & 0xff; c1 = this.T[t + 1] & 0xff; - this.SA[--bucketB[BUCKET_BSTAR (c0, c1)]] = i; + this.SA[--bucketB[BUCKET_BSTAR(c0, c1)]] = i; } t = this.SA[PAb + m - 1]; c0 = this.T[t] & 0xff; c1 = this.T[t + 1] & 0xff; - this.SA[--bucketB[BUCKET_BSTAR (c0, c1)]] = m - 1; + this.SA[--bucketB[BUCKET_BSTAR(c0, c1)]] = m - 1; int[] buf = this.SA; int bufoffset = m; @@ -2239,10 +2793,20 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) { for (c1 = 255; c0 < c1; j = i, --c1) { - i = bucketB[BUCKET_BSTAR (c0, c1)]; + i = bucketB[BUCKET_BSTAR(c0, c1)]; if (1 < (j - i)) { - this.subStringSort (PAb, i, j, buf, bufoffset, bufsize, 2, this.SA[i] == (m - 1), this.n); + this.subStringSort( + PAb, + i, + j, + buf, + bufoffset, + bufsize, + 2, + this.SA[i] == (m - 1), + this.n + ); } } } @@ -2270,28 +2834,28 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) this.SA[ISAb + this.SA[i]] = j; } - this.trSort (ISAb, m, 1); + this.trSort(ISAb, m, 1); i = this.n - 1; j = m; - if (((this.T[i] & 0xff) < (this.T[0] & 0xff)) || ((this.T[i] == this.T[0]) && (flag != 0))) + if ( + ((this.T[i] & 0xff) < (this.T[0] & 0xff)) + || ((this.T[i] == this.T[0]) && (flag != 0)) + ) { if (flag == 0) { this.SA[this.SA[ISAb + --j]] = i; } - for (--i; (0 <= i) && ((this.T[i] & 0xff) <= (this.T[i + 1] & 0xff)); --i) - { } + for (--i; (0 <= i) && ((this.T[i] & 0xff) <= (this.T[i + 1] & 0xff)); --i) { } } - for (; 0 <= i;) + for (; 0 <= i; ) { - for (--i; (0 <= i) && ((this.T[i] & 0xff) >= (this.T[i + 1] & 0xff)); --i) - { } + for (--i; (0 <= i) && ((this.T[i] & 0xff) >= (this.T[i + 1] & 0xff)); --i) { } if (0 <= i) { this.SA[this.SA[ISAb + --j]] = i; - for (--i; (0 <= i) && ((this.T[i] & 0xff) <= (this.T[i + 1] & 0xff)); --i) - { } + for (--i; (0 <= i) && ((this.T[i] & 0xff) <= (this.T[i + 1] & 0xff)); --i) { } } } @@ -2299,19 +2863,19 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) { for (c1 = 255; c0 < c1; --c1) { - t = i - bucketB[BUCKET_B (c0, c1)]; - bucketB[BUCKET_B (c0, c1)] = i + 1; + t = i - bucketB[BUCKET_B(c0, c1)]; + bucketB[BUCKET_B(c0, c1)] = i + 1; - for (i = t, j = bucketB[BUCKET_BSTAR (c0, c1)]; j <= k; --i, --k) + for (i = t, j = bucketB[BUCKET_BSTAR(c0, c1)]; j <= k; --i, --k) { this.SA[i] = this.SA[k]; } } - t = i - bucketB[BUCKET_B (c0, c0)]; - bucketB[BUCKET_B (c0, c0)] = i + 1; + t = i - bucketB[BUCKET_B(c0, c0)]; + bucketB[BUCKET_B(c0, c0)] = i + 1; if (c0 < 255) { - bucketB[BUCKET_BSTAR (c0, c0 + 1)] = t + 1; + bucketB[BUCKET_BSTAR(c0, c0 + 1)] = t + 1; } i = bucketA[c0]; } @@ -2319,18 +2883,25 @@ private int sortTypeBstar (int[] bucketA, int[] bucketB) return m; } - private int constructBWT (int[] bucketA, int[] bucketB) + private int constructBWT(int[] bucketA, int[] bucketB) { int i; int t = 0; - int s, s1; - int c0, c1, c2 = 0; + int s, + s1; + int c0, + c1, + c2 = 0; var orig = -1; for (c1 = 254; 0 <= c1; --c1) { int j; - for (i = bucketB[BUCKET_BSTAR (c1, c1 + 1)], j = bucketA[c1 + 1], t = 0, c2 = -1; i <= j; --j) + for ( + i = bucketB[BUCKET_BSTAR(c1, c1 + 1)], j = bucketA[c1 + 1], t = 0, c2 = -1; + i <= j; + --j + ) { if (0 <= (s1 = s = this.SA[j])) { @@ -2347,15 +2918,17 @@ private int constructBWT (int[] bucketA, int[] bucketB) if (c2 == c0) { this.SA[--t] = s; - } else + } + else { if (0 <= c2) - bucketB[BUCKET_B (c2, c1)] = t; + bucketB[BUCKET_B(c2, c1)] = t; - this.SA[t = bucketB[BUCKET_B (c2 = c0, c1)] - 1] = s; + this.SA[t = bucketB[BUCKET_B(c2 = c0, c1)] - 1] = s; } } - } else + } + else { this.SA[j] = ~s; } @@ -2377,15 +2950,16 @@ private int constructBWT (int[] bucketA, int[] bucketB) if (c0 == c2) { this.SA[++t] = s; - } else + } + else { if (c2 != -1) // BUGFIX: Original code can write to bucketA[-1] bucketA[c2] = t; this.SA[t = bucketA[c2 = c0] + 1] = s; } } - - } else + } + else { s1 = ~s1; } @@ -2394,7 +2968,8 @@ private int constructBWT (int[] bucketA, int[] bucketB) { this.SA[i] = this.T[this.n - 1]; orig = i; - } else + } + else { this.SA[i] = this.T[s1 - 1]; } diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs index 790cc7cfb..20a302799 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageDecoder.cs @@ -6,6 +6,7 @@ using System; using System.IO; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// @@ -23,22 +24,33 @@ internal class BZip2HuffmanStageDecoder private readonly byte[] selectors; // The minimum code length for each Huffman table - private readonly int[] minimumLengths = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES]; + private readonly int[] minimumLengths = new int[ + BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES + ]; /// /// An array of values for each Huffman table that must be subtracted from the numerical value of /// a Huffman code of a given bit length to give its canonical code index /// - private readonly int[,] codeBases = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2]; + private readonly int[,] codeBases = new int[ + BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, + BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2 + ]; /// /// An array of values for each Huffman table that gives the highest numerical value of a Huffman /// code of a given bit length /// - private readonly int[,] codeLimits = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 1]; + private readonly int[,] codeLimits = new int[ + BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, + BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 1 + ]; // A mapping for each Huffman table from canonical code index to output symbol - private readonly int[,] codeSymbols = new int[BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; + private readonly int[,] codeSymbols = new int[ + BZip2BlockDecompressor.HUFFMAN_MAXIMUM_TABLES, + BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE + ]; // The Huffman table for the current group private int currentTable; @@ -56,7 +68,12 @@ internal class BZip2HuffmanStageDecoder /// The total number of codes (uniform for each table) /// The Canonical Huffman code lengths for each table /// The Huffman table number to use for each group of 50 symbols - public BZip2HuffmanStageDecoder(IBZip2BitInputStream bitInputStream, int alphabetSize, byte[,] tableCodeLengths, byte[] selectors) + public BZip2HuffmanStageDecoder( + IBZip2BitInputStream bitInputStream, + int alphabetSize, + byte[,] tableCodeLengths, + byte[] selectors + ) { this.bitInputStream = bitInputStream; this.selectors = selectors; @@ -85,12 +102,19 @@ public int NextSymbol() // Starting with the minimum bit length for the table, read additional bits one at a time // until a complete code is recognised - for (uint codeBits = this.bitInputStream.ReadBits(codeLength); codeLength <= BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH; codeLength++) + for ( + uint codeBits = this.bitInputStream.ReadBits(codeLength); + codeLength <= BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH; + codeLength++ + ) { if (codeBits <= this.codeLimits[this.currentTable, codeLength]) { // Convert the code to a symbol index and return - return this.codeSymbols[this.currentTable, codeBits - this.codeBases[this.currentTable, codeLength]]; + return this.codeSymbols[ + this.currentTable, + codeBits - this.codeBases[this.currentTable, codeLength] + ]; } codeBits = (codeBits << 1) | this.bitInputStream.ReadBits(1); } @@ -98,15 +122,14 @@ public int NextSymbol() // A valid code was not recognised throw new IOException("Error decoding BZip2 block"); } - + /// /// Constructs Huffman decoding tables from lists of Canonical Huffman code lengths /// /// The total number of codes (uniform for each table) /// The Canonical Huffman code lengths for each table - private void CreateHuffmanDecodingTables (int alphabetSize, byte[,] tableCodeLengths) + private void CreateHuffmanDecodingTables(int alphabetSize, byte[,] tableCodeLengths) { - for (int table = 0; table < tableCodeLengths.GetLength(0); table++) { int minimumLength = BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH; @@ -115,8 +138,8 @@ private void CreateHuffmanDecodingTables (int alphabetSize, byte[,] tableCodeLe // Find the minimum and maximum code length for the table for (int i = 0; i < alphabetSize; i++) { - maximumLength = Math.Max (tableCodeLengths[table, i], maximumLength); - minimumLength = Math.Min (tableCodeLengths[table, i], minimumLength); + maximumLength = Math.Max(tableCodeLengths[table, i], maximumLength); + minimumLength = Math.Min(tableCodeLengths[table, i], minimumLength); } this.minimumLengths[table] = minimumLength; @@ -125,7 +148,11 @@ private void CreateHuffmanDecodingTables (int alphabetSize, byte[,] tableCodeLe { this.codeBases[table, tableCodeLengths[table, i] + 1]++; } - for (int i = 1; i < BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2; i++) + for ( + int i = 1; + i < BZip2HuffmanStageDecoder.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2; + i++ + ) { this.codeBases[table, i] += this.codeBases[table, i - 1]; } @@ -141,7 +168,11 @@ private void CreateHuffmanDecodingTables (int alphabetSize, byte[,] tableCodeLe } // Populate the mapping from canonical code index to output symbol - for (int bitLength = minimumLength, codeIndex = 0; bitLength <= maximumLength; bitLength++) + for ( + int bitLength = minimumLength, codeIndex = 0; + bitLength <= maximumLength; + bitLength++ + ) { for (int symbol = 0; symbol < alphabetSize; symbol++) { diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs index c03334e30..9f83d67a5 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/BZip2HuffmanStageEncoder.cs @@ -6,11 +6,12 @@ using System; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// /// An encoder for the BZip2 Huffman encoding stage - /// + /// internal class BZip2HuffmanStageEncoder { // Used in initial Huffman table generation @@ -45,7 +46,7 @@ internal class BZip2HuffmanStageEncoder // The selectors for each segment private readonly byte[] selectors; - + /// /// Public constructor /// @@ -54,7 +55,13 @@ internal class BZip2HuffmanStageEncoder /// The actual length of the MTF block /// The size of the MTF block's alphabet /// The frequencies the MTF block's symbols - public BZip2HuffmanStageEncoder(IBZip2BitOutputStream bitOutputStream, ushort[] mtfBlock, int mtfLength, int mtfAlphabetSize, int[] mtfSymbolFrequencies) + public BZip2HuffmanStageEncoder( + IBZip2BitOutputStream bitOutputStream, + ushort[] mtfBlock, + int mtfLength, + int mtfAlphabetSize, + int[] mtfSymbolFrequencies + ) { this.bitOutputStream = bitOutputStream; this.mtfBlock = mtfBlock; @@ -66,7 +73,10 @@ public BZip2HuffmanStageEncoder(IBZip2BitOutputStream bitOutputStream, ushort[] this.huffmanCodeLengths = new int[totalTables, mtfAlphabetSize]; this.huffmanMergedCodeSymbols = new int[totalTables, mtfAlphabetSize]; - this.selectors = new byte[(mtfLength + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH - 1) / BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH]; + this.selectors = new byte[ + (mtfLength + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH - 1) + / BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH + ]; } /// @@ -93,7 +103,7 @@ public void Encode() /// /// The length to select a table count for /// The selected table count - private static int selectTableCount (int mtfLength) + private static int selectTableCount(int mtfLength) { if (mtfLength >= 2400) return 6; @@ -111,7 +121,12 @@ private static int selectTableCount (int mtfLength) /// The frequencies of the symbols /// The array to which the generated code lengths should be written /// - private static void generateHuffmanCodeLengths (int alphabetSize, int[,] symbolFrequencies, int[,] codeLengths, int index) + private static void generateHuffmanCodeLengths( + int alphabetSize, + int[,] symbolFrequencies, + int[,] codeLengths, + int index + ) { var mergedFrequenciesAndIndices = new int[alphabetSize]; var sortedFrequencies = new int[alphabetSize]; @@ -131,7 +146,10 @@ private static void generateHuffmanCodeLengths (int alphabetSize, int[,] symbol sortedFrequencies[i] = mergedFrequenciesAndIndices[i] >> 9; // Allocate code lengths - the allocation is in place, so the code lengths will be in the sortedFrequencies array afterwards - HuffmanAllocator.AllocateHuffmanCodeLengths (sortedFrequencies, BZip2HuffmanStageEncoder.HUFFMAN_ENCODE_MAXIMUM_CODE_LENGTH); + HuffmanAllocator.AllocateHuffmanCodeLengths( + sortedFrequencies, + BZip2HuffmanStageEncoder.HUFFMAN_ENCODE_MAXIMUM_CODE_LENGTH + ); // Reverse the sort to place the code lengths in the same order as the symbols whose frequencies were passed in for (int i = 0; i < alphabetSize; i++) @@ -144,7 +162,7 @@ private static void generateHuffmanCodeLengths (int alphabetSize, int[,] symbol /// tables are invalid for actual Huffman code generation, and only serve as the seed for later /// iterative optimisation in optimiseSelectorsAndHuffmanTables(int) /// - private void generateHuffmanOptimisationSeeds () + private void generateHuffmanOptimisationSeeds() { int totalTables = this.huffmanCodeLengths.GetLength(0); int remainingLength = this.mtfLength; @@ -156,18 +174,27 @@ private void generateHuffmanOptimisationSeeds () int lowCostStart = lowCostEnd + 1; int actualCumulativeFrequency = 0; - while ((actualCumulativeFrequency < targetCumulativeFrequency) && (lowCostEnd < (this.mtfAlphabetSize - 1))) + while ( + (actualCumulativeFrequency < targetCumulativeFrequency) + && (lowCostEnd < (this.mtfAlphabetSize - 1)) + ) { actualCumulativeFrequency += this.mtfSymbolFrequencies[++lowCostEnd]; } - if ((lowCostEnd > lowCostStart) && (i != 0) && (i != (totalTables - 1)) && (((totalTables - i) & 1) == 0)) + if ( + (lowCostEnd > lowCostStart) + && (i != 0) + && (i != (totalTables - 1)) + && (((totalTables - i) & 1) == 0) + ) actualCumulativeFrequency -= this.mtfSymbolFrequencies[lowCostEnd--]; for (var j = 0; j < this.mtfAlphabetSize; j++) { if ((j < lowCostStart) || (j > lowCostEnd)) - this.huffmanCodeLengths[i, j] = BZip2HuffmanStageEncoder.HUFFMAN_HIGH_SYMBOL_COST; + this.huffmanCodeLengths[i, j] = + BZip2HuffmanStageEncoder.HUFFMAN_HIGH_SYMBOL_COST; } remainingLength -= actualCumulativeFrequency; @@ -182,7 +209,7 @@ private void generateHuffmanOptimisationSeeds () /// instead diverge (increase) slightly.
///
/// If true, write out the (final) chosen selectors - private void optimiseSelectorsAndHuffmanTables (bool storeSelectors) + private void optimiseSelectorsAndHuffmanTables(bool storeSelectors) { int totalTables = this.huffmanCodeLengths.GetLength(0); var tableFrequencies = new int[totalTables, this.mtfAlphabetSize]; @@ -190,9 +217,13 @@ private void optimiseSelectorsAndHuffmanTables (bool storeSelectors) int selectorIndex = 0; // Find the best table for each group of 50 block bytes based on the current Huffman code lengths - for (int groupStart = 0; groupStart < this.mtfLength;) + for (int groupStart = 0; groupStart < this.mtfLength; ) { - int groupEnd = Math.Min(groupStart + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH, this.mtfLength) - 1; + int groupEnd = + Math.Min( + groupStart + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH, + this.mtfLength + ) - 1; // Calculate the cost of this group when encoded by each table var cost = new int[totalTables]; @@ -208,7 +239,7 @@ private void optimiseSelectorsAndHuffmanTables (bool storeSelectors) // Find the table with the least cost for this group byte bestTable = 0; var bestCost = cost[0]; - for (byte i = 1 ; i < totalTables; i++) + for (byte i = 1; i < totalTables; i++) { var tableCost = cost[i]; if (tableCost < bestCost) @@ -236,7 +267,12 @@ private void optimiseSelectorsAndHuffmanTables (bool storeSelectors) // Generate new Huffman code lengths based on the frequencies for each table accumulated in this iteration for (int i = 0; i < totalTables; i++) { - generateHuffmanCodeLengths (this.mtfAlphabetSize, tableFrequencies, this.huffmanCodeLengths, i); + generateHuffmanCodeLengths( + this.mtfAlphabetSize, + tableFrequencies, + this.huffmanCodeLengths, + i + ); } } @@ -287,14 +323,14 @@ private void writeSelectorsAndHuffmanTables() int totalSelectors = this.selectors.Length; int totalTables = this.huffmanCodeLengths.GetLength(0); - this.bitOutputStream.WriteBits (3, (uint)totalTables); - this.bitOutputStream.WriteBits (15, (uint)totalSelectors); + this.bitOutputStream.WriteBits(3, (uint)totalTables); + this.bitOutputStream.WriteBits(15, (uint)totalSelectors); // Write the selectors var selectorMTF = new MoveToFront(); for (int i = 0; i < totalSelectors; i++) { - this.bitOutputStream.WriteUnary (selectorMTF.ValueToFront (this.selectors[i])); + this.bitOutputStream.WriteUnary(selectorMTF.ValueToFront(this.selectors[i])); } // Write the Huffman tables @@ -302,18 +338,18 @@ private void writeSelectorsAndHuffmanTables() { var currentLength = this.huffmanCodeLengths[i, 0]; - this.bitOutputStream.WriteBits (5, (uint)currentLength); + this.bitOutputStream.WriteBits(5, (uint)currentLength); for (var j = 0; j < this.mtfAlphabetSize; j++) { var codeLength = this.huffmanCodeLengths[i, j]; var value = (currentLength < codeLength) ? 2u : 3u; - var delta = Math.Abs (codeLength - currentLength); + var delta = Math.Abs(codeLength - currentLength); while (delta-- > 0) { - this.bitOutputStream.WriteBits (2, value); + this.bitOutputStream.WriteBits(2, value); } - this.bitOutputStream.WriteBoolean (false); + this.bitOutputStream.WriteBoolean(false); currentLength = codeLength; } } @@ -327,15 +363,22 @@ private void writeBlockData() { int selectorIndex = 0; - for (int mtfIndex = 0; mtfIndex < this.mtfLength;) + for (int mtfIndex = 0; mtfIndex < this.mtfLength; ) { - int groupEnd = Math.Min (mtfIndex + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH, this.mtfLength) - 1; + int groupEnd = + Math.Min( + mtfIndex + BZip2HuffmanStageEncoder.HUFFMAN_GROUP_RUN_LENGTH, + this.mtfLength + ) - 1; - int index = this.selectors [selectorIndex++]; + int index = this.selectors[selectorIndex++]; while (mtfIndex <= groupEnd) { - var mergedCodeSymbol = this.huffmanMergedCodeSymbols[index, this.mtfBlock[mtfIndex++]]; + var mergedCodeSymbol = this.huffmanMergedCodeSymbols[ + index, + this.mtfBlock[mtfIndex++] + ]; this.bitOutputStream.WriteBits(mergedCodeSymbol >> 24, (uint)mergedCodeSymbol); } } diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs index be28b7fb1..5985c6f4a 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/Bzip2MTFAndRLE2StageEncoder.cs @@ -18,15 +18,17 @@ internal class BZip2MTFAndRLE2StageEncoder // Actual length of the data in the bwtBlock array private readonly int bwtLength; - // At each position, true if the byte value with that index is present within the block, otherwise false + // At each position, true if the byte value with that index is present within the block, otherwise false private readonly bool[] bwtValuesInUse; // The output of the Move To Front Transform and Run-Length Encoding[2] stages private readonly ushort[] mtfBlock; // The global frequencies of values within the mtfBlock array - private readonly int[] mtfSymbolFrequencies = new int[BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; - + private readonly int[] mtfSymbolFrequencies = new int[ + BZip2MTFAndRLE2StageEncoder.HUFFMAN_MAXIMUM_ALPHABET_SIZE + ]; + // Maximum possible Huffman alphabet size public const int HUFFMAN_MAXIMUM_ALPHABET_SIZE = 258; @@ -35,14 +37,11 @@ internal class BZip2MTFAndRLE2StageEncoder // Huffman symbol used for run-length encoding public const ushort RLE_SYMBOL_RUNB = 1; - + // Gets the encoded MTF block public ushort[] MtfBlock { - get - { - return this.mtfBlock; - } + get { return this.mtfBlock; } } // Gets The actual length of the MTF block @@ -54,10 +53,7 @@ public ushort[] MtfBlock // Gets the frequencies of the MTF block's symbols public int[] MtfSymbolFrequencies { - get - { - return this.mtfSymbolFrequencies; - } + get { return this.mtfSymbolFrequencies; } } /// @@ -85,7 +81,7 @@ public void Encode() for (var i = 0; i < 256; i++) { if (this.bwtValuesInUse[i]) - huffmanSymbolMap[i] = (byte) totalUniqueValues++; + huffmanSymbolMap[i] = (byte)totalUniqueValues++; } int endOfBlockSymbol = totalUniqueValues + 1; @@ -103,7 +99,8 @@ public void Encode() if (mtfPosition == 0) { repeatCount++; - } else + } + else { if (repeatCount > 0) { @@ -112,11 +109,14 @@ public void Encode() { if ((repeatCount & 1) == 0) { - this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA; + this.mtfBlock[mtfIndex++] = + BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA; totalRunAs++; - } else + } + else { - this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB; + this.mtfBlock[mtfIndex++] = + BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB; totalRunBs++; } @@ -128,7 +128,7 @@ public void Encode() repeatCount = 0; } - this.mtfBlock[mtfIndex++] = (char) (mtfPosition + 1); + this.mtfBlock[mtfIndex++] = (char)(mtfPosition + 1); this.mtfSymbolFrequencies[mtfPosition + 1]++; } } @@ -142,7 +142,8 @@ public void Encode() { this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNA; totalRunAs++; - } else + } + else { this.mtfBlock[mtfIndex++] = BZip2MTFAndRLE2StageEncoder.RLE_SYMBOL_RUNB; totalRunBs++; @@ -162,7 +163,6 @@ public void Encode() this.MtfLength = mtfIndex + 1; this.MtfAlphabetSize = endOfBlockSymbol + 1; - } } } diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs index 2bbffb9bb..31594e263 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/CRC32.cs @@ -10,60 +10,284 @@ namespace SharpCompress.Compressors.BZip2MT.Algorithm /// internal class CRC32 { - /// The CRC lookup table + /// The CRC lookup table private static readonly uint[] Crc32Lookup = { - 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, - 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, - 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, - 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, - 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, - 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, - 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, - 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, - 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, - 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, - 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, - 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, - 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, - 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, - 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, - 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, - 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, - 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, - 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, - 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, - 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, - 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, - 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, - 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, - 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, - 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, - 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, - 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, - 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, - 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, - 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, - 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + 0x00000000, + 0x04c11db7, + 0x09823b6e, + 0x0d4326d9, + 0x130476dc, + 0x17c56b6b, + 0x1a864db2, + 0x1e475005, + 0x2608edb8, + 0x22c9f00f, + 0x2f8ad6d6, + 0x2b4bcb61, + 0x350c9b64, + 0x31cd86d3, + 0x3c8ea00a, + 0x384fbdbd, + 0x4c11db70, + 0x48d0c6c7, + 0x4593e01e, + 0x4152fda9, + 0x5f15adac, + 0x5bd4b01b, + 0x569796c2, + 0x52568b75, + 0x6a1936c8, + 0x6ed82b7f, + 0x639b0da6, + 0x675a1011, + 0x791d4014, + 0x7ddc5da3, + 0x709f7b7a, + 0x745e66cd, + 0x9823b6e0, + 0x9ce2ab57, + 0x91a18d8e, + 0x95609039, + 0x8b27c03c, + 0x8fe6dd8b, + 0x82a5fb52, + 0x8664e6e5, + 0xbe2b5b58, + 0xbaea46ef, + 0xb7a96036, + 0xb3687d81, + 0xad2f2d84, + 0xa9ee3033, + 0xa4ad16ea, + 0xa06c0b5d, + 0xd4326d90, + 0xd0f37027, + 0xddb056fe, + 0xd9714b49, + 0xc7361b4c, + 0xc3f706fb, + 0xceb42022, + 0xca753d95, + 0xf23a8028, + 0xf6fb9d9f, + 0xfbb8bb46, + 0xff79a6f1, + 0xe13ef6f4, + 0xe5ffeb43, + 0xe8bccd9a, + 0xec7dd02d, + 0x34867077, + 0x30476dc0, + 0x3d044b19, + 0x39c556ae, + 0x278206ab, + 0x23431b1c, + 0x2e003dc5, + 0x2ac12072, + 0x128e9dcf, + 0x164f8078, + 0x1b0ca6a1, + 0x1fcdbb16, + 0x018aeb13, + 0x054bf6a4, + 0x0808d07d, + 0x0cc9cdca, + 0x7897ab07, + 0x7c56b6b0, + 0x71159069, + 0x75d48dde, + 0x6b93dddb, + 0x6f52c06c, + 0x6211e6b5, + 0x66d0fb02, + 0x5e9f46bf, + 0x5a5e5b08, + 0x571d7dd1, + 0x53dc6066, + 0x4d9b3063, + 0x495a2dd4, + 0x44190b0d, + 0x40d816ba, + 0xaca5c697, + 0xa864db20, + 0xa527fdf9, + 0xa1e6e04e, + 0xbfa1b04b, + 0xbb60adfc, + 0xb6238b25, + 0xb2e29692, + 0x8aad2b2f, + 0x8e6c3698, + 0x832f1041, + 0x87ee0df6, + 0x99a95df3, + 0x9d684044, + 0x902b669d, + 0x94ea7b2a, + 0xe0b41de7, + 0xe4750050, + 0xe9362689, + 0xedf73b3e, + 0xf3b06b3b, + 0xf771768c, + 0xfa325055, + 0xfef34de2, + 0xc6bcf05f, + 0xc27dede8, + 0xcf3ecb31, + 0xcbffd686, + 0xd5b88683, + 0xd1799b34, + 0xdc3abded, + 0xd8fba05a, + 0x690ce0ee, + 0x6dcdfd59, + 0x608edb80, + 0x644fc637, + 0x7a089632, + 0x7ec98b85, + 0x738aad5c, + 0x774bb0eb, + 0x4f040d56, + 0x4bc510e1, + 0x46863638, + 0x42472b8f, + 0x5c007b8a, + 0x58c1663d, + 0x558240e4, + 0x51435d53, + 0x251d3b9e, + 0x21dc2629, + 0x2c9f00f0, + 0x285e1d47, + 0x36194d42, + 0x32d850f5, + 0x3f9b762c, + 0x3b5a6b9b, + 0x0315d626, + 0x07d4cb91, + 0x0a97ed48, + 0x0e56f0ff, + 0x1011a0fa, + 0x14d0bd4d, + 0x19939b94, + 0x1d528623, + 0xf12f560e, + 0xf5ee4bb9, + 0xf8ad6d60, + 0xfc6c70d7, + 0xe22b20d2, + 0xe6ea3d65, + 0xeba91bbc, + 0xef68060b, + 0xd727bbb6, + 0xd3e6a601, + 0xdea580d8, + 0xda649d6f, + 0xc423cd6a, + 0xc0e2d0dd, + 0xcda1f604, + 0xc960ebb3, + 0xbd3e8d7e, + 0xb9ff90c9, + 0xb4bcb610, + 0xb07daba7, + 0xae3afba2, + 0xaafbe615, + 0xa7b8c0cc, + 0xa379dd7b, + 0x9b3660c6, + 0x9ff77d71, + 0x92b45ba8, + 0x9675461f, + 0x8832161a, + 0x8cf30bad, + 0x81b02d74, + 0x857130c3, + 0x5d8a9099, + 0x594b8d2e, + 0x5408abf7, + 0x50c9b640, + 0x4e8ee645, + 0x4a4ffbf2, + 0x470cdd2b, + 0x43cdc09c, + 0x7b827d21, + 0x7f436096, + 0x7200464f, + 0x76c15bf8, + 0x68860bfd, + 0x6c47164a, + 0x61043093, + 0x65c52d24, + 0x119b4be9, + 0x155a565e, + 0x18197087, + 0x1cd86d30, + 0x029f3d35, + 0x065e2082, + 0x0b1d065b, + 0x0fdc1bec, + 0x3793a651, + 0x3352bbe6, + 0x3e119d3f, + 0x3ad08088, + 0x2497d08d, + 0x2056cd3a, + 0x2d15ebe3, + 0x29d4f654, + 0xc5a92679, + 0xc1683bce, + 0xcc2b1d17, + 0xc8ea00a0, + 0xd6ad50a5, + 0xd26c4d12, + 0xdf2f6bcb, + 0xdbee767c, + 0xe3a1cbc1, + 0xe760d676, + 0xea23f0af, + 0xeee2ed18, + 0xf0a5bd1d, + 0xf464a0aa, + 0xf9278673, + 0xfde69bc4, + 0x89b8fd09, + 0x8d79e0be, + 0x803ac667, + 0x84fbdbd0, + 0x9abc8bd5, + 0x9e7d9662, + 0x933eb0bb, + 0x97ffad0c, + 0xafb010b1, + 0xab710d06, + 0xa6322bdf, + 0xa2f33668, + 0xbcb4666d, + 0xb8757bda, + 0xb5365d03, + 0xb1f740b4, }; /// The current CRC private uint crc = 0xffffffff; - - /// Gets the current CRC + + /// Gets the current CRC public uint CRC => ~this.crc; /// Updates the CRC with a single byte /// The value to update the CRC with - public void UpdateCrc (int value) + public void UpdateCrc(int value) { this.crc = (this.crc << 8) ^ CRC32.Crc32Lookup[((this.crc >> 24) ^ value) & 0xff]; } - /// Update the CRC with a sequence of identical bytes + /// Update the CRC with a sequence of identical bytes /// The value to update the CRC with /// The number of bytes - public void UpdateCrc (int value, int count) + public void UpdateCrc(int value, int count) { while (count-- > 0) { diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs index 2c5ca5502..bc1eea761 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/HuffmanAllocator.cs @@ -4,11 +4,12 @@ // Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 using System; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// An in-place, length restricted Canonical Huffman code length allocator /// - /// Based on the algorithm proposed by R.L.Milidiú, A.A.Pessoa and E.S.Laber + /// Based on the algorithm proposed by R.L.Milidiú, A.A.Pessoa and E.S.Laber /// in "In-place Length-Restricted Prefix Coding" (see: http://www-di.inf.puc-rio.br/~laber/public/spire98.ps) /// and incorporating additional ideas from the implementation of "shcodec" by Simakov Alexander /// (see: http://webcenter.ru/~xander/) @@ -40,7 +41,8 @@ public static void AllocateHuffmanCodeLengths(int[] array, int maximumLength) if ((array[0] % array.Length) >= nodesToRelocate) { AllocateNodeLengths(array); - } else + } + else { var insertDepth = maximumLength - SignificantBits(nodesToRelocate - 1); AllocateNodeLengthsWithRelocation(array, nodesToRelocate, insertDepth); @@ -48,13 +50,13 @@ public static void AllocateHuffmanCodeLengths(int[] array, int maximumLength) } /// - /// + /// /// /// The code length array /// The input position /// The number of internal nodes to be relocated /// The smallest k such that nodesToMove <= k <= i and i <= (array[k] % array.length) - private static int First (int[] array, int i, int nodesToMove) + private static int First(int[] array, int i, int nodesToMove) { int length = array.Length; int limit = i; @@ -65,7 +67,7 @@ private static int First (int[] array, int i, int nodesToMove) k = i; i -= (limit - i + 1); } - i = Math.Max (nodesToMove - 1, i); + i = Math.Max(nodesToMove - 1, i); while (k > (i + 1)) { @@ -96,16 +98,21 @@ private static void SetExtendedParentPointers(int[] array) { temp = array[headNode]; array[headNode++] = tailNode; - } else + } + else { temp = array[topNode++]; } - if ((topNode >= length) || ((headNode < tailNode) && (array[headNode] < array[topNode]))) + if ( + (topNode >= length) + || ((headNode < tailNode) && (array[headNode] < array[topNode])) + ) { temp += array[headNode]; array[headNode++] = tailNode + length; - } else + } + else { temp += array[topNode++]; } @@ -120,12 +127,16 @@ private static void SetExtendedParentPointers(int[] array) /// The code length array /// The maximum bit length for the generated codes /// The number of nodes to relocate - private static int FindNodesToRelocate (int[] array, int maximumLength) + private static int FindNodesToRelocate(int[] array, int maximumLength) { int currentNode = array.Length - 2; - for (var currentDepth = 1; (currentDepth < (maximumLength - 1)) && (currentNode > 1); currentDepth++) - currentNode = First (array, currentNode - 1, 0); + for ( + var currentDepth = 1; + (currentDepth < (maximumLength - 1)) && (currentNode > 1); + currentDepth++ + ) + currentNode = First(array, currentNode - 1, 0); return currentNode; } @@ -134,7 +145,7 @@ private static int FindNodesToRelocate (int[] array, int maximumLength) /// A final allocation pass with no code length limit ///
/// The code length array - private static void AllocateNodeLengths (int[] array) + private static void AllocateNodeLengths(int[] array) { int firstNode = array.Length - 2; int nextNode = array.Length - 1; @@ -142,7 +153,7 @@ private static void AllocateNodeLengths (int[] array) for (int currentDepth = 1, availableNodes = 2; availableNodes > 0; currentDepth++) { int lastNode = firstNode; - firstNode = First (array, lastNode - 1, 0); + firstNode = First(array, lastNode - 1, 0); for (var i = availableNodes - (lastNode - firstNode); i > 0; i--) array[nextNode--] = currentDepth; @@ -157,7 +168,11 @@ private static void AllocateNodeLengths (int[] array) /// The code length array /// The number of internal nodes to be relocated /// The depth at which to insert relocated nodes - private static void AllocateNodeLengthsWithRelocation (int[] array, int nodesToMove, int insertDepth) + private static void AllocateNodeLengthsWithRelocation( + int[] array, + int nodesToMove, + int insertDepth + ) { int firstNode = array.Length - 2; int nextNode = array.Length - 1; @@ -167,13 +182,17 @@ private static void AllocateNodeLengthsWithRelocation (int[] array, int nodesTo for (int availableNodes = currentDepth << 1; availableNodes > 0; currentDepth++) { int lastNode = firstNode; - firstNode = (firstNode <= nodesToMove) ? firstNode : First (array, lastNode - 1, nodesToMove); + firstNode = + (firstNode <= nodesToMove) + ? firstNode + : First(array, lastNode - 1, nodesToMove); int offset = 0; if (currentDepth >= insertDepth) { - offset = Math.Min (nodesLeftToMove, 1 << (currentDepth - insertDepth)); - } else if (currentDepth == (insertDepth - 1)) + offset = Math.Min(nodesLeftToMove, 1 << (currentDepth - insertDepth)); + } + else if (currentDepth == (insertDepth - 1)) { offset = 1; if ((array[firstNode]) == lastNode) diff --git a/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs b/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs index 047985067..a9fa23251 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Algorithm/MoveToFront.cs @@ -4,6 +4,7 @@ // Ported from the Java implementation by Matthew Francis: https://github.com/MateuszBartosiewicz/bzip2 using System; + namespace SharpCompress.Compressors.BZip2MT.Algorithm { /// @@ -11,7 +12,7 @@ namespace SharpCompress.Compressors.BZip2MT.Algorithm /// internal class MoveToFront { - /// The Move To Front list + /// The Move To Front list private readonly byte[] mtf; /// Public constructor diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs index a59594021..d9f9e73de 100644 --- a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStream.cs @@ -5,6 +5,7 @@ using System.IO; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.InputStream { /// @@ -38,7 +39,8 @@ public bool ReadBoolean() if (this._bitCount > 0) { this._bitCount--; - } else + } + else { int byteRead = this._inputStream.ReadByte(); @@ -62,7 +64,8 @@ public uint ReadUnary() if (this._bitCount > 0) { this._bitCount--; - } else + } + else { var byteRead = this._inputStream.ReadByte(); diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs index 6d4f6866a..6e8c70159 100644 --- a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2BitInputStreamSplitter.cs @@ -4,9 +4,9 @@ using System; using System.IO; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.InputStream { - /// /// Helper class for splitting a BZip2 stream into multiple Memory Streams made up of individual BZip2 blocks. /// The spitting is done by looking for the magic block header 6 byte sequence. @@ -14,7 +14,6 @@ namespace SharpCompress.Compressors.BZip2MT.InputStream /// internal class BZip2BitInputStreamSplitter : IDisposable { - // The stream from which bits are read private BZip2BitInputStream? _bitInputStream; @@ -32,9 +31,14 @@ internal class BZip2BitInputStreamSplitter : IDisposable public int BlockSizeBytes => this._blockSizeBytes; public uint FinalCrc => this._finalCrc; - public BZip2BitInputStreamSplitter(Stream inputStream, InputStreamHeaderCheckType inputStreamHeaderCheck = InputStreamHeaderCheckType.FULL_HEADER, int manualBlockLevel = 9) + public BZip2BitInputStreamSplitter( + Stream inputStream, + InputStreamHeaderCheckType inputStreamHeaderCheck = + InputStreamHeaderCheckType.FULL_HEADER, + int manualBlockLevel = 9 + ) { - this._bitInputStream = new BZip2BitInputStream(inputStream); + this._bitInputStream = new BZip2BitInputStream(inputStream); this.Initialize(inputStreamHeaderCheck, manualBlockLevel); } @@ -53,9 +57,12 @@ private void Initialize(InputStreamHeaderCheckType inputStreamHeaderCheck, int b uint marker1 = this._bitInputStream.ReadBits(16); uint marker2 = this._bitInputStream.ReadBits(8); blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); - if (marker1 != BZip2Constants.STREAM_START_MARKER_1 || - marker2 != BZip2Constants.STREAM_START_MARKER_2 || - blockLevel < 1 || blockLevel > 9) + if ( + marker1 != BZip2Constants.STREAM_START_MARKER_1 + || marker2 != BZip2Constants.STREAM_START_MARKER_2 + || blockLevel < 1 + || blockLevel > 9 + ) { throw new IOException("Invalid BZip2 header"); } @@ -65,8 +72,11 @@ private void Initialize(InputStreamHeaderCheckType inputStreamHeaderCheck, int b { uint marker2 = this._bitInputStream.ReadBits(8); blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); - if (marker2 != BZip2Constants.STREAM_START_MARKER_2 || - blockLevel < 1 || blockLevel > 9) + if ( + marker2 != BZip2Constants.STREAM_START_MARKER_2 + || blockLevel < 1 + || blockLevel > 9 + ) { throw new IOException("Invalid BZip2 header"); } @@ -75,7 +85,7 @@ private void Initialize(InputStreamHeaderCheckType inputStreamHeaderCheck, int b case InputStreamHeaderCheckType.NO_BZH: { blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); - if (blockLevel < 1 || blockLevel > 9) + if (blockLevel < 1 || blockLevel > 9) { throw new IOException("Invalid BZip2 header"); } @@ -97,7 +107,8 @@ private void Initialize(InputStreamHeaderCheckType inputStreamHeaderCheck, int b } this._buffer = new MemoryStream(this._blockSizeBytes + 100000); - } catch (IOException) + } + catch (IOException) { // If the stream header was not valid, stop trying to read more data this._bitInputStream = null; @@ -175,6 +186,7 @@ void FlushPartialByte() } } } + public void Dispose() { this._bitInputStream?.Dispose(); diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs index 3ac770f22..2950e925b 100644 --- a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2InputStream.cs @@ -8,6 +8,7 @@ using System.IO; using SharpCompress.Compressors.BZip2MT.Algorithm; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.InputStream { /// An InputStream wrapper that decompresses BZip2 data @@ -41,13 +42,18 @@ public class BZip2InputStream : Stream // The decompressor for the current block private BZip2BlockDecompressor? _blockDecompressor; - /// Public constructor /// The InputStream to wrap /// if true, will close the stream when done /// /// Used when is NoHeader - public BZip2InputStream(Stream inputStream, bool isOwner = true, InputStreamHeaderCheckType inputStreamHeaderCheck = InputStreamHeaderCheckType.FULL_HEADER, int manualBlockLevel = 9) + public BZip2InputStream( + Stream inputStream, + bool isOwner = true, + InputStreamHeaderCheckType inputStreamHeaderCheck = + InputStreamHeaderCheckType.FULL_HEADER, + int manualBlockLevel = 9 + ) { this._inputStream = inputStream; this._bitInputStream = new BZip2BitInputStream(inputStream); @@ -61,11 +67,27 @@ public BZip2InputStream(Stream inputStream, bool isOwner = true, InputStreamHead #region Implementation of abstract members of Stream - #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public override void Flush() => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'Flush()' method."); - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); - public override void SetLength(long value) => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'SetLength(long value)' method."); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support 'Write(byte[] buffer, int offset, int count)' method."); +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Flush() => + throw new NotSupportedException( + $"{nameof(BZip2InputStream)} does not support 'Flush()' method." + ); + + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException( + $"{nameof(BZip2InputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method." + ); + + public override void SetLength(long value) => + throw new NotSupportedException( + $"{nameof(BZip2InputStream)} does not support 'SetLength(long value)' method." + ); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException( + $"{nameof(BZip2InputStream)} does not support 'Write(byte[] buffer, int offset, int count)' method." + ); + public override bool CanRead => this._inputStream.CanRead; public override bool CanSeek => false; public override bool CanWrite => false; @@ -73,7 +95,10 @@ public BZip2InputStream(Stream inputStream, bool isOwner = true, InputStreamHead public override long Position { get => this._inputStream.Position; - set =>throw new NotSupportedException($"{nameof(BZip2InputStream)} does not support Set operation for property 'Position'."); + set => + throw new NotSupportedException( + $"{nameof(BZip2InputStream)} does not support Set operation for property 'Position'." + ); } public override int ReadByte() @@ -92,7 +117,7 @@ public override int ReadByte() return nextByte; } - public override int Read(byte[] destination, int offset, int length) + public override int Read(byte[] destination, int offset, int length) { int bytesRead = this._blockDecompressor?.Read(destination, offset, length) ?? 0; @@ -105,7 +130,8 @@ public override int Read(byte[] destination, int offset, int length) } } - if (bytesRead == -1) bytesRead = 0; + if (bytesRead == -1) + bytesRead = 0; return bytesRead; } @@ -125,13 +151,16 @@ protected override void Dispose(bool disposing) } } - #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member #endregion /// Reads the stream header and checks that the data appears to be a valid BZip2 stream /// if the stream header is not valid - private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, int blockLevel) + private void InitializeStream( + InputStreamHeaderCheckType inputStreamHeaderCheck, + int blockLevel + ) { /* If the stream has been explicitly closed, throw an exception */ if (this._bitInputStream is null) @@ -151,9 +180,12 @@ private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, uint marker1 = this._bitInputStream.ReadBits(16); uint marker2 = this._bitInputStream.ReadBits(8); blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); - if (marker1 != BZip2Constants.STREAM_START_MARKER_1 || - marker2 != BZip2Constants.STREAM_START_MARKER_2 || - blockLevel < 1 || blockLevel > 9) + if ( + marker1 != BZip2Constants.STREAM_START_MARKER_1 + || marker2 != BZip2Constants.STREAM_START_MARKER_2 + || blockLevel < 1 + || blockLevel > 9 + ) { throw new IOException("Invalid BZip2 header"); } @@ -163,8 +195,11 @@ private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, { uint marker2 = this._bitInputStream.ReadBits(8); blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); - if (marker2 != BZip2Constants.STREAM_START_MARKER_2 || - blockLevel < 1 || blockLevel > 9) + if ( + marker2 != BZip2Constants.STREAM_START_MARKER_2 + || blockLevel < 1 + || blockLevel > 9 + ) { throw new IOException("Invalid BZip2 header"); } @@ -173,7 +208,7 @@ private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, case InputStreamHeaderCheckType.NO_BZH: { blockLevel = ((int)this._bitInputStream.ReadBits(8) - '0'); - if (blockLevel < 1 || blockLevel > 9) + if (blockLevel < 1 || blockLevel > 9) { throw new IOException("Invalid BZip2 header"); } @@ -182,7 +217,8 @@ private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, } this._streamBlockSize = (uint)(blockLevel * 100000); - } catch (IOException) + } + catch (IOException) { // If the stream header was not valid, stop trying to read more data this._streamComplete = true; @@ -198,7 +234,6 @@ private void InitializeStream(InputStreamHeaderCheckType inputStreamHeaderCheck, /// not a valid block-header or end-of-file marker, or if the following block could not be decoded private bool InitializeNextBlock() { - // If we're already at the end of the stream, do nothing if (this._streamComplete) return false; @@ -214,13 +249,20 @@ private bool InitializeNextBlock() uint marker1 = this._bitInputStream.ReadBits(24); uint marker2 = this._bitInputStream.ReadBits(24); - if (marker1 == BZip2Constants.BLOCK_HEADER_MARKER_1 && marker2 == BZip2Constants.BLOCK_HEADER_MARKER_2) + if ( + marker1 == BZip2Constants.BLOCK_HEADER_MARKER_1 + && marker2 == BZip2Constants.BLOCK_HEADER_MARKER_2 + ) { // Initialise a new block try { - this._blockDecompressor = new BZip2BlockDecompressor(this._bitInputStream, this._streamBlockSize); - } catch (IOException) + this._blockDecompressor = new BZip2BlockDecompressor( + this._bitInputStream, + this._streamBlockSize + ); + } + catch (IOException) { // If the block could not be decoded, stop trying to read more data this._streamComplete = true; @@ -228,7 +270,10 @@ private bool InitializeNextBlock() } return true; } - if (marker1 == BZip2Constants.STREAM_END_MARKER_1 && marker2 == BZip2Constants.STREAM_END_MARKER_2) + if ( + marker1 == BZip2Constants.STREAM_END_MARKER_1 + && marker2 == BZip2Constants.STREAM_END_MARKER_2 + ) { // Read and verify the end-of-stream CRC this._streamComplete = true; diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs index d265dfeaa..27742b331 100644 --- a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputDataBlock.cs @@ -1,11 +1,11 @@ using System; using System.IO; using SharpCompress.Compressors.BZip2MT.Algorithm; + namespace SharpCompress.Compressors.BZip2MT.InputStream { internal class BZip2ParallelInputDataBlock : IDisposable { - private int _blockId = 0; private MemoryStream? _inputBlockBuffer; private MemoryStream? _outputBuffer; @@ -21,7 +21,12 @@ internal class BZip2ParallelInputDataBlock : IDisposable public bool IsCrcOk => this._isCrcOk; public uint CrcValue => this._crcValue; - public BZip2ParallelInputDataBlock(int blockId, MemoryStream inputBlockBuffer, int blockSizeBytes, int outputBufferSize) + public BZip2ParallelInputDataBlock( + int blockId, + MemoryStream inputBlockBuffer, + int blockSizeBytes, + int outputBufferSize + ) { this._blockId = blockId; this._inputBlockBuffer = inputBlockBuffer; @@ -35,7 +40,6 @@ public void Dispose() this._outputBuffer?.Dispose(); } - /// /// Decompresses the full block /// @@ -49,7 +53,10 @@ public void Decompress() // note: we need to skip the first 6 magic bytes... this._inputBlockBuffer.Position = 6; using BZip2BitInputStream inputStream = new BZip2BitInputStream(this._inputBlockBuffer); - BZip2BlockDecompressor blockDecompressor = new BZip2BlockDecompressor(inputStream, (uint)this._blockSizeBytes); + BZip2BlockDecompressor blockDecompressor = new BZip2BlockDecompressor( + inputStream, + (uint)this._blockSizeBytes + ); int readCount = blockDecompressor.ReadAll(this._outputBuffer); @@ -60,7 +67,8 @@ public void Decompress() { this._crcValue = blockDecompressor.CheckCrc(); this._isCrcOk = true; - } catch (Exception) + } + catch (Exception) { this._crcValue = 0; this._isCrcOk = false; @@ -77,7 +85,9 @@ public void Decompress() public int Read() { if (this._outputBuffer is null) - throw new IOException("Attempted to read decompressed data before decompressing block"); + throw new IOException( + "Attempted to read decompressed data before decompressing block" + ); if (this._isCrcOk == false) return -1; @@ -96,13 +106,14 @@ public int Read() public int Read(byte[] destination, int offset, int length) { if (this._outputBuffer is null) - throw new IOException("Attempted to read decompressed data before decompressing block"); + throw new IOException( + "Attempted to read decompressed data before decompressing block" + ); if (this._isCrcOk == false) return 0; return this._outputBuffer.Read(destination, offset, length); } - } } diff --git a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs index 9196e4e02..d2fdd10a0 100644 --- a/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/InputStream/BZip2ParallelInputStream.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.InputStream { /// An InputStream wrapper that decompresses BZip2 data using multiple threads @@ -28,7 +29,8 @@ public class BZip2ParallelInputStream : Stream private Exception? _lastWorkerException = null; // dictionary of processed blocks - private readonly Dictionary _mtDecodedBlocks = new Dictionary(); + private readonly Dictionary _mtDecodedBlocks = + new Dictionary(); private int _mtPendingBlocks = 0; private readonly int _mtMaxPendingBlocks; @@ -37,7 +39,6 @@ public class BZip2ParallelInputStream : Stream private readonly object _syncRootProcesing = new object(); - // The stream from which compressed BZip2 data is read and decoded private Stream _inputStream; @@ -47,7 +48,6 @@ public class BZip2ParallelInputStream : Stream // An InputStream wrapper that provides bit-level reads private BZip2BitInputStreamSplitter _inputStreamSplitter; - // The merged CRC of all blocks decompressed so far private uint _streamCrc; @@ -62,7 +62,15 @@ public class BZip2ParallelInputStream : Stream /// Used when is NoHeader /// Maximum number of blocks to read/decompress at the same time /// Minimum buffer size for each worker thread during decompression, in bytes. - public BZip2ParallelInputStream(Stream inputStream, bool isOwner = true, InputStreamHeaderCheckType inputStreamHeaderCheck = InputStreamHeaderCheckType.FULL_HEADER, int manualBlockLevel = 9, int maxPendingBlocks = 0, int workerBufferSize = 12582912) + public BZip2ParallelInputStream( + Stream inputStream, + bool isOwner = true, + InputStreamHeaderCheckType inputStreamHeaderCheck = + InputStreamHeaderCheckType.FULL_HEADER, + int manualBlockLevel = 9, + int maxPendingBlocks = 0, + int workerBufferSize = 12582912 + ) { if (maxPendingBlocks == 0) { @@ -74,7 +82,11 @@ public BZip2ParallelInputStream(Stream inputStream, bool isOwner = true, InputSt // initialize stream this._inputStream = inputStream; this._isOwner = isOwner; - this._inputStreamSplitter = new BZip2BitInputStreamSplitter(inputStream, inputStreamHeaderCheck, manualBlockLevel); + this._inputStreamSplitter = new BZip2BitInputStreamSplitter( + inputStream, + inputStreamHeaderCheck, + manualBlockLevel + ); this._blockLevel = this._inputStreamSplitter.BlockLevel; this._blockSizeBytes = this._inputStreamSplitter.BlockSizeBytes; @@ -102,18 +114,23 @@ private void MultiThreadWorkerAction(object? blockData) this._mtDecodedBlocks.Add(dataBlock.BlockId, dataBlock); } } - } catch (Exception ex) + } + catch (Exception ex) { // set this without any locks... this._lastWorkerException = ex; this._unsafeFatalException = true; - throw new IOException("BZip2 Processing thread somehow crashed... See inner exception for details!", ex); - } finally + throw new IOException( + "BZip2 Processing thread somehow crashed... See inner exception for details!", + ex + ); + } + finally { lock (this._syncRootProcesing) { - this._mtPendingBlocks --; + this._mtPendingBlocks--; } } } @@ -128,7 +145,10 @@ private bool TryToQueueUpAnotherBlock() { if (this._unsafeFatalException) { - throw new IOException("One of the decompression threads somehow failed... This should never happen.", this._lastWorkerException); + throw new IOException( + "One of the decompression threads somehow failed... This should never happen.", + this._lastWorkerException + ); } if (this._inputStreamSplitter.IsStreamComplete) @@ -151,8 +171,13 @@ private bool TryToQueueUpAnotherBlock() lock (this._syncRootProcesing) { - this._mtPendingBlocks ++; - BZip2ParallelInputDataBlock data = new BZip2ParallelInputDataBlock(this._mtNextInputBlockId++, ms, this._blockSizeBytes, this._mtWorkerBufferSize); + this._mtPendingBlocks++; + BZip2ParallelInputDataBlock data = new BZip2ParallelInputDataBlock( + this._mtNextInputBlockId++, + ms, + this._blockSizeBytes, + this._mtWorkerBufferSize + ); ThreadPool.QueueUserWorkItem(this.MultiThreadWorkerAction, data); } @@ -170,14 +195,19 @@ private bool CheckIfCompletelyDone() { if (this._unsafeFatalException) { - throw new IOException("One of the decompression threads somehow failed... This should never happen.", this._lastWorkerException); + throw new IOException( + "One of the decompression threads somehow failed... This should never happen.", + this._lastWorkerException + ); } lock (this._syncRootProcesing) { - if (this._mtPendingBlocks == 0 && - this._mtDecodedBlocks.Count == 0 && - this._inputStreamSplitter.IsStreamComplete) + if ( + this._mtPendingBlocks == 0 + && this._mtDecodedBlocks.Count == 0 + && this._inputStreamSplitter.IsStreamComplete + ) { if (this._inputStreamSplitter.FinalCrc != this._streamCrc) { @@ -193,11 +223,27 @@ private bool CheckIfCompletelyDone() #region Implementation of abstract members of Stream - #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public override void Flush() => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'Flush()' method."); - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); - public override void SetLength(long value) => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'SetLength(long value)' method."); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support 'Write(byte[] buffer, int offset, int count)' method."); +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Flush() => + throw new NotSupportedException( + $"{nameof(BZip2ParallelInputStream)} does not support 'Flush()' method." + ); + + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException( + $"{nameof(BZip2ParallelInputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method." + ); + + public override void SetLength(long value) => + throw new NotSupportedException( + $"{nameof(BZip2ParallelInputStream)} does not support 'SetLength(long value)' method." + ); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException( + $"{nameof(BZip2ParallelInputStream)} does not support 'Write(byte[] buffer, int offset, int count)' method." + ); + public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; @@ -205,7 +251,10 @@ private bool CheckIfCompletelyDone() public override long Position { get => this._inputStream.Position; - set => throw new NotSupportedException($"{nameof(BZip2ParallelInputStream)} does not support Set operation for property 'Position'."); + set => + throw new NotSupportedException( + $"{nameof(BZip2ParallelInputStream)} does not support Set operation for property 'Position'." + ); } public override int ReadByte() @@ -240,10 +289,11 @@ public override int ReadByte() lock (this._syncRootProcesing) { uint blockCrc = data.CrcValue; - this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; + this._streamCrc = + ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; this._mtDecodedBlocks.Remove(this._mtNextOutputBlockId); - this._mtNextOutputBlockId ++; + this._mtNextOutputBlockId++; // continue to processing next block that is already decoded, skip queueing up more blocks... continue; @@ -264,7 +314,7 @@ public override int ReadByte() } } - public override int Read(byte[] destination, int offset, int length) + public override int Read(byte[] destination, int offset, int length) { int totalRead = 0; int remaining = length; @@ -303,10 +353,11 @@ public override int Read(byte[] destination, int offset, int length) lock (this._syncRootProcesing) { uint blockCrc = data.CrcValue; - this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; + this._streamCrc = + ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ blockCrc; this._mtDecodedBlocks.Remove(this._mtNextOutputBlockId); - this._mtNextOutputBlockId ++; + this._mtNextOutputBlockId++; // continue to processing next block that is already decoded, skip queueing up more blocks... continue; @@ -340,9 +391,8 @@ protected override void Dispose(bool disposing) } } - #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member #endregion - } } diff --git a/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs b/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs index 8518911ff..3a6d24067 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Interface/BZip2Constants.cs @@ -1,8 +1,10 @@ // Added by drone1400, December 2025 // Location: https://github.com/drone1400/bzip2 -namespace SharpCompress.Compressors.BZip2MT.Interface { - internal static class BZip2Constants { +namespace SharpCompress.Compressors.BZip2MT.Interface +{ + internal static class BZip2Constants + { public const uint STREAM_START_MARKER = 0x425A68; public const ulong STREAM_END_MARKER = 0x177245385090; public const ulong BLOCK_HEADER_MARKER = 0x314159265359; diff --git a/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs index 1501992e7..1da856cbe 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitInputStream.cs @@ -1,5 +1,6 @@ using System; using System.IO; + namespace SharpCompress.Compressors.BZip2MT.Interface { internal interface IBZip2BitInputStream : IDisposable diff --git a/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs index 4855e7b4a..6aa5651da 100644 --- a/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/Interface/IBZip2BitOutputStream.cs @@ -2,6 +2,7 @@ // Location: https://github.com/drone1400/bzip2 using System; + namespace SharpCompress.Compressors.BZip2MT.Interface { /// @@ -14,14 +15,14 @@ internal interface IBZip2BitOutputStream : IDisposable /// /// The bit to write /// if an error occurs writing to the stream - public void WriteBoolean (bool value); + public void WriteBoolean(bool value); /// /// Writes a zero-terminated unary number to the wrapped output stream /// /// The number to write (must be non-negative) /// if an error occurs writing to the stream - public void WriteUnary (int value); + public void WriteUnary(int value); /// /// Writes up to 24 bits to the wrapped output stream @@ -29,19 +30,19 @@ internal interface IBZip2BitOutputStream : IDisposable /// The number of bits to write (maximum 24) /// The bits to write /// if an error occurs writing to the stream - public void WriteBits (int count, uint value) ; + public void WriteBits(int count, uint value); /// /// Writes an integer as 32 bits of output /// /// The integer to write /// if an error occurs writing to the stream - public void WriteInteger (uint value) ; + public void WriteInteger(uint value); /// /// Writes any remaining bits to the output stream, zero padding to a whole byte as required /// /// if an error occurs writing to the stream - public void Flush() ; + public void Flush(); } } diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs index f947f7680..3e6fccb3f 100644 --- a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2BitOutputStream.cs @@ -6,6 +6,7 @@ using System.IO; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.OutputStream { /// Implements a bit-wise output stream @@ -38,7 +39,7 @@ public void Dispose() #region IBZip2BitOutputStream implementation - public void WriteBoolean (bool value) + public void WriteBoolean(bool value) { this._bitCount++; this._bitBuffer |= ((value ? 1u : 0u) << (32 - this._bitCount)); @@ -51,16 +52,16 @@ public void WriteBoolean (bool value) } } - public void WriteUnary (int value) + public void WriteUnary(int value) { while (value-- > 0) { - this.WriteBoolean (true); + this.WriteBoolean(true); } - this.WriteBoolean (false); + this.WriteBoolean(false); } - public void WriteBits (int count, uint value) + public void WriteBits(int count, uint value) { this._bitBuffer |= ((value << (32 - count)) >> this._bitCount); this._bitCount += count; @@ -73,17 +74,17 @@ public void WriteBits (int count, uint value) } } - public void WriteInteger (uint value) + public void WriteInteger(uint value) { - this.WriteBits (16, (value >> 16) & 0xffff); - this.WriteBits (16, value & 0xffff); + this.WriteBits(16, (value >> 16) & 0xffff); + this.WriteBits(16, value & 0xffff); } public void Flush() { if (this._bitCount > 0) { - this.WriteBits (8 - this._bitCount, 0); + this.WriteBits(8 - this._bitCount, 0); } } diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs index 437803384..4288ce2a6 100644 --- a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2OutputStream.cs @@ -8,6 +8,7 @@ using System.IO; using SharpCompress.Compressors.BZip2MT.Algorithm; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.OutputStream { /// An OutputStream wrapper that compresses BZip2 data @@ -42,7 +43,11 @@ public class BZip2OutputStream : Stream /// On any I/O error writing to the output stream /// Larger block sizes require more memory for both compression and decompression, /// but give better compression ratios. 9 will usually be the best value to use - public BZip2OutputStream(Stream outputStream, bool isOwner = true, int blockSizeMultiplier = 9) + public BZip2OutputStream( + Stream outputStream, + bool isOwner = true, + int blockSizeMultiplier = 9 + ) { if (outputStream is null) throw new ArgumentException("Null output stream"); @@ -64,14 +69,26 @@ public BZip2OutputStream(Stream outputStream, bool isOwner = true, int blockSize #region Implementation of abstract members of Stream - #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public override void Flush () => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'Flush()' method."); +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Flush() => + throw new NotSupportedException( + $"{nameof(BZip2OutputStream)} does not support 'Flush()' method." + ); - public override int Read (byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'Read(byte[] buffer, int offset, int count)' method."); + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException( + $"{nameof(BZip2OutputStream)} does not support 'Read(byte[] buffer, int offset, int count)' method." + ); - public override long Seek (long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException( + $"{nameof(BZip2OutputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method." + ); - public override void SetLength (long value) => throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support 'SetLength(long value)' method."); + public override void SetLength(long value) => + throw new NotSupportedException( + $"{nameof(BZip2OutputStream)} does not support 'SetLength(long value)' method." + ); public override bool CanRead => false; @@ -84,7 +101,10 @@ public BZip2OutputStream(Stream outputStream, bool isOwner = true, int blockSize public override long Position { get => this._outputStream.Position; - set =>throw new NotSupportedException($"{nameof(BZip2OutputStream)} does not support Set operation for property 'Position'."); + set => + throw new NotSupportedException( + $"{nameof(BZip2OutputStream)} does not support Set operation for property 'Position'." + ); } public override void WriteByte(byte value) @@ -134,12 +154,16 @@ protected override void Dispose(bool disposing) } } - #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member #endregion /// Initialises a new block for compression - private void InitialiseNextBlock() => this._blockCompressor = new BZip2BlockCompressor (this._bitOutputStream, this._streamBlockSize); + private void InitialiseNextBlock() => + this._blockCompressor = new BZip2BlockCompressor( + this._bitOutputStream, + this._streamBlockSize + ); /// Compress and write out the block currently in progress /// If no bytes have been written to the block, it is discarded @@ -150,7 +174,8 @@ private void CloseBlock() return; this._blockCompressor.CloseBlock(); - this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ this._blockCompressor.CRC; + this._streamCrc = + ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ this._blockCompressor.CRC; } /// Compresses and writes out any as yet unwritten data, then writes the end of the BZip2 stream @@ -169,7 +194,8 @@ private void Finish() this._bitOutputStream.WriteInteger(this._streamCrc); this._bitOutputStream.Flush(); this._outputStream.Flush(); - } finally + } + finally { this._blockCompressor = null; } diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs index e9a52e593..58de1843d 100644 --- a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputDataBlock.cs @@ -5,6 +5,7 @@ using System.IO; using SharpCompress.Compressors.BZip2MT.Algorithm; using SharpCompress.Compressors.BZip2MT.Interface; + namespace SharpCompress.Compressors.BZip2MT.OutputStream { /// A collection of bit output data @@ -144,26 +145,30 @@ public void WriteBoolean(bool value) this._bitCount++; this._internalBitStream.WriteBoolean(value); } + public void WriteUnary(int value) { while (value-- > 0) { this._bitCount++; - this._internalBitStream.WriteBoolean (true); + this._internalBitStream.WriteBoolean(true); } this._bitCount++; - this._internalBitStream.WriteBoolean (false); + this._internalBitStream.WriteBoolean(false); } + public void WriteBits(int count, uint value) { this._bitCount += count; this._internalBitStream.WriteBits(count, value); } + public void WriteInteger(uint value) { this._bitCount += 32; this._internalBitStream.WriteInteger(value); } + public void Flush() => this._internalBitStream.Flush(); #endregion diff --git a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs index 20152c048..9e370b800 100644 --- a/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2MT/OutputStream/BZip2ParallelOutputStream.cs @@ -5,9 +5,9 @@ using System.Collections.Generic; using System.IO; using System.Threading; + namespace SharpCompress.Compressors.BZip2MT.OutputStream { - /// An OutputStream wrapper that compresses BZip2 data using multiple threads /// Instances of this class are not threadsafe public class BZip2ParallelOutputStream : Stream @@ -29,7 +29,8 @@ public class BZip2ParallelOutputStream : Stream private Exception? _lastWorkerException = null; // dictionary of processed blocks - private readonly Dictionary _mtProcessedBlocks = new Dictionary(); + private readonly Dictionary _mtProcessedBlocks = + new Dictionary(); private int _mtPendingBlocks = 0; @@ -66,8 +67,10 @@ public BZip2ParallelOutputStream(Stream output, bool isOwner = true, int blockLe this._outputStream = output; this._bitStream = new BZip2BitOutputStream(output); - if (blockLevel < 1 ) blockLevel = 1; - if (blockLevel > 9 ) blockLevel = 9; + if (blockLevel < 1) + blockLevel = 1; + if (blockLevel > 9) + blockLevel = 9; this._blockLevel = blockLevel; // evaluate thread count @@ -79,7 +82,10 @@ public BZip2ParallelOutputStream(Stream output, bool isOwner = true, int blockLe this._compressBlockSize = 100000 * this._blockLevel; // initialize initial meta buffer - this._currentBlockBuffer = new BZip2ParallelOutputDataBlock(this._compressBlockSize, this._mtNextInputBlockId++); + this._currentBlockBuffer = new BZip2ParallelOutputDataBlock( + this._compressBlockSize, + this._mtNextInputBlockId++ + ); this._isOwner = isOwner; @@ -96,10 +102,12 @@ private bool TryWriteOutputBlockAndIncrementId() { if (this._unsafeFatalException) { - throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + throw new IOException( + "One of the compression threads somehow failed... This should never happen.", + this._lastWorkerException + ); } - try { BZip2ParallelOutputDataBlock? currentOutput = null; @@ -123,18 +131,23 @@ private bool TryWriteOutputBlockAndIncrementId() lock (this._syncRootOutputStream) { // update file CRC - this._streamCrc = ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ currentOutput.BlockCrc; + this._streamCrc = + ((this._streamCrc << 1) | (this._streamCrc >> 31)) ^ currentOutput.BlockCrc; currentOutput.WriteToRealOutputStream(this._bitStream); } return true; - } catch (Exception ex) + } + catch (Exception ex) { // set this without any locks... this._lastWorkerException = ex; this._unsafeFatalException = true; // rethrow exception, hopefully something catches it?... - throw new IOException("BZip2 error writing output data! See inner exception for details!", ex); + throw new IOException( + "BZip2 error writing output data! See inner exception for details!", + ex + ); } } @@ -158,18 +171,23 @@ private void MultiThreadWorkerAction(object? blockData) this._mtProcessedBlocks.Add(buffer.BlockId, buffer); } } - } catch (Exception ex) + } + catch (Exception ex) { // set this without any locks... this._lastWorkerException = ex; this._unsafeFatalException = true; - throw new IOException("BZip2 Processing thread somehow crashed... See inner exception for details!", ex); - } finally + throw new IOException( + "BZip2 Processing thread somehow crashed... See inner exception for details!", + ex + ); + } + finally { lock (this._syncRootProcesing) { - this._mtPendingBlocks --; + this._mtPendingBlocks--; } } } @@ -182,9 +200,9 @@ private void WriteBz2Header() lock (this._syncRootOutputStream) { // write BZIP file header - this._bitStream.WriteBits(8, 0x42); // B - this._bitStream.WriteBits(8, 0x5A); // Z - this._bitStream.WriteBits(8, 0x68); // h + this._bitStream.WriteBits(8, 0x42); // B + this._bitStream.WriteBits(8, 0x5A); // Z + this._bitStream.WriteBits(8, 0x68); // h this._bitStream.WriteBits(8, (uint)(0x30 + this._blockLevel)); // block level digit } } @@ -223,7 +241,10 @@ private bool TryEnqueueCurrentBlockBuffer() { if (this._unsafeFatalException) { - throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + throw new IOException( + "One of the compression threads somehow failed... This should never happen.", + this._lastWorkerException + ); } // make sure current block has data @@ -232,15 +253,20 @@ private bool TryEnqueueCurrentBlockBuffer() lock (this._syncRootProcesing) { - this._mtPendingBlocks ++; - ThreadPool.QueueUserWorkItem(this.MultiThreadWorkerAction, this._currentBlockBuffer); - this._currentBlockBuffer = new BZip2ParallelOutputDataBlock(this._compressBlockSize, this._mtNextInputBlockId++); + this._mtPendingBlocks++; + ThreadPool.QueueUserWorkItem( + this.MultiThreadWorkerAction, + this._currentBlockBuffer + ); + this._currentBlockBuffer = new BZip2ParallelOutputDataBlock( + this._compressBlockSize, + this._mtNextInputBlockId++ + ); } return true; } - /// /// Waits for any pending data blocks to be written to the bit stream and writes the BZ2 footer /// @@ -249,7 +275,10 @@ private void FinishBitstream() { if (this._unsafeFatalException) { - throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + throw new IOException( + "One of the compression threads somehow failed... This should never happen.", + this._lastWorkerException + ); } lock (this._syncRootProcesing) @@ -269,7 +298,10 @@ private void FinishBitstream() { if (this._unsafeFatalException) { - throw new IOException("One of the compression threads somehow failed... This should never happen.", this._lastWorkerException); + throw new IOException( + "One of the compression threads somehow failed... This should never happen.", + this._lastWorkerException + ); } lock (this._syncRootProcesing) @@ -282,16 +314,14 @@ private void FinishBitstream() } // try writing output block and keep doing so while successful - while (this.TryWriteOutputBlockAndIncrementId()) - { } + while (this.TryWriteOutputBlockAndIncrementId()) { } Thread.Sleep(1); } // sanity check, this should be impossible and should be caught by some test right?... lock (this._syncRootProcesing) { - if (this._mtPendingBlocks != 0 || - this._mtProcessedBlocks.Count > 0) + if (this._mtPendingBlocks != 0 || this._mtProcessedBlocks.Count > 0) { throw new IOException("BZip2 dispose operation sanity check failed!..."); } @@ -305,7 +335,7 @@ private void FinishBitstream() #region Implementation of abstract members of Stream - #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member // overriding Dispose instead of Close as recommended in https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.close?view=net-6.0 protected override void Dispose(bool disposing) @@ -321,12 +351,23 @@ protected override void Dispose(bool disposing) public override void Flush() { // try writing output block and keep doing so while successful - while (this.TryWriteOutputBlockAndIncrementId()) - { } + while (this.TryWriteOutputBlockAndIncrementId()) { } } - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method."); - public override void SetLength(long value) => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support 'SetLength(long value)' method."); - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support 'Read(byte[] buffer, int offset, int count)' method."); + + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException( + $"{nameof(BZip2ParallelOutputStream)} does not support 'Seek(long offset, SeekOrigin origin)' method." + ); + + public override void SetLength(long value) => + throw new NotSupportedException( + $"{nameof(BZip2ParallelOutputStream)} does not support 'SetLength(long value)' method." + ); + + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException( + $"{nameof(BZip2ParallelOutputStream)} does not support 'Read(byte[] buffer, int offset, int count)' method." + ); public override void WriteByte(byte value) { @@ -371,11 +412,13 @@ public override void Write(byte[] data, int offset, int length) public override long Position { get => this._outputStream.Position; - set => throw new NotSupportedException($"{nameof(BZip2ParallelOutputStream)} does not support Set operation for property 'Position'."); + set => + throw new NotSupportedException( + $"{nameof(BZip2ParallelOutputStream)} does not support Set operation for property 'Position'." + ); } - - #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member #endregion }